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:
larabr
2020-09-04 14:23:17 +02:00
committed by Daniel Huigens
parent 8854b097b4
commit 3a75eadaa0
24 changed files with 634 additions and 776 deletions

View File

@@ -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:

View File

@@ -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);

View File

@@ -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()));
},
/**

View File

@@ -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
};

View File

@@ -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 };

View File

@@ -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 };
//////////////////////////

View File

@@ -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 };

View File

@@ -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()
};
},

View File

@@ -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),

View File

@@ -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) ?

View File

@@ -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;
}

View File

@@ -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))) {

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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.