mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2026-02-28 14:03:26 +00:00
Store named key params in key objects (#1141)
- Store private and public params separately and by name in objects, instead of as an array - Do not keep params in MPI form, but convert them to Uint8Arrays when generating/parsing the key - Modify low-level crypto functions to always accept and return Uint8Arrays instead of BigIntegers - Move PKCS1 padding to lower level functions
This commit is contained in:
@@ -36,13 +36,12 @@ import publicKey from './public_key';
|
||||
import cipher from './cipher';
|
||||
import random from './random';
|
||||
import type_ecdh_symkey from '../type/ecdh_symkey';
|
||||
import type_kdf_params from '../type/kdf_params';
|
||||
import KDFParams from '../type/kdf_params';
|
||||
import type_mpi from '../type/mpi';
|
||||
import type_oid from '../type/oid';
|
||||
import enums from '../enums';
|
||||
import util from '../util';
|
||||
import pkcs1 from './pkcs1';
|
||||
import pkcs5 from './pkcs5';
|
||||
import OID from '../type/oid';
|
||||
import Curve from './public_key/elliptic/curves';
|
||||
|
||||
function constructParams(types, data) {
|
||||
return types.map(function(type, i) {
|
||||
@@ -57,40 +56,30 @@ export default {
|
||||
/**
|
||||
* Encrypts data using specified algorithm and public key parameters.
|
||||
* See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} for public key algorithms.
|
||||
* @param {module:enums.publicKey} algo Public key algorithm
|
||||
* @param {Array<module:type/mpi|
|
||||
module:type/oid|
|
||||
module:type/kdf_params>} pub_params Algorithm-specific public key parameters
|
||||
* @param {Uint8Array} data Data to be encrypted
|
||||
* @param {Uint8Array} fingerprint Recipient fingerprint
|
||||
* @param {module:enums.publicKey} algo Public key algorithm
|
||||
* @param {Object} pubParams Algorithm-specific public key parameters
|
||||
* @param {Uint8Array} data Data to be encrypted
|
||||
* @param {Uint8Array} fingerprint Recipient fingerprint
|
||||
* @returns {Array<module:type/mpi|
|
||||
* module:type/ecdh_symkey>} encrypted session key parameters
|
||||
* @async
|
||||
*/
|
||||
publicKeyEncrypt: async function(algo, pub_params, data, fingerprint) {
|
||||
publicKeyEncrypt: async function(algo, publicParams, data, fingerprint) {
|
||||
const types = this.getEncSessionKeyParamTypes(algo);
|
||||
switch (algo) {
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaEncryptSign: {
|
||||
const n = pub_params[0].toUint8Array();
|
||||
const e = pub_params[1].toUint8Array();
|
||||
const { n, e } = publicParams;
|
||||
const res = await publicKey.rsa.encrypt(data, n, e);
|
||||
return constructParams(types, [res]);
|
||||
}
|
||||
case enums.publicKey.elgamal: {
|
||||
data = new type_mpi(await pkcs1.eme.encode(data, pub_params[0].byteLength()));
|
||||
const m = await data.toBigInteger();
|
||||
const p = await pub_params[0].toBigInteger();
|
||||
const g = await pub_params[1].toBigInteger();
|
||||
const y = await pub_params[2].toBigInteger();
|
||||
const res = await publicKey.elgamal.encrypt(m, p, g, y);
|
||||
const { p, g, y } = publicParams;
|
||||
const res = await publicKey.elgamal.encrypt(data, p, g, y);
|
||||
return constructParams(types, [res.c1, res.c2]);
|
||||
}
|
||||
case enums.publicKey.ecdh: {
|
||||
data = new type_mpi(pkcs5.encode(data));
|
||||
const oid = pub_params[0];
|
||||
const Q = pub_params[1].toUint8Array();
|
||||
const kdfParams = pub_params[2];
|
||||
const { oid, Q, kdfParams } = publicParams;
|
||||
const { publicKey: V, wrappedKey: C } = await publicKey.elliptic.ecdh.encrypt(
|
||||
oid, kdfParams, data, Q, fingerprint);
|
||||
return constructParams(types, [V, C]);
|
||||
@@ -104,125 +93,133 @@ export default {
|
||||
* Decrypts data using specified algorithm and private key parameters.
|
||||
* See {@link https://tools.ietf.org/html/rfc4880#section-5.5.3|RFC 4880 5.5.3}
|
||||
* @param {module:enums.publicKey} algo Public key algorithm
|
||||
* @param {Array<module:type/mpi|
|
||||
module:type/oid|
|
||||
module:type/kdf_params>} key_params Algorithm-specific public, private key parameters
|
||||
* @param {Object} publicKeyParams Algorithm-specific public key parameters
|
||||
* @param {Object} privateKeyParams Algorithm-specific private key parameters
|
||||
* @param {Array<module:type/mpi|
|
||||
module:type/ecdh_symkey>}
|
||||
data_params encrypted session key parameters
|
||||
* @param {String} fingerprint Recipient fingerprint
|
||||
* @returns {String} String containing the decrypted data
|
||||
* @param {Uint8Array} fingerprint Recipient fingerprint
|
||||
* @returns {Uint8Array} decrypted data
|
||||
* @async
|
||||
*/
|
||||
publicKeyDecrypt: async function(algo, key_params, data_params, fingerprint) {
|
||||
publicKeyDecrypt: async function(algo, publicKeyParams, privateKeyParams, data_params, fingerprint) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
case enums.publicKey.rsaEncrypt: {
|
||||
const c = data_params[0].toUint8Array();
|
||||
const n = key_params[0].toUint8Array(); // n = pq
|
||||
const e = key_params[1].toUint8Array();
|
||||
const d = key_params[2].toUint8Array(); // de = 1 mod (p-1)(q-1)
|
||||
const p = key_params[3].toUint8Array();
|
||||
const q = key_params[4].toUint8Array();
|
||||
const u = key_params[5].toUint8Array(); // p^-1 mod q
|
||||
const { n, e } = publicKeyParams;
|
||||
const { d, p, q, u } = privateKeyParams;
|
||||
return publicKey.rsa.decrypt(c, n, e, d, p, q, u);
|
||||
}
|
||||
case enums.publicKey.elgamal: {
|
||||
const c1 = await data_params[0].toBigInteger();
|
||||
const c2 = await data_params[1].toBigInteger();
|
||||
const p = await key_params[0].toBigInteger();
|
||||
const x = await key_params[3].toBigInteger();
|
||||
const result = new type_mpi(await publicKey.elgamal.decrypt(c1, c2, p, x));
|
||||
return pkcs1.eme.decode(result.toUint8Array('be', p.byteLength()));
|
||||
const c1 = data_params[0].toUint8Array();
|
||||
const c2 = data_params[1].toUint8Array();
|
||||
const p = publicKeyParams.p;
|
||||
const x = privateKeyParams.x;
|
||||
return publicKey.elgamal.decrypt(c1, c2, p, x);
|
||||
}
|
||||
case enums.publicKey.ecdh: {
|
||||
const oid = key_params[0];
|
||||
const kdfParams = key_params[2];
|
||||
const { oid, Q, kdfParams } = publicKeyParams;
|
||||
const { d } = privateKeyParams;
|
||||
const V = data_params[0].toUint8Array();
|
||||
const C = data_params[1].data;
|
||||
const Q = key_params[1].toUint8Array();
|
||||
const d = key_params[3].toUint8Array();
|
||||
const result = new type_mpi(await publicKey.elliptic.ecdh.decrypt(
|
||||
oid, kdfParams, V, C, Q, d, fingerprint));
|
||||
return pkcs5.decode(result.toUint8Array());
|
||||
return publicKey.elliptic.ecdh.decrypt(
|
||||
oid, kdfParams, V, C, Q, d, fingerprint);
|
||||
}
|
||||
default:
|
||||
throw new Error('Invalid public key encryption algorithm.');
|
||||
}
|
||||
},
|
||||
|
||||
/** Returns the types comprising the private key of an algorithm
|
||||
* @param {module:enums.publicKey} algo The public key algorithm
|
||||
* @returns {Array<Object>} The array of types
|
||||
/**
|
||||
* Parse public key material in binary form to get the key parameters
|
||||
* @param {module:enums.publicKey} algo The key algorithm
|
||||
* @param {Uint8Array} bytes The key material to parse
|
||||
* @returns {Object} key parameters referenced by name
|
||||
* @returns { read: Number, publicParams: Object } number of read bytes plus key parameters referenced by name
|
||||
*/
|
||||
getPrivKeyParamTypes: function(algo) {
|
||||
parsePublicKeyParams: function(algo, bytes) {
|
||||
let read = 0;
|
||||
switch (algo) {
|
||||
// Algorithm-Specific Fields for RSA secret keys:
|
||||
// - multiprecision integer (MPI) of RSA secret exponent d.
|
||||
// - MPI of RSA secret prime value p.
|
||||
// - MPI of RSA secret prime value q (p < q).
|
||||
// - MPI of u, the multiplicative inverse of p, mod q.
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
case enums.publicKey.rsaSign:
|
||||
return [type_mpi, type_mpi, type_mpi, type_mpi];
|
||||
// Algorithm-Specific Fields for Elgamal secret keys:
|
||||
// - MPI of Elgamal secret exponent x.
|
||||
case enums.publicKey.elgamal:
|
||||
return [type_mpi];
|
||||
// Algorithm-Specific Fields for DSA secret keys:
|
||||
// - MPI of DSA secret exponent x.
|
||||
case enums.publicKey.dsa:
|
||||
return [type_mpi];
|
||||
// Algorithm-Specific Fields for ECDSA or ECDH secret keys:
|
||||
// - MPI of an integer representing the secret key.
|
||||
case enums.publicKey.ecdh:
|
||||
case enums.publicKey.ecdsa:
|
||||
case enums.publicKey.eddsa:
|
||||
return [type_mpi];
|
||||
case enums.publicKey.rsaSign: {
|
||||
let read = 0;
|
||||
const n = util.readMPI(bytes.subarray(read)); read += n.length + 2;
|
||||
const e = util.readMPI(bytes.subarray(read)); read += e.length + 2;
|
||||
return { read, publicParams: { n, e } };
|
||||
}
|
||||
case enums.publicKey.dsa: {
|
||||
const p = util.readMPI(bytes.subarray(read)); read += p.length + 2;
|
||||
const q = util.readMPI(bytes.subarray(read)); read += q.length + 2;
|
||||
const g = util.readMPI(bytes.subarray(read)); read += g.length + 2;
|
||||
const y = util.readMPI(bytes.subarray(read)); read += y.length + 2;
|
||||
return { read, publicParams: { p, q, g, y } };
|
||||
}
|
||||
case enums.publicKey.elgamal: {
|
||||
const p = util.readMPI(bytes.subarray(read)); read += p.length + 2;
|
||||
const g = util.readMPI(bytes.subarray(read)); read += g.length + 2;
|
||||
const y = util.readMPI(bytes.subarray(read)); read += y.length + 2;
|
||||
return { read, publicParams: { p, g, y } };
|
||||
}
|
||||
case enums.publicKey.ecdsa: {
|
||||
const oid = new OID(); read += oid.read(bytes);
|
||||
const Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2;
|
||||
return { read: read, publicParams: { oid, Q } };
|
||||
}
|
||||
case enums.publicKey.eddsa: {
|
||||
const oid = new OID(); read += oid.read(bytes);
|
||||
let Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2;
|
||||
Q = util.padToLength(Q, 33);
|
||||
return { read: read, publicParams: { oid, Q } };
|
||||
}
|
||||
case enums.publicKey.ecdh: {
|
||||
const oid = new OID(); read += oid.read(bytes);
|
||||
const Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2;
|
||||
const kdfParams = new KDFParams(); read += kdfParams.read(bytes.subarray(read));
|
||||
return { read: read, publicParams: { oid, Q, kdfParams } };
|
||||
}
|
||||
default:
|
||||
throw new Error('Invalid public key encryption algorithm.');
|
||||
}
|
||||
},
|
||||
|
||||
/** Returns the types comprising the public key of an algorithm
|
||||
* @param {module:enums.publicKey} algo The public key algorithm
|
||||
* @returns {Array<Object>} The array of types
|
||||
/**
|
||||
* Parse private key material in binary form to get the key parameters
|
||||
* @param {module:enums.publicKey} algo The key algorithm
|
||||
* @param {Uint8Array} bytes The key material to parse
|
||||
* @param {Object} publicParams (ECC only) public params, needed to format some private params
|
||||
* @returns { read: Number, privateParams: Object } number of read bytes plus the key parameters referenced by name
|
||||
*/
|
||||
getPubKeyParamTypes: function(algo) {
|
||||
parsePrivateKeyParams: function(algo, bytes, publicParams) {
|
||||
let read = 0;
|
||||
switch (algo) {
|
||||
// Algorithm-Specific Fields for RSA public keys:
|
||||
// - a multiprecision integer (MPI) of RSA public modulus n;
|
||||
// - an MPI of RSA public encryption exponent e.
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
case enums.publicKey.rsaSign:
|
||||
return [type_mpi, type_mpi];
|
||||
// Algorithm-Specific Fields for Elgamal public keys:
|
||||
// - MPI of Elgamal prime p;
|
||||
// - MPI of Elgamal group generator g;
|
||||
// - MPI of Elgamal public key value y (= g**x mod p where x is secret).
|
||||
case enums.publicKey.elgamal:
|
||||
return [type_mpi, type_mpi, type_mpi];
|
||||
// Algorithm-Specific Fields for DSA public keys:
|
||||
// - MPI of DSA prime p;
|
||||
// - MPI of DSA group order q (q is a prime divisor of p-1);
|
||||
// - MPI of DSA group generator g;
|
||||
// - MPI of DSA public-key value y (= g**x mod p where x is secret).
|
||||
case enums.publicKey.rsaSign: {
|
||||
const d = util.readMPI(bytes.subarray(read)); read += d.length + 2;
|
||||
const p = util.readMPI(bytes.subarray(read)); read += p.length + 2;
|
||||
const q = util.readMPI(bytes.subarray(read)); read += q.length + 2;
|
||||
const u = util.readMPI(bytes.subarray(read)); read += u.length + 2;
|
||||
return { read, privateParams: { d, p, q, u } };
|
||||
}
|
||||
case enums.publicKey.dsa:
|
||||
return [type_mpi, type_mpi, type_mpi, type_mpi];
|
||||
// Algorithm-Specific Fields for ECDSA/EdDSA public keys:
|
||||
// - OID of curve;
|
||||
// - MPI of EC point representing public key.
|
||||
case enums.publicKey.elgamal: {
|
||||
const x = util.readMPI(bytes.subarray(read)); read += x.length + 2;
|
||||
return { read, privateParams: { x } };
|
||||
}
|
||||
case enums.publicKey.ecdsa:
|
||||
case enums.publicKey.eddsa:
|
||||
return [type_oid, type_mpi];
|
||||
// Algorithm-Specific Fields for ECDH public keys:
|
||||
// - OID of curve;
|
||||
// - MPI of EC point representing public key.
|
||||
// - KDF: variable-length field containing KDF parameters.
|
||||
case enums.publicKey.ecdh:
|
||||
return [type_oid, type_mpi, type_kdf_params];
|
||||
case enums.publicKey.ecdh: {
|
||||
const curve = new Curve(publicParams.oid);
|
||||
let d = util.readMPI(bytes.subarray(read)); read += d.length + 2;
|
||||
d = util.padToLength(d, curve.payloadSize);
|
||||
return { read, privateParams: { d } };
|
||||
}
|
||||
case enums.publicKey.eddsa: {
|
||||
let seed = util.readMPI(bytes.subarray(read)); read += seed.length + 2;
|
||||
seed = util.padToLength(seed, 32);
|
||||
return { read, privateParams: { seed } };
|
||||
}
|
||||
default:
|
||||
throw new Error('Invalid public key encryption algorithm.');
|
||||
}
|
||||
@@ -255,42 +252,60 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
/** Generate algorithm-specific key parameters
|
||||
/**
|
||||
* Convert params to MPI and serializes them in the proper order
|
||||
* @param {module:enums.publicKey} algo The public key algorithm
|
||||
* @param {Object} params The key parameters indexed by name
|
||||
* @returns {Uint8Array} The array containing the MPIs
|
||||
*/
|
||||
serializeKeyParams: function(algo, params) {
|
||||
const orderedParams = Object.keys(params).map(name => {
|
||||
const param = params[name];
|
||||
return util.isUint8Array(param) ? util.uint8ArrayToMpi(param) : param.write();
|
||||
});
|
||||
return util.concatUint8Array(orderedParams);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate algorithm-specific key parameters
|
||||
* @param {module:enums.publicKey} algo The public key algorithm
|
||||
* @param {Integer} bits Bit length for RSA keys
|
||||
* @param {module:type/oid} oid Object identifier for ECC keys
|
||||
* @returns {Array} The array of parameters
|
||||
* @returns { publicParams, privateParams: {Object} } The parameters referenced by name
|
||||
* @async
|
||||
*/
|
||||
generateParams: function(algo, bits, oid) {
|
||||
const types = [].concat(this.getPubKeyParamTypes(algo), this.getPrivKeyParamTypes(algo));
|
||||
switch (algo) {
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
case enums.publicKey.rsaSign: {
|
||||
return publicKey.rsa.generate(bits, 65537).then(function(keyObject) {
|
||||
return constructParams(
|
||||
types, [keyObject.n, keyObject.e, keyObject.d, keyObject.p, keyObject.q, keyObject.u]
|
||||
);
|
||||
});
|
||||
return publicKey.rsa.generate(bits, 65537).then(({ n, e, d, p, q, u }) => ({
|
||||
privateParams: { d, p, q, u },
|
||||
publicParams: { n, e }
|
||||
}));
|
||||
}
|
||||
case enums.publicKey.ecdsa:
|
||||
return publicKey.elliptic.generate(oid).then(({ oid, Q, secret }) => ({
|
||||
privateParams: { d: secret },
|
||||
publicParams: { oid: new OID(oid), Q }
|
||||
}));
|
||||
case enums.publicKey.eddsa:
|
||||
return publicKey.elliptic.generate(oid).then(({ oid, Q, secret }) => ({
|
||||
privateParams: { seed: secret },
|
||||
publicParams: { oid: new OID(oid), Q }
|
||||
}));
|
||||
case enums.publicKey.ecdh:
|
||||
return publicKey.elliptic.generate(oid).then(({ oid, Q, secret, hash, cipher }) => ({
|
||||
privateParams: { d: secret },
|
||||
publicParams: {
|
||||
oid: new OID(oid),
|
||||
Q,
|
||||
kdfParams: new KDFParams({ hash, cipher })
|
||||
}
|
||||
}));
|
||||
case enums.publicKey.dsa:
|
||||
case enums.publicKey.elgamal:
|
||||
throw new Error('Unsupported algorithm for key generation.');
|
||||
case enums.publicKey.ecdsa:
|
||||
case enums.publicKey.eddsa:
|
||||
return publicKey.elliptic.generate(oid).then(function (keyObject) {
|
||||
return constructParams(types, [keyObject.oid, keyObject.Q, keyObject.d]);
|
||||
});
|
||||
case enums.publicKey.ecdh:
|
||||
return publicKey.elliptic.generate(oid).then(function (keyObject) {
|
||||
return constructParams(types, [
|
||||
keyObject.oid,
|
||||
keyObject.Q,
|
||||
{ hash: keyObject.hash, cipher: keyObject.cipher },
|
||||
keyObject.d
|
||||
]);
|
||||
});
|
||||
default:
|
||||
throw new Error('Invalid public key algorithm.');
|
||||
}
|
||||
@@ -299,65 +314,43 @@ export default {
|
||||
/**
|
||||
* Validate algorithm-specific key parameters
|
||||
* @param {module:enums.publicKey} algo The public key algorithm
|
||||
* @param {Array} params The array of parameters
|
||||
* @returns {Promise<Boolean>} whether the parameters are valid
|
||||
* @param {Object} publicParams Algorithm-specific public key parameters
|
||||
* @param {Object} privateParams Algorithm-specific private key parameters
|
||||
* @returns {Promise<Boolean>} whether the parameters are valid
|
||||
* @async
|
||||
*/
|
||||
validateParams: async function(algo, params) {
|
||||
validateParams: async function(algo, publicParams, privateParams) {
|
||||
if (!publicParams || !privateParams) {
|
||||
throw new Error('Missing key parameters');
|
||||
}
|
||||
switch (algo) {
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
case enums.publicKey.rsaSign: {
|
||||
if (params.length < 6) {
|
||||
throw new Error('Missing key parameters');
|
||||
}
|
||||
const n = params[0].toUint8Array();
|
||||
const e = params[1].toUint8Array();
|
||||
const d = params[2].toUint8Array();
|
||||
const p = params[3].toUint8Array();
|
||||
const q = params[4].toUint8Array();
|
||||
const u = params[5].toUint8Array();
|
||||
const { n, e } = publicParams;
|
||||
const { d, p, q, u } = privateParams;
|
||||
return publicKey.rsa.validateParams(n, e, d, p, q, u);
|
||||
}
|
||||
case enums.publicKey.dsa: {
|
||||
if (params.length < 5) {
|
||||
throw new Error('Missing key parameters');
|
||||
}
|
||||
const p = params[0].toUint8Array();
|
||||
const q = params[1].toUint8Array();
|
||||
const g = params[2].toUint8Array();
|
||||
const y = params[3].toUint8Array();
|
||||
const x = params[4].toUint8Array();
|
||||
const { p, q, g, y } = publicParams;
|
||||
const { x } = privateParams;
|
||||
return publicKey.dsa.validateParams(p, q, g, y, x);
|
||||
}
|
||||
case enums.publicKey.elgamal: {
|
||||
if (params.length < 4) {
|
||||
throw new Error('Missing key parameters');
|
||||
}
|
||||
const p = params[0].toUint8Array();
|
||||
const g = params[1].toUint8Array();
|
||||
const y = params[2].toUint8Array();
|
||||
const x = params[3].toUint8Array();
|
||||
const { p, g, y } = publicParams;
|
||||
const { x } = privateParams;
|
||||
return publicKey.elgamal.validateParams(p, g, y, x);
|
||||
}
|
||||
case enums.publicKey.ecdsa:
|
||||
case enums.publicKey.ecdh: {
|
||||
const expectedLen = algo === enums.publicKey.ecdh ? 3 : 2;
|
||||
if (params.length < expectedLen) {
|
||||
throw new Error('Missing key parameters');
|
||||
}
|
||||
|
||||
const algoModule = publicKey.elliptic[enums.read(enums.publicKey, algo)];
|
||||
const { oid, Q, d } = algoModule.parseParams(params);
|
||||
const { oid, Q } = publicParams;
|
||||
const { d } = privateParams;
|
||||
return algoModule.validateParams(oid, Q, d);
|
||||
}
|
||||
case enums.publicKey.eddsa: {
|
||||
const expectedLen = 3;
|
||||
if (params.length < expectedLen) {
|
||||
throw new Error('Missing key parameters');
|
||||
}
|
||||
|
||||
const { oid, Q, seed } = publicKey.elliptic.eddsa.parseParams(params);
|
||||
const { oid, Q } = publicParams;
|
||||
const { seed } = privateParams;
|
||||
return publicKey.elliptic.eddsa.validateParams(oid, Q, seed);
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -36,16 +36,21 @@ export default {
|
||||
* DSA Sign function
|
||||
* @param {Integer} hash_algo
|
||||
* @param {Uint8Array} hashed
|
||||
* @param {BigInteger} g
|
||||
* @param {BigInteger} p
|
||||
* @param {BigInteger} q
|
||||
* @param {BigInteger} x
|
||||
* @returns {{ r: BigInteger, s: BigInteger }}
|
||||
* @param {Uint8Array} g
|
||||
* @param {Uint8Array} p
|
||||
* @param {Uint8Array} q
|
||||
* @param {Uint8Array} x
|
||||
* @returns {{ r: Uint8Array, s: Uint8Array }}
|
||||
* @async
|
||||
*/
|
||||
sign: async function(hash_algo, hashed, g, p, q, x) {
|
||||
const BigInteger = await util.getBigInteger();
|
||||
const one = new BigInteger(1);
|
||||
p = new BigInteger(p);
|
||||
q = new BigInteger(q);
|
||||
g = new BigInteger(g);
|
||||
x = new BigInteger(x);
|
||||
|
||||
let k;
|
||||
let r;
|
||||
let s;
|
||||
@@ -87,30 +92,37 @@ export default {
|
||||
/**
|
||||
* DSA Verify function
|
||||
* @param {Integer} hash_algo
|
||||
* @param {BigInteger} r
|
||||
* @param {BigInteger} s
|
||||
* @param {Uint8Array} r
|
||||
* @param {Uint8Array} s
|
||||
* @param {Uint8Array} hashed
|
||||
* @param {BigInteger} g
|
||||
* @param {BigInteger} p
|
||||
* @param {BigInteger} q
|
||||
* @param {BigInteger} y
|
||||
* @param {Uint8Array} g
|
||||
* @param {Uint8Array} p
|
||||
* @param {Uint8Array} q
|
||||
* @param {Uint8Array} y
|
||||
* @returns {boolean}
|
||||
* @async
|
||||
*/
|
||||
verify: async function(hash_algo, r, s, hashed, g, p, q, y) {
|
||||
const BigInteger = await util.getBigInteger();
|
||||
const zero = new BigInteger(0);
|
||||
r = new BigInteger(r);
|
||||
s = new BigInteger(s);
|
||||
|
||||
p = new BigInteger(p);
|
||||
q = new BigInteger(q);
|
||||
g = new BigInteger(g);
|
||||
y = new BigInteger(y);
|
||||
|
||||
if (r.lte(zero) || r.gte(q) ||
|
||||
s.lte(zero) || s.gte(q)) {
|
||||
util.printDebug("invalid DSA Signature");
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
const h = new BigInteger(hashed.subarray(0, q.byteLength())).imod(q);
|
||||
const w = s.modInv(q); // s**-1 mod q
|
||||
if (w.isZero()) {
|
||||
util.printDebug("invalid DSA Signature");
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
g = g.mod(p);
|
||||
|
||||
@@ -24,38 +24,55 @@
|
||||
|
||||
import util from '../../util';
|
||||
import random from '../random';
|
||||
import pkcs1 from '../pkcs1';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* ElGamal Encryption function
|
||||
* @param {BigInteger} m
|
||||
* @param {BigInteger} p
|
||||
* @param {BigInteger} g
|
||||
* @param {BigInteger} y
|
||||
* @returns {{ c1: BigInteger, c2: BigInteger }}
|
||||
* Note that in OpenPGP, the message needs to be padded with PKCS#1 (same as RSA)
|
||||
* @param {Uint8Array} data to be padded and encrypted
|
||||
* @param {Uint8Array} p
|
||||
* @param {Uint8Array} g
|
||||
* @param {Uint8Array} y
|
||||
* @returns {{ c1: Uint8Array, c2: Uint8Array }}
|
||||
* @async
|
||||
*/
|
||||
encrypt: async function(m, p, g, y) {
|
||||
encrypt: async function(data, p, g, y) {
|
||||
const BigInteger = await util.getBigInteger();
|
||||
// See Section 11.5 here: https://crypto.stanford.edu/~dabo/cryptobook/BonehShoup_0_4.pdf
|
||||
const k = await random.getRandomBigInteger(new BigInteger(0), p); // returns in [0, p-1]
|
||||
p = new BigInteger(p);
|
||||
g = new BigInteger(g);
|
||||
y = new BigInteger(y);
|
||||
|
||||
const padded = await pkcs1.eme.encode(data, p.byteLength());
|
||||
const m = new BigInteger(padded);
|
||||
|
||||
// OpenPGP uses a "special" version of ElGamal where g is generator of the full group Z/pZ*
|
||||
// hence g has order p-1, and to avoid that k = 0 mod p-1, we need to pick k in [1, p-2]
|
||||
const k = await random.getRandomBigInteger(new BigInteger(1), p.dec());
|
||||
return {
|
||||
c1: g.modExp(k, p),
|
||||
c2: y.modExp(k, p).imul(m).imod(p)
|
||||
c1: g.modExp(k, p).toUint8Array(),
|
||||
c2: y.modExp(k, p).imul(m).imod(p).toUint8Array()
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* ElGamal Encryption function
|
||||
* @param {BigInteger} c1
|
||||
* @param {BigInteger} c2
|
||||
* @param {BigInteger} p
|
||||
* @param {BigInteger} x
|
||||
* @returns BigInteger
|
||||
* @param {Uint8Array} c1
|
||||
* @param {Uint8Array} c2
|
||||
* @param {Uint8Array} p
|
||||
* @param {Uint8Array} x
|
||||
* @returns {Uint8Array} unpadded message
|
||||
* @async
|
||||
*/
|
||||
decrypt: async function(c1, c2, p, x) {
|
||||
return c1.modExp(x, p).modInv(p).imul(c2).imod(p);
|
||||
const BigInteger = await util.getBigInteger();
|
||||
c1 = new BigInteger(c1);
|
||||
c2 = new BigInteger(c2);
|
||||
p = new BigInteger(p);
|
||||
x = new BigInteger(x);
|
||||
|
||||
const padded = c1.modExp(x, p).modInv(p).imul(c2).imod(p);
|
||||
return pkcs1.eme.decode(padded.toUint8Array('be', p.byteLength()));
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -214,10 +214,12 @@ async function generate(curve) {
|
||||
|
||||
curve = new Curve(curve);
|
||||
const keyPair = await curve.genKeyPair();
|
||||
const Q = new BigInteger(keyPair.publicKey).toUint8Array();
|
||||
const secret = new BigInteger(keyPair.privateKey).toUint8Array('be', curve.payloadSize);
|
||||
return {
|
||||
oid: curve.oid,
|
||||
Q: new BigInteger(keyPair.publicKey),
|
||||
d: new BigInteger(keyPair.privateKey),
|
||||
Q,
|
||||
secret,
|
||||
hash: curve.hash,
|
||||
cipher: curve.cipher
|
||||
};
|
||||
|
||||
@@ -37,6 +37,8 @@ import random from '../../random';
|
||||
import hash from '../../hash';
|
||||
import enums from '../../../enums';
|
||||
import util from '../../../util';
|
||||
import pkcs5 from '../../pkcs5';
|
||||
import MPI from '../../../type/mpi';
|
||||
import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
@@ -65,31 +67,6 @@ function buildEcdhParam(public_algo, oid, kdfParams, fingerprint) {
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses MPI params and returns them as byte arrays of fixed length
|
||||
* @param {Array} params key parameters
|
||||
* @returns {Object} parameters in the form
|
||||
* { oid, kdfParams, d: Uint8Array, Q: Uint8Array }
|
||||
*/
|
||||
function parseParams(params) {
|
||||
if (params.length < 3 || params.length > 4) {
|
||||
throw new Error('Unexpected number of parameters');
|
||||
}
|
||||
|
||||
const oid = params[0];
|
||||
const curve = new Curve(oid);
|
||||
const parsedParams = { oid };
|
||||
// The public point never has leading zeros, as it is prefixed by 0x40 or 0x04
|
||||
parsedParams.Q = params[1].toUint8Array();
|
||||
parsedParams.kdfParams = params[2];
|
||||
|
||||
if (params.length === 4) {
|
||||
parsedParams.d = params[3].toUint8Array('be', curve.payloadSize);
|
||||
}
|
||||
|
||||
return parsedParams;
|
||||
}
|
||||
|
||||
// Key Derivation Function (RFC 6637)
|
||||
async function kdf(hash_algo, X, length, param, stripLeading = false, stripTrailing = false) {
|
||||
// Note: X is little endian for Curve25519, big-endian for all others.
|
||||
@@ -151,13 +128,15 @@ async function genPublicEphemeralKey(curve, Q) {
|
||||
*
|
||||
* @param {module:type/oid} oid Elliptic curve object identifier
|
||||
* @param {module:type/kdf_params} kdfParams KDF params including cipher and algorithm to use
|
||||
* @param {module:type/mpi} m Value derived from session key (RFC 6637)
|
||||
* @param {Uint8Array} data Unpadded session key data
|
||||
* @param {Uint8Array} Q Recipient public key
|
||||
* @param {Uint8Array} fingerprint Recipient fingerprint
|
||||
* @returns {Promise<{publicKey: Uint8Array, wrappedKey: Uint8Array}>}
|
||||
* @async
|
||||
*/
|
||||
async function encrypt(oid, kdfParams, m, Q, fingerprint) {
|
||||
async function encrypt(oid, kdfParams, data, Q, fingerprint) {
|
||||
const m = new MPI(pkcs5.encode(data));
|
||||
|
||||
const curve = new Curve(oid);
|
||||
const { publicKey, sharedKey } = await genPublicEphemeralKey(curve, Q);
|
||||
const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint);
|
||||
@@ -214,12 +193,10 @@ async function genPrivateEphemeralKey(curve, V, Q, d) {
|
||||
* @param {Uint8Array} Q Recipient public key
|
||||
* @param {Uint8Array} d Recipient private key
|
||||
* @param {Uint8Array} fingerprint Recipient fingerprint
|
||||
* @returns {Promise<BigInteger>} Value derived from session key
|
||||
* @returns {Promise<Uint8Array>} Value derived from session key
|
||||
* @async
|
||||
*/
|
||||
async function decrypt(oid, kdfParams, V, C, Q, d, fingerprint) {
|
||||
const BigInteger = await util.getBigInteger();
|
||||
|
||||
const curve = new Curve(oid);
|
||||
const { sharedKey } = await genPrivateEphemeralKey(curve, V, Q, d);
|
||||
const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint);
|
||||
@@ -229,7 +206,7 @@ async function decrypt(oid, kdfParams, V, C, Q, d, fingerprint) {
|
||||
try {
|
||||
// Work around old go crypto bug and old OpenPGP.js bug, respectively.
|
||||
const Z = await kdf(kdfParams.hash, sharedKey, cipher[cipher_algo].keySize, param, i === 1, i === 2);
|
||||
return new BigInteger(aes_kw.unwrap(Z, C));
|
||||
return pkcs5.decode(aes_kw.unwrap(Z, C));
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
@@ -411,4 +388,4 @@ async function nodePublicEphemeralKey(curve, Q) {
|
||||
return { publicKey, sharedKey };
|
||||
}
|
||||
|
||||
export default { encrypt, decrypt, genPublicEphemeralKey, genPrivateEphemeralKey, buildEcdhParam, kdf, webPublicEphemeralKey, webPrivateEphemeralKey, ellipticPublicEphemeralKey, ellipticPrivateEphemeralKey, nodePublicEphemeralKey, nodePrivateEphemeralKey, validateParams, parseParams };
|
||||
export default { encrypt, decrypt, genPublicEphemeralKey, genPrivateEphemeralKey, buildEcdhParam, kdf, webPublicEphemeralKey, webPrivateEphemeralKey, ellipticPublicEphemeralKey, ellipticPrivateEphemeralKey, nodePublicEphemeralKey, nodePrivateEphemeralKey, validateParams };
|
||||
|
||||
@@ -152,31 +152,7 @@ async function validateParams(oid, Q, d) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses MPI params and returns them as byte arrays of fixed length
|
||||
* @param {Array} params key parameters
|
||||
* @returns {Object} parameters in the form
|
||||
* { oid, d: Uint8Array, Q: Uint8Array }
|
||||
*/
|
||||
function parseParams(params) {
|
||||
if (params.length < 2 || params.length > 3) {
|
||||
throw new Error('Unexpected number of parameters');
|
||||
}
|
||||
|
||||
const oid = params[0];
|
||||
const curve = new Curve(oid);
|
||||
const parsedParams = { oid };
|
||||
// The public point never has leading zeros, as it is prefixed by 0x40 or 0x04
|
||||
parsedParams.Q = params[1].toUint8Array();
|
||||
if (params.length === 3) {
|
||||
parsedParams.d = params[2].toUint8Array('be', curve.payloadSize);
|
||||
}
|
||||
|
||||
return parsedParams;
|
||||
}
|
||||
|
||||
|
||||
export default { sign, verify, ellipticVerify, ellipticSign, validateParams, parseParams };
|
||||
export default { sign, verify, ellipticVerify, ellipticSign, validateParams };
|
||||
|
||||
|
||||
//////////////////////////
|
||||
|
||||
@@ -92,28 +92,4 @@ async function validateParams(oid, Q, k) {
|
||||
return util.equalsUint8Array(Q, dG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses MPI params and returns them as byte arrays of fixed length
|
||||
* @param {Array} params key parameters
|
||||
* @returns {Object} parameters in the form
|
||||
* { oid, seed: Uint8Array, Q: Uint8Array }
|
||||
*/
|
||||
function parseParams(params) {
|
||||
if (params.length < 2 || params.length > 3) {
|
||||
throw new Error('Unexpected number of parameters');
|
||||
}
|
||||
|
||||
const parsedParams = {
|
||||
oid: params[0],
|
||||
Q: params[1].toUint8Array('be', 33)
|
||||
};
|
||||
|
||||
if (params.length === 3) {
|
||||
parsedParams.seed = params[2].toUint8Array('be', 32);
|
||||
}
|
||||
|
||||
return parsedParams;
|
||||
}
|
||||
|
||||
|
||||
export default { sign, verify, validateParams, parseParams };
|
||||
export default { sign, verify, validateParams };
|
||||
|
||||
@@ -171,14 +171,13 @@ export default {
|
||||
* @param {Integer} bits RSA bit length
|
||||
* @param {Integer} e RSA public exponent
|
||||
* @returns {{n, e, d,
|
||||
* p, q ,u: BigInteger}} RSA public modulus, RSA public exponent, RSA private exponent,
|
||||
* RSA private prime p, RSA private prime q, u = q ** 1 mod p
|
||||
* p, q ,u: Uint8Array}} RSA public modulus, RSA public exponent, RSA private exponent,
|
||||
* RSA private prime p, RSA private prime q, u = p ** -1 mod q
|
||||
* @async
|
||||
*/
|
||||
generate: async function(bits, e) {
|
||||
const BigInteger = await util.getBigInteger();
|
||||
|
||||
let key;
|
||||
e = new BigInteger(e);
|
||||
|
||||
// Native RSA keygen using Web Crypto
|
||||
@@ -221,17 +220,17 @@ export default {
|
||||
if (jwk instanceof ArrayBuffer) {
|
||||
jwk = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(jwk)));
|
||||
}
|
||||
// map JWK parameters to BN
|
||||
key = {};
|
||||
key.n = new BigInteger(util.b64ToUint8Array(jwk.n));
|
||||
key.e = e;
|
||||
key.d = new BigInteger(util.b64ToUint8Array(jwk.d));
|
||||
// switch p and q
|
||||
key.p = new BigInteger(util.b64ToUint8Array(jwk.q));
|
||||
key.q = new BigInteger(util.b64ToUint8Array(jwk.p));
|
||||
// Since p and q are switched in places, we could keep u
|
||||
key.u = new BigInteger(util.b64ToUint8Array(jwk.qi));
|
||||
return key;
|
||||
// map JWK parameters to corresponding OpenPGP names
|
||||
return {
|
||||
n: util.b64ToUint8Array(jwk.n),
|
||||
e: e.toUint8Array(),
|
||||
d: util.b64ToUint8Array(jwk.d),
|
||||
// switch p and q
|
||||
p: util.b64ToUint8Array(jwk.q),
|
||||
q: util.b64ToUint8Array(jwk.p),
|
||||
// Since p and q are switched in places, u is the inverse of jwk.q
|
||||
u: util.b64ToUint8Array(jwk.qi)
|
||||
};
|
||||
} else if (util.getNodeCrypto() && nodeCrypto.generateKeyPair && RSAPrivateKey) {
|
||||
const opts = {
|
||||
modulusLength: bits,
|
||||
@@ -246,19 +245,20 @@ export default {
|
||||
resolve(RSAPrivateKey.decode(der, 'der'));
|
||||
}
|
||||
}));
|
||||
/** PGP spec differs from DER spec, DER: `(inverse of q) mod p`, PGP: `(inverse of p) mod q`.
|
||||
/**
|
||||
* OpenPGP spec differs from DER spec, DER: `u = (inverse of q) mod p`, OpenPGP: `u = (inverse of p) mod q`.
|
||||
* @link https://tools.ietf.org/html/rfc3447#section-3.2
|
||||
* @link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-08#section-5.6.1
|
||||
*/
|
||||
return {
|
||||
n: prv.modulus,
|
||||
e: prv.publicExponent,
|
||||
d: prv.privateExponent,
|
||||
n: prv.modulus.toArrayLike(Uint8Array),
|
||||
e: prv.publicExponent.toArrayLike(Uint8Array),
|
||||
d: prv.privateExponent.toArrayLike(Uint8Array),
|
||||
// switch p and q
|
||||
p: prv.prime2,
|
||||
q: prv.prime1,
|
||||
// Since p and q are switched in places, we could keep u
|
||||
u: prv.coefficient // PGP type of u
|
||||
p: prv.prime2.toArrayLike(Uint8Array),
|
||||
q: prv.prime1.toArrayLike(Uint8Array),
|
||||
// Since p and q are switched in places, we can keep u as defined by DER
|
||||
u: prv.coefficient.toArrayLike(Uint8Array)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -271,17 +271,16 @@ export default {
|
||||
if (q.lt(p)) {
|
||||
[p, q] = [q, p];
|
||||
}
|
||||
|
||||
const phi = p.dec().imul(q.dec());
|
||||
return {
|
||||
n: p.mul(q),
|
||||
e,
|
||||
d: e.modInv(phi),
|
||||
p: p,
|
||||
q: q,
|
||||
n: p.mul(q).toUint8Array(),
|
||||
e: e.toUint8Array(),
|
||||
d: e.modInv(phi).toUint8Array(),
|
||||
p: p.toUint8Array(),
|
||||
q: q.toUint8Array(),
|
||||
// dp: d.mod(p.subn(1)),
|
||||
// dq: d.mod(q.subn(1)),
|
||||
u: p.modInv(q)
|
||||
u: p.modInv(q).toUint8Array()
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -18,45 +18,37 @@ export default {
|
||||
* See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1}
|
||||
* and {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4}
|
||||
* for public key and hash algorithms.
|
||||
* @param {module:enums.publicKey} algo Public key algorithm
|
||||
* @param {module:enums.hash} hash_algo Hash algorithm
|
||||
* @param {Array<module:type/mpi>} msg_MPIs Algorithm-specific signature parameters
|
||||
* @param {Array<module:type/mpi>} pub_MPIs Algorithm-specific public key parameters
|
||||
* @param {Uint8Array} data Data for which the signature was created
|
||||
* @param {Uint8Array} hashed The hashed data
|
||||
* @returns {Boolean} True if signature is valid
|
||||
* @param {module:enums.publicKey} algo Public key algorithm
|
||||
* @param {module:enums.hash} hash_algo Hash algorithm
|
||||
* @param {Array<module:type/mpi>} msg_MPIs Algorithm-specific signature parameters
|
||||
* @param {Object} publicParams Algorithm-specific public key parameters
|
||||
* @param {Uint8Array} data Data for which the signature was created
|
||||
* @param {Uint8Array} hashed The hashed data
|
||||
* @returns {Boolean} True if signature is valid
|
||||
* @async
|
||||
*/
|
||||
verify: async function(algo, hash_algo, msg_MPIs, pub_MPIs, data, hashed) {
|
||||
const types = crypto.getPubKeyParamTypes(algo);
|
||||
if (pub_MPIs.length < types.length) {
|
||||
throw new Error('Missing public key parameters');
|
||||
}
|
||||
verify: async function(algo, hash_algo, msg_MPIs, publicParams, data, hashed) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaSign: {
|
||||
const n = pub_MPIs[0].toUint8Array();
|
||||
const e = pub_MPIs[1].toUint8Array();
|
||||
const { n, e } = publicParams;
|
||||
const m = msg_MPIs[0].toUint8Array('be', n.length);
|
||||
return publicKey.rsa.verify(hash_algo, data, m, n, e, hashed);
|
||||
}
|
||||
case enums.publicKey.dsa: {
|
||||
const r = await msg_MPIs[0].toBigInteger();
|
||||
const s = await msg_MPIs[1].toBigInteger();
|
||||
const p = await pub_MPIs[0].toBigInteger();
|
||||
const q = await pub_MPIs[1].toBigInteger();
|
||||
const g = await pub_MPIs[2].toBigInteger();
|
||||
const y = await pub_MPIs[3].toBigInteger();
|
||||
const r = await msg_MPIs[0].toUint8Array();
|
||||
const s = await msg_MPIs[1].toUint8Array();
|
||||
const { g, p, q, y } = publicParams;
|
||||
return publicKey.dsa.verify(hash_algo, r, s, hashed, g, p, q, y);
|
||||
}
|
||||
case enums.publicKey.ecdsa: {
|
||||
const { oid, Q } = publicKey.elliptic.ecdsa.parseParams(pub_MPIs);
|
||||
const { oid, Q } = publicParams;
|
||||
const signature = { r: msg_MPIs[0].toUint8Array(), s: msg_MPIs[1].toUint8Array() };
|
||||
return publicKey.elliptic.ecdsa.verify(oid, hash_algo, signature, data, Q, hashed);
|
||||
}
|
||||
case enums.publicKey.eddsa: {
|
||||
const { oid, Q } = publicKey.elliptic.eddsa.parseParams(pub_MPIs);
|
||||
const { oid, Q } = publicParams;
|
||||
// EdDSA signature params are expected in little-endian format
|
||||
const signature = {
|
||||
R: msg_MPIs[0].toUint8Array('le', 32),
|
||||
@@ -74,37 +66,31 @@ export default {
|
||||
* See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1}
|
||||
* and {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4}
|
||||
* for public key and hash algorithms.
|
||||
* @param {module:enums.publicKey} algo Public key algorithm
|
||||
* @param {module:enums.hash} hash_algo Hash algorithm
|
||||
* @param {Array<module:type/mpi>} key_params Algorithm-specific public and private key parameters
|
||||
* @param {Uint8Array} data Data to be signed
|
||||
* @param {Uint8Array} hashed The hashed data
|
||||
* @returns {Uint8Array} Signature
|
||||
* @param {module:enums.publicKey} algo Public key algorithm
|
||||
* @param {module:enums.hash} hash_algo Hash algorithm
|
||||
* @param {Object} publicKeyParams Algorithm-specific public and private key parameters
|
||||
* @param {Object} privateKeyParams Algorithm-specific public and private key parameters
|
||||
* @param {Uint8Array} data Data to be signed
|
||||
* @param {Uint8Array} hashed The hashed data
|
||||
* @returns {Uint8Array} Signature
|
||||
* @async
|
||||
*/
|
||||
sign: async function(algo, hash_algo, key_params, data, hashed) {
|
||||
const types = [].concat(crypto.getPubKeyParamTypes(algo), crypto.getPrivKeyParamTypes(algo));
|
||||
if (key_params.length < types.length) {
|
||||
throw new Error('Missing private key parameters');
|
||||
sign: async function(algo, hash_algo, publicKeyParams, privateKeyParams, data, hashed) {
|
||||
if (!publicKeyParams || !privateKeyParams) {
|
||||
throw new Error('Missing key parameters');
|
||||
}
|
||||
switch (algo) {
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaSign: {
|
||||
const n = key_params[0].toUint8Array();
|
||||
const e = key_params[1].toUint8Array();
|
||||
const d = key_params[2].toUint8Array();
|
||||
const p = key_params[3].toUint8Array();
|
||||
const q = key_params[4].toUint8Array();
|
||||
const u = key_params[5].toUint8Array();
|
||||
const { n, e } = publicKeyParams;
|
||||
const { d, p, q, u } = privateKeyParams;
|
||||
const signature = await publicKey.rsa.sign(hash_algo, data, n, e, d, p, q, u, hashed);
|
||||
return util.uint8ArrayToMpi(signature);
|
||||
}
|
||||
case enums.publicKey.dsa: {
|
||||
const p = await key_params[0].toBigInteger();
|
||||
const q = await key_params[1].toBigInteger();
|
||||
const g = await key_params[2].toBigInteger();
|
||||
const x = await key_params[4].toBigInteger();
|
||||
const { g, p, q } = publicKeyParams;
|
||||
const { x } = privateKeyParams;
|
||||
const signature = await publicKey.dsa.sign(hash_algo, hashed, g, p, q, x);
|
||||
return util.concatUint8Array([
|
||||
util.uint8ArrayToMpi(signature.r),
|
||||
@@ -115,7 +101,8 @@ export default {
|
||||
throw new Error('Signing with Elgamal is not defined in the OpenPGP standard.');
|
||||
}
|
||||
case enums.publicKey.ecdsa: {
|
||||
const { oid, Q, d } = publicKey.elliptic.ecdsa.parseParams(key_params);
|
||||
const { oid, Q } = publicKeyParams;
|
||||
const { d } = privateKeyParams;
|
||||
const signature = await publicKey.elliptic.ecdsa.sign(oid, hash_algo, data, Q, d, hashed);
|
||||
return util.concatUint8Array([
|
||||
util.uint8ArrayToMpi(signature.r),
|
||||
@@ -123,7 +110,8 @@ export default {
|
||||
]);
|
||||
}
|
||||
case enums.publicKey.eddsa: {
|
||||
const { oid, Q, seed } = publicKey.elliptic.eddsa.parseParams(key_params);
|
||||
const { oid, Q } = publicKeyParams;
|
||||
const { seed } = privateKeyParams;
|
||||
const signature = await publicKey.elliptic.eddsa.sign(oid, hash_algo, data, Q, seed, hashed);
|
||||
return util.concatUint8Array([
|
||||
util.uint8ArrayToMpi(signature.R),
|
||||
|
||||
@@ -151,7 +151,7 @@ export async function getPreferredHashAlgo(key, keyPacket, date = new Date(), us
|
||||
case 'ecdh':
|
||||
case 'ecdsa':
|
||||
case 'eddsa':
|
||||
pref_algo = crypto.publicKey.elliptic.getPreferredHashAlgo(keyPacket.params[0]);
|
||||
pref_algo = crypto.publicKey.elliptic.getPreferredHashAlgo(keyPacket.publicParams.oid);
|
||||
}
|
||||
}
|
||||
return crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ?
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
import { Sha1 } from 'asmcrypto.js/dist_es8/hash/sha1/sha1';
|
||||
import { Sha256 } from 'asmcrypto.js/dist_es8/hash/sha256/sha256';
|
||||
import type_keyid from '../type/keyid';
|
||||
import type_mpi from '../type/mpi';
|
||||
import config from '../config';
|
||||
import crypto from '../crypto';
|
||||
import enums from '../enums';
|
||||
@@ -70,10 +69,10 @@ class PublicKeyPacket {
|
||||
*/
|
||||
this.algorithm = null;
|
||||
/**
|
||||
* Algorithm specific params
|
||||
* @type {Array<Object>}
|
||||
* Algorithm specific public params
|
||||
* @type {Object}
|
||||
*/
|
||||
this.params = [];
|
||||
this.publicParams = null;
|
||||
/**
|
||||
* Time until expiration in days (V3 only)
|
||||
* @type {Integer}
|
||||
@@ -116,16 +115,13 @@ class PublicKeyPacket {
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
// - A series of values comprising the key material. This is
|
||||
// algorithm-specific and described in section XXXX.
|
||||
const types = crypto.getPubKeyParamTypes(algo);
|
||||
this.params = crypto.constructParams(types);
|
||||
|
||||
for (let i = 0; i < types.length && pos < bytes.length; i++) {
|
||||
pos += this.params[i].read(bytes.subarray(pos, bytes.length));
|
||||
if (pos > bytes.length) {
|
||||
throw new Error('Error reading MPI @:' + pos);
|
||||
}
|
||||
// - A series of values comprising the key material.
|
||||
try {
|
||||
const { read, publicParams } = crypto.parsePublicKeyParams(algo, bytes.subarray(pos));
|
||||
this.publicParams = publicParams;
|
||||
pos += read;
|
||||
} catch (err) {
|
||||
throw new Error('Error reading MPIs');
|
||||
}
|
||||
|
||||
return pos;
|
||||
@@ -134,9 +130,8 @@ class PublicKeyPacket {
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as write_private_key, but has less information because of
|
||||
* public key.
|
||||
* @returns {Uint8Array} OpenPGP packet body contents,
|
||||
* Creates an OpenPGP public key packet for the given key.
|
||||
* @returns {Uint8Array} Bytes encoding the public key OpenPGP packet
|
||||
*/
|
||||
write() {
|
||||
const arr = [];
|
||||
@@ -147,8 +142,7 @@ class PublicKeyPacket {
|
||||
const algo = enums.write(enums.publicKey, this.algorithm);
|
||||
arr.push(new Uint8Array([algo]));
|
||||
|
||||
const paramCount = crypto.getPubKeyParamTypes(algo).length;
|
||||
const params = util.concatUint8Array(this.params.slice(0, paramCount).map(param => param.write()));
|
||||
const params = crypto.serializeKeyParams(algo, this.publicParams);
|
||||
if (this.version === 5) {
|
||||
// A four-octet scalar octet count for the following key material
|
||||
arr.push(util.writeNumber(params.length, 4));
|
||||
@@ -243,11 +237,11 @@ class PublicKeyPacket {
|
||||
getAlgorithmInfo() {
|
||||
const result = {};
|
||||
result.algorithm = this.algorithm;
|
||||
if (this.params[0] instanceof type_mpi) {
|
||||
result.rsaBits = this.params[0].byteLength() * 8;
|
||||
if (this.publicParams.n) {
|
||||
result.rsaBits = this.publicParams.n.length * 8;
|
||||
result.bits = result.rsaBits; // Deprecated.
|
||||
} else {
|
||||
result.curve = this.params[0].getName();
|
||||
result.curve = this.publicParams.oid.getName();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ class PublicKeyEncryptedSessionKeyPacket {
|
||||
]);
|
||||
const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm);
|
||||
this.encrypted = await crypto.publicKeyEncrypt(
|
||||
algo, key.params, data, key.getFingerprintBytes());
|
||||
algo, key.publicParams, data, key.getFingerprintBytes());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ class PublicKeyEncryptedSessionKeyPacket {
|
||||
if (algo !== keyAlgo) {
|
||||
throw new Error('Decryption error');
|
||||
}
|
||||
const decoded = await crypto.publicKeyDecrypt(algo, key.params, this.encrypted, key.getFingerprintBytes());
|
||||
const decoded = await crypto.publicKeyDecrypt(algo, key.publicParams, key.privateParams, this.encrypted, key.getFingerprintBytes());
|
||||
const checksum = decoded.subarray(decoded.length - 2);
|
||||
const sessionKey = decoded.subarray(1, decoded.length - 2);
|
||||
if (!util.equalsUint8Array(checksum, util.writeChecksum(sessionKey))) {
|
||||
|
||||
@@ -73,6 +73,11 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||
* @type {String}
|
||||
*/
|
||||
this.aead = null;
|
||||
/**
|
||||
* Decrypted private parameters, referenced by name
|
||||
* @type {Object}
|
||||
*/
|
||||
this.privateParams = null;
|
||||
}
|
||||
|
||||
// 5.5.3. Secret-Key Packet Formats
|
||||
@@ -154,8 +159,13 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||
if (!util.equalsUint8Array(util.writeChecksum(cleartext), this.keyMaterial.subarray(-2))) {
|
||||
throw new Error('Key checksum mismatch');
|
||||
}
|
||||
const privParams = parse_cleartext_params(cleartext, this.algorithm);
|
||||
this.params = this.params.concat(privParams);
|
||||
try {
|
||||
const algo = enums.write(enums.publicKey, this.algorithm);
|
||||
const { privateParams } = crypto.parsePrivateKeyParams(algo, cleartext, this.publicParams);
|
||||
this.privateParams = privateParams;
|
||||
} catch (err) {
|
||||
throw new Error('Error reading MPIs');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +210,8 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||
|
||||
if (!this.isDummy()) {
|
||||
if (!this.s2k_usage) {
|
||||
const cleartextParams = write_cleartext_params(this.params, this.algorithm);
|
||||
const algo = enums.write(enums.publicKey, this.algorithm);
|
||||
const cleartextParams = crypto.serializeKeyParams(algo, this.privateParams);
|
||||
this.keyMaterial = util.concatUint8Array([
|
||||
cleartextParams,
|
||||
util.writeChecksum(cleartextParams)
|
||||
@@ -282,7 +293,8 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||
|
||||
this.s2k = new type_s2k();
|
||||
this.s2k.salt = await crypto.random.getRandomBytes(8);
|
||||
const cleartext = write_cleartext_params(this.params, this.algorithm);
|
||||
const algo = enums.write(enums.publicKey, this.algorithm);
|
||||
const cleartext = crypto.serializeKeyParams(algo, this.privateParams);
|
||||
this.symmetric = 'aes256';
|
||||
const key = await produceEncryptionKey(this.s2k, passphrase, this.symmetric);
|
||||
const blockLen = crypto.cipher[this.symmetric].blockSize;
|
||||
@@ -354,8 +366,13 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||
}
|
||||
}
|
||||
|
||||
const privParams = parse_cleartext_params(cleartext, this.algorithm);
|
||||
this.params = this.params.concat(privParams);
|
||||
try {
|
||||
const algo = enums.write(enums.publicKey, this.algorithm);
|
||||
const { privateParams } = crypto.parsePrivateKeyParams(algo, cleartext, this.publicParams);
|
||||
this.privateParams = privateParams;
|
||||
} catch (err) {
|
||||
throw new Error('Error reading MPIs');
|
||||
}
|
||||
this.isEncrypted = false;
|
||||
this.keyMaterial = null;
|
||||
this.s2k_usage = 0;
|
||||
@@ -378,7 +395,14 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||
}
|
||||
|
||||
const algo = enums.write(enums.publicKey, this.algorithm);
|
||||
const validParams = await crypto.validateParams(algo, this.params);
|
||||
|
||||
let validParams;
|
||||
try {
|
||||
// this can throw if some parameters are undefined
|
||||
validParams = await crypto.validateParams(algo, this.publicParams, this.privateParams);
|
||||
} catch (_) {
|
||||
validParams = false;
|
||||
}
|
||||
if (!validParams) {
|
||||
throw new Error('Key is invalid');
|
||||
}
|
||||
@@ -387,7 +411,9 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||
|
||||
async generate(bits, curve) {
|
||||
const algo = enums.write(enums.publicKey, this.algorithm);
|
||||
this.params = await crypto.generateParams(algo, bits, curve);
|
||||
const { privateParams, publicParams } = await crypto.generateParams(algo, bits, curve);
|
||||
this.privateParams = privateParams;
|
||||
this.publicParams = publicParams;
|
||||
this.isEncrypted = false;
|
||||
}
|
||||
|
||||
@@ -400,47 +426,16 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||
return;
|
||||
}
|
||||
|
||||
const algo = enums.write(enums.publicKey, this.algorithm);
|
||||
const publicParamCount = crypto.getPubKeyParamTypes(algo).length;
|
||||
this.params.slice(publicParamCount).forEach(param => {
|
||||
param.data.fill(0);
|
||||
Object.keys(this.privateParams).forEach(name => {
|
||||
const param = this.privateParams[name];
|
||||
param.fill(0);
|
||||
delete this.privateParams[name];
|
||||
});
|
||||
this.params.length = publicParamCount;
|
||||
this.privateParams = null;
|
||||
this.isEncrypted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function
|
||||
|
||||
function parse_cleartext_params(cleartext, algorithm) {
|
||||
const algo = enums.write(enums.publicKey, algorithm);
|
||||
const types = crypto.getPrivKeyParamTypes(algo);
|
||||
const params = crypto.constructParams(types);
|
||||
let p = 0;
|
||||
|
||||
for (let i = 0; i < types.length && p < cleartext.length; i++) {
|
||||
p += params[i].read(cleartext.subarray(p, cleartext.length));
|
||||
if (p > cleartext.length) {
|
||||
throw new Error('Error reading param @:' + p);
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function write_cleartext_params(params, algorithm) {
|
||||
const arr = [];
|
||||
const algo = enums.write(enums.publicKey, algorithm);
|
||||
const numPublicParams = crypto.getPubKeyParamTypes(algo).length;
|
||||
|
||||
for (let i = numPublicParams; i < params.length; i++) {
|
||||
arr.push(params[i].write());
|
||||
}
|
||||
|
||||
return util.concatUint8Array(arr);
|
||||
}
|
||||
|
||||
|
||||
async function produceEncryptionKey(s2k, passphrase, algorithm) {
|
||||
return s2k.produce_key(
|
||||
passphrase,
|
||||
|
||||
@@ -177,9 +177,8 @@ class SignaturePacket {
|
||||
const hash = await this.hash(signatureType, data, toHash, detached);
|
||||
|
||||
this.signedHashValue = stream.slice(stream.clone(hash), 0, 2);
|
||||
const params = key.params;
|
||||
const signed = async () => crypto.signature.sign(
|
||||
publicKeyAlgorithm, hashAlgorithm, params, toHash, await stream.readToEnd(hash)
|
||||
publicKeyAlgorithm, hashAlgorithm, key.publicParams, key.privateParams, toHash, await stream.readToEnd(hash)
|
||||
);
|
||||
if (streaming) {
|
||||
this.signature = stream.fromAsync(signed);
|
||||
@@ -714,7 +713,7 @@ class SignaturePacket {
|
||||
i += mpi[j].read(this.signature.subarray(i, this.signature.length), endian);
|
||||
}
|
||||
const verified = await crypto.signature.verify(
|
||||
publicKeyAlgorithm, hashAlgorithm, mpi, key.params,
|
||||
publicKeyAlgorithm, hashAlgorithm, mpi, key.publicParams,
|
||||
toHash, hash
|
||||
);
|
||||
if (!verified) {
|
||||
|
||||
25
src/util.js
25
src/util.js
@@ -167,6 +167,31 @@ export default {
|
||||
return str;
|
||||
},
|
||||
|
||||
/**
|
||||
* Read one MPI from bytes in input
|
||||
* @param {Uint8Array} bytes input data to parse
|
||||
* @returns {Uint8Array} parsed MPI
|
||||
*/
|
||||
readMPI: function (bytes) {
|
||||
const bits = (bytes[0] << 8) | bytes[1];
|
||||
const bytelen = (bits + 7) >>> 3;
|
||||
return bytes.subarray(2, 2 + bytelen);
|
||||
},
|
||||
|
||||
/**
|
||||
* Pad Uint8Array to length by adding 0x0 bytes
|
||||
* @param {Uint8Array} bytes data to pad
|
||||
* @param {Number} length padded length
|
||||
* @param {'be'|'le'} endianess endianess of input data
|
||||
* @return {Uint8Array} padded bytes
|
||||
*/
|
||||
padToLength(bytes, length, endianess = 'be') {
|
||||
const padded = new Uint8Array(length);
|
||||
const offset = (endianess === 'be') ? 0 : (length - bytes.length);
|
||||
padded.set(bytes, offset);
|
||||
return padded;
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a Uint8Array to an MPI-formatted Uint8Array.
|
||||
* Note: the output is **not** an MPI object.
|
||||
|
||||
Reference in New Issue
Block a user