Store named signature parameters (#1158)
Also, remove the now-unnecessary MPI type.
This commit is contained in:
parent
d5dd247b2c
commit
331a0c27a9
|
@ -29,8 +29,8 @@ import util from '../util';
|
|||
/**
|
||||
* AES key wrap
|
||||
* @function
|
||||
* @param {String} key
|
||||
* @param {String} data
|
||||
* @param {Uint8Array} key
|
||||
* @param {Uint8Array} data
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function wrap(key, data) {
|
||||
|
|
|
@ -247,7 +247,7 @@ export function parseEncSessionKeyParams(algo, bytes) {
|
|||
* @param {Object} params The key parameters indexed by name
|
||||
* @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 param = params[name];
|
||||
return util.isUint8Array(param) ? util.uint8ArrayToMpi(param) : param.write();
|
||||
|
|
|
@ -38,7 +38,6 @@ import hash from '../../hash';
|
|||
import enums from '../../../enums';
|
||||
import util from '../../../util';
|
||||
import * as pkcs5 from '../../pkcs5';
|
||||
import MPI from '../../../type/mpi';
|
||||
import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
|
@ -135,14 +134,14 @@ async function genPublicEphemeralKey(curve, Q) {
|
|||
* @async
|
||||
*/
|
||||
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 { publicKey, sharedKey } = await genPublicEphemeralKey(curve, Q);
|
||||
const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint);
|
||||
const cipher_algo = enums.read(enums.symmetric, kdfParams.cipher);
|
||||
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 };
|
||||
}
|
||||
|
||||
|
|
|
@ -38,8 +38,8 @@ nacl.hash = bytes => new Uint8Array(sha512().update(bytes).digest());
|
|||
* @param {Uint8Array} publicKey Public key
|
||||
* @param {Uint8Array} privateKey Private key used to sign the message
|
||||
* @param {Uint8Array} hashed The hashed message
|
||||
* @returns {{R: Uint8Array,
|
||||
* S: Uint8Array}} Signature of the message
|
||||
* @returns {{r: Uint8Array,
|
||||
* s: Uint8Array}} Signature of the message
|
||||
* @async
|
||||
*/
|
||||
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);
|
||||
// EdDSA signature params are returned in little-endian format
|
||||
return {
|
||||
R: signature.subarray(0, 32),
|
||||
S: signature.subarray(32)
|
||||
r: signature.subarray(0, 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
|
||||
* @param {module:type/oid} oid Elliptic curve object identifier
|
||||
* @param {module:enums.hash} hash_algo Hash algorithm used in the signature
|
||||
* @param {{R: Uint8Array,
|
||||
S: Uint8Array}} signature Signature to verify the message
|
||||
* @param {{r: Uint8Array,
|
||||
s: Uint8Array}} signature Signature to verify the message
|
||||
* @param {Uint8Array} m Message to verify
|
||||
* @param {Uint8Array} publicKey Public key used to verify the message
|
||||
* @param {Uint8Array} hashed The hashed message
|
||||
* @returns {Boolean}
|
||||
* @async
|
||||
*/
|
||||
export async function verify(oid, hash_algo, { R, S }, m, publicKey, hashed) {
|
||||
const signature = util.concatUint8Array([R, S]);
|
||||
export async function verify(oid, hash_algo, { r, s }, m, publicKey, hashed) {
|
||||
const signature = util.concatUint8Array([r, s]);
|
||||
return nacl.sign.detached.verify(hashed, signature, publicKey.subarray(1));
|
||||
}
|
||||
/**
|
||||
|
|
|
@ -10,48 +10,87 @@ import publicKey from './public_key';
|
|||
import enums from '../enums';
|
||||
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.
|
||||
* 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 {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
|
||||
* @param {module:enums.publicKey} algo Public key algorithm
|
||||
* @param {module:enums.hash} hashAlgo Hash algorithm
|
||||
* @param {Object} signature Named 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
|
||||
*/
|
||||
export async function verify(algo, hash_algo, msg_MPIs, publicParams, data, hashed) {
|
||||
export async function verify(algo, hashAlgo, signature, publicParams, data, hashed) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.rsaEncryptSign:
|
||||
case enums.publicKey.rsaEncrypt:
|
||||
case enums.publicKey.rsaSign: {
|
||||
const { n, e } = publicParams;
|
||||
const m = msg_MPIs[0].toUint8Array('be', n.length);
|
||||
return publicKey.rsa.verify(hash_algo, data, m, n, e, hashed);
|
||||
const { s } = signature;
|
||||
return publicKey.rsa.verify(hashAlgo, data, s, n, e, hashed);
|
||||
}
|
||||
case enums.publicKey.dsa: {
|
||||
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);
|
||||
const { r, s } = signature;
|
||||
return publicKey.dsa.verify(hashAlgo, r, s, hashed, g, p, q, y);
|
||||
}
|
||||
case enums.publicKey.ecdsa: {
|
||||
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);
|
||||
return publicKey.elliptic.ecdsa.verify(oid, hashAlgo, signature, data, Q, hashed);
|
||||
}
|
||||
case enums.publicKey.eddsa: {
|
||||
const { oid, Q } = publicParams;
|
||||
// EdDSA signature params are expected in little-endian format
|
||||
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);
|
||||
return publicKey.elliptic.eddsa.verify(oid, hashAlgo, signature, data, Q, hashed);
|
||||
}
|
||||
default:
|
||||
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}
|
||||
* for public key and hash algorithms.
|
||||
* @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} privateKeyParams Algorithm-specific public and private key parameters
|
||||
* @param {Uint8Array} data Data to be signed
|
||||
* @param {Uint8Array} hashed The hashed data
|
||||
* @returns {Uint8Array} Signature
|
||||
* @returns {Object} Signature Object containing named signature parameters
|
||||
* @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) {
|
||||
throw new Error('Missing key parameters');
|
||||
}
|
||||
|
@ -82,17 +121,13 @@ export async function sign(algo, hash_algo, publicKeyParams, privateKeyParams, d
|
|||
case enums.publicKey.rsaSign: {
|
||||
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);
|
||||
const s = await publicKey.rsa.sign(hashAlgo, data, n, e, d, p, q, u, hashed);
|
||||
return { s };
|
||||
}
|
||||
case enums.publicKey.dsa: {
|
||||
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),
|
||||
util.uint8ArrayToMpi(signature.s)
|
||||
]);
|
||||
return publicKey.dsa.sign(hashAlgo, hashed, g, p, q, x);
|
||||
}
|
||||
case enums.publicKey.elgamal: {
|
||||
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: {
|
||||
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),
|
||||
util.uint8ArrayToMpi(signature.s)
|
||||
]);
|
||||
return publicKey.elliptic.ecdsa.sign(oid, hashAlgo, data, Q, d, hashed);
|
||||
}
|
||||
case enums.publicKey.eddsa: {
|
||||
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),
|
||||
util.uint8ArrayToMpi(signature.S)
|
||||
]);
|
||||
return publicKey.elliptic.eddsa.sign(oid, hashAlgo, data, Q, seed, hashed);
|
||||
}
|
||||
default:
|
||||
throw new Error('Invalid signature algorithm.');
|
||||
|
|
|
@ -52,12 +52,6 @@ export { default as util } from './util';
|
|||
*/
|
||||
export * from './packet';
|
||||
|
||||
/**
|
||||
* @see module:type/mpi
|
||||
* @name module:openpgp.MPI
|
||||
*/
|
||||
export { default as MPI } from './type/mpi';
|
||||
|
||||
/**
|
||||
* @see module:type/s2k
|
||||
* @name module:openpgp.S2K
|
||||
|
|
|
@ -238,7 +238,7 @@ export async function mergeSignatures(source, dest, attr, checkFn) {
|
|||
await Promise.all(source.map(async function(sourceSig) {
|
||||
if (!sourceSig.isExpired() && (!checkFn || await checkFn(sourceSig)) &&
|
||||
!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);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
/**
|
||||
* @requires type/keyid
|
||||
* @requires type/mpi
|
||||
* @requires config
|
||||
* @requires crypto
|
||||
* @requires enums
|
||||
|
@ -142,7 +141,7 @@ class PublicKeyPacket {
|
|||
const algo = enums.write(enums.publicKey, this.algorithm);
|
||||
arr.push(new Uint8Array([algo]));
|
||||
|
||||
const params = crypto.serializeKeyParams(algo, this.publicParams);
|
||||
const params = crypto.serializeParams(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));
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
/**
|
||||
* @requires type/keyid
|
||||
* @requires type/mpi
|
||||
* @requires crypto
|
||||
* @requires enums
|
||||
* @requires util
|
||||
|
@ -56,8 +55,8 @@ class PublicKeyEncryptedSessionKeyPacket {
|
|||
this.sessionKey = null;
|
||||
this.sessionKeyAlgorithm = null;
|
||||
|
||||
/** @type {Array<module:type/mpi>} */
|
||||
this.encrypted = [];
|
||||
/** @type {Object} */
|
||||
this.encrypted = {};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,7 +85,7 @@ class PublicKeyEncryptedSessionKeyPacket {
|
|||
new Uint8Array([this.version]),
|
||||
this.publicKeyId.write(),
|
||||
new Uint8Array([enums.write(enums.publicKey, this.publicKeyAlgorithm)]),
|
||||
crypto.serializeKeyParams(algo, this.encrypted)
|
||||
crypto.serializeParams(algo, this.encrypted)
|
||||
];
|
||||
|
||||
return util.concatUint8Array(arr);
|
||||
|
|
|
@ -211,7 +211,7 @@ class SecretKeyPacket extends PublicKeyPacket {
|
|||
if (!this.isDummy()) {
|
||||
if (!this.s2k_usage) {
|
||||
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([
|
||||
cleartextParams,
|
||||
util.writeChecksum(cleartextParams)
|
||||
|
@ -294,7 +294,7 @@ class SecretKeyPacket extends PublicKeyPacket {
|
|||
this.s2k = new type_s2k();
|
||||
this.s2k.salt = await crypto.random.getRandomBytes(8);
|
||||
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';
|
||||
const key = await produceEncryptionKey(this.s2k, passphrase, this.symmetric);
|
||||
const blockLen = crypto.cipher[this.symmetric].blockSize;
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
* @requires web-stream-tools
|
||||
* @requires packet/packet
|
||||
* @requires type/keyid
|
||||
* @requires type/mpi
|
||||
* @requires crypto
|
||||
* @requires enums
|
||||
* @requires util
|
||||
|
@ -28,7 +27,6 @@
|
|||
import stream from 'web-stream-tools';
|
||||
import { readSimpleLength, writeSimpleLength } from './packet';
|
||||
import type_keyid from '../type/keyid.js';
|
||||
import type_mpi from '../type/mpi.js';
|
||||
import crypto from '../crypto';
|
||||
import enums from '../enums';
|
||||
import util from '../util';
|
||||
|
@ -133,7 +131,19 @@ class SignaturePacket {
|
|||
this.signedHashValue = bytes.subarray(i, 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() {
|
||||
|
@ -141,7 +151,7 @@ class SignaturePacket {
|
|||
arr.push(this.signatureData);
|
||||
arr.push(this.write_unhashed_sub_packets());
|
||||
arr.push(this.signedHashValue);
|
||||
arr.push(stream.clone(this.signature));
|
||||
arr.push(this.write_params());
|
||||
return util.concat(arr);
|
||||
}
|
||||
|
||||
|
@ -181,9 +191,9 @@ class SignaturePacket {
|
|||
publicKeyAlgorithm, hashAlgorithm, key.publicParams, key.privateParams, toHash, await stream.readToEnd(hash)
|
||||
);
|
||||
if (streaming) {
|
||||
this.signature = stream.fromAsync(signed);
|
||||
this.params = signed();
|
||||
} else {
|
||||
this.signature = await signed();
|
||||
this.params = await signed();
|
||||
|
||||
// Store the fact that this signature is valid, e.g. for when we call `await
|
||||
// getLatestValidSignature(this.revocationSignatures, key, data)` later.
|
||||
|
@ -661,6 +671,7 @@ class SignaturePacket {
|
|||
* @param {module:enums.signature} signatureType expected signature type
|
||||
* @param {String|Object} data data which on the signature applies
|
||||
* @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.
|
||||
* @async
|
||||
*/
|
||||
|
@ -687,33 +698,10 @@ class SignaturePacket {
|
|||
throw new Error('Message digest did not match');
|
||||
}
|
||||
|
||||
let mpicount = 0;
|
||||
// Algorithm-Specific Fields for RSA signatures:
|
||||
// - multiprecision number (MPI) of RSA signature value m**d mod n.
|
||||
if (publicKeyAlgorithm > 0 && publicKeyAlgorithm < 4) {
|
||||
mpicount = 1;
|
||||
this.params = await this.params;
|
||||
|
||||
// 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(
|
||||
publicKeyAlgorithm, hashAlgorithm, mpi, key.publicParams,
|
||||
publicKeyAlgorithm, hashAlgorithm, this.params, key.publicParams,
|
||||
toHash, hash
|
||||
);
|
||||
if (!verified) {
|
||||
|
|
137
src/type/mpi.js
137
src/type/mpi.js
|
@ -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;
|
|
@ -194,9 +194,6 @@ export default {
|
|||
|
||||
/**
|
||||
* 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
|
||||
* @returns {Uint8Array} MPI-formatted Uint8Array
|
||||
*/
|
||||
|
|
|
@ -211,35 +211,24 @@ module.exports = () => describe('API functional testing', function() {
|
|||
|
||||
describe('Sign and verify', function () {
|
||||
it('RSA', async function () {
|
||||
return crypto.signature.sign(
|
||||
1, 2, RSAPublicParams, RSAPrivateParams, data, await crypto.hash.digest(2, data)
|
||||
).then(async RSAsignedData => {
|
||||
const RSAsignedDataMPI = new openpgp.MPI();
|
||||
RSAsignedDataMPI.read(RSAsignedData);
|
||||
return crypto.signature.verify(
|
||||
1, 2, [RSAsignedDataMPI], RSAPublicParams, data, await crypto.hash.digest(2, data)
|
||||
).then(success => {
|
||||
return expect(success).to.be.true;
|
||||
});
|
||||
});
|
||||
const RSAsignedData = await crypto.signature.sign(
|
||||
openpgp.enums.publicKey.rsaEncryptSign, openpgp.enums.hash.sha1, RSAPublicParams, RSAPrivateParams, data, await crypto.hash.digest(2, data)
|
||||
);
|
||||
const success = await crypto.signature.verify(
|
||||
openpgp.enums.publicKey.rsaEncryptSign, openpgp.enums.hash.sha1, RSAsignedData, RSAPublicParams, data, await crypto.hash.digest(2, data)
|
||||
);
|
||||
return expect(success).to.be.true;
|
||||
});
|
||||
|
||||
it('DSA', async function () {
|
||||
return crypto.signature.sign(
|
||||
17, 2, DSAPublicParams, DSAPrivateParams, data, await crypto.hash.digest(2, data)
|
||||
).then(async DSAsignedData => {
|
||||
DSAsignedData = util.uint8ArrayToStr(DSAsignedData);
|
||||
const DSAmsgMPIs = [];
|
||||
DSAmsgMPIs[0] = new openpgp.MPI();
|
||||
DSAmsgMPIs[1] = new openpgp.MPI();
|
||||
DSAmsgMPIs[0].read(DSAsignedData.substring(0,34));
|
||||
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;
|
||||
});
|
||||
});
|
||||
const DSAsignedData = await crypto.signature.sign(
|
||||
openpgp.enums.publicKey.dsa, openpgp.enums.hash.sha1, DSAPublicParams, DSAPrivateParams, data, await crypto.hash.digest(2, data)
|
||||
);
|
||||
const success = await crypto.signature.verify(
|
||||
openpgp.enums.publicKey.dsa, openpgp.enums.hash.sha1, DSAsignedData, DSAPublicParams, data, await crypto.hash.digest(2, data)
|
||||
);
|
||||
|
||||
return expect(success).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -39,8 +39,7 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra
|
|||
const { n, e, d, p, q, u } = { ...publicParams, ...privateParams };
|
||||
const message = await openpgp.crypto.generateSessionKey('aes256');
|
||||
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(result.toUint8Array(), n, e, d, p, q, u);
|
||||
const decrypted = await openpgp.crypto.publicKey.rsa.decrypt(encrypted, n, e, d, p, q, u);
|
||||
expect(decrypted).to.deep.equal(message);
|
||||
});
|
||||
|
||||
|
|
|
@ -155,14 +155,4 @@ module.exports = () => describe('BigInteger interface', function() {
|
|||
const expected = nBN.testn(5) ? 1 : 0;
|
||||
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;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -656,7 +656,7 @@ WPVMYDzj6X7I1A+nWeNiPlp2PoUUUvdCLisY1aU1wyTJa7wBsLARsrhXk5/R1pQt
|
|||
Blk+CJ7ytHy6En8542bB/yC+Z9/zWbVuhg==
|
||||
=jmT1
|
||||
-----END PGP PUBLIC KEY BLOCK-----`;
|
||||
|
||||
|
||||
const msg_sig_expired =
|
||||
['-----BEGIN PGP MESSAGE-----',
|
||||
'Comment: GPGTools - https://gpgtools.org',
|
||||
|
@ -1609,21 +1609,17 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA
|
|||
const privKey2 = await openpgp.readArmoredKey(priv_key_arm2);
|
||||
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
|
||||
return openpgp.generateKey(opt).then(function(gen) {
|
||||
const generatedKey = gen.key;
|
||||
return msg.signDetached([generatedKey, privKey2]).then(detachedSig => {
|
||||
return msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]).then(async result => {
|
||||
expect(await result[0].verified).to.be.true;
|
||||
expect(await result[1].verified).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
const { key: generatedKey } = await openpgp.generateKey(opt);
|
||||
const detachedSig = await msg.signDetached([generatedKey, privKey2]);
|
||||
const result = await msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]);
|
||||
expect(await result[0].verified).to.be.true;
|
||||
expect(await result[1].verified).to.be.true;
|
||||
});
|
||||
|
||||
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) {
|
||||
const key = gen.key;
|
||||
let message = openpgp.Message.fromText('hello world');
|
||||
|
|
|
@ -971,4 +971,4 @@ module.exports = () => describe('Streaming', function() {
|
|||
expect(await openpgp.stream.readToEnd(decrypted.data)).to.deep.equal(plaintext);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -216,8 +216,8 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr
|
|||
const util = openpgp.util;
|
||||
function testVector(vector) {
|
||||
const curve = new elliptic.Curve('ed25519');
|
||||
const { publicKey } = openpgp.crypto.publicKey.nacl.sign.keyPair.fromSeed(openpgp.util.hexToUint8Array(vector.SECRET_KEY));
|
||||
expect(publicKey).to.deep.equal(openpgp.util.hexToUint8Array(vector.PUBLIC_KEY));
|
||||
const { publicKey } = openpgp.crypto.publicKey.nacl.sign.keyPair.fromSeed(util.hexToUint8Array(vector.SECRET_KEY));
|
||||
expect(publicKey).to.deep.equal(util.hexToUint8Array(vector.PUBLIC_KEY));
|
||||
const data = util.strToUint8Array(vector.MESSAGE);
|
||||
const privateParams = {
|
||||
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),
|
||||
Q: util.hexToUint8Array('40' + vector.PUBLIC_KEY)
|
||||
};
|
||||
const msg_MPIs = [
|
||||
new openpgp.MPI(util.uint8ArrayToStr(util.hexToUint8Array(vector.SIGNATURE.R).reverse())),
|
||||
new openpgp.MPI(util.uint8ArrayToStr(util.hexToUint8Array(vector.SIGNATURE.S).reverse()))
|
||||
];
|
||||
const R = util.hexToUint8Array(vector.SIGNATURE.R);
|
||||
const S = util.hexToUint8Array(vector.SIGNATURE.S);
|
||||
return Promise.all([
|
||||
signature.sign(22, undefined, publicParams, privateParams, undefined, data).then(signed => {
|
||||
const len = (((signed[0] << 8) | signed[1]) + 7) / 8;
|
||||
expect(util.hexToUint8Array(vector.SIGNATURE.R)).to.deep.eq(signed.slice(2, 2 + len));
|
||||
expect(util.hexToUint8Array(vector.SIGNATURE.S)).to.deep.eq(signed.slice(4 + len));
|
||||
signature.sign(22, undefined, publicParams, privateParams, undefined, data).then(({ r, s }) => {
|
||||
expect(R).to.deep.eq(r);
|
||||
expect(S).to.deep.eq(s);
|
||||
}),
|
||||
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;
|
||||
})
|
||||
]);
|
||||
|
|
Loading…
Reference in New Issue
Block a user