@isomorphic-pgp/sign-and-verify
Version:
Create and verify OpenPGP signatures
120 lines (106 loc) • 3.78 kB
JavaScript
const { BigInteger } = require("jsbn");
const { hashes } = require("./hashes.js");
const arrayBufferToHex = require("array-buffer-to-hex");
const { encode } = require("isomorphic-textencoder");
const Message = require("@isomorphic-pgp/parser/Message.js");
const UrlSafeBase64 = require("@isomorphic-pgp/parser/UrlSafeBase64.js");
const Uint16 = require("@isomorphic-pgp/parser/Uint16.js");
const EMSA = require("@isomorphic-pgp/parser/emsa.js");
const { HashAlgorithm } = require("@isomorphic-pgp/parser/constants.js");
const { payloadSignatureHashData } = require("@isomorphic-pgp/parser/payloadSignatureHashData.js");
const { trimZeros } = require("@isomorphic-pgp/util/trimZeros.js");
module.exports.sign = async function sign(openpgpPrivateKey, payload, timestamp) {
let parsed = Message.parse(openpgpPrivateKey);
let privateKeyPacket = parsed.packets[0].packet;
let userIdPacket = parsed.packets[1].packet;
// TODO: Assert that this is all correct and stuff.
let selfSignaturePacket = parsed.packets[2].packet;
let keyid = selfSignaturePacket.unhashed.subpackets.find(subpacket => subpacket.type === 16).subpacket.issuer;
let e = UrlSafeBase64.serialize(privateKeyPacket.mpi.e);
let n = UrlSafeBase64.serialize(privateKeyPacket.mpi.n);
let d = UrlSafeBase64.serialize(privateKeyPacket.mpi.d);
let p = UrlSafeBase64.serialize(privateKeyPacket.mpi.p);
let q = UrlSafeBase64.serialize(privateKeyPacket.mpi.q);
let u = UrlSafeBase64.serialize(privateKeyPacket.mpi.u);
// SIGN
let D = new BigInteger(arrayBufferToHex(d), 16);
let P = new BigInteger(arrayBufferToHex(p), 16);
let Q = new BigInteger(arrayBufferToHex(q), 16);
let U = new BigInteger(arrayBufferToHex(u), 16);
let partialSignaturePacket = {
version: 4,
type: 0,
alg: 1,
hash: 8,
hashed: {
subpackets: [
{
type: 2,
subpacket: {
creation: timestamp
}
}
]
}
};
const hashType = HashAlgorithm[partialSignaturePacket.hash]
payload = typeof payload === "string" ? encode(payload) : payload;
let buffer = await payloadSignatureHashData(payload, partialSignaturePacket);
let hash = await hashes[hashType](buffer, { outputFormat: "buffer" });
hash = new Uint8Array(hash);
let left16 = Uint16.parse([hash[0], hash[1]]);
// Wrap `hash` in the dumbass EMSA-PKCS1-v1_5 padded message format:
hash = EMSA.encode(hashType, hash, n.byteLength);
let M = new BigInteger(arrayBufferToHex(hash), 16);
// // Straightforward solution: ~ 679ms
// console.time("standard");
// let S = M.modPow(D, N);
// console.timeEnd("standard");
// Fast solution using Chinese Remainder Theorem: ~184ms
// from libgcryp docs:
/*
* m1 = c ^ (d mod (p-1)) mod p
* m2 = c ^ (d mod (q-1)) mod q
* h = u * (m2 - m1) mod q
* m = m1 + h * p
*/
let ONE = new BigInteger("01", 16);
let DP = D.mod(P.subtract(ONE));
let DQ = D.mod(Q.subtract(ONE));
let M1 = M.modPow(DP, P);
let M2 = M.modPow(DQ, Q);
let H = U.multiply(M2.subtract(M1)).mod(Q);
let S = M1.add(H.multiply(P));
let signature = new Uint8Array(S.toByteArray());
signature = trimZeros(signature);
signature = UrlSafeBase64.parse(signature);
let completeSignaturePacket = Object.assign({}, partialSignaturePacket, {
unhashed: {
subpackets: [
{
type: 16,
subpacket: {
issuer: keyid
}
}
]
},
left16,
mpi: {
signature
}
});
let message = {
type: "PGP SIGNATURE",
packets: [
{
type: 0,
tag: 2,
tag_s: "Signature Packet",
packet: completeSignaturePacket
}
]
};
let text = Message.serialize(message);
return text;
}