import * as stream from '@openpgp/web-stream-tools';import * as base64 from './base64';import enums from '../enums';import util from '../util';import defaultConfig from '../config';
function getType(text) { const reHeader = /^-----BEGIN PGP (MESSAGE, PART \d+\/\d+|MESSAGE, PART \d+|SIGNED MESSAGE|MESSAGE|PUBLIC KEY BLOCK|PRIVATE KEY BLOCK|SIGNATURE)-----$/m;
const header = text.match(reHeader);
if (!header) { throw new Error('Unknown ASCII armor type'); }
if (/MESSAGE, PART \d+\/\d+/.test(header[1])) { return enums.armor.multipartSection; } else if (/MESSAGE, PART \d+/.test(header[1])) { return enums.armor.multipartLast; } else if (/SIGNED MESSAGE/.test(header[1])) { return enums.armor.signed; } else if (/MESSAGE/.test(header[1])) { return enums.armor.message; } else if (/PUBLIC KEY BLOCK/.test(header[1])) { return enums.armor.publicKey; } else if (/PRIVATE KEY BLOCK/.test(header[1])) { return enums.armor.privateKey; } else if (/SIGNATURE/.test(header[1])) { return enums.armor.signature; }}
function addheader(customComment, config) { let result = ''; if (config.showVersion) { result += 'Version: ' + config.versionString + '\n'; } if (config.showComment) { result += 'Comment: ' + config.commentString + '\n'; } if (customComment) { result += 'Comment: ' + customComment + '\n'; } result += '\n'; return result;}
function getCheckSum(data) { const crc = createcrc24(data); return base64.encode(crc);}
const crc_table = [ new Array(0xFF), new Array(0xFF), new Array(0xFF), new Array(0xFF)];
for (let i = 0; i <= 0xFF; i++) { let crc = i << 16; for (let j = 0; j < 8; j++) { crc = (crc << 1) ^ ((crc & 0x800000) !== 0 ? 0x864CFB : 0); } crc_table[0][i] = ((crc & 0xFF0000) >> 16) | (crc & 0x00FF00) | ((crc & 0x0000FF) << 16);}for (let i = 0; i <= 0xFF; i++) { crc_table[1][i] = (crc_table[0][i] >> 8) ^ crc_table[0][crc_table[0][i] & 0xFF];}for (let i = 0; i <= 0xFF; i++) { crc_table[2][i] = (crc_table[1][i] >> 8) ^ crc_table[0][crc_table[1][i] & 0xFF];}for (let i = 0; i <= 0xFF; i++) { crc_table[3][i] = (crc_table[2][i] >> 8) ^ crc_table[0][crc_table[2][i] & 0xFF];}
const isLittleEndian = (function() { const buffer = new ArrayBuffer(2); new DataView(buffer).setInt16(0, 0xFF, true ); return new Int16Array(buffer)[0] === 0xFF;}());
function createcrc24(input) { let crc = 0xCE04B7; return stream.transform(input, value => { const len32 = isLittleEndian ? Math.floor(value.length / 4) : 0; const arr32 = new Uint32Array(value.buffer, value.byteOffset, len32); for (let i = 0; i < len32; i++) { crc ^= arr32[i]; crc = crc_table[0][(crc >> 24) & 0xFF] ^ crc_table[1][(crc >> 16) & 0xFF] ^ crc_table[2][(crc >> 8) & 0xFF] ^ crc_table[3][(crc >> 0) & 0xFF]; } for (let i = len32 * 4; i < value.length; i++) { crc = (crc >> 8) ^ crc_table[0][(crc & 0xFF) ^ value[i]]; } }, () => new Uint8Array([crc, crc >> 8, crc >> 16]));}
function verifyHeaders(headers) { for (let i = 0; i < headers.length; i++) { if (!/^([^\s:]|[^\s:][^:]*[^\s:]): .+$/.test(headers[i])) { throw new Error('Improperly formatted armor header: ' + headers[i]); } if (!/^(Version|Comment|MessageID|Hash|Charset): .+$/.test(headers[i])) { util.printDebugError(new Error('Unknown header: ' + headers[i])); } }}
function splitChecksum(text) { let body = text; let checksum = '';
const lastEquals = text.lastIndexOf('=');
if (lastEquals >= 0 && lastEquals !== text.length - 1) { body = text.slice(0, lastEquals); checksum = text.slice(lastEquals + 1).substr(0, 4); }
return { body: body, checksum: checksum };}
export function unarmor(input, config = defaultConfig) { return new Promise(async (resolve, reject) => { try { const reSplit = /^-----[^-]+-----$/m; const reEmptyLine = /^[ \f\r\t\u00a0\u2000-\u200a\u202f\u205f\u3000]*$/;
let type; const headers = []; let lastHeaders = headers; let headersDone; let text = []; let textDone; let checksum; let data = base64.decode(stream.transformPair(input, async (readable, writable) => { const reader = stream.getReader(readable); try { while (true) { let line = await reader.readLine(); if (line === undefined) { throw new Error('Misformed armored text'); } line = util.removeTrailingSpaces(line.replace(/[\r\n]/g, '')); if (!type) { if (reSplit.test(line)) { type = getType(line); } } else if (!headersDone) { if (reSplit.test(line)) { reject(new Error('Mandatory blank line missing between armor headers and armor data')); } if (!reEmptyLine.test(line)) { lastHeaders.push(line); } else { verifyHeaders(lastHeaders); headersDone = true; if (textDone || type !== 2) { resolve({ text, data, headers, type }); break; } } } else if (!textDone && type === 2) { if (!reSplit.test(line)) { text.push(line.replace(/^- /, '')); } else { text = text.join('\r\n'); textDone = true; verifyHeaders(lastHeaders); lastHeaders = []; headersDone = false; } } } } catch (e) { reject(e); return; } const writer = stream.getWriter(writable); try { while (true) { await writer.ready; const { done, value } = await reader.read(); if (done) { throw new Error('Misformed armored text'); } const line = value + ''; if (line.indexOf('=') === -1 && line.indexOf('-') === -1) { await writer.write(line); } else { let remainder = await reader.readToEnd(); if (!remainder.length) remainder = ''; remainder = line + remainder; remainder = util.removeTrailingSpaces(remainder.replace(/\r/g, '')); const parts = remainder.split(reSplit); if (parts.length === 1) { throw new Error('Misformed armored text'); } const split = splitChecksum(parts[0].slice(0, -1)); checksum = split.checksum; await writer.write(split.body); break; } } await writer.ready; await writer.close(); } catch (e) { await writer.abort(e); } })); data = stream.transformPair(data, async (readable, writable) => { const checksumVerified = stream.readToEnd(getCheckSum(stream.passiveClone(readable))); checksumVerified.catch(() => {}); await stream.pipe(readable, writable, { preventClose: true }); const writer = stream.getWriter(writable); try { const checksumVerifiedString = (await checksumVerified).replace('\n', ''); if (checksum !== checksumVerifiedString && (checksum || config.checksumRequired)) { throw new Error('Ascii armor integrity check failed'); } await writer.ready; await writer.close(); } catch (e) { await writer.abort(e); } }); } catch (e) { reject(e); } }).then(async result => { if (stream.isArrayStream(result.data)) { result.data = await stream.readToEnd(result.data); } return result; });}
export function armor(messageType, body, partIndex, partTotal, customComment, config = defaultConfig) { let text; let hash; if (messageType === enums.armor.signed) { text = body.text; hash = body.hash; body = body.data; } const bodyClone = stream.passiveClone(body); const result = []; switch (messageType) { case enums.armor.multipartSection: result.push('-----BEGIN PGP MESSAGE, PART ' + partIndex + '/' + partTotal + '-----\n'); result.push(addheader(customComment, config)); result.push(base64.encode(body)); result.push('=', getCheckSum(bodyClone)); result.push('-----END PGP MESSAGE, PART ' + partIndex + '/' + partTotal + '-----\n'); break; case enums.armor.multipartLast: result.push('-----BEGIN PGP MESSAGE, PART ' + partIndex + '-----\n'); result.push(addheader(customComment, config)); result.push(base64.encode(body)); result.push('=', getCheckSum(bodyClone)); result.push('-----END PGP MESSAGE, PART ' + partIndex + '-----\n'); break; case enums.armor.signed: result.push('\n-----BEGIN PGP SIGNED MESSAGE-----\n'); result.push('Hash: ' + hash + '\n\n'); result.push(text.replace(/^-/mg, '- -')); result.push('\n-----BEGIN PGP SIGNATURE-----\n'); result.push(addheader(customComment, config)); result.push(base64.encode(body)); result.push('=', getCheckSum(bodyClone)); result.push('-----END PGP SIGNATURE-----\n'); break; case enums.armor.message: result.push('-----BEGIN PGP MESSAGE-----\n'); result.push(addheader(customComment, config)); result.push(base64.encode(body)); result.push('=', getCheckSum(bodyClone)); result.push('-----END PGP MESSAGE-----\n'); break; case enums.armor.publicKey: result.push('-----BEGIN PGP PUBLIC KEY BLOCK-----\n'); result.push(addheader(customComment, config)); result.push(base64.encode(body)); result.push('=', getCheckSum(bodyClone)); result.push('-----END PGP PUBLIC KEY BLOCK-----\n'); break; case enums.armor.privateKey: result.push('-----BEGIN PGP PRIVATE KEY BLOCK-----\n'); result.push(addheader(customComment, config)); result.push(base64.encode(body)); result.push('=', getCheckSum(bodyClone)); result.push('-----END PGP PRIVATE KEY BLOCK-----\n'); break; case enums.armor.signature: result.push('-----BEGIN PGP SIGNATURE-----\n'); result.push(addheader(customComment, config)); result.push(base64.encode(body)); result.push('=', getCheckSum(bodyClone)); result.push('-----END PGP SIGNATURE-----\n'); break; }
return util.concat(result);}