Skip to main content
Module

x/openpgp/src/util.js

OpenPGP implementation for JavaScript
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
// 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
/* eslint-disable no-console */
/** * This object contains utility functions * @module util * @private */
import * as stream from '@openpgp/web-stream-tools';import { getBigInteger } from './biginteger';
const debugMode = (() => { try { return process.env.NODE_ENV === 'development'; // eslint-disable-line no-process-env } catch (e) {} return false;})();
const util = { isString: function(data) { return typeof data === 'string' || String.prototype.isPrototypeOf(data); },
isArray: function(data) { return Array.prototype.isPrototypeOf(data); },
isUint8Array: stream.isUint8Array,
isStream: stream.isStream,
readNumber: function (bytes) { let n = 0; for (let i = 0; i < bytes.length; i++) { n += (256 ** i) * bytes[bytes.length - 1 - i]; } return n; },
writeNumber: function (n, bytes) { const b = new Uint8Array(bytes); for (let i = 0; i < bytes; i++) { b[i] = (n >> (8 * (bytes - i - 1))) & 0xFF; }
return b; },
readDate: function (bytes) { const n = util.readNumber(bytes); const d = new Date(n * 1000); return d; },
writeDate: function (time) { const numeric = Math.floor(time.getTime() / 1000);
return util.writeNumber(numeric, 4); },
normalizeDate: function (time = Date.now()) { return time === null || time === Infinity ? time : new Date(Math.floor(+time / 1000) * 1000); },
/** * Read one MPI from bytes in input * @param {Uint8Array} bytes - Input data to parse * @returns {Uint8Array} Parsed MPI. */ readMPI: function (bytes) { const bits = (bytes[0] << 8) | bytes[1]; const bytelen = (bits + 7) >>> 3; return bytes.subarray(2, 2 + bytelen); },
/** * Left-pad Uint8Array to length by adding 0x0 bytes * @param {Uint8Array} bytes - Data to pad * @param {Number} length - Padded length * @returns {Uint8Array} Padded bytes. */ leftPad(bytes, length) { const padded = new Uint8Array(length); const offset = length - bytes.length; padded.set(bytes, offset); return padded; },
/** * Convert a Uint8Array to an MPI-formatted Uint8Array. * @param {Uint8Array} bin - An array of 8-bit integers to convert * @returns {Uint8Array} MPI-formatted Uint8Array. */ uint8ArrayToMPI: function (bin) { const bitSize = util.uint8ArrayBitLength(bin); if (bitSize === 0) { throw new Error('Zero MPI'); } const stripped = bin.subarray(bin.length - Math.ceil(bitSize / 8)); const prefix = new Uint8Array([(bitSize & 0xFF00) >> 8, bitSize & 0xFF]); return util.concatUint8Array([prefix, stripped]); },
/** * Return bit length of the input data * @param {Uint8Array} bin input data (big endian) * @returns bit length */ uint8ArrayBitLength: function (bin) { let i; // index of leading non-zero byte for (i = 0; i < bin.length; i++) if (bin[i] !== 0) break; if (i === bin.length) { return 0; } const stripped = bin.subarray(i); return (stripped.length - 1) * 8 + util.nbits(stripped[0]); },
/** * Convert a hex string to an array of 8-bit integers * @param {String} hex - A hex string to convert * @returns {Uint8Array} An array of 8-bit integers. */ hexToUint8Array: function (hex) { const result = new Uint8Array(hex.length >> 1); for (let k = 0; k < hex.length >> 1; k++) { result[k] = parseInt(hex.substr(k << 1, 2), 16); } return result; },
/** * Convert an array of 8-bit integers to a hex string * @param {Uint8Array} bytes - Array of 8-bit integers to convert * @returns {String} Hexadecimal representation of the array. */ uint8ArrayToHex: function (bytes) { const r = []; const e = bytes.length; let c = 0; let h; while (c < e) { h = bytes[c++].toString(16); while (h.length < 2) { h = '0' + h; } r.push('' + h); } return r.join(''); },
/** * Convert a string to an array of 8-bit integers * @param {String} str - String to convert * @returns {Uint8Array} An array of 8-bit integers. */ stringToUint8Array: function (str) { return stream.transform(str, str => { if (!util.isString(str)) { throw new Error('stringToUint8Array: Data must be in the form of a string'); }
const result = new Uint8Array(str.length); for (let i = 0; i < str.length; i++) { result[i] = str.charCodeAt(i); } return result; }); },
/** * Convert an array of 8-bit integers to a string * @param {Uint8Array} bytes - An array of 8-bit integers to convert * @returns {String} String representation of the array. */ uint8ArrayToString: function (bytes) { bytes = new Uint8Array(bytes); const result = []; const bs = 1 << 14; const j = bytes.length;
for (let i = 0; i < j; i += bs) { result.push(String.fromCharCode.apply(String, bytes.subarray(i, i + bs < j ? i + bs : j))); } return result.join(''); },
/** * Convert a native javascript string to a Uint8Array of utf8 bytes * @param {String|ReadableStream} str - The string to convert * @returns {Uint8Array|ReadableStream} A valid squence of utf8 bytes. */ encodeUTF8: function (str) { const encoder = new TextEncoder('utf-8'); // eslint-disable-next-line no-inner-declarations function process(value, lastChunk = false) { return encoder.encode(value, { stream: !lastChunk }); } return stream.transform(str, process, () => process('', true)); },
/** * Convert a Uint8Array of utf8 bytes to a native javascript string * @param {Uint8Array|ReadableStream} utf8 - A valid squence of utf8 bytes * @returns {String|ReadableStream} A native javascript string. */ decodeUTF8: function (utf8) { const decoder = new TextDecoder('utf-8'); // eslint-disable-next-line no-inner-declarations function process(value, lastChunk = false) { return decoder.decode(value, { stream: !lastChunk }); } return stream.transform(utf8, process, () => process(new Uint8Array(), true)); },
/** * Concat a list of Uint8Arrays, Strings or Streams * The caller must 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. */ concat: stream.concat,
/** * Concat Uint8Arrays * @param {Array<Uint8Array>} Array - Of Uint8Arrays to concatenate * @returns {Uint8Array} Concatenated array. */ concatUint8Array: stream.concatUint8Array,
/** * Check Uint8Array equality * @param {Uint8Array} array1 - First array * @param {Uint8Array} array2 - Second array * @returns {Boolean} Equality. */ equalsUint8Array: function (array1, array2) { if (!util.isUint8Array(array1) || !util.isUint8Array(array2)) { throw new Error('Data must be in the form of a Uint8Array'); }
if (array1.length !== array2.length) { return false; }
for (let i = 0; i < array1.length; i++) { if (array1[i] !== array2[i]) { return false; } } return true; },
/** * Calculates a 16bit sum of a Uint8Array by adding each character * codes modulus 65535 * @param {Uint8Array} Uint8Array - To create a sum of * @returns {Uint8Array} 2 bytes containing the sum of all charcodes % 65535. */ writeChecksum: function (text) { let s = 0; for (let i = 0; i < text.length; i++) { s = (s + text[i]) & 0xFFFF; } return util.writeNumber(s, 2); },
/** * Helper function to print a debug message. Debug * messages are only printed if * @param {String} str - String of the debug message */ printDebug: function (str) { if (debugMode) { console.log(str); } },
/** * Helper function to print a debug error. Debug * messages are only printed if * @param {String} str - String of the debug message */ printDebugError: function (error) { if (debugMode) { console.error(error); } },
// returns bit length of the integer x nbits: function (x) { let r = 1; let t = x >>> 16; if (t !== 0) { x = t; r += 16; } t = x >> 8; if (t !== 0) { x = t; r += 8; } t = x >> 4; if (t !== 0) { x = t; r += 4; } t = x >> 2; if (t !== 0) { x = t; r += 2; } t = x >> 1; if (t !== 0) { x = t; r += 1; } return r; },
/** * If S[1] == 0, then double(S) == (S[2..128] || 0); * otherwise, double(S) == (S[2..128] || 0) xor * (zeros(120) || 10000111). * * Both OCB and EAX (through CMAC) require this function to be constant-time. * * @param {Uint8Array} data */ double: function(data) { const doubleVar = new Uint8Array(data.length); const last = data.length - 1; for (let i = 0; i < last; i++) { doubleVar[i] = (data[i] << 1) ^ (data[i + 1] >> 7); } doubleVar[last] = (data[last] << 1) ^ ((data[0] >> 7) * 0x87); return doubleVar; },
/** * Shift a Uint8Array to the right by n bits * @param {Uint8Array} array - The array to shift * @param {Integer} bits - Amount of bits to shift (MUST be smaller * than 8) * @returns {String} Resulting array. */ shiftRight: function (array, bits) { if (bits) { for (let i = array.length - 1; i >= 0; i--) { array[i] >>= bits; if (i > 0) { array[i] |= (array[i - 1] << (8 - bits)); } } } return array; },
/** * Get native Web Cryptography api, only the current version of the spec. * @returns {Object} The SubtleCrypto api or 'undefined'. */ getWebCrypto: function() { return typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.subtle; },
/** * Detect Node.js runtime. */ detectNode: function() { return typeof globalThis.process === 'object' && typeof globalThis.process.versions === 'object'; },
/** * Detect native BigInt support */ detectBigInt: () => typeof BigInt !== 'undefined',
/** * Get BigInteger class * It wraps the native BigInt type if it's available * Otherwise it relies on bn.js * @returns {BigInteger} * @async */ getBigInteger,
/** * Get native Node.js crypto api. * @returns {Object} The crypto module or 'undefined'. */ getNodeCrypto: function() { return require('crypto'); },
getNodeZlib: function() { return require('zlib'); },
/** * Get native Node.js Buffer constructor. This should be used since * Buffer is not available under browserify. * @returns {Function} The Buffer constructor or 'undefined'. */ getNodeBuffer: function() { return (require('buffer') || {}).Buffer; },
getHardwareConcurrency: function() { if (util.detectNode()) { const os = require('os'); return os.cpus().length; }
return (typeof navigator !== 'undefined' && navigator.hardwareConcurrency) || 1; },
isEmailAddress: function(data) { if (!util.isString(data)) { return false; } const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+([a-zA-Z]{2,}|xn--[a-zA-Z\-0-9]+)))$/; return re.test(data); },
/** * Normalize line endings to <CR><LF> * Support any encoding where CR=0x0D, LF=0x0A */ canonicalizeEOL: function(data) { const CR = 13; const LF = 10; let carryOverCR = false;
return stream.transform(data, bytes => { if (carryOverCR) { bytes = util.concatUint8Array([new Uint8Array([CR]), bytes]); }
if (bytes[bytes.length - 1] === CR) { carryOverCR = true; bytes = bytes.subarray(0, -1); } else { carryOverCR = false; }
let index; const indices = []; for (let i = 0; ; i = index) { index = bytes.indexOf(LF, i) + 1; if (index) { if (bytes[index - 2] !== CR) indices.push(index); } else { break; } } if (!indices.length) { return bytes; }
const normalized = new Uint8Array(bytes.length + indices.length); let j = 0; for (let i = 0; i < indices.length; i++) { const sub = bytes.subarray(indices[i - 1] || 0, indices[i]); normalized.set(sub, j); j += sub.length; normalized[j - 1] = CR; normalized[j] = LF; j++; } normalized.set(bytes.subarray(indices[indices.length - 1] || 0), j); return normalized; }, () => (carryOverCR ? new Uint8Array([CR]) : undefined)); },
/** * Convert line endings from canonicalized <CR><LF> to native <LF> * Support any encoding where CR=0x0D, LF=0x0A */ nativeEOL: function(data) { const CR = 13; const LF = 10; let carryOverCR = false;
return stream.transform(data, bytes => { if (carryOverCR && bytes[0] !== LF) { bytes = util.concatUint8Array([new Uint8Array([CR]), bytes]); } else { bytes = new Uint8Array(bytes); // Don't mutate passed bytes }
if (bytes[bytes.length - 1] === CR) { carryOverCR = true; bytes = bytes.subarray(0, -1); } else { carryOverCR = false; }
let index; let j = 0; for (let i = 0; i !== bytes.length; i = index) { index = bytes.indexOf(CR, i) + 1; if (!index) index = bytes.length; const last = index - (bytes[index] === LF ? 1 : 0); if (i) bytes.copyWithin(j, i, last); j += last - i; } return bytes.subarray(0, j); }, () => (carryOverCR ? new Uint8Array([CR]) : undefined)); },
/** * Remove trailing spaces and tabs from each line */ removeTrailingSpaces: function(text) { return text.split('\n').map(line => { let i = line.length - 1; for (; i >= 0 && (line[i] === ' ' || line[i] === '\t'); i--); return line.substr(0, i + 1); }).join('\n'); },
wrapError: function(message, error) { if (!error) { return new Error(message); }
// update error message try { error.message = message + ': ' + error.message; } catch (e) {}
return error; },
/** * Map allowed packet tags to corresponding classes * Meant to be used to format `allowedPacket` for Packetlist.read * @param {Array<Object>} allowedClasses * @returns {Object} map from enum.packet to corresponding *Packet class */ constructAllowedPackets: function(allowedClasses) { const map = {}; allowedClasses.forEach(PacketClass => { if (!PacketClass.tag) { throw new Error('Invalid input: expected a packet class'); } map[PacketClass.tag] = PacketClass; }); return map; },
/** * Return a Promise that will resolve as soon as one of the promises in input resolves * or will reject if all input promises all rejected * (similar to Promise.any, but with slightly different error handling) * @param {Array<Promise>} promises * @return {Promise<Any>} Promise resolving to the result of the fastest fulfilled promise * or rejected with the Error of the last resolved Promise (if all promises are rejected) */ anyPromise: function(promises) { return new Promise(async (resolve, reject) => { let exception; await Promise.all(promises.map(async promise => { try { resolve(await promise); } catch (e) { exception = e; } })); reject(exception); }); },
/** * Return either `a` or `b` based on `cond`, in algorithmic constant time. * @param {Boolean} cond * @param {Uint8Array} a * @param {Uint8Array} b * @returns `a` if `cond` is true, `b` otherwise */ selectUint8Array: function(cond, a, b) { const length = Math.max(a.length, b.length); const result = new Uint8Array(length); let end = 0; for (let i = 0; i < result.length; i++) { result[i] = (a[i] & (256 - cond)) | (b[i] & (255 + cond)); end += (cond & i < a.length) | ((1 - cond) & i < b.length); } return result.subarray(0, end); }, /** * Return either `a` or `b` based on `cond`, in algorithmic constant time. * NB: it only supports `a, b` with values between 0-255. * @param {Boolean} cond * @param {Uint8} a * @param {Uint8} b * @returns `a` if `cond` is true, `b` otherwise */ selectUint8: function(cond, a, b) { return (a & (256 - cond)) | (b & (255 + cond)); }};
export default util;