refactor mpi, generalize mpi to params to account for non-mpi algorithm-specific data

This commit is contained in:
Sanjana Rajan 2017-07-26 23:11:50 +02:00
parent 840c0229f8
commit cdc7004b96
9 changed files with 150 additions and 137 deletions

View File

@ -38,14 +38,26 @@ import type_kdf_params from '../type/kdf_params.js';
import type_mpi from '../type/mpi.js';
import type_oid from '../type/oid.js';
function BigInteger2mpi(bn) {
var mpi = new type_mpi();
mpi.fromBigInteger(bn);
return mpi;
function createType(data, type) {
switch(type) {
case 'mpi':
return new type_mpi(data);
case 'oid':
return new type_oid(data);
case 'kdf':
return new type_kdf_params(data);
case 'ecdh_symkey':
return new type_ecdh_symkey(data);
default:
return null;
}
}
function mapResult(result) {
return result.map(BigInteger2mpi);
function mapResult(result, types) {
for (var i=0; i < result.length; i++) {
result[i] = createType(result[i], types[i]);
}
return result;
}
export default {
@ -53,38 +65,39 @@ export default {
* Encrypts data using the specified public key multiprecision integers
* and the specified algorithm.
* @param {module:enums.publicKey} algo Algorithm to be used (See {@link http://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1})
* @param {Array<module:type/mpi>} publicMPIs Algorithm dependent multiprecision integers
* @param {Array<module:type/mpi>} publicParams Algorithm dependent multiprecision integers
* @param {module:type/mpi} data Data to be encrypted as MPI
* @return {Array<module:type/mpi>} if RSA an module:type/mpi;
* if elgamal encryption an array of two module:type/mpi is returned; otherwise null
*/
publicKeyEncrypt: function(algo, publicMPIs, data, fingerprint) {
publicKeyEncrypt: function(algo, publicParams, data, fingerprint) {
var types = this.getEncSessionKeyParamTypes(algo);
var result = (function() {
var m;
switch (algo) {
case 'rsa_encrypt':
case 'rsa_encrypt_sign':
var rsa = new publicKey.rsa();
var n = publicMPIs[0].toBigInteger();
var e = publicMPIs[1].toBigInteger();
var n = publicParams[0].toBigInteger();
var e = publicParams[1].toBigInteger();
m = data.toBigInteger();
return mapResult([rsa.encrypt(m, e, n)]);
return mapResult([rsa.encrypt(m, e, n)], types);
case 'elgamal':
var elgamal = new publicKey.elgamal();
var p = publicMPIs[0].toBigInteger();
var g = publicMPIs[1].toBigInteger();
var y = publicMPIs[2].toBigInteger();
var p = publicParams[0].toBigInteger();
var g = publicParams[1].toBigInteger();
var y = publicParams[2].toBigInteger();
m = data.toBigInteger();
return mapResult(elgamal.encrypt(m, g, p, y));
return mapResult(elgamal.encrypt(m, g, p, y), types);
case 'ecdh':
var ecdh = publicKey.elliptic.ecdh;
var curve = publicMPIs[0];
var kdf_params = publicMPIs[2];
var R = publicMPIs[1].toBigInteger();
var curve = publicParams[0];
var kdf_params = publicParams[2];
var R = publicParams[1].toBigInteger();
var res = ecdh.encrypt(curve.oid, kdf_params.cipher, kdf_params.hash, data, R, fingerprint);
return [BigInteger2mpi(res.V), new type_ecdh_symkey(res.C)];
return mapResult([res.V, res.C], types);
default:
return [];
@ -98,7 +111,7 @@ export default {
* Decrypts data using the specified public key multiprecision integers of the private key,
* the specified secretMPIs of the private key and the specified algorithm.
* @param {module:enums.publicKey} algo Algorithm to be used (See {@link http://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1})
* @param {Array<module:type/mpi>} publicMPIs Algorithm dependent multiprecision integers
* @param {Array<module:type/mpi>} publicParams Algorithm dependent multiprecision integers
* of the public key part of the private key
* @param {Array<module:type/mpi>} secretMPIs Algorithm dependent multiprecision integers
* of the private key used
@ -146,8 +159,7 @@ export default {
}
})();
var result = new type_mpi();
result.fromBigInteger(bn);
var result = new type_mpi(bn);
return result;
},
@ -155,7 +167,7 @@ export default {
* @param {String} algo The public key algorithm
* @return {Integer} The number of integers.
*/
getPrivateMpiCount: function(algo) {
getPrivKeyParamTypes: function(algo) {
switch (algo) {
case 'rsa_encrypt':
case 'rsa_encrypt_sign':
@ -165,27 +177,30 @@ export default {
// - 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.
return 4;
return ['mpi', 'mpi', 'mpi', 'mpi'];
case 'elgamal':
// Algorithm-Specific Fields for Elgamal secret keys:
// - MPI of Elgamal secret exponent x.
return 1;
return ['mpi'];
case 'dsa':
// Algorithm-Specific Fields for DSA secret keys:
// - MPI of DSA secret exponent x.
return 1;
return ['mpi'];
case 'ecdh':
case 'ecdsa':
// Algorithm-Specific Fields for ECDSA or ECDH secret keys:
// - MPI of an integer representing the secret key.
return 1;
return ['mpi'];
default:
throw new Error('Unknown algorithm');
}
},
getPublicMpiCount: function(algo) {
// - A series of multiprecision integers comprising the key material:
getPrivKeyParamCount: function(algo) {
return this.getPrivKeyParamTypes(algo).length;
},
getPubKeyParamTypes: function(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.
@ -193,42 +208,71 @@ export default {
case 'rsa_encrypt':
case 'rsa_encrypt_sign':
case 'rsa_sign':
return 2;
return ['mpi', '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 'elgamal':
return 3;
return ['mpi', 'mpi', '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 'dsa':
return 4;
return ['mpi', 'mpi', 'mpi', 'mpi'];
// Algorithm-Specific Fields for ECDSA public keys:
// - OID of curve;
// - MPI of EC point representing public key.
case 'ecdsa':
return 2;
return ['oid', 'mpi'];
// Algorithm-Specific Fields for ECDH public keys:
// - OID of curve;
// - MPI of EC point representing public key.
// - variable-length field containing KDF parameters.
case 'ecdh':
return 3;
return ['oid', 'mpi', 'kdf'];
default:
throw new Error('Unknown algorithm.');
}
},
getPubKeyParamCount: function(algo) {
return this.getPubKeyParamTypes(algo).length;
},
getEncSessionKeyParamTypes: function(algo) {
switch (algo) {
// Algorithm-Specific Fields for RSA encrypted session keys:
// - MPI of RSA encrypted value m**e mod n.
case 'rsa_encrypt':
case 'rsa_encrypt_sign':
return ['mpi'];
// Algorithm-Specific Fields for Elgamal encrypted session keys:
// - MPI of Elgamal value g**k mod p
// - MPI of Elgamal value m * y**k mod p
case 'elgamal':
return ['mpi', 'mpi'];
// Algorithm-Specific Fields for ECDH encrypted session keys:
// - MPI containing the ephemeral key used to establish the shared secret
// - EcdhSymmetricKey
case 'ecdh':
return ['mpi', 'ecdh_symkey'];
default:
throw new Error('Unknown algorithm.');
}
},
generateMpi: function(algo, bits, curve) {
getEncSessionKeyParamCount: function(algo) {
return this.getEncSessionKeyParamTypes(algo).length;
},
generateParams: function(algo, bits, curve) {
var types = this.getPubKeyParamTypes(algo).concat(this.getPrivKeyParamTypes(algo));
switch (algo) {
case 'rsa_encrypt':
case 'rsa_encrypt_sign':
@ -236,33 +280,17 @@ export default {
//remember "publicKey" refers to the crypto/public_key dir
var rsa = new publicKey.rsa();
return rsa.generate(bits, "10001").then(function(keyObject) {
var output = [];
output.push(keyObject.n);
output.push(keyObject.ee);
output.push(keyObject.d);
output.push(keyObject.p);
output.push(keyObject.q);
output.push(keyObject.u);
return mapResult(output);
return mapResult([keyObject.n, keyObject.ee, keyObject.d, keyObject.p, keyObject.q, keyObject.u], types);
});
case 'ecdsa':
return publicKey.elliptic.generate(curve).then(function (keyObject) {
return [
new type_oid(keyObject.oid),
BigInteger2mpi(keyObject.R),
BigInteger2mpi(keyObject.r)
];
return mapResult([keyObject.oid, keyObject.R, keyObject.r], types);
});
case 'ecdh':
return publicKey.elliptic.generate(curve).then(function (keyObject) {
return [
new type_oid(keyObject.oid),
BigInteger2mpi(keyObject.R),
new type_kdf_params(keyObject.hash, keyObject.cipher),
BigInteger2mpi(keyObject.r)
];
return mapResult([keyObject.oid, keyObject.R, [keyObject.hash, keyObject.cipher], keyObject.r], types);
});
default:

View File

@ -113,8 +113,7 @@ export default {
randomBits.charCodeAt(0)) +
randomBits.substring(1);
}
var mpi = new type_mpi();
mpi.fromBytes(randomBits);
var mpi = new type_mpi(randomBits);
return mpi.toBigInteger();
},

View File

@ -380,7 +380,7 @@ Key.prototype.encrypt = function(passphrase) {
var keys = this.getAllKeyPackets();
for (var i = 0; i < keys.length; i++) {
keys[i].encrypt(passphrase);
keys[i].clearPrivateMPIs();
keys[i].clearPrivateParams();
}
};
@ -1279,8 +1279,8 @@ function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) {
packetlist.push(subkeySignaturePacket);
if (!options.unlocked) {
secretKeyPacket.clearPrivateMPIs();
secretSubkeyPacket.clearPrivateMPIs();
secretKeyPacket.clearPrivateParams();
secretSubkeyPacket.clearPrivateParams();
}
return new Key(packetlist);

View File

@ -51,12 +51,8 @@ export default function PublicKey() {
/** Key creation date.
* @type {Date} */
this.created = new Date();
/** A list of multiprecision integers
* @type {module:type/mpi} */
this.mpi = [];
/** Public key algorithm
* @type {module:enums.publicKey} */
this.algorithm = 'rsa_sign';
/* Algorithm specific params */
this.params = [];
// time in days (V3 only)
this.expirationTimeV3 = 0;
/**
@ -97,22 +93,22 @@ PublicKey.prototype.read = function (bytes) {
// - A one-octet number denoting the public-key algorithm of this key.
this.algorithm = enums.read(enums.publicKey, bytes[pos++]);
var mpicount = crypto.getPublicMpiCount(this.algorithm);
this.mpi = [];
var param_count = crypto.getPubKeyParamCount(this.algorithm);
this.params = [];
var bmpi = bytes.subarray(pos, bytes.length);
var p = 0;
for (var i = 0; i < mpicount && p < bmpi.length; i++) {
for (var i = 0; i < param_count && p < bmpi.length; i++) {
if ((this.algorithm === 'ecdsa' || this.algorithm === 'ecdh') && i === 0) {
this.mpi[i] = new type_oid();
this.params[i] = new type_oid();
} else if (this.algorithm === 'ecdh' && i === 2) {
this.mpi[i] = new type_kdf_params();
this.params[i] = new type_kdf_params();
} else {
this.mpi[i] = new type_mpi();
this.params[i] = new type_mpi();
}
p += this.mpi[i].read(bmpi.subarray(p, bmpi.length));
p += this.params[i].read(bmpi.subarray(p, bmpi.length));
if (p > bmpi.length) {
throw new Error('Error reading MPI @:' + p);
@ -147,10 +143,10 @@ PublicKey.prototype.write = function () {
}
arr.push(new Uint8Array([enums.write(enums.publicKey, this.algorithm)]));
var mpicount = crypto.getPublicMpiCount(this.algorithm);
var param_count = crypto.getPubKeyParamCount(this.algorithm);
for (var i = 0; i < mpicount; i++) {
arr.push(this.mpi[i].write());
for (var i = 0; i < param_count; i++) {
arr.push(this.params[i].write());
}
return util.concatUint8Array(arr);
@ -183,7 +179,7 @@ PublicKey.prototype.getKeyId = function () {
if (this.version === 4) {
this.keyid.read(util.str2Uint8Array(util.hex2bin(this.getFingerprint()).substr(12, 8)));
} else if (this.version === 3) {
var arr = this.mpi[0].write();
var arr = this.params[0].write();
this.keyid.read(arr.subarray(arr.length - 8, arr.length));
}
return this.keyid;
@ -202,9 +198,9 @@ PublicKey.prototype.getFingerprint = function () {
toHash = this.writeOld();
this.fingerprint = util.Uint8Array2str(crypto.hash.sha1(toHash));
} else if (this.version === 3) {
var mpicount = crypto.getPublicMpiCount(this.algorithm);
for (var i = 0; i < mpicount; i++) {
toHash += this.mpi[i].toBytes();
var param_count = crypto.getPubKeyParamCount(this.algorithm);
for (var i = 0; i < param_count; i++) {
toHash += this.params[i].toBytes();
}
this.fingerprint = util.Uint8Array2str(crypto.hash.md5(util.str2Uint8Array(toHash)));
}
@ -217,15 +213,15 @@ PublicKey.prototype.getFingerprint = function () {
* @return {int} Number of bits
*/
PublicKey.prototype.getBitSize = function () {
return this.mpi[0].byteLength() * 8;
return this.params[0].byteLength() * 8;
};
/**
* Fix custom types after cloning
*/
PublicKey.prototype.postCloneTypeFix = function() {
for (var i = 0; i < this.mpi.length; i++) {
this.mpi[i] = type_mpi.fromClone(this.mpi[i]);
for (var i = 0; i < this.params.length; i++) {
this.params[i] = type_mpi.fromClone(this.params[i]);
}
if (this.keyid) {
this.keyid = type_keyid.fromClone(this.keyid);

View File

@ -55,10 +55,7 @@ export default function PublicKeyEncryptedSessionKey() {
this.version = 3;
this.publicKeyId = new type_keyid();
this.publicKeyAlgorithm = 'rsa_encrypt';
this.sessionKey = null;
this.sessionKeyAlgorithm = 'aes256';
/** @type {Array<module:type/mpi>} */
this.encrypted = [];
@ -81,34 +78,20 @@ PublicKeyEncryptedSessionKey.prototype.read = function (bytes) {
var i = 10;
var integerCount = (function(algo) {
switch (algo) {
case 'rsa_encrypt':
case 'rsa_encrypt_sign':
return 1;
var integerCount = crypto.getEncSessionKeyParamCount(this.publicKeyAlgorithm);
case 'elgamal':
return 2;
case 'ecdh':
return 2;
default:
throw new Error("Invalid algorithm.");
}
})(this.publicKeyAlgorithm);
this.encrypted = [];
for (var j = 0; j < integerCount; j++) {
var mpi;
var param;
if (this.publicKeyAlgorithm === 'ecdh' && j === 1) {
mpi = new type_ecdh_symkey();
param = new type_ecdh_symkey();
} else {
mpi = new type_mpi();
param = new type_mpi();
}
i += mpi.read(bytes.subarray(i, bytes.length));
this.encrypted.push(mpi);
i += param.read(bytes.subarray(i, bytes.length));
this.encrypted.push(param);
}
};
@ -136,20 +119,20 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = function (key) {
var checksum = util.calc_checksum(this.sessionKey);
data += util.Uint8Array2str(util.writeNumber(checksum, 2));
var mpi;
var param;
if (this.publicKeyAlgorithm === 'ecdh') {
mpi = util.str2Uint8Array(crypto.pkcs5.addPadding(data));
param = util.str2Uint8Array(crypto.pkcs5.addPadding(data));
} else {
mpi = new type_mpi();
mpi.fromBytes(crypto.pkcs1.eme.encode(
param = new type_mpi();
param.fromBytes(crypto.pkcs1.eme.encode(
data,
key.mpi[0].byteLength()));
key.params[0].byteLength()));
}
this.encrypted = crypto.publicKeyEncrypt(
this.publicKeyAlgorithm,
key.mpi,
mpi,
key.params,
param,
key.fingerprint);
};
@ -164,7 +147,7 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = function (key) {
PublicKeyEncryptedSessionKey.prototype.decrypt = function (key) {
var result = crypto.publicKeyDecrypt(
this.publicKeyAlgorithm,
key.mpi,
key.params,
this.encrypted,
key.fingerprint).toBytes();

View File

@ -89,7 +89,7 @@ function parse_cleartext_mpi(hash_algorithm, cleartext, algorithm) {
return new Error("Hash mismatch.");
}
var mpis = crypto.getPrivateMpiCount(algorithm);
var mpis = crypto.getPrivKeyParamCount(algorithm);
var j = 0;
var mpi = [];
@ -104,7 +104,7 @@ function parse_cleartext_mpi(hash_algorithm, cleartext, algorithm) {
function write_cleartext_mpi(hash_algorithm, algorithm, mpi) {
var arr = [];
var discard = crypto.getPublicMpiCount(algorithm);
var discard = crypto.getPubKeyParamCount(algorithm);
for (var i = discard; i < mpi.length; i++) {
arr.push(mpi[i].write());
@ -147,7 +147,7 @@ SecretKey.prototype.read = function (bytes) {
if (parsedMPI instanceof Error) {
throw parsedMPI;
}
this.mpi = this.mpi.concat(parsedMPI);
this.params = this.params.concat(parsedMPI);
this.isDecrypted = true;
}
@ -161,7 +161,7 @@ SecretKey.prototype.write = function () {
if (!this.encrypted) {
arr.push(new Uint8Array([0]));
arr.push(write_cleartext_mpi('mod', this.algorithm, this.mpi));
arr.push(write_cleartext_mpi('mod', this.algorithm, this.params));
} else {
arr.push(this.encrypted);
}
@ -188,7 +188,7 @@ SecretKey.prototype.encrypt = function (passphrase) {
var s2k = new type_s2k(),
symmetric = 'aes256',
cleartext = write_cleartext_mpi('sha1', this.algorithm, this.mpi),
cleartext = write_cleartext_mpi('sha1', this.algorithm, this.params),
key = produceEncryptionKey(s2k, passphrase, symmetric),
blockLen = crypto.cipher[symmetric].blockSize,
iv = crypto.random.getRandomBytes(blockLen);
@ -267,7 +267,7 @@ SecretKey.prototype.decrypt = function (passphrase) {
if (parsedMPI instanceof Error) {
return false;
}
this.mpi = this.mpi.concat(parsedMPI);
this.params = this.params.concat(parsedMPI);
this.isDecrypted = true;
this.encrypted = null;
return true;
@ -276,8 +276,8 @@ SecretKey.prototype.decrypt = function (passphrase) {
SecretKey.prototype.generate = function (bits, curve) {
var self = this;
return crypto.generateMpi(self.algorithm, bits, curve).then(function(mpi) {
self.mpi = mpi;
return crypto.generateParams(self.algorithm, bits, curve).then(function(params) {
self.params = params;
self.isDecrypted = true;
});
};
@ -285,10 +285,10 @@ SecretKey.prototype.generate = function (bits, curve) {
/**
* Clear private MPIs, return to initial state
*/
SecretKey.prototype.clearPrivateMPIs = function () {
SecretKey.prototype.clearPrivateParams = function () {
if (!this.encrypted) {
throw new Error('If secret key is not encrypted, clearing private MPIs is irreversible.');
}
this.mpi = this.mpi.slice(0, crypto.getPublicMpiCount(this.algorithm));
this.params = this.params.slice(0, crypto.getPubKeyParamCount(this.algorithm));
this.isDecrypted = false;
};

View File

@ -243,7 +243,7 @@ Signature.prototype.sign = function (key, data) {
this.signedHashValue = hash.subarray(0, 2);
this.signature = crypto.signature.sign(hashAlgorithm,
publicKeyAlgorithm, key.mpi, toHash);
publicKeyAlgorithm, key.params, toHash);
};
/**
@ -644,7 +644,7 @@ Signature.prototype.verify = function (key, data) {
}
this.verified = crypto.signature.verify(publicKeyAlgorithm,
hashAlgorithm, mpi, key.mpi,
hashAlgorithm, mpi, key.params,
util.concatUint8Array([bytes, this.signatureData, trailer]));
return this.verified;

View File

@ -42,9 +42,15 @@ import util from '../util.js';
/**
* @constructor
*/
export default function MPI() {
export default function MPI(data) {
/** An implementation dependent integer */
this.data = null;
if (data instanceof BigInteger) {
this.fromBigInteger(data);
} else if (typeof data === 'string') {
this.fromBytes(data);
} else {
this.data = null;
}
}
/**

View File

@ -191,13 +191,13 @@ describe("Packet", function() {
enc.publicKeyAlgorithm = 'rsa_encrypt';
enc.sessionKeyAlgorithm = 'aes256';
enc.publicKeyId.bytes = '12345678';
enc.encrypt({ mpi: mpi });
enc.encrypt({ params: mpi });
msg.push(enc);
msg2.read(msg.write());
msg2[0].decrypt({ mpi: mpi });
msg2[0].decrypt({ params: mpi });
expect(stringify(msg2[0].sessionKey)).to.equal(stringify(enc.sessionKey));
expect(msg2[0].sessionKeyAlgorithm).to.equal(enc.sessionKeyAlgorithm);
@ -445,8 +445,8 @@ describe("Packet", function() {
return mpi;
});
key[0].mpi = mpi;
key[0].params = mpi;
key[0].algorithm = "rsa_sign";
key[0].encrypt('hello');
var raw = key.write();
@ -455,7 +455,7 @@ describe("Packet", function() {
key2.read(raw);
key2[0].decrypt('hello');
expect(key[0].mpi.toString()).to.equal(key2[0].mpi.toString());
expect(key[0].params.toString()).to.equal(key2[0].params.toString());
});
});
@ -473,7 +473,8 @@ describe("Packet", function() {
return mpi;
});
key.mpi = mpi;
key.params = mpi;
key.algorithm = "rsa_sign";
var signed = new openpgp.packet.List(),
literal = new openpgp.packet.Literal(),