import { armor, unarmor } from './encoding/armor';import enums from './enums';import util from './util';import { PacketList, LiteralDataPacket, SignaturePacket } from './packet';import { Signature } from './signature';import { createVerificationObjects, createSignaturePackets } from './message';import defaultConfig from './config';
const allowedPackets = util.constructAllowedPackets([SignaturePacket]);
export class CleartextMessage { constructor(text, signature) { this.text = util.removeTrailingSpaces(text).replace(/\r?\n/g, '\r\n'); if (signature && !(signature instanceof Signature)) { throw new Error('Invalid signature input'); } this.signature = signature || new Signature(new PacketList()); }
getSigningKeyIDs() { const keyIDs = []; const signatureList = this.signature.packets; signatureList.forEach(function(packet) { keyIDs.push(packet.issuerKeyID); }); return keyIDs; }
async sign(privateKeys, signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], config = defaultConfig) { const literalDataPacket = new LiteralDataPacket(); literalDataPacket.setText(this.text); const newSignature = new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, signingKeyIDs, date, userIDs, true, config)); return new CleartextMessage(this.text, newSignature); }
verify(keys, date = new Date(), config = defaultConfig) { const signatureList = this.signature.packets; const literalDataPacket = new LiteralDataPacket(); literalDataPacket.setText(this.text); return createVerificationObjects(signatureList, [literalDataPacket], keys, date, true, config); }
getText() { return this.text.replace(/\r\n/g, '\n'); }
armor(config = defaultConfig) { let hashes = this.signature.packets.map(function(packet) { return enums.read(enums.hash, packet.hashAlgorithm).toUpperCase(); }); hashes = hashes.filter(function(item, i, ar) { return ar.indexOf(item) === i; }); const body = { hash: hashes.join(), text: this.text, data: this.signature.packets.write() }; return armor(enums.armor.signed, body, undefined, undefined, undefined, config); }}
export async function readCleartextMessage({ cleartextMessage, config, ...rest }) { config = { ...defaultConfig, ...config }; if (!cleartextMessage) { throw new Error('readCleartextMessage: must pass options object containing `cleartextMessage`'); } if (!util.isString(cleartextMessage)) { throw new Error('readCleartextMessage: options.cleartextMessage must be a string'); } const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`);
const input = await unarmor(cleartextMessage); if (input.type !== enums.armor.signed) { throw new Error('No cleartext signed message.'); } const packetlist = await PacketList.fromBinary(input.data, allowedPackets, config); verifyHeaders(input.headers, packetlist); const signature = new Signature(packetlist); return new CleartextMessage(input.text, signature);}
function verifyHeaders(headers, packetlist) { const checkHashAlgos = function(hashAlgos) { const check = packet => algo => packet.hashAlgorithm === algo;
for (let i = 0; i < packetlist.length; i++) { if (packetlist[i].constructor.tag === enums.packet.signature && !hashAlgos.some(check(packetlist[i]))) { return false; } } return true; };
let oneHeader = null; let hashAlgos = []; headers.forEach(function(header) { oneHeader = header.match(/Hash: (.+)/); if (oneHeader) { oneHeader = oneHeader[1].replace(/\s/g, ''); oneHeader = oneHeader.split(','); oneHeader = oneHeader.map(function(hash) { hash = hash.toLowerCase(); try { return enums.write(enums.hash, hash); } catch (e) { throw new Error('Unknown hash algorithm in armor header: ' + hash); } }); hashAlgos = hashAlgos.concat(oneHeader); } else { throw new Error('Only "Hash" header allowed in cleartext signed message'); } });
if (!hashAlgos.length && !checkHashAlgos([enums.hash.md5])) { throw new Error('If no "Hash" header in cleartext signed message, then only MD5 signatures allowed'); } else if (hashAlgos.length && !checkHashAlgos(hashAlgos)) { throw new Error('Hash algorithm mismatch in armor header and signature'); }}
export async function createCleartextMessage({ text, ...rest }) { if (!text) { throw new Error('createCleartextMessage: must pass options object containing `text`'); } if (!util.isString(text)) { throw new Error('createCleartextMessage: options.text must be a string'); } const unknownOptions = Object.keys(rest); if (unknownOptions.length > 0) throw new Error(`Unknown option: ${unknownOptions.join(', ')}`);
return new CleartextMessage(text);}