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
JavaScript
/*! 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