UNPKG

openpgp

Version:

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

1,580 lines (1,487 loc) 1.08 MB
/*! OpenPGP.js v6.1.0 - 2025-01-30 - 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 : {}; function _mergeNamespaces(n, m) { m.forEach(function (e) { e && typeof e !== 'string' && !Array.isArray(e) && Object.keys(e).forEach(function (k) { if (k !== 'default' && !(k in n)) { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); }); return Object.freeze(n); } const doneWritingPromise = Symbol('doneWritingPromise'); const doneWritingResolve = Symbol('doneWritingResolve'); const doneWritingReject = Symbol('doneWritingReject'); const readingIndex = Symbol('readingIndex'); class ArrayStream extends Array { constructor() { super(); // ES5 patch, see https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work Object.setPrototypeOf(this, ArrayStream.prototype); 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() {}; /* eslint-disable no-prototype-builtins */ typeof globalThis.process === 'object' && typeof globalThis.process.versions === 'object'; /** * Check whether data is a Stream, and if so of which type * @param {Any} input data to check * @returns {'web'|'node'|'array'|'web-like'|false} */ function isStream(input) { if (isArrayStream(input)) { return 'array'; } if (globalThis.ReadableStream && globalThis.ReadableStream.prototype.isPrototypeOf(input)) { return 'web'; } // try and detect a node native stream without having to import its class if (input && !(globalThis.ReadableStream && input instanceof globalThis.ReadableStream) && typeof input._read === 'function' && typeof input._readableState === 'object') { throw new Error('Native Node streams are no longer supported: please manually convert the stream to a WebStream, using e.g. `stream.Readable.toWeb`'); } 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 doneReadingSet = new WeakSet(); /** * The external buffer is used to store values that have been peeked or unshifted from the original stream. * Because of how streams are implemented, such values cannot be "put back" in the original stream, * but they need to be returned first when reading from the input again. */ 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 = () => {}; return; } let streamType = isStream(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; // eslint-disable-next-line no-constant-condition 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 = []; // eslint-disable-next-line no-constant-condition while (true) { const { done, value } = await this.read(); if (done) break; result.push(value); } return join(result); }; /** * 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) { return input; } return new ReadableStream({ start(controller) { controller.enqueue(input); controller.close(); } }); } /** * Convert non-streamed data to ArrayStream; this is a noop if `input` is already a stream. * @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(''); } 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; } /** * 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 { // eslint-disable-next-line no-constant-condition 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 { // eslint-disable-next-line no-constant-condition 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); } 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)) { return input.subarray(begin, end === Infinity ? input.length : 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) { const cancelled = await input.cancel(reason); // the stream is not always cancelled at this point, so we wait some more await new Promise(setTimeout); return cancelled; } 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; } /** * 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); } /** * @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 */ 'nistP256': 'nistP256', /** @deprecated use `nistP256` instead */ 'p256': 'nistP256', /** NIST P-384 Curve */ 'nistP384': 'nistP384', /** @deprecated use `nistP384` instead */ 'p384': 'nistP384', /** NIST P-521 Curve */ 'nistP521': 'nistP521', /** @deprecated use `nistP521` instead */ 'p521': 'nistP521', /** SECG SECP256k1 Curve */ 'secp256k1': 'secp256k1', /** Ed25519 - deprecated by crypto-refresh (replaced by standaone Ed25519 algo) */ 'ed25519Legacy': 'ed25519Legacy', /** @deprecated use `ed25519Legacy` instead */ 'ed25519': 'ed25519Legacy', /** Curve25519 - deprecated by crypto-refresh (replaced by standaone X25519 algo) */ 'curve25519Legacy': 'curve25519Legacy', /** @deprecated use `curve25519Legacy` instead */ 'curve25519': 'curve25519Legacy', /** BrainpoolP256r1 Curve */ 'brainpoolP256r1': 'brainpoolP256r1', /** BrainpoolP384r1 Curve */ 'brainpoolP384r1': 'brainpoolP384r1', /** BrainpoolP512r1 Curve */ 'brainpoolP512r1': 'brainpoolP512r1' }, /** A string to key specifier type * @enum {Integer} * @readonly */ s2k: { simple: 0, salted: 1, iterated: 3, argon2: 4, 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, /** 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: { /** 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, sha3_256: 12, sha3_512: 14 }, /** 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://www.rfc-editor.org/rfc/rfc9580.html#name-aead-algorithms} * @enum {Integer} * @readonly */ aead: { eax: 1, ocb: 2, gcm: 3, /** @deprecated used by OpenPGP.js v5 for legacy AEAD support; use `gcm` instead for the RFC9580-standardized ID */ 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 padding: 21 }, /** 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 the key being revoked. A * revoked key is not to be used. Only revocation signatures by the * key being revoked, or by an authorized revocation key, should be * considered valid revocation signatures.a */ keyRevocation: 32, /** 0x28: Subkey revocation signature * * The signature is calculated directly on the subkey being revoked. * A revoked subkey is not to be used. Only revocation signatures * by the top-level signature key that is bound to this subkey, or * by an authorized revocation key, should be considered valid * revocation signatures. * * Key revocation signatures (types 0x20 and 0x28) * hash only the key being revoked. */ subkeyRevocation: 40, /** 0x40: Timestamp signature. * This signature is only meaningful for the timestamp contained in * it. */ timestamp: 64, /** 0x50: Third-Party Confirmation signature. * * This signature is a signature over some other OpenPGP Signature * packet(s). It is analogous to a notary seal on the signed data. * A third-party signature SHOULD include Signature Target * subpacket(s) to give easy identification. Note that we really do * mean SHOULD. There are plausible uses for this (such as a blind * party that only sees the signature, not the key or source * document) that cannot include a target subpacket. */ thirdParty: 80 }, /** Signature subpacket type * @enum {Integer} * @readonly */ signatureSubpacket: { signatureCreationTime: 2, signatureExpirationTime: 3, exportableCertification: 4, trustSignature: 5, regularExpression: 6, revocable: 7, keyExpirationTime: 9, placeholderBackwardsCompatibility: 10, preferredSymmetricAlgorithms: 11, revocationKey: 12, issuerKeyID: 16, notationData: 20, preferredHashAlgorithms: 21, preferredCompressionAlgorithms: 22, keyServerPreferences: 23, preferredKeyServer: 24, primaryUserID: 25, policyURI: 26, keyFlags: 27, signersUserID: 28, reasonForRevocation: 29, features: 30, signatureTarget: 31, embeddedSignature: 32, issuerFingerprint: 33, preferredAEADAlgorithms: 34, preferredCipherSuites: 39 }, /** Key flags * @enum {Integer} * @readonly */ keyFlags: { /** 0x01 - This key may be used to certify other keys. */ certifyKeys: 1, /** 0x02 - This key may be used to sign data. */ signData: 2, /** 0x04 - This key may be used to encrypt communications. */ encryptCommunication: 4, /** 0x08 - This key may be used to encrypt storage. */ encryptStorage: 8, /** 0x10 - The private component of this key may have been split * by a secret-sharing mechanism. */ splitPrivateKey: 16, /** 0x20 - This key may be used for authentication. */ authentication: 32, /** 0x80 - The private component of this key may be in the * possession of more than one person. */ sharedPrivateKey: 128 }, /** Armor type * @enum {Integer} * @readonly */ armor: { multipartSection: 0, multipartLast: 1, signed: 2, message: 3, publicKey: 4, privateKey: 5, signature: 6 }, /** {@link https://tools.ietf.org/html/rfc4880#section-5.2.3.23|RFC4880, section 5.2.3.23} * @enum {Integer} * @readonly */ reasonForRevocation: { /** No reason specified (key revocations or cert revocations) */ noReason: 0, /** Key is superseded (key revocations) */ keySuperseded: 1, /** Key material has been compromised (key revocations) */ keyCompromised: 2, /** Key is retired and no longer used (key revocations) */ keyRetired: 3, /** User ID information is no longer valid (cert revocations) */ userIDInvalid: 32 }, /** {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04#section-5.2.3.25|RFC4880bis-04, section 5.2.3.25} * @enum {Integer} * @readonly */ features: { /** 0x01 - Modification Detection (packets 18 and 19) */ modificationDetection: 1, /** 0x02 - AEAD Encrypted Data Packet (packet 20) and version 5 * Symmetric-Key Encrypted Session Key Packets (packet 3) */ aead: 2, /** 0x04 - Version 5 Public-Key Packet format and corresponding new * fingerprint format */ v5Keys: 4, seipdv2: 8 }, /** * Asserts validity of given value and converts from string/integer to integer. * @param {Object} type target enum type * @param {String|Integer} e value to check and/or convert * @returns {Integer} enum value if it exists * @throws {Error} if the value is invalid */ write: function(type, e) { if (typeof e === 'number') { e = this.read(type, e); } if (type[e] !== undefined) { return type[e]; } throw new Error('Invalid enum value.'); }, /** * Converts enum integer value to the corresponding string, if it exists. * @param {Object} type target enum type * @param {Integer} e value to convert * @returns {String} name of enum value if it exists * @throws {Error} if the value is invalid */ read: function(type, e) { if (!type[byValue]) { type[byValue] = []; Object.entries(type).forEach(([key, value]) => { type[byValue][value] = key; }); } if (type[byValue][e] !== undefined) { return type[byValue][e]; } throw new Error('Invalid enum value.'); } }; // 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 var config = { /** * @memberof module:config * @property {Integer} preferredHashAlgorithm Default hash algorithm {@link module:enums.hash} */ preferredHashAlgorithm: enums.hash.sha512, /** * @memberof module:config * @property {Integer} preferredSymmetricAlgorithm Default encryption cipher {@link module:enums.symmetric} */ preferredSymmetricAlgorithm: enums.symmetric.aes256, /** * @memberof module:config * @property {Integer} compression Default compression algorithm {@link module:enums.compression} */ preferredCompressionAlgorithm: enums.compression.uncompressed, /** * Use Authenticated Encryption with Additional Data (AEAD) protection for symmetric encryption. * This option is applicable to: * - key generation (encryption key preferences), * - password-based message encryption, and * - private key encryption. * In the case of message encryption using public keys, the encryption key preferences are respected instead. * Note: not all OpenPGP implementations are compatible with this option. * @see {@link https://tools.ietf.org/html/draft-ietf-openpgp-crypto-refresh-10.html|draft-crypto-refresh-10} * @memberof module:config * @property {Boolean} aeadProtect */ aeadProtect: false, /** * When reading OpenPGP v4 private keys (e.g. those generated in OpenPGP.js when not setting `config.v5Keys = true`) * which were encrypted by OpenPGP.js v5 (or older) using `config.aeadProtect = true`, * this option must be set, otherwise key parsing and/or key decryption will fail. * Note: only set this flag if you know that the keys are of the legacy type, as non-legacy keys * will be processed incorrectly. */ parseAEADEncryptedV4KeysAsLegacy: false, /** * Default Authenticated Encryption with Additional Data (AEAD) encryption mode * Only has an effect when aeadProtect is set to true. * @memberof module:config * @property {Integer} preferredAEADAlgorithm Default AEAD mode {@link module:enums.aead} */ preferredAEADAlgorithm: enums.aead.gcm, /** * Chunk Size Byte for Authenticated Encryption with Additional Data (AEAD) mode * Only has an effect when aeadProtect is set to true. * Must be an integer value from 0 to 56. * @memberof module:config * @property {Integer} aeadChunkSizeByte */ aeadChunkSizeByte: 12, /** * Use v6 keys. * Note: not all OpenPGP implementations are compatible with this option. * **FUTURE OPENPGP.JS VERSIONS MAY BREAK COMPATIBILITY WHEN USING THIS OPTION** * @memberof module:config * @property {Boolean} v6Keys */ v6Keys: false, /** * Enable parsing v5 keys and v5 signatures (which is different from the AEAD-encrypted SEIPDv2 packet). * These are non-standard entities, which in the crypto-refresh have been superseded * by v6 keys and v6 signatures, respectively. * However, generation of v5 entities was supported behind config flag in OpenPGP.js v5, and some other libraries, * hence parsing them might be necessary in some cases. */ enableParsingV5Entities: false, /** * S2K (String to Key) type, used for key derivation in the context of secret key encryption * and password-encrypted data. Weaker s2k options are not allowed. * Note: Argon2 is the strongest option but not all OpenPGP implementations are compatible with it * (pending standardisation). * @memberof module:config * @property {enums.s2k.argon2|enums.s2k.iterated} s2kType {@link module:enums.s2k} */ s2kType: enums.s2k.iterated, /** * {@link https://tools.ietf.org/html/rfc4880#section-3.7.1.3| RFC4880 3.7.1.3}: * Iteration Count Byte for Iterated and Salted S2K (String to Key). * Only relevant if `config.s2kType` is set to `enums.s2k.iterated`. * Note: this is the exponent value, not the final number of iterations (refer to specs for more details). * @memberof module:config * @property {Integer} s2kIterationCountByte */ s2kIterationCountByte: 224, /** * {@link https://tools.ietf.org/html/draft-ietf-openpgp-crypto-refresh-07.html#section-3.7.1.4| draft-crypto-refresh 3.7.1.4}: * Argon2 parameters for S2K (String to Key). * Only relevant if `config.s2kType` is set to `enums.s2k.argon2`. * Default settings correspond to the second recommendation from RFC9106 ("uniformly safe option"), * to ensure compatibility with memory-constrained environments. * For more details on the choice of parameters, see https://tools.ietf.org/html/rfc9106#section-4. * @memberof module:config * @property {Object} params * @property {Integer} params.passes - number of iterations t * @property {Integer} params.parallelism - degree of parallelism p * @property {Integer} params.memoryExponent - one-octet exponent indicating the memory size, which will be: 2**memoryExponent kibibytes. */ s2kArgon2Params: { passes: 3, parallelism: 4, // lanes memoryExponent: 16 // 64 MiB of RAM }, /** * Allow decryption of messages without integrity protection. * This is an **insecure** setting: * - message modifications cannot be detected, thus processing the decrypted data is potentially unsafe. * - it enables downgrade attacks against integrity-protected messages. * @memberof module:config * @property {Boolean} allowUnauthenticatedMessages */ allowUnauthenticatedMessages: false, /** * Allow streaming unauthenticated data before its integrity has been checked. This would allow the application to * process large streams while limiting memory usage by releasing the decrypted chunks as soon as possible * and deferring checking their integrity until the decrypted stream has been read in full. * * This setting is **insecure** if the encrypted data has been corrupted by a malicious entity: * - if the partially decrypted message is processed further or displayed to the user, it opens up the possibility of attacks such as EFAIL * (see https://efail.de/). * - an attacker with access to traces or timing info of internal processing errors could learn some info about the data. * * NB: this setting does not apply to AEAD-encrypted data, where the AEAD data chunk is never released until integrity is confirmed. * @memberof module:config * @property {Boolean} allowUnauthenticatedStream */ allowUnauthenticatedStream: false, /** * Minimum RSA key size allowed for key gen