Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
* You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
"use strict";
6
7
/**
8
* This file is a port of a subset of Chromium's implementation from
10
* which is Copyright 2018 The Chromium Authors. All rights reserved.
11
*/
12
13
const EXPORTED_SYMBOLS = ["PasswordGenerator"];
14
15
const { XPCOMUtils } = ChromeUtils.import(
17
);
18
19
XPCOMUtils.defineLazyGlobalGetters(this, ["crypto"]);
20
21
const DEFAULT_PASSWORD_LENGTH = 15;
22
const MAX_UINT8 = Math.pow(2, 8) - 1;
23
const MAX_UINT32 = Math.pow(2, 32) - 1;
24
25
// Some characters are removed due to visual similarity:
26
const LOWER_CASE_ALPHA = "abcdefghijkmnpqrstuvwxyz"; // no 'l' or 'o'
27
const UPPER_CASE_ALPHA = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // no 'I' or 'O'
28
const DIGITS = "23456789"; // no '1' or '0'
29
const ALL_CHARACTERS = LOWER_CASE_ALPHA + UPPER_CASE_ALPHA + DIGITS;
30
31
const REQUIRED_CHARACTER_CLASSES = [LOWER_CASE_ALPHA, UPPER_CASE_ALPHA, DIGITS];
32
33
this.PasswordGenerator = {
34
/**
35
* @param {Number} length of the password to generate
36
* @returns {string} password that was generated
37
* @throws Error if `length` is invalid
38
* @copyright 2018 The Chromium Authors. All rights reserved.
40
*/
41
generatePassword(length = DEFAULT_PASSWORD_LENGTH) {
42
if (length < REQUIRED_CHARACTER_CLASSES.length) {
43
throw new Error("requested password length is too short");
44
}
45
46
if (length > MAX_UINT8) {
47
throw new Error("requested password length is too long");
48
}
49
let password = "";
50
51
// Generate one of each required class
52
for (const charClassString of REQUIRED_CHARACTER_CLASSES) {
53
password +=
54
charClassString[this._randomUInt8Index(charClassString.length)];
55
}
56
57
// Now fill the rest of the password with random characters.
58
while (password.length < length) {
59
password += ALL_CHARACTERS[this._randomUInt8Index(ALL_CHARACTERS.length)];
60
}
61
62
// So far the password contains the minimally required characters at the
63
// the beginning. Therefore, we create a random permutation.
64
password = this._shuffleString(password);
65
66
return password;
67
},
68
69
/**
70
* @param range to generate the number in
71
* @returns a random number in range [0, range).
72
* @copyright 2018 The Chromium Authors. All rights reserved.
74
*/
75
_randomUInt8Index(range) {
76
if (range > MAX_UINT8) {
77
throw new Error("`range` cannot fit into uint8");
78
}
79
// We must discard random results above this number, as they would
80
// make the random generator non-uniform (consider e.g. if
81
// MAX_UINT64 was 7 and |range| was 5, then a result of 1 would be twice
82
// as likely as a result of 3 or 4).
84
const MAX_ACCEPTABLE_VALUE = Math.floor(MAX_UINT8 / range) * range - 1;
85
86
const randomValueArr = new Uint8Array(1);
87
do {
88
crypto.getRandomValues(randomValueArr);
89
} while (randomValueArr[0] > MAX_ACCEPTABLE_VALUE);
90
return randomValueArr[0] % range;
91
},
92
93
/**
94
* Shuffle the order of characters in a string.
95
* @param {string} str to shuffle
96
* @returns {string} shuffled string
97
*/
98
_shuffleString(str) {
99
let arr = Array.from(str);
100
// Generate all the random numbers that will be needed.
101
const randomValues = new Uint32Array(arr.length - 1);
102
crypto.getRandomValues(randomValues);
103
104
// Fisher-Yates Shuffle
106
for (let i = arr.length - 1; i > 0; i--) {
107
const j = Math.floor((randomValues[i - 1] / MAX_UINT32) * (i + 1));
108
[arr[i], arr[j]] = [arr[j], arr[i]];
109
}
110
return arr.join("");
111
},
112
};