From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../passwordmgr/PasswordGenerator.sys.mjs | 229 +++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 toolkit/components/passwordmgr/PasswordGenerator.sys.mjs (limited to 'toolkit/components/passwordmgr/PasswordGenerator.sys.mjs') diff --git a/toolkit/components/passwordmgr/PasswordGenerator.sys.mjs b/toolkit/components/passwordmgr/PasswordGenerator.sys.mjs new file mode 100644 index 0000000000..0f2a407cea --- /dev/null +++ b/toolkit/components/passwordmgr/PasswordGenerator.sys.mjs @@ -0,0 +1,229 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file is a port of a subset of Chromium's implementation from + * https://cs.chromium.org/chromium/src/components/password_manager/core/browser/generation/password_generator.cc?l=93&rcl=a896a3ac4ea731b5ab3d2ab5bd76a139885d5c4f + * which is Copyright 2018 The Chromium Authors. All rights reserved. + */ + +const DEFAULT_PASSWORD_LENGTH = 15; +const MAX_UINT8 = Math.pow(2, 8) - 1; +const MAX_UINT32 = Math.pow(2, 32) - 1; + +// Some characters are removed due to visual similarity: +const LOWER_CASE_ALPHA = "abcdefghijkmnpqrstuvwxyz"; // no 'l' or 'o' +const UPPER_CASE_ALPHA = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // no 'I' or 'O' +const DIGITS = "23456789"; // no '1' or '0' +const SPECIAL_CHARACTERS = "-~!@#$%^&*_+=)}:;\"'>,.?]"; + +const REQUIRED_CHARACTER_CLASSES = [ + LOWER_CASE_ALPHA, + UPPER_CASE_ALPHA, + DIGITS, + SPECIAL_CHARACTERS, +]; + +// Consts for different password rules +const REQUIRED = "required"; +const MAX_LENGTH = "maxlength"; +const MIN_LENGTH = "minlength"; +const MAX_CONSECUTIVE = "max-consecutive"; +const UPPER = "upper"; +const LOWER = "lower"; +const DIGIT = "digit"; +const SPECIAL = "special"; + +// Default password rules +const DEFAULT_RULES = new Map(); +DEFAULT_RULES.set(MIN_LENGTH, REQUIRED_CHARACTER_CLASSES.length); +DEFAULT_RULES.set(MAX_LENGTH, MAX_UINT8); +DEFAULT_RULES.set(REQUIRED, [UPPER, LOWER, DIGIT, SPECIAL]); + +export const PasswordGenerator = { + /** + * @param {Object} options + * @param {number} options.length - length of the generated password if there are no rules that override the length + * @param {Map} options.rules - map of password rules + * @returns {string} password that was generated + * @throws Error if `length` is invalid + * @copyright 2018 The Chromium Authors. All rights reserved. + * @see https://cs.chromium.org/chromium/src/components/password_manager/core/browser/generation/password_generator.cc?l=93&rcl=a896a3ac4ea731b5ab3d2ab5bd76a139885d5c4f + */ + generatePassword({ + length = DEFAULT_PASSWORD_LENGTH, + rules = DEFAULT_RULES, + inputMaxLength, + }) { + rules = new Map([...DEFAULT_RULES, ...rules]); + if (rules.get(MIN_LENGTH) > length) { + length = rules.get(MIN_LENGTH); + } + if (rules.get(MAX_LENGTH) < length) { + length = rules.get(MAX_LENGTH); + } + if (inputMaxLength > 0 && inputMaxLength < length) { + length = inputMaxLength; + } + + let password = ""; + let requiredClasses = []; + let allRequiredCharacters = ""; + + // Generate one character of each required class and/or required character list from the rules + this._addRequiredClassesAndCharacters(rules, requiredClasses); + + // Generate one of each required class + for (const charClassString of requiredClasses) { + password += + charClassString[this._randomUInt8Index(charClassString.length)]; + if (Array.isArray(charClassString)) { + // Convert array into single string so that commas aren't + // concatenated with each character in the arbitrary character array. + allRequiredCharacters += charClassString.join(""); + } else { + allRequiredCharacters += charClassString; + } + } + + // Now fill the rest of the password with random characters. + while (password.length < length) { + password += + allRequiredCharacters[ + this._randomUInt8Index(allRequiredCharacters.length) + ]; + } + + // So far the password contains the minimally required characters at the + // the beginning. Therefore, we create a random permutation. + password = this._shuffleString(password); + + // Make sure the password passes the "max-consecutive" rule, if the rule exists + if (rules.has(MAX_CONSECUTIVE)) { + // Ensures that a password isn't shuffled an infinite number of times. + const DEFAULT_NUMBER_OF_SHUFFLES = 15; + let shuffleCount = 0; + let consecutiveFlag = this._checkConsecutiveCharacters( + password, + rules.get(MAX_CONSECUTIVE) + ); + while (!consecutiveFlag) { + password = this._shuffleString(password); + consecutiveFlag = this._checkConsecutiveCharacters( + password, + rules.get(MAX_CONSECUTIVE) + ); + ++shuffleCount; + if (shuffleCount === DEFAULT_NUMBER_OF_SHUFFLES) { + consecutiveFlag = true; + } + } + } + + return password; + }, + + /** + * Adds special characters and/or other required characters to the requiredCharacters array. + * @param {Map} rules + * @param {string[]} requiredClasses + */ + _addRequiredClassesAndCharacters(rules, requiredClasses) { + for (const charClass of rules.get(REQUIRED)) { + if (charClass === UPPER) { + requiredClasses.push(UPPER_CASE_ALPHA); + } else if (charClass === LOWER) { + requiredClasses.push(LOWER_CASE_ALPHA); + } else if (charClass === DIGIT) { + requiredClasses.push(DIGITS); + } else if (charClass === SPECIAL) { + requiredClasses.push(SPECIAL_CHARACTERS); + } else { + requiredClasses.push(charClass); + } + } + }, + + /** + * @param range to generate the number in + * @returns a random number in range [0, range). + * @copyright 2018 The Chromium Authors. All rights reserved. + * @see https://cs.chromium.org/chromium/src/base/rand_util.cc?l=58&rcl=648a59893e4ed5303b5c381b03ce0c75e4165617 + */ + _randomUInt8Index(range) { + if (range > MAX_UINT8) { + throw new Error("`range` cannot fit into uint8"); + } + // We must discard random results above this number, as they would + // make the random generator non-uniform (consider e.g. if + // MAX_UINT64 was 7 and |range| was 5, then a result of 1 would be twice + // as likely as a result of 3 or 4). + // See https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Modulo_bias + const MAX_ACCEPTABLE_VALUE = Math.floor(MAX_UINT8 / range) * range - 1; + + const randomValueArr = new Uint8Array(1); + do { + crypto.getRandomValues(randomValueArr); + } while (randomValueArr[0] > MAX_ACCEPTABLE_VALUE); + return randomValueArr[0] % range; + }, + + /** + * Shuffle the order of characters in a string. + * @param {string} str to shuffle + * @returns {string} shuffled string + */ + _shuffleString(str) { + let arr = Array.from(str); + // Generate all the random numbers that will be needed. + const randomValues = new Uint32Array(arr.length - 1); + crypto.getRandomValues(randomValues); + + // Fisher-Yates Shuffle + // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor((randomValues[i - 1] / MAX_UINT32) * (i + 1)); + [arr[i], arr[j]] = [arr[j], arr[i]]; + } + return arr.join(""); + }, + + /** + * Determine the number of consecutive characters in a string. + * This is primarily used to validate the "max-consecutive" rule + * of a generated password. + * @param {string} generatedPassword + * @param {number} value the number of consecutive characters allowed + * @return {boolean} `true` if the generatePassword has less than the value argument number of characters, `false` otherwise + */ + _checkConsecutiveCharacters(generatedPassword, value) { + let max = 0; + for (let start = 0, end = 1; end < generatedPassword.length; ) { + if (generatedPassword[end] === generatedPassword[start]) { + if (max < end - start + 1) { + max = end - start + 1; + if (max > value) { + return false; + } + } + end++; + } else { + start = end++; + } + } + return true; + }, + _getUpperCaseCharacters() { + return UPPER_CASE_ALPHA; + }, + _getLowerCaseCharacters() { + return LOWER_CASE_ALPHA; + }, + _getDigits() { + return DIGITS; + }, + _getSpecialCharacters() { + return SPECIAL_CHARACTERS; + }, +}; -- cgit v1.2.3