VulpesOS/assets/webapps/system.gaiamobile.org/shared/js/passcode_helper.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

138 lines
4.7 KiB
JavaScript

/* exported PasscodeHelper */
/* globals crypto, TextEncoder */
(function(exports) {
'use strict';
const SET_DIGEST_VALUE = 'lockscreen.passcode-lock.digest.value';
const SET_DIGEST_SALT = 'lockscreen.passcode-lock.digest.salt';
const SET_DIGEST_ITERATIONS = 'lockscreen.passcode-lock.digest.iterations';
const SET_DIGEST_ALGORITHM = 'lockscreen.passcode-lock.digest.algorithm';
const DEFAULT_ALGORITHM = 'SHA-1'; //XXX Update to SHA-256 after bug 554827?
const DEFAULT_ITERATIONS = 1000;
const PBKDF2_OUTPUT_LENGTH = 160; //XXX HMAC-SHA-1 = 160. Change for SHA-256!
function _encode(str) {
return new TextEncoder('utf-8').encode(str);
}
function _toTypedArray(obj) {
// SettingsAPI doesnt like arrays and gives us an Object { 1: .., 2: .. }
var a = [];
for (var key of Object.keys(obj)) {
a.push(obj[key]);
}
return new Uint8Array(a);
}
function _deriveBits(pwKey, salt, iterations, algorithm) {
var params = {
name: 'PBKDF2',
hash: algorithm,
salt: salt,
iterations: iterations
};
return crypto.subtle.deriveBits(params, pwKey, PBKDF2_OUTPUT_LENGTH);
}
function _makeDigest(pass, salt, iterations, algorithm) {
var bytes = _encode(pass);
var promise = crypto.subtle.importKey('raw', bytes, 'PBKDF2', false,
['deriveBits']);
promise = promise.then((pwKey) => {
return _deriveBits(pwKey, salt, iterations, algorithm);
});
promise = promise.catch((error) => {
console.error('PasscodeHelper: _derive_bits() failed!', error);
throw error;
});
return promise;
}
function setPasscode(newPass) {
// Always generate a new salt.
var salt = crypto.getRandomValues(new Uint8Array(8));
/* the combined lock.get() makes it quite ugly. we get
an Array of objects, each with just one key/value,
which is the requested setting. let's destruct as follows:
*/
var promise = _makeDigest(newPass, salt, DEFAULT_ITERATIONS,
DEFAULT_ALGORITHM);
promise = promise.then((digest) => {
return _storeNewPasscode(digest, salt);
});
promise = promise.catch((error) => {
console.error('PasscodeHelper: Could not make digest:', error);
throw error;
});
return promise;
}
function _storeNewPasscode(digest, salt) {
// Note: We can now store the salt, since digest was generated!
var digestUint8 = new Uint8Array(digest);
var newSettings = {
[SET_DIGEST_SALT]: salt,
[SET_DIGEST_VALUE]: digestUint8,
[SET_DIGEST_ITERATIONS]: DEFAULT_ITERATIONS,
[SET_DIGEST_ALGORITHM]: DEFAULT_ALGORITHM,
};
var lock = navigator.mozSettings.createLock();
var req = lock.set(newSettings);
return req.then(() => {
return digestUint8;
}, (error) => {
console.error('PasscodeHelper: Couldnt store new digest', error);
throw error;
});
}
function checkPasscode(testPass) {
//get salt & digest out of settings
var lock = navigator.mozSettings.createLock();
var storedParams = Promise.all([
lock.get(SET_DIGEST_SALT),
lock.get(SET_DIGEST_ITERATIONS),
lock.get(SET_DIGEST_ALGORITHM),
lock.get(SET_DIGEST_VALUE)
]);
return storedParams.then((values) => {
return _makeAndCompare(...values, testPass);
})
.catch((error) => {
console.error('PasscodeHelper: Couldnt get digest Settings:', error);
throw error;
});
}
function _makeAndCompare(salt, iterations, algorithm, storedDigest,
testPass) {
/* the combined lock.get() makes it quite ugly. we get
an Array of objects, each with just one key/value,
which is the requested setting. let's destruct as follows:
*/
var _salt = _toTypedArray(salt[SET_DIGEST_SALT]);
var _iterations = iterations[SET_DIGEST_ITERATIONS];
var _algorithm = algorithm[SET_DIGEST_ALGORITHM];
var _storedDigest = _toTypedArray(storedDigest[SET_DIGEST_VALUE]);
var promise = _makeDigest(testPass, _salt, _iterations, _algorithm);
promise = promise.then((digest) => {
var typedDigest = new Uint8Array(digest);
return _compareDigests(_storedDigest, typedDigest);
});
promise = promise.catch((error) => {
console.error('PasscodeHelper: Couldnt create digest', error);
throw error;
});
return promise;
}
function _compareDigests(buf1, buf2) {
if (buf1.byteLength !== buf2.byteLength) {
return false;
}
for (var i = 0; i < buf1.byteLength; i++) {
if (buf1[i] !== buf2[i]) {
return false;
}
}
return true;
}
exports.PasscodeHelper = {
set: setPasscode,
check: checkPasscode
};
})(this);