Store named signature parameters (#1158)

Also, remove the now-unnecessary MPI type.
This commit is contained in:
Dan Ristea 2020-09-22 18:24:29 +01:00 committed by Daniel Huigens
parent d5dd247b2c
commit 331a0c27a9
19 changed files with 140 additions and 303 deletions

View File

@ -29,8 +29,8 @@ import util from '../util';
/** /**
* AES key wrap * AES key wrap
* @function * @function
* @param {String} key * @param {Uint8Array} key
* @param {String} data * @param {Uint8Array} data
* @returns {Uint8Array} * @returns {Uint8Array}
*/ */
export function wrap(key, data) { export function wrap(key, data) {

View File

@ -247,7 +247,7 @@ export function parseEncSessionKeyParams(algo, bytes) {
* @param {Object} params The key parameters indexed by name * @param {Object} params The key parameters indexed by name
* @returns {Uint8Array} The array containing the MPIs * @returns {Uint8Array} The array containing the MPIs
*/ */
export function serializeKeyParams(algo, params) { export function serializeParams(algo, params) {
const orderedParams = Object.keys(params).map(name => { const orderedParams = Object.keys(params).map(name => {
const param = params[name]; const param = params[name];
return util.isUint8Array(param) ? util.uint8ArrayToMpi(param) : param.write(); return util.isUint8Array(param) ? util.uint8ArrayToMpi(param) : param.write();

View File

@ -38,7 +38,6 @@ import hash from '../../hash';
import enums from '../../../enums'; import enums from '../../../enums';
import util from '../../../util'; import util from '../../../util';
import * as pkcs5 from '../../pkcs5'; import * as pkcs5 from '../../pkcs5';
import MPI from '../../../type/mpi';
import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey'; import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey';
const webCrypto = util.getWebCrypto(); const webCrypto = util.getWebCrypto();
@ -135,14 +134,14 @@ async function genPublicEphemeralKey(curve, Q) {
* @async * @async
*/ */
export async function encrypt(oid, kdfParams, data, Q, fingerprint) { export async function encrypt(oid, kdfParams, data, Q, fingerprint) {
const m = new MPI(pkcs5.encode(data)); const m = pkcs5.encode(data);
const curve = new Curve(oid); const curve = new Curve(oid);
const { publicKey, sharedKey } = await genPublicEphemeralKey(curve, Q); const { publicKey, sharedKey } = await genPublicEphemeralKey(curve, Q);
const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint); const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint);
const cipher_algo = enums.read(enums.symmetric, kdfParams.cipher); const cipher_algo = enums.read(enums.symmetric, kdfParams.cipher);
const Z = await kdf(kdfParams.hash, sharedKey, cipher[cipher_algo].keySize, param); const Z = await kdf(kdfParams.hash, sharedKey, cipher[cipher_algo].keySize, param);
const wrappedKey = aes_kw.wrap(Z, m.toString()); const wrappedKey = aes_kw.wrap(Z, m);
return { publicKey, wrappedKey }; return { publicKey, wrappedKey };
} }

View File

@ -38,8 +38,8 @@ nacl.hash = bytes => new Uint8Array(sha512().update(bytes).digest());
* @param {Uint8Array} publicKey Public key * @param {Uint8Array} publicKey Public key
* @param {Uint8Array} privateKey Private key used to sign the message * @param {Uint8Array} privateKey Private key used to sign the message
* @param {Uint8Array} hashed The hashed message * @param {Uint8Array} hashed The hashed message
* @returns {{R: Uint8Array, * @returns {{r: Uint8Array,
* S: Uint8Array}} Signature of the message * s: Uint8Array}} Signature of the message
* @async * @async
*/ */
export async function sign(oid, hash_algo, message, publicKey, privateKey, hashed) { export async function sign(oid, hash_algo, message, publicKey, privateKey, hashed) {
@ -47,8 +47,8 @@ export async function sign(oid, hash_algo, message, publicKey, privateKey, hashe
const signature = nacl.sign.detached(hashed, secretKey); const signature = nacl.sign.detached(hashed, secretKey);
// EdDSA signature params are returned in little-endian format // EdDSA signature params are returned in little-endian format
return { return {
R: signature.subarray(0, 32), r: signature.subarray(0, 32),
S: signature.subarray(32) s: signature.subarray(32)
}; };
} }
@ -56,16 +56,16 @@ export async function sign(oid, hash_algo, message, publicKey, privateKey, hashe
* Verifies if a signature is valid for a message * Verifies if a signature is valid for a message
* @param {module:type/oid} oid Elliptic curve object identifier * @param {module:type/oid} oid Elliptic curve object identifier
* @param {module:enums.hash} hash_algo Hash algorithm used in the signature * @param {module:enums.hash} hash_algo Hash algorithm used in the signature
* @param {{R: Uint8Array, * @param {{r: Uint8Array,
S: Uint8Array}} signature Signature to verify the message s: Uint8Array}} signature Signature to verify the message
* @param {Uint8Array} m Message to verify * @param {Uint8Array} m Message to verify
* @param {Uint8Array} publicKey Public key used to verify the message * @param {Uint8Array} publicKey Public key used to verify the message
* @param {Uint8Array} hashed The hashed message * @param {Uint8Array} hashed The hashed message
* @returns {Boolean} * @returns {Boolean}
* @async * @async
*/ */
export async function verify(oid, hash_algo, { R, S }, m, publicKey, hashed) { export async function verify(oid, hash_algo, { r, s }, m, publicKey, hashed) {
const signature = util.concatUint8Array([R, S]); const signature = util.concatUint8Array([r, s]);
return nacl.sign.detached.verify(hashed, signature, publicKey.subarray(1)); return nacl.sign.detached.verify(hashed, signature, publicKey.subarray(1));
} }
/** /**

View File

@ -10,48 +10,87 @@ import publicKey from './public_key';
import enums from '../enums'; import enums from '../enums';
import util from '../util'; import util from '../util';
/**
* Parse signature in binary form to get the parameters.
* See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1}
* See {@link https://tools.ietf.org/html/rfc4880#section-5.2.2|RFC 4880 5.2.2.}
* @param {module:enums.publicKey} algo Public key algorithm
* @param {Uint8Array} signature Data for which the signature was created
* @returns {Object} True if signature is valid
* @async
*/
export function parseSignatureParams(algo, signature) {
let read = 0;
switch (algo) {
// Algorithm-Specific Fields for RSA signatures:
// - MPI of RSA signature value m**d mod n.
case enums.publicKey.rsaEncryptSign:
case enums.publicKey.rsaEncrypt:
case enums.publicKey.rsaSign: {
const s = util.readMPI(signature.subarray(read));
return { s };
}
// Algorithm-Specific Fields for DSA or ECDSA signatures:
// - MPI of DSA or ECDSA value r.
// - MPI of DSA or ECDSA value s.
case enums.publicKey.dsa:
case enums.publicKey.ecdsa:
{
const r = util.readMPI(signature.subarray(read)); read += r.length + 2;
const s = util.readMPI(signature.subarray(read));
return { r, s };
}
// Algorithm-Specific Fields for EdDSA signatures:
// - MPI of an EC point r.
// - EdDSA value s, in MPI, in the little endian representation.
// EdDSA signature parameters are encoded in little-endian format
// https://tools.ietf.org/html/rfc8032#section-5.1.2
case enums.publicKey.eddsa: {
const r = util.padToLength(util.readMPI(signature.subarray(read)), 32, 'le'); read += r.length + 2;
const s = util.padToLength(util.readMPI(signature.subarray(read)), 32, 'le');
return { r, s };
}
default:
throw new Error('Invalid signature algorithm.');
}
}
/** /**
* Verifies the signature provided for data using specified algorithms and public key parameters. * Verifies the signature provided for data using specified algorithms and public key parameters.
* See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} * 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} * and {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4}
* for public key and hash algorithms. * for public key and hash algorithms.
* @param {module:enums.publicKey} algo Public key algorithm * @param {module:enums.publicKey} algo Public key algorithm
* @param {module:enums.hash} hash_algo Hash algorithm * @param {module:enums.hash} hashAlgo Hash algorithm
* @param {Array<module:type/mpi>} msg_MPIs Algorithm-specific signature parameters * @param {Object} signature Named algorithm-specific signature parameters
* @param {Object} publicParams Algorithm-specific public key parameters * @param {Object} publicParams Algorithm-specific public key parameters
* @param {Uint8Array} data Data for which the signature was created * @param {Uint8Array} data Data for which the signature was created
* @param {Uint8Array} hashed The hashed data * @param {Uint8Array} hashed The hashed data
* @returns {Boolean} True if signature is valid * @returns {Boolean} True if signature is valid
* @async * @async
*/ */
export async function verify(algo, hash_algo, msg_MPIs, publicParams, data, hashed) { export async function verify(algo, hashAlgo, signature, publicParams, data, hashed) {
switch (algo) { switch (algo) {
case enums.publicKey.rsaEncryptSign: case enums.publicKey.rsaEncryptSign:
case enums.publicKey.rsaEncrypt: case enums.publicKey.rsaEncrypt:
case enums.publicKey.rsaSign: { case enums.publicKey.rsaSign: {
const { n, e } = publicParams; const { n, e } = publicParams;
const m = msg_MPIs[0].toUint8Array('be', n.length); const { s } = signature;
return publicKey.rsa.verify(hash_algo, data, m, n, e, hashed); return publicKey.rsa.verify(hashAlgo, data, s, n, e, hashed);
} }
case enums.publicKey.dsa: { case enums.publicKey.dsa: {
const r = await msg_MPIs[0].toUint8Array();
const s = await msg_MPIs[1].toUint8Array();
const { g, p, q, y } = publicParams; const { g, p, q, y } = publicParams;
return publicKey.dsa.verify(hash_algo, r, s, hashed, g, p, q, y); const { r, s } = signature;
return publicKey.dsa.verify(hashAlgo, r, s, hashed, g, p, q, y);
} }
case enums.publicKey.ecdsa: { case enums.publicKey.ecdsa: {
const { oid, Q } = publicParams; const { oid, Q } = publicParams;
const signature = { r: msg_MPIs[0].toUint8Array(), s: msg_MPIs[1].toUint8Array() }; return publicKey.elliptic.ecdsa.verify(oid, hashAlgo, signature, data, Q, hashed);
return publicKey.elliptic.ecdsa.verify(oid, hash_algo, signature, data, Q, hashed);
} }
case enums.publicKey.eddsa: { case enums.publicKey.eddsa: {
const { oid, Q } = publicParams; const { oid, Q } = publicParams;
// EdDSA signature params are expected in little-endian format return publicKey.elliptic.eddsa.verify(oid, hashAlgo, signature, data, Q, hashed);
const signature = {
R: msg_MPIs[0].toUint8Array('le', 32),
S: msg_MPIs[1].toUint8Array('le', 32)
};
return publicKey.elliptic.eddsa.verify(oid, hash_algo, signature, data, Q, hashed);
} }
default: default:
throw new Error('Invalid signature algorithm.'); throw new Error('Invalid signature algorithm.');
@ -64,15 +103,15 @@ export async function verify(algo, hash_algo, msg_MPIs, publicParams, data, hash
* and {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4} * and {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4}
* for public key and hash algorithms. * for public key and hash algorithms.
* @param {module:enums.publicKey} algo Public key algorithm * @param {module:enums.publicKey} algo Public key algorithm
* @param {module:enums.hash} hash_algo Hash algorithm * @param {module:enums.hash} hashAlgo Hash algorithm
* @param {Object} publicKeyParams Algorithm-specific public and private key parameters * @param {Object} publicKeyParams Algorithm-specific public and private key parameters
* @param {Object} privateKeyParams 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} data Data to be signed
* @param {Uint8Array} hashed The hashed data * @param {Uint8Array} hashed The hashed data
* @returns {Uint8Array} Signature * @returns {Object} Signature Object containing named signature parameters
* @async * @async
*/ */
export async function sign(algo, hash_algo, publicKeyParams, privateKeyParams, data, hashed) { export async function sign(algo, hashAlgo, publicKeyParams, privateKeyParams, data, hashed) {
if (!publicKeyParams || !privateKeyParams) { if (!publicKeyParams || !privateKeyParams) {
throw new Error('Missing key parameters'); throw new Error('Missing key parameters');
} }
@ -82,17 +121,13 @@ export async function sign(algo, hash_algo, publicKeyParams, privateKeyParams, d
case enums.publicKey.rsaSign: { case enums.publicKey.rsaSign: {
const { n, e } = publicKeyParams; const { n, e } = publicKeyParams;
const { d, p, q, u } = privateKeyParams; const { d, p, q, u } = privateKeyParams;
const signature = await publicKey.rsa.sign(hash_algo, data, n, e, d, p, q, u, hashed); const s = await publicKey.rsa.sign(hashAlgo, data, n, e, d, p, q, u, hashed);
return util.uint8ArrayToMpi(signature); return { s };
} }
case enums.publicKey.dsa: { case enums.publicKey.dsa: {
const { g, p, q } = publicKeyParams; const { g, p, q } = publicKeyParams;
const { x } = privateKeyParams; const { x } = privateKeyParams;
const signature = await publicKey.dsa.sign(hash_algo, hashed, g, p, q, x); return publicKey.dsa.sign(hashAlgo, hashed, g, p, q, x);
return util.concatUint8Array([
util.uint8ArrayToMpi(signature.r),
util.uint8ArrayToMpi(signature.s)
]);
} }
case enums.publicKey.elgamal: { case enums.publicKey.elgamal: {
throw new Error('Signing with Elgamal is not defined in the OpenPGP standard.'); throw new Error('Signing with Elgamal is not defined in the OpenPGP standard.');
@ -100,20 +135,12 @@ export async function sign(algo, hash_algo, publicKeyParams, privateKeyParams, d
case enums.publicKey.ecdsa: { case enums.publicKey.ecdsa: {
const { oid, Q } = publicKeyParams; const { oid, Q } = publicKeyParams;
const { d } = privateKeyParams; const { d } = privateKeyParams;
const signature = await publicKey.elliptic.ecdsa.sign(oid, hash_algo, data, Q, d, hashed); return publicKey.elliptic.ecdsa.sign(oid, hashAlgo, data, Q, d, hashed);
return util.concatUint8Array([
util.uint8ArrayToMpi(signature.r),
util.uint8ArrayToMpi(signature.s)
]);
} }
case enums.publicKey.eddsa: { case enums.publicKey.eddsa: {
const { oid, Q } = publicKeyParams; const { oid, Q } = publicKeyParams;
const { seed } = privateKeyParams; const { seed } = privateKeyParams;
const signature = await publicKey.elliptic.eddsa.sign(oid, hash_algo, data, Q, seed, hashed); return publicKey.elliptic.eddsa.sign(oid, hashAlgo, data, Q, seed, hashed);
return util.concatUint8Array([
util.uint8ArrayToMpi(signature.R),
util.uint8ArrayToMpi(signature.S)
]);
} }
default: default:
throw new Error('Invalid signature algorithm.'); throw new Error('Invalid signature algorithm.');

View File

@ -52,12 +52,6 @@ export { default as util } from './util';
*/ */
export * from './packet'; export * from './packet';
/**
* @see module:type/mpi
* @name module:openpgp.MPI
*/
export { default as MPI } from './type/mpi';
/** /**
* @see module:type/s2k * @see module:type/s2k
* @name module:openpgp.S2K * @name module:openpgp.S2K

View File

@ -238,7 +238,7 @@ export async function mergeSignatures(source, dest, attr, checkFn) {
await Promise.all(source.map(async function(sourceSig) { await Promise.all(source.map(async function(sourceSig) {
if (!sourceSig.isExpired() && (!checkFn || await checkFn(sourceSig)) && if (!sourceSig.isExpired() && (!checkFn || await checkFn(sourceSig)) &&
!dest[attr].some(function(destSig) { !dest[attr].some(function(destSig) {
return util.equalsUint8Array(destSig.signature, sourceSig.signature); return util.equalsUint8Array(destSig.write_params(), sourceSig.write_params());
})) { })) {
dest[attr].push(sourceSig); dest[attr].push(sourceSig);
} }

View File

@ -19,7 +19,6 @@
/** /**
* @requires type/keyid * @requires type/keyid
* @requires type/mpi
* @requires config * @requires config
* @requires crypto * @requires crypto
* @requires enums * @requires enums
@ -142,7 +141,7 @@ class PublicKeyPacket {
const algo = enums.write(enums.publicKey, this.algorithm); const algo = enums.write(enums.publicKey, this.algorithm);
arr.push(new Uint8Array([algo])); arr.push(new Uint8Array([algo]));
const params = crypto.serializeKeyParams(algo, this.publicParams); const params = crypto.serializeParams(algo, this.publicParams);
if (this.version === 5) { if (this.version === 5) {
// A four-octet scalar octet count for the following key material // A four-octet scalar octet count for the following key material
arr.push(util.writeNumber(params.length, 4)); arr.push(util.writeNumber(params.length, 4));

View File

@ -17,7 +17,6 @@
/** /**
* @requires type/keyid * @requires type/keyid
* @requires type/mpi
* @requires crypto * @requires crypto
* @requires enums * @requires enums
* @requires util * @requires util
@ -56,8 +55,8 @@ class PublicKeyEncryptedSessionKeyPacket {
this.sessionKey = null; this.sessionKey = null;
this.sessionKeyAlgorithm = null; this.sessionKeyAlgorithm = null;
/** @type {Array<module:type/mpi>} */ /** @type {Object} */
this.encrypted = []; this.encrypted = {};
} }
/** /**
@ -86,7 +85,7 @@ class PublicKeyEncryptedSessionKeyPacket {
new Uint8Array([this.version]), new Uint8Array([this.version]),
this.publicKeyId.write(), this.publicKeyId.write(),
new Uint8Array([enums.write(enums.publicKey, this.publicKeyAlgorithm)]), new Uint8Array([enums.write(enums.publicKey, this.publicKeyAlgorithm)]),
crypto.serializeKeyParams(algo, this.encrypted) crypto.serializeParams(algo, this.encrypted)
]; ];
return util.concatUint8Array(arr); return util.concatUint8Array(arr);

View File

@ -211,7 +211,7 @@ class SecretKeyPacket extends PublicKeyPacket {
if (!this.isDummy()) { if (!this.isDummy()) {
if (!this.s2k_usage) { if (!this.s2k_usage) {
const algo = enums.write(enums.publicKey, this.algorithm); const algo = enums.write(enums.publicKey, this.algorithm);
const cleartextParams = crypto.serializeKeyParams(algo, this.privateParams); const cleartextParams = crypto.serializeParams(algo, this.privateParams);
this.keyMaterial = util.concatUint8Array([ this.keyMaterial = util.concatUint8Array([
cleartextParams, cleartextParams,
util.writeChecksum(cleartextParams) util.writeChecksum(cleartextParams)
@ -294,7 +294,7 @@ class SecretKeyPacket extends PublicKeyPacket {
this.s2k = new type_s2k(); this.s2k = new type_s2k();
this.s2k.salt = await crypto.random.getRandomBytes(8); this.s2k.salt = await crypto.random.getRandomBytes(8);
const algo = enums.write(enums.publicKey, this.algorithm); const algo = enums.write(enums.publicKey, this.algorithm);
const cleartext = crypto.serializeKeyParams(algo, this.privateParams); const cleartext = crypto.serializeParams(algo, this.privateParams);
this.symmetric = 'aes256'; this.symmetric = 'aes256';
const key = await produceEncryptionKey(this.s2k, passphrase, this.symmetric); const key = await produceEncryptionKey(this.s2k, passphrase, this.symmetric);
const blockLen = crypto.cipher[this.symmetric].blockSize; const blockLen = crypto.cipher[this.symmetric].blockSize;

View File

@ -19,7 +19,6 @@
* @requires web-stream-tools * @requires web-stream-tools
* @requires packet/packet * @requires packet/packet
* @requires type/keyid * @requires type/keyid
* @requires type/mpi
* @requires crypto * @requires crypto
* @requires enums * @requires enums
* @requires util * @requires util
@ -28,7 +27,6 @@
import stream from 'web-stream-tools'; import stream from 'web-stream-tools';
import { readSimpleLength, writeSimpleLength } from './packet'; import { readSimpleLength, writeSimpleLength } from './packet';
import type_keyid from '../type/keyid.js'; import type_keyid from '../type/keyid.js';
import type_mpi from '../type/mpi.js';
import crypto from '../crypto'; import crypto from '../crypto';
import enums from '../enums'; import enums from '../enums';
import util from '../util'; import util from '../util';
@ -133,7 +131,19 @@ class SignaturePacket {
this.signedHashValue = bytes.subarray(i, i + 2); this.signedHashValue = bytes.subarray(i, i + 2);
i += 2; i += 2;
this.signature = bytes.subarray(i, bytes.length); this.params = crypto.signature.parseSignatureParams(this.publicKeyAlgorithm, bytes.subarray(i, bytes.length));
}
/**
* @returns {Uint8Array | ReadableStream<Uint8Array>}
*/
write_params() {
if (this.params instanceof Promise) {
return stream.fromAsync(
async () => crypto.serializeParams(this.publicKeyAlgorithm, await this.params)
);
}
return crypto.serializeParams(this.publicKeyAlgorithm, this.params);
} }
write() { write() {
@ -141,7 +151,7 @@ class SignaturePacket {
arr.push(this.signatureData); arr.push(this.signatureData);
arr.push(this.write_unhashed_sub_packets()); arr.push(this.write_unhashed_sub_packets());
arr.push(this.signedHashValue); arr.push(this.signedHashValue);
arr.push(stream.clone(this.signature)); arr.push(this.write_params());
return util.concat(arr); return util.concat(arr);
} }
@ -181,9 +191,9 @@ class SignaturePacket {
publicKeyAlgorithm, hashAlgorithm, key.publicParams, key.privateParams, toHash, await stream.readToEnd(hash) publicKeyAlgorithm, hashAlgorithm, key.publicParams, key.privateParams, toHash, await stream.readToEnd(hash)
); );
if (streaming) { if (streaming) {
this.signature = stream.fromAsync(signed); this.params = signed();
} else { } else {
this.signature = await signed(); this.params = await signed();
// Store the fact that this signature is valid, e.g. for when we call `await // Store the fact that this signature is valid, e.g. for when we call `await
// getLatestValidSignature(this.revocationSignatures, key, data)` later. // getLatestValidSignature(this.revocationSignatures, key, data)` later.
@ -661,6 +671,7 @@ class SignaturePacket {
* @param {module:enums.signature} signatureType expected signature type * @param {module:enums.signature} signatureType expected signature type
* @param {String|Object} data data which on the signature applies * @param {String|Object} data data which on the signature applies
* @param {Boolean} detached (optional) whether to verify a detached signature * @param {Boolean} detached (optional) whether to verify a detached signature
* @param {Boolean} streaming (optional) whether to process data as a stream
* @returns {Promise<Boolean>} True if message is verified, else false. * @returns {Promise<Boolean>} True if message is verified, else false.
* @async * @async
*/ */
@ -687,33 +698,10 @@ class SignaturePacket {
throw new Error('Message digest did not match'); throw new Error('Message digest did not match');
} }
let mpicount = 0; this.params = await this.params;
// Algorithm-Specific Fields for RSA signatures:
// - multiprecision number (MPI) of RSA signature value m**d mod n.
if (publicKeyAlgorithm > 0 && publicKeyAlgorithm < 4) {
mpicount = 1;
// Algorithm-Specific Fields for DSA, ECDSA, and EdDSA signatures:
// - MPI of DSA value r.
// - MPI of DSA value s.
} else if (publicKeyAlgorithm === enums.publicKey.dsa ||
publicKeyAlgorithm === enums.publicKey.ecdsa ||
publicKeyAlgorithm === enums.publicKey.eddsa) {
mpicount = 2;
}
// EdDSA signature parameters are encoded in little-endian format
// https://tools.ietf.org/html/rfc8032#section-5.1.2
const endian = publicKeyAlgorithm === enums.publicKey.eddsa ? 'le' : 'be';
const mpi = [];
let i = 0;
this.signature = await stream.readToEnd(this.signature);
for (let j = 0; j < mpicount; j++) {
mpi[j] = new type_mpi();
i += mpi[j].read(this.signature.subarray(i, this.signature.length), endian);
}
const verified = await crypto.signature.verify( const verified = await crypto.signature.verify(
publicKeyAlgorithm, hashAlgorithm, mpi, key.publicParams, publicKeyAlgorithm, hashAlgorithm, this.params, key.publicParams,
toHash, hash toHash, hash
); );
if (!verified) { if (!verified) {

View File

@ -1,137 +0,0 @@
// GPG4Browsers - An OpenPGP implementation in javascript
// Copyright (C) 2011 Recurity Labs GmbH
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3.0 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// Hint: We hold our MPIs as an array of octets in big endian format preceding a two
// octet scalar: MPI: [a,b,c,d,e,f]
// - MPI size: (a << 8) | b
// - MPI = c | d << 8 | e << ((MPI.length -2)*8) | f ((MPI.length -2)*8)
/**
* Implementation of type MPI ({@link https://tools.ietf.org/html/rfc4880#section-3.2|RFC4880 3.2})
* Multiprecision integers (also called MPIs) are unsigned integers used
* to hold large integers such as the ones used in cryptographic
* calculations.
* An MPI consists of two pieces: a two-octet scalar that is the length
* of the MPI in bits followed by a string of octets that contain the
* actual integer.
* @requires util
* @module type/mpi
*/
import util from '../util';
class MPI {
constructor(data) {
/** An implementation dependent integer */
if (data instanceof MPI) {
this.data = data.data;
} else if (util.isBigInteger(data)) {
this.fromBigInteger(data);
} else if (util.isBN(data)) {
this.fromBN(data);
} else if (util.isUint8Array(data)) {
this.fromUint8Array(data);
} else if (util.isString(data)) {
this.fromString(data);
} else {
this.data = null;
}
}
/**
* Parsing function for a MPI ({@link https://tools.ietf.org/html/rfc4880#section-3.2|RFC 4880 3.2}).
* @param {Uint8Array|string} bytes Payload of MPI data
* @param {'be'|'le'} endian Endianness of the data; 'be' for big-endian or 'le' for little-endian
* @returns {Integer} Length of data read
*/
read(bytes, endian = 'be') {
if (util.isString(bytes)) {
bytes = util.strToUint8Array(bytes);
}
const bits = (bytes[0] << 8) | bytes[1];
const bytelen = (bits + 7) >>> 3;
const payload = bytes.subarray(2, 2 + bytelen);
this.fromUint8Array(payload, endian);
return 2 + bytelen;
}
/**
* Converts the mpi object to a bytes as specified in
* {@link https://tools.ietf.org/html/rfc4880#section-3.2|RFC4880 3.2}
* @param {String} endian Endianness of the payload; 'be' for big-endian or 'le' for little-endian
* @param {Integer} length Length of the data part of the MPI
* @returns {Uint8Aray} mpi Byte representation
*/
write(endian, length) {
return util.uint8ArrayToMpi(this.toUint8Array(endian, length));
}
bitLength() {
return (this.data.length - 1) * 8 + util.nbits(this.data[0]);
}
byteLength() {
return this.data.length;
}
toUint8Array(endian, length) {
endian = endian || 'be';
length = length || this.data.length;
const payload = new Uint8Array(length);
const start = endian === 'le' ? 0 : length - this.data.length;
payload.set(this.data, start);
if (endian === 'le') {
payload.reverse();
}
return payload;
}
fromUint8Array(bytes, endian = 'be') {
this.data = new Uint8Array(bytes.length);
this.data.set(bytes);
if (endian === 'le') {
this.data.reverse();
}
}
toString() {
return util.uint8ArrayToStr(this.toUint8Array());
}
fromString(str, endian = 'be') {
this.fromUint8Array(util.strToUint8Array(str), endian);
}
async toBigInteger() {
const BigInteger = await util.getBigInteger();
return new BigInteger(this.toUint8Array());
}
fromBigInteger(n) {
this.data = n.toUint8Array();
}
fromBN(bn) {
this.data = bn.toArrayLike(Uint8Array);
}
}
export default MPI;

View File

@ -194,9 +194,6 @@ export default {
/** /**
* Convert a Uint8Array to an MPI-formatted Uint8Array. * Convert a Uint8Array to an MPI-formatted Uint8Array.
* Note: the output is **not** an MPI object.
* @see {@link module:type/mpi/MPI.fromUint8Array}
* @see {@link module:type/mpi/MPI.toUint8Array}
* @param {Uint8Array} bin An array of 8-bit integers to convert * @param {Uint8Array} bin An array of 8-bit integers to convert
* @returns {Uint8Array} MPI-formatted Uint8Array * @returns {Uint8Array} MPI-formatted Uint8Array
*/ */

View File

@ -211,35 +211,24 @@ module.exports = () => describe('API functional testing', function() {
describe('Sign and verify', function () { describe('Sign and verify', function () {
it('RSA', async function () { it('RSA', async function () {
return crypto.signature.sign( const RSAsignedData = await crypto.signature.sign(
1, 2, RSAPublicParams, RSAPrivateParams, data, await crypto.hash.digest(2, data) openpgp.enums.publicKey.rsaEncryptSign, openpgp.enums.hash.sha1, RSAPublicParams, RSAPrivateParams, data, await crypto.hash.digest(2, data)
).then(async RSAsignedData => { );
const RSAsignedDataMPI = new openpgp.MPI(); const success = await crypto.signature.verify(
RSAsignedDataMPI.read(RSAsignedData); openpgp.enums.publicKey.rsaEncryptSign, openpgp.enums.hash.sha1, RSAsignedData, RSAPublicParams, data, await crypto.hash.digest(2, data)
return crypto.signature.verify( );
1, 2, [RSAsignedDataMPI], RSAPublicParams, data, await crypto.hash.digest(2, data) return expect(success).to.be.true;
).then(success => {
return expect(success).to.be.true;
});
});
}); });
it('DSA', async function () { it('DSA', async function () {
return crypto.signature.sign( const DSAsignedData = await crypto.signature.sign(
17, 2, DSAPublicParams, DSAPrivateParams, data, await crypto.hash.digest(2, data) openpgp.enums.publicKey.dsa, openpgp.enums.hash.sha1, DSAPublicParams, DSAPrivateParams, data, await crypto.hash.digest(2, data)
).then(async DSAsignedData => { );
DSAsignedData = util.uint8ArrayToStr(DSAsignedData); const success = await crypto.signature.verify(
const DSAmsgMPIs = []; openpgp.enums.publicKey.dsa, openpgp.enums.hash.sha1, DSAsignedData, DSAPublicParams, data, await crypto.hash.digest(2, data)
DSAmsgMPIs[0] = new openpgp.MPI(); );
DSAmsgMPIs[1] = new openpgp.MPI();
DSAmsgMPIs[0].read(DSAsignedData.substring(0,34)); return expect(success).to.be.true;
DSAmsgMPIs[1].read(DSAsignedData.substring(34,68));
return crypto.signature.verify(
17, 2, DSAmsgMPIs, DSAPublicParams, data, await crypto.hash.digest(2, data)
).then(success => {
return expect(success).to.be.true;
});
});
}); });
}); });

View File

@ -39,8 +39,7 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra
const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const { n, e, d, p, q, u } = { ...publicParams, ...privateParams };
const message = await openpgp.crypto.generateSessionKey('aes256'); const message = await openpgp.crypto.generateSessionKey('aes256');
const encrypted = await openpgp.crypto.publicKey.rsa.encrypt(message, n, e); const encrypted = await openpgp.crypto.publicKey.rsa.encrypt(message, n, e);
const result = new openpgp.MPI(encrypted); const decrypted = await openpgp.crypto.publicKey.rsa.decrypt(encrypted, n, e, d, p, q, u);
const decrypted = await openpgp.crypto.publicKey.rsa.decrypt(result.toUint8Array(), n, e, d, p, q, u);
expect(decrypted).to.deep.equal(message); expect(decrypted).to.deep.equal(message);
}); });

View File

@ -155,14 +155,4 @@ module.exports = () => describe('BigInteger interface', function() {
const expected = nBN.testn(5) ? 1 : 0; const expected = nBN.testn(5) ? 1 : 0;
expect(n.getBit(i) === expected).to.be.true; expect(n.getBit(i) === expected).to.be.true;
}); });
describe('MPI and BigInteger conversions', function() {
it('MPI to/from BigInteger is correct', async function() {
const input = '417653931840771530406225971293556769925351769207235721650257629558293828796031115397206059067934284452829611906818956352854418342467914729341523414945427019410284762464062112274326172407819051167058569790660930309496043254270888417520676082271432948852231332576271876251597199882908964994070268531832274431027';
const n = new BigInteger(input);
const mpi = new openpgp.MPI(n);
expect((await mpi.toBigInteger()).equal(n)).to.be.true;
});
});
}); });

View File

@ -1609,21 +1609,17 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA
const privKey2 = await openpgp.readArmoredKey(priv_key_arm2); const privKey2 = await openpgp.readArmoredKey(priv_key_arm2);
await privKey2.decrypt('hello world'); await privKey2.decrypt('hello world');
const opt = {rsaBits: 512, userIds: { name:'test', email:'a@b.com' }, passphrase: null}; const opt = { rsaBits: 512, userIds: { name:'test', email:'a@b.com' }, passphrase: null };
if (openpgp.util.getWebCryptoAll()) { opt.rsaBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys if (openpgp.util.getWebCryptoAll()) { opt.rsaBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
return openpgp.generateKey(opt).then(function(gen) { const { key: generatedKey } = await openpgp.generateKey(opt);
const generatedKey = gen.key; const detachedSig = await msg.signDetached([generatedKey, privKey2]);
return msg.signDetached([generatedKey, privKey2]).then(detachedSig => { const result = await msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]);
return msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]).then(async result => { expect(await result[0].verified).to.be.true;
expect(await result[0].verified).to.be.true; expect(await result[1].verified).to.be.true;
expect(await result[1].verified).to.be.true;
});
});
});
}); });
it('Sign message with key without password', function() { it('Sign message with key without password', function() {
const opt = {userIds: { name:'test', email:'a@b.com' }, passphrase: null}; const opt = { userIds: { name:'test', email:'a@b.com' }, passphrase: null };
return openpgp.generateKey(opt).then(function(gen) { return openpgp.generateKey(opt).then(function(gen) {
const key = gen.key; const key = gen.key;
let message = openpgp.Message.fromText('hello world'); let message = openpgp.Message.fromText('hello world');

View File

@ -216,8 +216,8 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr
const util = openpgp.util; const util = openpgp.util;
function testVector(vector) { function testVector(vector) {
const curve = new elliptic.Curve('ed25519'); const curve = new elliptic.Curve('ed25519');
const { publicKey } = openpgp.crypto.publicKey.nacl.sign.keyPair.fromSeed(openpgp.util.hexToUint8Array(vector.SECRET_KEY)); const { publicKey } = openpgp.crypto.publicKey.nacl.sign.keyPair.fromSeed(util.hexToUint8Array(vector.SECRET_KEY));
expect(publicKey).to.deep.equal(openpgp.util.hexToUint8Array(vector.PUBLIC_KEY)); expect(publicKey).to.deep.equal(util.hexToUint8Array(vector.PUBLIC_KEY));
const data = util.strToUint8Array(vector.MESSAGE); const data = util.strToUint8Array(vector.MESSAGE);
const privateParams = { const privateParams = {
seed: util.hexToUint8Array(vector.SECRET_KEY) seed: util.hexToUint8Array(vector.SECRET_KEY)
@ -226,17 +226,14 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr
oid: new openpgp.OID(curve.oid), oid: new openpgp.OID(curve.oid),
Q: util.hexToUint8Array('40' + vector.PUBLIC_KEY) Q: util.hexToUint8Array('40' + vector.PUBLIC_KEY)
}; };
const msg_MPIs = [ const R = util.hexToUint8Array(vector.SIGNATURE.R);
new openpgp.MPI(util.uint8ArrayToStr(util.hexToUint8Array(vector.SIGNATURE.R).reverse())), const S = util.hexToUint8Array(vector.SIGNATURE.S);
new openpgp.MPI(util.uint8ArrayToStr(util.hexToUint8Array(vector.SIGNATURE.S).reverse()))
];
return Promise.all([ return Promise.all([
signature.sign(22, undefined, publicParams, privateParams, undefined, data).then(signed => { signature.sign(22, undefined, publicParams, privateParams, undefined, data).then(({ r, s }) => {
const len = (((signed[0] << 8) | signed[1]) + 7) / 8; expect(R).to.deep.eq(r);
expect(util.hexToUint8Array(vector.SIGNATURE.R)).to.deep.eq(signed.slice(2, 2 + len)); expect(S).to.deep.eq(s);
expect(util.hexToUint8Array(vector.SIGNATURE.S)).to.deep.eq(signed.slice(4 + len));
}), }),
signature.verify(22, undefined, msg_MPIs, publicParams, undefined, data).then(result => { signature.verify(22, undefined, { r: R, s: S }, publicParams, undefined, data).then(result => {
expect(result).to.be.true; expect(result).to.be.true;
}) })
]); ]);