UNPKG

openpgp

Version:

OpenPGP.js is a Javascript implementation of the OpenPGP protocol. This is defined in RFC 4880.

1,855 lines (1,702 loc) 1.48 MB
/*! OpenPGP.js v5.11.2 - 2024-06-19 - this is LGPL licensed code, see LICENSE/our website https://openpgpjs.org/ for more information. */ const globalThis = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; const doneWritingPromise = Symbol('doneWritingPromise'); const doneWritingResolve = Symbol('doneWritingResolve'); const doneWritingReject = Symbol('doneWritingReject'); const readingIndex = Symbol('readingIndex'); class ArrayStream extends Array { constructor() { super(); this[doneWritingPromise] = new Promise((resolve, reject) => { this[doneWritingResolve] = resolve; this[doneWritingReject] = reject; }); this[doneWritingPromise].catch(() => {}); } } ArrayStream.prototype.getReader = function() { if (this[readingIndex] === undefined) { this[readingIndex] = 0; } return { read: async () => { await this[doneWritingPromise]; if (this[readingIndex] === this.length) { return { value: undefined, done: true }; } return { value: this[this[readingIndex]++], done: false }; } }; }; ArrayStream.prototype.readToEnd = async function(join) { await this[doneWritingPromise]; const result = join(this.slice(this[readingIndex])); this.length = 0; return result; }; ArrayStream.prototype.clone = function() { const clone = new ArrayStream(); clone[doneWritingPromise] = this[doneWritingPromise].then(() => { clone.push(...this); }); return clone; }; /** * Check whether data is an ArrayStream * @param {Any} input data to check * @returns {boolean} */ function isArrayStream(input) { return input && input.getReader && Array.isArray(input); } /** * A wrapper class over the native WritableStreamDefaultWriter. * It also lets you "write data to" array streams instead of streams. * @class */ function Writer(input) { if (!isArrayStream(input)) { const writer = input.getWriter(); const releaseLock = writer.releaseLock; writer.releaseLock = () => { writer.closed.catch(function() {}); releaseLock.call(writer); }; return writer; } this.stream = input; } /** * Write a chunk of data. * @returns {Promise<undefined>} * @async */ Writer.prototype.write = async function(chunk) { this.stream.push(chunk); }; /** * Close the stream. * @returns {Promise<undefined>} * @async */ Writer.prototype.close = async function() { this.stream[doneWritingResolve](); }; /** * Error the stream. * @returns {Promise<Object>} * @async */ Writer.prototype.abort = async function(reason) { this.stream[doneWritingReject](reason); return reason; }; /** * Release the writer's lock. * @returns {undefined} * @async */ Writer.prototype.releaseLock = function() {}; const isNode = typeof globalThis.process === 'object' && typeof globalThis.process.versions === 'object'; const NodeReadableStream = isNode && void('stream').Readable; /** * Check whether data is a Stream, and if so of which type * @param {Any} input data to check * @returns {'web'|'ponyfill'|'node'|'array'|'web-like'|false} */ function isStream(input) { if (isArrayStream(input)) { return 'array'; } if (globalThis.ReadableStream && globalThis.ReadableStream.prototype.isPrototypeOf(input)) { return 'web'; } if (ReadableStream && ReadableStream.prototype.isPrototypeOf(input)) { return 'ponyfill'; } if (NodeReadableStream && NodeReadableStream.prototype.isPrototypeOf(input)) { return 'node'; } if (input && input.getReader) { return 'web-like'; } return false; } /** * Check whether data is a Uint8Array * @param {Any} input data to check * @returns {Boolean} */ function isUint8Array(input) { return Uint8Array.prototype.isPrototypeOf(input); } /** * Concat Uint8Arrays * @param {Array<Uint8array>} Array of Uint8Arrays to concatenate * @returns {Uint8array} Concatenated array */ function concatUint8Array(arrays) { if (arrays.length === 1) return arrays[0]; let totalLength = 0; for (let i = 0; i < arrays.length; i++) { if (!isUint8Array(arrays[i])) { throw new Error('concatUint8Array: Data must be in the form of a Uint8Array'); } totalLength += arrays[i].length; } const result = new Uint8Array(totalLength); let pos = 0; arrays.forEach(function (element) { result.set(element, pos); pos += element.length; }); return result; } const NodeBuffer = isNode && void('buffer').Buffer; const NodeReadableStream$1 = isNode && void('stream').Readable; /** * Web / node stream conversion functions * From https://github.com/gwicke/node-web-streams */ let nodeToWeb; let webToNode; if (NodeReadableStream$1) { /** * Convert a Node Readable Stream to a Web ReadableStream * @param {Readable} nodeStream * @returns {ReadableStream} */ nodeToWeb = function(nodeStream) { let canceled = false; return new ReadableStream({ start(controller) { nodeStream.pause(); nodeStream.on('data', chunk => { if (canceled) { return; } if (NodeBuffer.isBuffer(chunk)) { chunk = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength); } controller.enqueue(chunk); nodeStream.pause(); }); nodeStream.on('end', () => { if (canceled) { return; } controller.close(); }); nodeStream.on('error', e => controller.error(e)); }, pull() { nodeStream.resume(); }, cancel(reason) { canceled = true; nodeStream.destroy(reason); } }); }; class NodeReadable extends NodeReadableStream$1 { constructor(webStream, options) { super(options); this._reader = getReader(webStream); } async _read(size) { try { while (true) { const { done, value } = await this._reader.read(); if (done) { this.push(null); break; } if (!this.push(value)) { break; } } } catch (e) { this.destroy(e); } } async _destroy(error, callback) { this._reader.cancel(error).then(callback, callback); } } /** * Convert a Web ReadableStream to a Node Readable Stream * @param {ReadableStream} webStream * @param {Object} options * @returns {Readable} */ webToNode = function(webStream, options) { return new NodeReadable(webStream, options); }; } const doneReadingSet = new WeakSet(); const externalBuffer = Symbol('externalBuffer'); /** * A wrapper class over the native ReadableStreamDefaultReader. * This additionally implements pushing back data on the stream, which * lets us implement peeking and a host of convenience functions. * It also lets you read data other than streams, such as a Uint8Array. * @class */ function Reader(input) { this.stream = input; if (input[externalBuffer]) { this[externalBuffer] = input[externalBuffer].slice(); } if (isArrayStream(input)) { const reader = input.getReader(); this._read = reader.read.bind(reader); this._releaseLock = () => {}; this._cancel = async () => {}; return; } let streamType = isStream(input); if (streamType === 'node') { input = nodeToWeb(input); } if (streamType) { const reader = input.getReader(); this._read = reader.read.bind(reader); this._releaseLock = () => { reader.closed.catch(function() {}); reader.releaseLock(); }; this._cancel = reader.cancel.bind(reader); return; } let doneReading = false; this._read = async () => { if (doneReading || doneReadingSet.has(input)) { return { value: undefined, done: true }; } doneReading = true; return { value: input, done: false }; }; this._releaseLock = () => { if (doneReading) { try { doneReadingSet.add(input); } catch(e) {} } }; } /** * Read a chunk of data. * @returns {Promise<Object>} Either { done: false, value: Uint8Array | String } or { done: true, value: undefined } * @async */ Reader.prototype.read = async function() { if (this[externalBuffer] && this[externalBuffer].length) { const value = this[externalBuffer].shift(); return { done: false, value }; } return this._read(); }; /** * Allow others to read the stream. */ Reader.prototype.releaseLock = function() { if (this[externalBuffer]) { this.stream[externalBuffer] = this[externalBuffer]; } this._releaseLock(); }; /** * Cancel the stream. */ Reader.prototype.cancel = function(reason) { return this._cancel(reason); }; /** * Read up to and including the first \n character. * @returns {Promise<String|Undefined>} * @async */ Reader.prototype.readLine = async function() { let buffer = []; let returnVal; while (!returnVal) { let { done, value } = await this.read(); value += ''; if (done) { if (buffer.length) return concat(buffer); return; } const lineEndIndex = value.indexOf('\n') + 1; if (lineEndIndex) { returnVal = concat(buffer.concat(value.substr(0, lineEndIndex))); buffer = []; } if (lineEndIndex !== value.length) { buffer.push(value.substr(lineEndIndex)); } } this.unshift(...buffer); return returnVal; }; /** * Read a single byte/character. * @returns {Promise<Number|String|Undefined>} * @async */ Reader.prototype.readByte = async function() { const { done, value } = await this.read(); if (done) return; const byte = value[0]; this.unshift(slice(value, 1)); return byte; }; /** * Read a specific amount of bytes/characters, unless the stream ends before that amount. * @returns {Promise<Uint8Array|String|Undefined>} * @async */ Reader.prototype.readBytes = async function(length) { const buffer = []; let bufferLength = 0; while (true) { const { done, value } = await this.read(); if (done) { if (buffer.length) return concat(buffer); return; } buffer.push(value); bufferLength += value.length; if (bufferLength >= length) { const bufferConcat = concat(buffer); this.unshift(slice(bufferConcat, length)); return slice(bufferConcat, 0, length); } } }; /** * Peek (look ahead) a specific amount of bytes/characters, unless the stream ends before that amount. * @returns {Promise<Uint8Array|String|Undefined>} * @async */ Reader.prototype.peekBytes = async function(length) { const bytes = await this.readBytes(length); this.unshift(bytes); return bytes; }; /** * Push data to the front of the stream. * Data must have been read in the last call to read*. * @param {...(Uint8Array|String|Undefined)} values */ Reader.prototype.unshift = function(...values) { if (!this[externalBuffer]) { this[externalBuffer] = []; } if ( values.length === 1 && isUint8Array(values[0]) && this[externalBuffer].length && values[0].length && this[externalBuffer][0].byteOffset >= values[0].length ) { this[externalBuffer][0] = new Uint8Array( this[externalBuffer][0].buffer, this[externalBuffer][0].byteOffset - values[0].length, this[externalBuffer][0].byteLength + values[0].length ); return; } this[externalBuffer].unshift(...values.filter(value => value && value.length)); }; /** * Read the stream to the end and return its contents, concatenated by the join function (defaults to streams.concat). * @param {Function} join * @returns {Promise<Uint8array|String|Any>} the return value of join() * @async */ Reader.prototype.readToEnd = async function(join=concat) { const result = []; while (true) { const { done, value } = await this.read(); if (done) break; result.push(value); } return join(result); }; let { ReadableStream, WritableStream, TransformStream } = globalThis; let toPonyfillReadable, toNativeReadable; async function loadStreamsPonyfill() { if (TransformStream) { return; } const [ponyfill, adapter] = await Promise.all([ Promise.resolve().then(function () { return ponyfill_es6; }), Promise.resolve().then(function () { return webStreamsAdapter; }) ]); ({ ReadableStream, WritableStream, TransformStream } = ponyfill); const { createReadableStreamWrapper } = adapter; if (globalThis.ReadableStream && ReadableStream !== globalThis.ReadableStream) { toPonyfillReadable = createReadableStreamWrapper(ReadableStream); toNativeReadable = createReadableStreamWrapper(globalThis.ReadableStream); } } const NodeBuffer$1 = isNode && void('buffer').Buffer; /** * Convert data to Stream * @param {ReadableStream|Uint8array|String} input data to convert * @returns {ReadableStream} Converted data */ function toStream(input) { let streamType = isStream(input); if (streamType === 'node') { return nodeToWeb(input); } if (streamType === 'web' && toPonyfillReadable) { return toPonyfillReadable(input); } if (streamType) { return input; } return new ReadableStream({ start(controller) { controller.enqueue(input); controller.close(); } }); } /** * Convert data to ArrayStream * @param {Object} input data to convert * @returns {ArrayStream} Converted data */ function toArrayStream(input) { if (isStream(input)) { return input; } const stream = new ArrayStream(); (async () => { const writer = getWriter(stream); await writer.write(input); await writer.close(); })(); return stream; } /** * Concat a list of Uint8Arrays, Strings or Streams * The caller should not mix Uint8Arrays with Strings, but may mix Streams with non-Streams. * @param {Array<Uint8array|String|ReadableStream>} Array of Uint8Arrays/Strings/Streams to concatenate * @returns {Uint8array|String|ReadableStream} Concatenated array */ function concat(list) { if (list.some(stream => isStream(stream) && !isArrayStream(stream))) { return concatStream(list); } if (list.some(stream => isArrayStream(stream))) { return concatArrayStream(list); } if (typeof list[0] === 'string') { return list.join(''); } if (NodeBuffer$1 && NodeBuffer$1.isBuffer(list[0])) { return NodeBuffer$1.concat(list); } return concatUint8Array(list); } /** * Concat a list of Streams * @param {Array<ReadableStream|Uint8array|String>} list Array of Uint8Arrays/Strings/Streams to concatenate * @returns {ReadableStream} Concatenated list */ function concatStream(list) { list = list.map(toStream); const transform = transformWithCancel(async function(reason) { await Promise.all(transforms.map(stream => cancel(stream, reason))); }); let prev = Promise.resolve(); const transforms = list.map((stream, i) => transformPair(stream, (readable, writable) => { prev = prev.then(() => pipe(readable, transform.writable, { preventClose: i !== list.length - 1 })); return prev; })); return transform.readable; } /** * Concat a list of ArrayStreams * @param {Array<ArrayStream|Uint8array|String>} list Array of Uint8Arrays/Strings/ArrayStreams to concatenate * @returns {ArrayStream} Concatenated streams */ function concatArrayStream(list) { const result = new ArrayStream(); let prev = Promise.resolve(); list.forEach((stream, i) => { prev = prev.then(() => pipe(stream, result, { preventClose: i !== list.length - 1 })); return prev; }); return result; } /** * Get a Reader * @param {ReadableStream|Uint8array|String} input * @returns {Reader} */ function getReader(input) { return new Reader(input); } /** * Get a Writer * @param {WritableStream} input * @returns {Writer} */ function getWriter(input) { return new Writer(input); } /** * Pipe a readable stream to a writable stream. Don't throw on input stream errors, but forward them to the output stream. * @param {ReadableStream|Uint8array|String} input * @param {WritableStream} target * @param {Object} (optional) options * @returns {Promise<undefined>} Promise indicating when piping has finished (input stream closed or errored) * @async */ async function pipe(input, target, { preventClose = false, preventAbort = false, preventCancel = false } = {}) { if (isStream(input) && !isArrayStream(input)) { input = toStream(input); try { if (input[externalBuffer]) { const writer = getWriter(target); for (let i = 0; i < input[externalBuffer].length; i++) { await writer.ready; await writer.write(input[externalBuffer][i]); } writer.releaseLock(); } await input.pipeTo(target, { preventClose, preventAbort, preventCancel }); } catch(e) {} return; } input = toArrayStream(input); const reader = getReader(input); const writer = getWriter(target); try { while (true) { await writer.ready; const { done, value } = await reader.read(); if (done) { if (!preventClose) await writer.close(); break; } await writer.write(value); } } catch (e) { if (!preventAbort) await writer.abort(e); } finally { reader.releaseLock(); writer.releaseLock(); } } /** * Pipe a readable stream through a transform stream. * @param {ReadableStream|Uint8array|String} input * @param {Object} (optional) options * @returns {ReadableStream} transformed stream */ function transformRaw(input, options) { const transformStream = new TransformStream(options); pipe(input, transformStream.writable); return transformStream.readable; } /** * Create a cancelable TransformStream. * @param {Function} cancel * @returns {TransformStream} */ function transformWithCancel(customCancel) { let pulled = false; let cancelled = false; let backpressureChangePromiseResolve, backpressureChangePromiseReject; let outputController; return { readable: new ReadableStream({ start(controller) { outputController = controller; }, pull() { if (backpressureChangePromiseResolve) { backpressureChangePromiseResolve(); } else { pulled = true; } }, async cancel(reason) { cancelled = true; if (customCancel) { await customCancel(reason); } if (backpressureChangePromiseReject) { backpressureChangePromiseReject(reason); } } }, {highWaterMark: 0}), writable: new WritableStream({ write: async function(chunk) { if (cancelled) { throw new Error('Stream is cancelled'); } outputController.enqueue(chunk); if (!pulled) { await new Promise((resolve, reject) => { backpressureChangePromiseResolve = resolve; backpressureChangePromiseReject = reject; }); backpressureChangePromiseResolve = null; backpressureChangePromiseReject = null; } else { pulled = false; } }, close: outputController.close.bind(outputController), abort: outputController.error.bind(outputController) }) }; } /** * Transform a stream using helper functions which are called on each chunk, and on stream close, respectively. * @param {ReadableStream|Uint8array|String} input * @param {Function} process * @param {Function} finish * @returns {ReadableStream|Uint8array|String} */ function transform(input, process = () => undefined, finish = () => undefined) { if (isArrayStream(input)) { const output = new ArrayStream(); (async () => { const writer = getWriter(output); try { const data = await readToEnd(input); const result1 = process(data); const result2 = finish(); let result; if (result1 !== undefined && result2 !== undefined) result = concat([result1, result2]); else result = result1 !== undefined ? result1 : result2; await writer.write(result); await writer.close(); } catch (e) { await writer.abort(e); } })(); return output; } if (isStream(input)) { return transformRaw(input, { async transform(value, controller) { try { const result = await process(value); if (result !== undefined) controller.enqueue(result); } catch(e) { controller.error(e); } }, async flush(controller) { try { const result = await finish(); if (result !== undefined) controller.enqueue(result); } catch(e) { controller.error(e); } } }); } const result1 = process(input); const result2 = finish(); if (result1 !== undefined && result2 !== undefined) return concat([result1, result2]); return result1 !== undefined ? result1 : result2; } /** * Transform a stream using a helper function which is passed a readable and a writable stream. * This function also maintains the possibility to cancel the input stream, * and does so on cancelation of the output stream, despite cancelation * normally being impossible when the input stream is being read from. * @param {ReadableStream|Uint8array|String} input * @param {Function} fn * @returns {ReadableStream} */ function transformPair(input, fn) { if (isStream(input) && !isArrayStream(input)) { let incomingTransformController; const incoming = new TransformStream({ start(controller) { incomingTransformController = controller; } }); const pipeDonePromise = pipe(input, incoming.writable); const outgoing = transformWithCancel(async function(reason) { incomingTransformController.error(reason); await pipeDonePromise; await new Promise(setTimeout); }); fn(incoming.readable, outgoing.writable); return outgoing.readable; } input = toArrayStream(input); const output = new ArrayStream(); fn(input, output); return output; } /** * Parse a stream using a helper function which is passed a Reader. * The reader additionally has a remainder() method which returns a * stream pointing to the remainder of input, and is linked to input * for cancelation. * @param {ReadableStream|Uint8array|String} input * @param {Function} fn * @returns {Any} the return value of fn() */ function parse(input, fn) { let returnValue; const transformed = transformPair(input, (readable, writable) => { const reader = getReader(readable); reader.remainder = () => { reader.releaseLock(); pipe(readable, writable); return transformed; }; returnValue = fn(reader); }); return returnValue; } /** * Tee a Stream for reading it twice. The input stream can no longer be read after tee()ing. * Reading either of the two returned streams will pull from the input stream. * The input stream will only be canceled if both of the returned streams are canceled. * @param {ReadableStream|Uint8array|String} input * @returns {Array<ReadableStream|Uint8array|String>} array containing two copies of input */ function tee(input) { if (isArrayStream(input)) { throw new Error('ArrayStream cannot be tee()d, use clone() instead'); } if (isStream(input)) { const teed = toStream(input).tee(); teed[0][externalBuffer] = teed[1][externalBuffer] = input[externalBuffer]; return teed; } return [slice(input), slice(input)]; } /** * Clone a Stream for reading it twice. The input stream can still be read after clone()ing. * Reading from the clone will pull from the input stream. * The input stream will only be canceled if both the clone and the input stream are canceled. * @param {ReadableStream|Uint8array|String} input * @returns {ReadableStream|Uint8array|String} cloned input */ function clone(input) { if (isArrayStream(input)) { return input.clone(); } if (isStream(input)) { const teed = tee(input); overwrite(input, teed[0]); return teed[1]; } return slice(input); } /** * Clone a Stream for reading it twice. Data will arrive at the same rate as the input stream is being read. * Reading from the clone will NOT pull from the input stream. Data only arrives when reading the input stream. * The input stream will NOT be canceled if the clone is canceled, only if the input stream are canceled. * If the input stream is canceled, the clone will be errored. * @param {ReadableStream|Uint8array|String} input * @returns {ReadableStream|Uint8array|String} cloned input */ function passiveClone(input) { if (isArrayStream(input)) { return clone(input); } if (isStream(input)) { return new ReadableStream({ start(controller) { const transformed = transformPair(input, async (readable, writable) => { const reader = getReader(readable); const writer = getWriter(writable); try { while (true) { await writer.ready; const { done, value } = await reader.read(); if (done) { try { controller.close(); } catch(e) {} await writer.close(); return; } try { controller.enqueue(value); } catch(e) {} await writer.write(value); } } catch(e) { controller.error(e); await writer.abort(e); } }); overwrite(input, transformed); } }); } return slice(input); } /** * Modify a stream object to point to a different stream object. * This is used internally by clone() and passiveClone() to provide an abstraction over tee(). * @param {ReadableStream} input * @param {ReadableStream} clone */ function overwrite(input, clone) { // Overwrite input.getReader, input.locked, etc to point to clone Object.entries(Object.getOwnPropertyDescriptors(input.constructor.prototype)).forEach(([name, descriptor]) => { if (name === 'constructor') { return; } if (descriptor.value) { descriptor.value = descriptor.value.bind(clone); } else { descriptor.get = descriptor.get.bind(clone); } Object.defineProperty(input, name, descriptor); }); } /** * Return a stream pointing to a part of the input stream. * @param {ReadableStream|Uint8array|String} input * @returns {ReadableStream|Uint8array|String} clone */ function slice(input, begin=0, end=Infinity) { if (isArrayStream(input)) { throw new Error('Not implemented'); } if (isStream(input)) { if (begin >= 0 && end >= 0) { let bytesRead = 0; return transformRaw(input, { transform(value, controller) { if (bytesRead < end) { if (bytesRead + value.length >= begin) { controller.enqueue(slice(value, Math.max(begin - bytesRead, 0), end - bytesRead)); } bytesRead += value.length; } else { controller.terminate(); } } }); } if (begin < 0 && (end < 0 || end === Infinity)) { let lastBytes = []; return transform(input, value => { if (value.length >= -begin) lastBytes = [value]; else lastBytes.push(value); }, () => slice(concat(lastBytes), begin, end)); } if (begin === 0 && end < 0) { let lastBytes; return transform(input, value => { const returnValue = lastBytes ? concat([lastBytes, value]) : value; if (returnValue.length >= -end) { lastBytes = slice(returnValue, end); return slice(returnValue, begin, end); } else { lastBytes = returnValue; } }); } console.warn(`stream.slice(input, ${begin}, ${end}) not implemented efficiently.`); return fromAsync(async () => slice(await readToEnd(input), begin, end)); } if (input[externalBuffer]) { input = concat(input[externalBuffer].concat([input])); } if (isUint8Array(input) && !(NodeBuffer$1 && NodeBuffer$1.isBuffer(input))) { if (end === Infinity) end = input.length; return input.subarray(begin, end); } return input.slice(begin, end); } /** * Read a stream to the end and return its contents, concatenated by the join function (defaults to concat). * @param {ReadableStream|Uint8array|String} input * @param {Function} join * @returns {Promise<Uint8array|String|Any>} the return value of join() * @async */ async function readToEnd(input, join=concat) { if (isArrayStream(input)) { return input.readToEnd(join); } if (isStream(input)) { return getReader(input).readToEnd(join); } return input; } /** * Cancel a stream. * @param {ReadableStream|Uint8array|String} input * @param {Any} reason * @returns {Promise<Any>} indicates when the stream has been canceled * @async */ async function cancel(input, reason) { if (isStream(input)) { if (input.cancel) { return input.cancel(reason); } if (input.destroy) { input.destroy(reason); await new Promise(setTimeout); return reason; } } } /** * Convert an async function to an ArrayStream. When the function returns, its return value is written to the stream. * @param {Function} fn * @returns {ArrayStream} */ function fromAsync(fn) { const arrayStream = new ArrayStream(); (async () => { const writer = getWriter(arrayStream); try { await writer.write(await fn()); await writer.close(); } catch (e) { await writer.abort(e); } })(); return arrayStream; } /* eslint-disable new-cap */ /** * @fileoverview * BigInteger implementation of basic operations * that wraps the native BigInt library. * Operations are not constant time, * but we try and limit timing leakage where we can * @module biginteger/native * @private */ /** * @private */ class BigInteger { /** * Get a BigInteger (input must be big endian for strings and arrays) * @param {Number|String|Uint8Array} n - Value to convert * @throws {Error} on null or undefined input */ constructor(n) { if (n === undefined) { throw new Error('Invalid BigInteger input'); } if (n instanceof Uint8Array) { const bytes = n; const hex = new Array(bytes.length); for (let i = 0; i < bytes.length; i++) { const hexByte = bytes[i].toString(16); hex[i] = (bytes[i] <= 0xF) ? ('0' + hexByte) : hexByte; } this.value = BigInt('0x0' + hex.join('')); } else { this.value = BigInt(n); } } clone() { return new BigInteger(this.value); } /** * BigInteger increment in place */ iinc() { this.value++; return this; } /** * BigInteger increment * @returns {BigInteger} this + 1. */ inc() { return this.clone().iinc(); } /** * BigInteger decrement in place */ idec() { this.value--; return this; } /** * BigInteger decrement * @returns {BigInteger} this - 1. */ dec() { return this.clone().idec(); } /** * BigInteger addition in place * @param {BigInteger} x - Value to add */ iadd(x) { this.value += x.value; return this; } /** * BigInteger addition * @param {BigInteger} x - Value to add * @returns {BigInteger} this + x. */ add(x) { return this.clone().iadd(x); } /** * BigInteger subtraction in place * @param {BigInteger} x - Value to subtract */ isub(x) { this.value -= x.value; return this; } /** * BigInteger subtraction * @param {BigInteger} x - Value to subtract * @returns {BigInteger} this - x. */ sub(x) { return this.clone().isub(x); } /** * BigInteger multiplication in place * @param {BigInteger} x - Value to multiply */ imul(x) { this.value *= x.value; return this; } /** * BigInteger multiplication * @param {BigInteger} x - Value to multiply * @returns {BigInteger} this * x. */ mul(x) { return this.clone().imul(x); } /** * Compute value modulo m, in place * @param {BigInteger} m - Modulo */ imod(m) { this.value %= m.value; if (this.isNegative()) { this.iadd(m); } return this; } /** * Compute value modulo m * @param {BigInteger} m - Modulo * @returns {BigInteger} this mod m. */ mod(m) { return this.clone().imod(m); } /** * Compute modular exponentiation using square and multiply * @param {BigInteger} e - Exponent * @param {BigInteger} n - Modulo * @returns {BigInteger} this ** e mod n. */ modExp(e, n) { if (n.isZero()) throw Error('Modulo cannot be zero'); if (n.isOne()) return new BigInteger(0); if (e.isNegative()) throw Error('Unsopported negative exponent'); let exp = e.value; let x = this.value; x %= n.value; let r = BigInt(1); while (exp > BigInt(0)) { const lsb = exp & BigInt(1); exp >>= BigInt(1); // e / 2 // Always compute multiplication step, to reduce timing leakage const rx = (r * x) % n.value; // Update r only if lsb is 1 (odd exponent) r = lsb ? rx : r; x = (x * x) % n.value; // Square } return new BigInteger(r); } /** * Compute the inverse of this value modulo n * Note: this and and n must be relatively prime * @param {BigInteger} n - Modulo * @returns {BigInteger} x such that this*x = 1 mod n * @throws {Error} if the inverse does not exist */ modInv(n) { const { gcd, x } = this._egcd(n); if (!gcd.isOne()) { throw new Error('Inverse does not exist'); } return x.add(n).mod(n); } /** * Extended Eucleadian algorithm (http://anh.cs.luc.edu/331/notes/xgcd.pdf) * Given a = this and b, compute (x, y) such that ax + by = gdc(a, b) * @param {BigInteger} b - Second operand * @returns {{ gcd, x, y: BigInteger }} */ _egcd(b) { let x = BigInt(0); let y = BigInt(1); let xPrev = BigInt(1); let yPrev = BigInt(0); let a = this.value; b = b.value; while (b !== BigInt(0)) { const q = a / b; let tmp = x; x = xPrev - q * x; xPrev = tmp; tmp = y; y = yPrev - q * y; yPrev = tmp; tmp = b; b = a % b; a = tmp; } return { x: new BigInteger(xPrev), y: new BigInteger(yPrev), gcd: new BigInteger(a) }; } /** * Compute greatest common divisor between this and n * @param {BigInteger} b - Operand * @returns {BigInteger} gcd */ gcd(b) { let a = this.value; b = b.value; while (b !== BigInt(0)) { const tmp = b; b = a % b; a = tmp; } return new BigInteger(a); } /** * Shift this to the left by x, in place * @param {BigInteger} x - Shift value */ ileftShift(x) { this.value <<= x.value; return this; } /** * Shift this to the left by x * @param {BigInteger} x - Shift value * @returns {BigInteger} this << x. */ leftShift(x) { return this.clone().ileftShift(x); } /** * Shift this to the right by x, in place * @param {BigInteger} x - Shift value */ irightShift(x) { this.value >>= x.value; return this; } /** * Shift this to the right by x * @param {BigInteger} x - Shift value * @returns {BigInteger} this >> x. */ rightShift(x) { return this.clone().irightShift(x); } /** * Whether this value is equal to x * @param {BigInteger} x * @returns {Boolean} */ equal(x) { return this.value === x.value; } /** * Whether this value is less than x * @param {BigInteger} x * @returns {Boolean} */ lt(x) { return this.value < x.value; } /** * Whether this value is less than or equal to x * @param {BigInteger} x * @returns {Boolean} */ lte(x) { return this.value <= x.value; } /** * Whether this value is greater than x * @param {BigInteger} x * @returns {Boolean} */ gt(x) { return this.value > x.value; } /** * Whether this value is greater than or equal to x * @param {BigInteger} x * @returns {Boolean} */ gte(x) { return this.value >= x.value; } isZero() { return this.value === BigInt(0); } isOne() { return this.value === BigInt(1); } isNegative() { return this.value < BigInt(0); } isEven() { return !(this.value & BigInt(1)); } abs() { const res = this.clone(); if (this.isNegative()) { res.value = -res.value; } return res; } /** * Get this value as a string * @returns {String} this value. */ toString() { return this.value.toString(); } /** * Get this value as an exact Number (max 53 bits) * Fails if this value is too large * @returns {Number} */ toNumber() { const number = Number(this.value); if (number > Number.MAX_SAFE_INTEGER) { // We throw and error to conform with the bn.js implementation throw new Error('Number can only safely store up to 53 bits'); } return number; } /** * Get value of i-th bit * @param {Number} i - Bit index * @returns {Number} Bit value. */ getBit(i) { const bit = (this.value >> BigInt(i)) & BigInt(1); return (bit === BigInt(0)) ? 0 : 1; } /** * Compute bit length * @returns {Number} Bit length. */ bitLength() { const zero = new BigInteger(0); const one = new BigInteger(1); const negOne = new BigInteger(-1); // -1n >> -1n is -1n // 1n >> 1n is 0n const target = this.isNegative() ? negOne : zero; let bitlen = 1; const tmp = this.clone(); while (!tmp.irightShift(one).equal(target)) { bitlen++; } return bitlen; } /** * Compute byte length * @returns {Number} Byte length. */ byteLength() { const zero = new BigInteger(0); const negOne = new BigInteger(-1); const target = this.isNegative() ? negOne : zero; const eight = new BigInteger(8); let len = 1; const tmp = this.clone(); while (!tmp.irightShift(eight).equal(target)) { len++; } return len; } /** * Get Uint8Array representation of this number * @param {String} endian - Endianess of output array (defaults to 'be') * @param {Number} length - Of output array * @returns {Uint8Array} */ toUint8Array(endian = 'be', length) { // we get and parse the hex string (https://coolaj86.com/articles/convert-js-bigints-to-typedarrays/) // this is faster than shift+mod iterations let hex = this.value.toString(16); if (hex.length % 2 === 1) { hex = '0' + hex; } const rawLength = hex.length / 2; const bytes = new Uint8Array(length || rawLength); // parse hex const offset = length ? (length - rawLength) : 0; let i = 0; while (i < rawLength) { bytes[i + offset] = parseInt(hex.slice(2 * i, 2 * i + 2), 16); i++; } if (endian !== 'be') { bytes.reverse(); } return bytes; } } const detectBigInt = () => typeof BigInt !== 'undefined'; async function getBigInteger() { if (detectBigInt()) { return BigInteger; } else { const { default: BigInteger } = await Promise.resolve().then(function () { return bn_interface; }); return BigInteger; } } /** * @module enums */ const byValue = Symbol('byValue'); var enums = { /** Maps curve names under various standards to one * @see {@link https://wiki.gnupg.org/ECC|ECC - GnuPG wiki} * @enum {String} * @readonly */ curve: { /** NIST P-256 Curve */ 'p256': 'p256', 'P-256': 'p256', 'secp256r1': 'p256', 'prime256v1': 'p256', '1.2.840.10045.3.1.7': 'p256', '2a8648ce3d030107': 'p256', '2A8648CE3D030107': 'p256', /** NIST P-384 Curve */ 'p384': 'p384', 'P-384': 'p384', 'secp384r1': 'p384', '1.3.132.0.34': 'p384', '2b81040022': 'p384', '2B81040022': 'p384', /** NIST P-521 Curve */ 'p521': 'p521', 'P-521': 'p521', 'secp521r1': 'p521', '1.3.132.0.35': 'p521', '2b81040023': 'p521', '2B81040023': 'p521', /** SECG SECP256k1 Curve */ 'secp256k1': 'secp256k1', '1.3.132.0.10': 'secp256k1', '2b8104000a': 'secp256k1', '2B8104000A': 'secp256k1', /** Ed25519 - deprecated by crypto-refresh (replaced by standaone Ed25519 algo) */ 'ed25519Legacy': 'ed25519', 'ED25519': 'ed25519', /** @deprecated use `ed25519Legacy` instead */ 'ed25519': 'ed25519', 'Ed25519': 'ed25519', '1.3.6.1.4.1.11591.15.1': 'ed25519', '2b06010401da470f01': 'ed25519', '2B06010401DA470F01': 'ed25519', /** Curve25519 - deprecated by crypto-refresh (replaced by standaone X25519 algo) */ 'curve25519Legacy': 'curve25519', 'X25519': 'curve25519', 'cv25519': 'curve25519', /** @deprecated use `curve25519Legacy` instead */ 'curve25519': 'curve25519', 'Curve25519': 'curve25519', '1.3.6.1.4.1.3029.1.5.1': 'curve25519', '2b060104019755010501': 'curve25519', '2B060104019755010501': 'curve25519', /** BrainpoolP256r1 Curve */ 'brainpoolP256r1': 'brainpoolP256r1', '1.3.36.3.3.2.8.1.1.7': 'brainpoolP256r1', '2b2403030208010107': 'brainpoolP256r1', '2B2403030208010107': 'brainpoolP256r1', /** BrainpoolP384r1 Curve */ 'brainpoolP384r1': 'brainpoolP384r1', '1.3.36.3.3.2.8.1.1.11': 'brainpoolP384r1', '2b240303020801010b': 'brainpoolP384r1', '2B240303020801010B': 'brainpoolP384r1', /** BrainpoolP512r1 Curve */ 'brainpoolP512r1': 'brainpoolP512r1', '1.3.36.3.3.2.8.1.1.13': 'brainpoolP512r1', '2b240303020801010d': 'brainpoolP512r1', '2B240303020801010D': 'brainpoolP512r1' }, /** A string to key specifier type * @enum {Integer} * @readonly */ s2k: { simple: 0, salted: 1, iterated: 3, gnu: 101 }, /** {@link https://tools.ietf.org/html/draft-ietf-openpgp-crypto-refresh-08.html#section-9.1|crypto-refresh RFC, section 9.1} * @enum {Integer} * @readonly */ publicKey: { /** RSA (Encrypt or Sign) [HAC] */ rsaEncryptSign: 1, /** RSA (Encrypt only) [HAC] */ rsaEncrypt: 2, /** RSA (Sign only) [HAC] */ rsaSign: 3, /** Elgamal (Encrypt only) [ELGAMAL] [HAC] */ elgamal: 16, /** DSA (Sign only) [FIPS186] [HAC] */ dsa: 17, /** ECDH (Encrypt only) [RFC6637] */ ecdh: 18, /** ECDSA (Sign only) [RFC6637] */ ecdsa: 19, /** EdDSA (Sign only) - deprecated by crypto-refresh (replaced by `ed25519` identifier below) * [{@link https://tools.ietf.org/html/draft-koch-eddsa-for-openpgp-04|Draft RFC}] */ eddsaLegacy: 22, // NB: this is declared before `eddsa` to translate 22 to 'eddsa' for backwards compatibility /** @deprecated use `eddsaLegacy` instead */ ed25519Legacy: 22, /** @deprecated use `eddsaLegacy` instead */ eddsa: 22, /** Reserved for AEDH */ aedh: 23, /** Reserved for AEDSA */ aedsa: 24, /** X25519 (Encrypt only) */ x25519: 25, /** X448 (Encrypt only) */ x448: 26, /** Ed25519 (Sign only) */ ed25519: 27, /** Ed448 (Sign only) */ ed448: 28 }, /** {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC4880, section 9.2} * @enum {Integer} * @readonly */ symmetric: { plaintext: 0, /** Not implemented! */ idea: 1, tripledes: 2, cast5: 3, blowfish: 4, aes128: 7, aes192: 8, aes256: 9, twofish: 10 }, /** {@link https://tools.ietf.org/html/rfc4880#section-9.3|RFC4880, section 9.3} * @enum {Integer} * @readonly */ compression: { uncompressed: 0, /** RFC1951 */ zip: 1, /** RFC1950 */ zlib: 2, bzip2: 3 }, /** {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC4880, section 9.4} * @enum {Integer} * @readonly */ hash: { md5: 1, sha1: 2, ripemd: 3, sha256: 8, sha384: 9, sha512: 10, sha224: 11 }, /** A list of hash names as accepted by webCrypto functions. * {@link https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest|Parameters, algo} * @enum {String} */ webHash: { 'SHA-1': 2, 'SHA-256': 8, 'SHA-384': 9, 'SHA-512': 10 }, /** {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04#section-9.6|RFC4880bis-04, section 9.6} * @enum {Integer} * @readonly */ aead: { eax: 1, ocb: 2, experimentalGCM: 100 // Private algorithm }, /** A list of packet types and numeric tags associated with them. * @enum {Integer} * @readonly */ packet: { publicKeyEncryptedSessionKey: 1, signature: 2, symEncryptedSessionKey: 3, onePassSignature: 4, secretKey: 5, publicKey: 6, secretSubkey: 7, compressedData: 8, symmetricallyEncryptedData: 9, marker: 10, literalData: 11, trust: 12, userID: 13, publicSubkey: 14, userAttribute: 17, symEncryptedIntegrityProtectedData: 18, modificationDetectionCode: 19, aeadEncryptedData: 20 // see IETF draft: https://tools.ietf.org/html/draft-ford-openpgp-format-00#section-2.1 }, /** Data types in the literal packet * @enum {Integer} * @readonly */ literal: { /** Binary data 'b' */ binary: 'b'.charCodeAt(), /** Text data 't' */ text: 't'.charCodeAt(), /** Utf8 data 'u' */ utf8: 'u'.charCodeAt(), /** MIME message body part 'm' */ mime: 'm'.charCodeAt() }, /** One pass signature packet type * @enum {Integer} * @readonly */ signature: { /** 0x00: Signature of a binary document. */ binary: 0, /** 0x01: Signature of a canonical text document. * * Canonicalyzing the document by converting line endings. */ text: 1, /** 0x02: Standalone signature. * * This signature is a signature of only its own subpacket contents. * It is calculated identically to a signature over a zero-lengh * binary document. Note that it doesn't make sense to have a V3 * standalone signature. */ standalone: 2, /** 0x10: Generic certification of a User ID and Public-Key packet. * * The issuer of this certification does not make any particular * assertion as to how well the certifier has checked that the owner * of the key is in fact the person described by the User ID. */ certGeneric: 16, /** 0x11: Persona certification of a User ID and Public-Key packet. * * The issuer of this certification has not done any verification of * the claim that the owner of this key is the User ID specified. */ certPersona: 17, /** 0x12: Casual certification of a User ID and Public-Key packet. * * The issuer of this certification has done some casual * verification of the claim of identity. */ certCasual: 18, /** 0x13: Positive certification of a User ID and Public-Key packet. * * The issuer of this certification has done substantial * verification of the claim of identity. * * Most OpenPGP implementations make their "key signatures" as 0x10 * certifications. Some implementations can issue 0x11-0x13 * certifications, but few differentiate between the types. */ certPositive: 19, /** 0x30: Certification revocation signature * * This signature revokes an earlier User ID certification signature * (signature class 0x10 through 0x13) or direct-key signature * (0x1F). It should be issued by the same key that issued the * revoked signature or an authorized revocation key. The signature * is computed over the same data as the certificate that it * revokes, and should have a later creation date than that * certificate. */ certRevocation: 48, /** 0x18: Subkey Binding Signature * * This signature is a statement by the top-level signing key that * indicates that it owns the subkey. This signature is calculated * directly on the primary key and subkey, and not on any User ID or * other packets. A signature that binds a signing subkey MUST have * an Embedded Signature subpacket in this binding signature that * contains a 0x19 signature made by the signing subkey on the * primary key and subkey. */ subkeyBinding: 24, /** 0x19: Primary Key Binding Signature * * This signature is a statement by a signing subkey, indicating * that it is owned by the primary key and subkey. This signature * is calculated the same way as a 0x18 signature: directly on the * primary key and subkey, and not on any User ID or other packets. * * When a signature is made over a key, the hash data starts with the * octet 0x99, followed by a two-octet length of the key, and then body * of the key packet. (Note that this is an old-style packet header for * a key packet with two-octet length.) A subkey binding signature * (type 0x18) or primary key binding signature (type 0x19) then hashes * the subkey using the same format as the main key (also using 0x99 as * the first octet). */ keyBinding: 25, /** 0x1F: Signature directly on a key * * This signature is calculated directly on a key. It binds the * information in the Signature subpackets to the key, and is * appropriate to be used for subpackets that provide information * about the key, such as the Revocation Key subpacket. It is also * appropriate for statements that non-self certifiers want to make * about the key itself, rather than the binding between a key and a * name. */ key: 31, /** 0x20: Key revocation signature * * The signature is calculated directly on