mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-08 20:28:42 +02:00
# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D36052 --HG-- extra : source : b5be5b4f4b47c256e28a29f665dc754f6407ee7f
112 lines
4.1 KiB
JavaScript
112 lines
4.1 KiB
JavaScript
/* 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/. */
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* 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 EXPORTED_SYMBOLS = ["PasswordGenerator"];
|
|
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
);
|
|
|
|
XPCOMUtils.defineLazyGlobalGetters(this, ["crypto"]);
|
|
|
|
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 ALL_CHARACTERS = LOWER_CASE_ALPHA + UPPER_CASE_ALPHA + DIGITS;
|
|
|
|
const REQUIRED_CHARACTER_CLASSES = [LOWER_CASE_ALPHA, UPPER_CASE_ALPHA, DIGITS];
|
|
|
|
this.PasswordGenerator = {
|
|
/**
|
|
* @param {Number} length of the password to generate
|
|
* @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) {
|
|
if (length < REQUIRED_CHARACTER_CLASSES.length) {
|
|
throw new Error("requested password length is too short");
|
|
}
|
|
|
|
if (length > MAX_UINT8) {
|
|
throw new Error("requested password length is too long");
|
|
}
|
|
let password = "";
|
|
|
|
// Generate one of each required class
|
|
for (const charClassString of REQUIRED_CHARACTER_CLASSES) {
|
|
password +=
|
|
charClassString[this._randomUInt8Index(charClassString.length)];
|
|
}
|
|
|
|
// Now fill the rest of the password with random characters.
|
|
while (password.length < length) {
|
|
password += ALL_CHARACTERS[this._randomUInt8Index(ALL_CHARACTERS.length)];
|
|
}
|
|
|
|
// So far the password contains the minimally required characters at the
|
|
// the beginning. Therefore, we create a random permutation.
|
|
password = this._shuffleString(password);
|
|
|
|
return password;
|
|
},
|
|
|
|
/**
|
|
* @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("");
|
|
},
|
|
};
|