mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 1518300 - Refactor CryptoUtils and add JWK/JWE methods to jwcrypto. r=rfkelly,tjr
Differential Revision: https://phabricator.services.mozilla.com/D15868 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
600eb759a4
commit
f35506cf5d
@ -79,7 +79,7 @@ HAWKAuthenticatedRESTRequest.prototype = {
|
|||||||
payload: data && JSON.stringify(data) || "",
|
payload: data && JSON.stringify(data) || "",
|
||||||
contentType,
|
contentType,
|
||||||
};
|
};
|
||||||
let header = CryptoUtils.computeHAWK(this.uri, method, options);
|
let header = await CryptoUtils.computeHAWK(this.uri, method, options);
|
||||||
this.setHeader("Authorization", header.field);
|
this.setHeader("Authorization", header.field);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,22 +113,17 @@ HAWKAuthenticatedRESTRequest.prototype = {
|
|||||||
* @return credentials
|
* @return credentials
|
||||||
* Returns an object:
|
* Returns an object:
|
||||||
* {
|
* {
|
||||||
* algorithm: sha256
|
|
||||||
* id: the Hawk id (from the first 32 bytes derived)
|
* id: the Hawk id (from the first 32 bytes derived)
|
||||||
* key: the Hawk key (from bytes 32 to 64)
|
* key: the Hawk key (from bytes 32 to 64)
|
||||||
* extra: size - 64 extra bytes (if size > 64)
|
* extra: size - 64 extra bytes (if size > 64)
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
function deriveHawkCredentials(tokenHex,
|
async function deriveHawkCredentials(tokenHex, context, size = 96) {
|
||||||
context,
|
|
||||||
size = 96,
|
|
||||||
hexKey = false) {
|
|
||||||
let token = CommonUtils.hexToBytes(tokenHex);
|
let token = CommonUtils.hexToBytes(tokenHex);
|
||||||
let out = CryptoUtils.hkdf(token, undefined, Credentials.keyWord(context), size);
|
let out = await CryptoUtils.hkdfLegacy(token, undefined, Credentials.keyWord(context), size);
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
algorithm: "sha256",
|
key: out.slice(32, 64),
|
||||||
key: hexKey ? CommonUtils.bytesAsHex(out.slice(32, 64)) : out.slice(32, 64),
|
|
||||||
id: CommonUtils.bytesAsHex(out.slice(0, 32)),
|
id: CommonUtils.bytesAsHex(out.slice(0, 32)),
|
||||||
};
|
};
|
||||||
if (size > 64) {
|
if (size > 64) {
|
||||||
|
@ -658,7 +658,7 @@ TokenAuthenticatedRESTRequest.prototype = {
|
|||||||
__proto__: RESTRequest.prototype,
|
__proto__: RESTRequest.prototype,
|
||||||
|
|
||||||
async dispatch(method, data) {
|
async dispatch(method, data) {
|
||||||
let sig = CryptoUtils.computeHTTPMACSHA1(
|
let sig = await CryptoUtils.computeHTTPMACSHA1(
|
||||||
this.authToken.id, this.authToken.key, method, this.uri, this.extra
|
this.authToken.id, this.authToken.key, method, this.uri, this.extra
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -469,16 +469,6 @@ add_task(async function test_401_then_500() {
|
|||||||
await promiseStopServer(server);
|
await promiseStopServer(server);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(async function throw_if_not_json_body() {
|
|
||||||
let client = new HawkClient("https://example.com");
|
|
||||||
try {
|
|
||||||
await client.request("/bogus", "GET", {}, "I am not json");
|
|
||||||
do_throw("Expected an error");
|
|
||||||
} catch (err) {
|
|
||||||
Assert.ok(!!err.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// End of tests.
|
// End of tests.
|
||||||
// Utility functions follow
|
// Utility functions follow
|
||||||
|
|
||||||
|
@ -197,11 +197,8 @@ add_task(async function test_hawk_language_pref_changed() {
|
|||||||
await promiseStopServer(server);
|
await promiseStopServer(server);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_deriveHawkCredentials() {
|
add_task(async function test_deriveHawkCredentials() {
|
||||||
let credentials = deriveHawkCredentials(
|
let credentials = await deriveHawkCredentials(SESSION_KEYS.sessionToken, "sessionToken");
|
||||||
SESSION_KEYS.sessionToken, "sessionToken");
|
|
||||||
|
|
||||||
Assert.equal(credentials.algorithm, "sha256");
|
|
||||||
Assert.equal(credentials.id, SESSION_KEYS.tokenID);
|
Assert.equal(credentials.id, SESSION_KEYS.tokenID);
|
||||||
Assert.equal(CommonUtils.bytesAsHex(credentials.key), SESSION_KEYS.reqHMACkey);
|
Assert.equal(CommonUtils.bytesAsHex(credentials.key), SESSION_KEYS.reqHMACkey);
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,7 @@ add_task(async function test_authenticated_request() {
|
|||||||
let key = "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=";
|
let key = "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=";
|
||||||
let method = "GET";
|
let method = "GET";
|
||||||
|
|
||||||
let nonce = btoa(CryptoUtils.generateRandomBytes(16));
|
let nonce = btoa(CryptoUtils.generateRandomBytesLegacy(16));
|
||||||
let ts = Math.floor(Date.now() / 1000);
|
let ts = Math.floor(Date.now() / 1000);
|
||||||
let extra = {ts, nonce};
|
let extra = {ts, nonce};
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ add_task(async function test_authenticated_request() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
let uri = CommonUtils.makeURI(server.baseURI + "/foo");
|
let uri = CommonUtils.makeURI(server.baseURI + "/foo");
|
||||||
let sig = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri, extra);
|
let sig = await CryptoUtils.computeHTTPMACSHA1(id, key, method, uri, extra);
|
||||||
auth = sig.getHeader();
|
auth = sig.getHeader();
|
||||||
|
|
||||||
let req = new TokenAuthenticatedRESTRequest(uri, {id, key}, extra);
|
let req = new TokenAuthenticatedRESTRequest(uri, {id, key}, extra);
|
||||||
|
@ -197,6 +197,28 @@ var CommonUtils = {
|
|||||||
return Array.prototype.slice.call(bytesString).map(c => c.charCodeAt(0));
|
return Array.prototype.slice.call(bytesString).map(c => c.charCodeAt(0));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// A lot of Util methods work with byte strings instead of ArrayBuffers.
|
||||||
|
// A patch should address this problem, but in the meantime let's provide
|
||||||
|
// helpers method to convert byte strings to Uint8Array.
|
||||||
|
byteStringToArrayBuffer(byteString) {
|
||||||
|
if (byteString === undefined) {
|
||||||
|
return new Uint8Array();
|
||||||
|
}
|
||||||
|
const bytes = new Uint8Array(byteString.length);
|
||||||
|
for (let i = 0; i < byteString.length; ++i) {
|
||||||
|
bytes[i] = byteString.charCodeAt(i) & 0xff;
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
},
|
||||||
|
|
||||||
|
arrayBufferToByteString(buffer) {
|
||||||
|
return CommonUtils.byteArrayToString([...buffer]);
|
||||||
|
},
|
||||||
|
|
||||||
|
bufferToHex(buffer) {
|
||||||
|
return Array.prototype.map.call(buffer, (x) => ("00" + x.toString(16)).slice(-2)).join("");
|
||||||
|
},
|
||||||
|
|
||||||
bytesAsHex: function bytesAsHex(bytes) {
|
bytesAsHex: function bytesAsHex(bytes) {
|
||||||
let s = "";
|
let s = "";
|
||||||
for (let i = 0, len = bytes.length; i < len; i++) {
|
for (let i = 0, len = bytes.length; i < len; i++) {
|
||||||
@ -225,6 +247,11 @@ var CommonUtils = {
|
|||||||
return String.fromCharCode.apply(String, bytes);
|
return String.fromCharCode.apply(String, bytes);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hexToArrayBuffer(str) {
|
||||||
|
const octString = CommonUtils.hexToBytes(str);
|
||||||
|
return CommonUtils.byteStringToArrayBuffer(octString);
|
||||||
|
},
|
||||||
|
|
||||||
hexAsString: function hexAsString(hex) {
|
hexAsString: function hexAsString(hex) {
|
||||||
return CommonUtils.decodeUTF8(CommonUtils.hexToBytes(hex));
|
return CommonUtils.decodeUTF8(CommonUtils.hexToBytes(hex));
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
|
|
||||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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,
|
* 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/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
@ -15,20 +13,20 @@ XPCOMUtils.defineLazyServiceGetter(this,
|
|||||||
"IdentityCryptoService",
|
"IdentityCryptoService",
|
||||||
"@mozilla.org/identity/crypto-service;1",
|
"@mozilla.org/identity/crypto-service;1",
|
||||||
"nsIIdentityCryptoService");
|
"nsIIdentityCryptoService");
|
||||||
|
XPCOMUtils.defineLazyGlobalGetters(this, ["crypto"]);
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["jwcrypto"];
|
const EXPORTED_SYMBOLS = ["jwcrypto"];
|
||||||
|
|
||||||
const PREF_LOG_LEVEL = "services.crypto.jwcrypto.log.level";
|
const PREF_LOG_LEVEL = "services.crypto.jwcrypto.log.level";
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "log", function() {
|
XPCOMUtils.defineLazyGetter(this, "log", function() {
|
||||||
let log = Log.repository.getLogger("Services.Crypto.jwcrypto");
|
const log = Log.repository.getLogger("Services.Crypto.jwcrypto");
|
||||||
// Default log level is "Error", but consumers can change this with the pref
|
// Default log level is "Error", but consumers can change this with the pref
|
||||||
// "services.crypto.jwcrypto.log.level".
|
// "services.crypto.jwcrypto.log.level".
|
||||||
log.level = Log.Level.Error;
|
log.level = Log.Level.Error;
|
||||||
let appender = new Log.DumpAppender();
|
const appender = new Log.DumpAppender();
|
||||||
log.addAppender(appender);
|
log.addAppender(appender);
|
||||||
try {
|
try {
|
||||||
let level =
|
const level =
|
||||||
Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
|
Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
|
||||||
&& Services.prefs.getCharPref(PREF_LOG_LEVEL);
|
&& Services.prefs.getCharPref(PREF_LOG_LEVEL);
|
||||||
log.level = Log.Level[level] || Log.Level.Error;
|
log.level = Log.Level[level] || Log.Level.Error;
|
||||||
@ -39,94 +37,164 @@ XPCOMUtils.defineLazyGetter(this, "log", function() {
|
|||||||
return log;
|
return log;
|
||||||
});
|
});
|
||||||
|
|
||||||
const ALGORITHMS = { RS256: "RS256", DS160: "DS160" };
|
const ASSERTION_DEFAULT_DURATION_MS = 1000 * 60 * 2; // 2 minutes default assertion lifetime
|
||||||
const DURATION_MS = 1000 * 60 * 2; // 2 minutes default assertion lifetime
|
const ECDH_PARAMS = {
|
||||||
|
name: "ECDH",
|
||||||
|
namedCurve: "P-256",
|
||||||
|
};
|
||||||
|
const AES_PARAMS = {
|
||||||
|
name: "AES-GCM",
|
||||||
|
length: 256,
|
||||||
|
};
|
||||||
|
const AES_TAG_LEN = 128;
|
||||||
|
const AES_GCM_IV_SIZE = 12;
|
||||||
|
const UTF8_ENCODER = new TextEncoder();
|
||||||
|
const UTF8_DECODER = new TextDecoder();
|
||||||
|
|
||||||
function generateKeyPair(aAlgorithmName, aCallback) {
|
class JWCrypto {
|
||||||
log.debug("Generate key pair; alg = " + aAlgorithmName);
|
/**
|
||||||
|
* Encrypts the given data into a JWE using AES-256-GCM content encryption.
|
||||||
IdentityCryptoService.generateKeyPair(aAlgorithmName, function(rv, aKeyPair) {
|
|
||||||
if (!Components.isSuccessCode(rv)) {
|
|
||||||
return aCallback("key generation failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
var publicKey;
|
|
||||||
|
|
||||||
switch (aKeyPair.keyType) {
|
|
||||||
case ALGORITHMS.RS256:
|
|
||||||
publicKey = {
|
|
||||||
algorithm: "RS",
|
|
||||||
exponent: aKeyPair.hexRSAPublicKeyExponent,
|
|
||||||
modulus: aKeyPair.hexRSAPublicKeyModulus,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ALGORITHMS.DS160:
|
|
||||||
publicKey = {
|
|
||||||
algorithm: "DS",
|
|
||||||
y: aKeyPair.hexDSAPublicValue,
|
|
||||||
p: aKeyPair.hexDSAPrime,
|
|
||||||
q: aKeyPair.hexDSASubPrime,
|
|
||||||
g: aKeyPair.hexDSAGenerator,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return aCallback("unknown key type");
|
|
||||||
}
|
|
||||||
|
|
||||||
let keyWrapper = {
|
|
||||||
serializedPublicKey: JSON.stringify(publicKey),
|
|
||||||
_kp: aKeyPair,
|
|
||||||
};
|
|
||||||
|
|
||||||
return aCallback(null, keyWrapper);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sign(aPayload, aKeypair, aCallback) {
|
|
||||||
aKeypair._kp.sign(aPayload, function(rv, signature) {
|
|
||||||
if (!Components.isSuccessCode(rv)) {
|
|
||||||
log.error("signer.sign failed");
|
|
||||||
return aCallback("Sign failed");
|
|
||||||
}
|
|
||||||
log.debug("signer.sign: success");
|
|
||||||
return aCallback(null, signature);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function jwcryptoClass() {
|
|
||||||
}
|
|
||||||
|
|
||||||
jwcryptoClass.prototype = {
|
|
||||||
/*
|
|
||||||
* Determine the expiration of the assertion. Returns expiry date
|
|
||||||
* in milliseconds as integer.
|
|
||||||
*
|
*
|
||||||
* @param localtimeOffsetMsec (optional)
|
* This function implements a very small subset of the JWE encryption standard
|
||||||
* The number of milliseconds that must be added to the local clock
|
* from https://tools.ietf.org/html/rfc7516. The only supported content encryption
|
||||||
* for it to agree with the server. For example, if the local clock
|
* algorithm is enc="A256GCM" [1] and the only supported key encryption algorithm
|
||||||
* if two minutes fast, localtimeOffsetMsec would be -120000
|
* is alg="ECDH-ES" [2].
|
||||||
|
* The IV is generated randomly: if you are using long-lived keys you might be
|
||||||
|
* exposing yourself to a birthday attack. Please consult your nearest cryptographer.
|
||||||
*
|
*
|
||||||
* @param now (options)
|
* @param {Object} key Peer Public JWK.
|
||||||
* Current date in milliseconds. Useful for mocking clock
|
* @param {ArrayBuffer} data
|
||||||
* skew in testing.
|
*
|
||||||
|
* [1] https://tools.ietf.org/html/rfc7518#section-5.3
|
||||||
|
* [2] https://tools.ietf.org/html/rfc7518#section-4.6
|
||||||
|
*
|
||||||
|
* @returns {Promise<String>}
|
||||||
*/
|
*/
|
||||||
getExpiration(duration = DURATION_MS, localtimeOffsetMsec = 0, now = Date.now()) {
|
async generateJWE(key, data) {
|
||||||
return now + localtimeOffsetMsec + duration;
|
// Generate an ephemeral key to use just for this encryption.
|
||||||
},
|
const epk = await crypto.subtle.generateKey(ECDH_PARAMS, true, ["deriveKey"]);
|
||||||
|
const peerPublicKey = await crypto.subtle.importKey("jwk", key, ECDH_PARAMS, false, ["deriveKey"]);
|
||||||
|
return this._generateJWE(epk, peerPublicKey, data);
|
||||||
|
}
|
||||||
|
|
||||||
isCertValid(aCert, aCallback) {
|
async _generateJWE(epk, peerPublicKey, data) {
|
||||||
// XXX check expiration, bug 769850
|
let iv = crypto.getRandomValues(new Uint8Array(AES_GCM_IV_SIZE));
|
||||||
aCallback(true);
|
const ownPublicJWK = await crypto.subtle.exportKey("jwk", epk.publicKey);
|
||||||
},
|
delete ownPublicJWK.key_ops;
|
||||||
|
// Do ECDH agreement to get the content encryption key.
|
||||||
|
const contentKey = await deriveECDHSharedAESKey(epk.privateKey, peerPublicKey, ["encrypt"]);
|
||||||
|
let header = {alg: "ECDH-ES", enc: "A256GCM", epk: ownPublicJWK};
|
||||||
|
// Yes, additionalData is the byte representation of the base64 representation of the stringified header.
|
||||||
|
const additionalData = UTF8_ENCODER.encode(ChromeUtils.base64URLEncode(UTF8_ENCODER.encode(JSON.stringify(header)), {pad: false}));
|
||||||
|
const encrypted = await crypto.subtle.encrypt({
|
||||||
|
name: "AES-GCM",
|
||||||
|
iv,
|
||||||
|
additionalData,
|
||||||
|
tagLength: AES_TAG_LEN,
|
||||||
|
},
|
||||||
|
contentKey,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
const tagIdx = encrypted.byteLength - ((AES_TAG_LEN + 7) >> 3);
|
||||||
|
let ciphertext = encrypted.slice(0, tagIdx);
|
||||||
|
let tag = encrypted.slice(tagIdx);
|
||||||
|
// JWE serialization.
|
||||||
|
header = UTF8_ENCODER.encode(JSON.stringify(header));
|
||||||
|
header = ChromeUtils.base64URLEncode(header, {pad: false});
|
||||||
|
tag = ChromeUtils.base64URLEncode(tag, {pad: false});
|
||||||
|
ciphertext = ChromeUtils.base64URLEncode(ciphertext, {pad: false});
|
||||||
|
iv = ChromeUtils.base64URLEncode(iv, {pad: false});
|
||||||
|
return `${header}..${iv}.${ciphertext}.${tag}`; // No CEK
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the given JWE using AES-256-GCM content encryption into a byte array.
|
||||||
|
* This function does the opposite of `JWCrypto.generateJWE`.
|
||||||
|
* The only supported content encryption algorithm is enc="A256GCM" [1]
|
||||||
|
* and the only supported key encryption algorithm is alg="ECDH-ES" [2].
|
||||||
|
*
|
||||||
|
* @param {"ECDH-ES"} algorithm
|
||||||
|
* @param {CryptoKey} key Local private key
|
||||||
|
*
|
||||||
|
* [1] https://tools.ietf.org/html/rfc7518#section-5.3
|
||||||
|
* [2] https://tools.ietf.org/html/rfc7518#section-4.6
|
||||||
|
*
|
||||||
|
* @returns {Promise<Uint8Array>}
|
||||||
|
*/
|
||||||
|
async decryptJWE(jwe, key) {
|
||||||
|
let [header, cek, iv, ciphertext, authTag] = jwe.split(".");
|
||||||
|
const additionalData = UTF8_ENCODER.encode(header);
|
||||||
|
header = JSON.parse(UTF8_DECODER.decode(ChromeUtils.base64URLDecode(header, {padding: "reject"})));
|
||||||
|
if (cek.length > 0 || header.enc !== "A256GCM" || header.alg !== "ECDH-ES") {
|
||||||
|
throw new Error("Unknown algorithm.");
|
||||||
|
}
|
||||||
|
if ("apu" in header || "apv" in header) {
|
||||||
|
throw new Error("apu and apv header values are not supported.");
|
||||||
|
}
|
||||||
|
const peerPublicKey = await crypto.subtle.importKey("jwk", header.epk, ECDH_PARAMS, false, ["deriveKey"]);
|
||||||
|
// Do ECDH agreement to get the content encryption key.
|
||||||
|
const contentKey = await deriveECDHSharedAESKey(key, peerPublicKey, ["decrypt"]);
|
||||||
|
iv = ChromeUtils.base64URLDecode(iv, {padding: "reject"});
|
||||||
|
ciphertext = new Uint8Array(ChromeUtils.base64URLDecode(ciphertext, {padding: "reject"}));
|
||||||
|
authTag = new Uint8Array(ChromeUtils.base64URLDecode(authTag, {padding: "reject"}));
|
||||||
|
const bundle = new Uint8Array([...ciphertext, ...authTag]);
|
||||||
|
|
||||||
|
const decrypted = await crypto.subtle.decrypt({
|
||||||
|
name: "AES-GCM",
|
||||||
|
iv,
|
||||||
|
tagLength: AES_TAG_LEN,
|
||||||
|
additionalData,
|
||||||
|
},
|
||||||
|
contentKey,
|
||||||
|
bundle
|
||||||
|
);
|
||||||
|
return new Uint8Array(decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
generateKeyPair(aAlgorithmName, aCallback) {
|
generateKeyPair(aAlgorithmName, aCallback) {
|
||||||
log.debug("generating");
|
log.debug("generating");
|
||||||
generateKeyPair(aAlgorithmName, aCallback);
|
log.debug("Generate key pair; alg = " + aAlgorithmName);
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
IdentityCryptoService.generateKeyPair(aAlgorithmName, (rv, aKeyPair) => {
|
||||||
|
if (!Components.isSuccessCode(rv)) {
|
||||||
|
return aCallback("key generation failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
let publicKey;
|
||||||
|
|
||||||
|
switch (aKeyPair.keyType) {
|
||||||
|
case "RS256":
|
||||||
|
publicKey = {
|
||||||
|
algorithm: "RS",
|
||||||
|
exponent: aKeyPair.hexRSAPublicKeyExponent,
|
||||||
|
modulus: aKeyPair.hexRSAPublicKeyModulus,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "DS160":
|
||||||
|
publicKey = {
|
||||||
|
algorithm: "DS",
|
||||||
|
y: aKeyPair.hexDSAPublicValue,
|
||||||
|
p: aKeyPair.hexDSAPrime,
|
||||||
|
q: aKeyPair.hexDSASubPrime,
|
||||||
|
g: aKeyPair.hexDSAGenerator,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return aCallback("unknown key type");
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyWrapper = {
|
||||||
|
serializedPublicKey: JSON.stringify(publicKey),
|
||||||
|
_kp: aKeyPair,
|
||||||
|
};
|
||||||
|
|
||||||
|
return aCallback(null, keyWrapper);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* Generate an assertion and return it through the provided callback.
|
* Generate an assertion and return it through the provided callback.
|
||||||
*
|
*
|
||||||
* @param aCert
|
* @param aCert
|
||||||
@ -163,29 +231,68 @@ jwcryptoClass.prototype = {
|
|||||||
|
|
||||||
// for now, we hack the algorithm name
|
// for now, we hack the algorithm name
|
||||||
// XXX bug 769851
|
// XXX bug 769851
|
||||||
var header = {"alg": "DS128"};
|
const header = {"alg": "DS128"};
|
||||||
var headerBytes = IdentityCryptoService.base64UrlEncode(
|
const headerBytes = IdentityCryptoService.base64UrlEncode(
|
||||||
JSON.stringify(header));
|
JSON.stringify(header));
|
||||||
|
|
||||||
var payload = {
|
|
||||||
exp: this.getExpiration(
|
function getExpiration(duration = ASSERTION_DEFAULT_DURATION_MS, localtimeOffsetMsec = 0, now = Date.now()) {
|
||||||
aOptions.duration, aOptions.localtimeOffsetMsec, aOptions.now),
|
return now + localtimeOffsetMsec + duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
exp: getExpiration(aOptions.duration, aOptions.localtimeOffsetMsec, aOptions.now),
|
||||||
aud: aAudience,
|
aud: aAudience,
|
||||||
};
|
};
|
||||||
var payloadBytes = IdentityCryptoService.base64UrlEncode(
|
const payloadBytes = IdentityCryptoService.base64UrlEncode(
|
||||||
JSON.stringify(payload));
|
JSON.stringify(payload));
|
||||||
|
|
||||||
log.debug("payload", { payload, payloadBytes });
|
log.debug("payload", { payload, payloadBytes });
|
||||||
sign(headerBytes + "." + payloadBytes, aKeyPair, function(err, signature) {
|
const message = headerBytes + "." + payloadBytes;
|
||||||
if (err)
|
aKeyPair._kp.sign(message, (rv, signature) => {
|
||||||
return aCallback(err);
|
if (!Components.isSuccessCode(rv)) {
|
||||||
|
log.error("signer.sign failed");
|
||||||
var signedAssertion = headerBytes + "." + payloadBytes + "." + signature;
|
aCallback("Sign failed");
|
||||||
return aCallback(null, aCert + "~" + signedAssertion);
|
return;
|
||||||
|
}
|
||||||
|
log.debug("signer.sign: success");
|
||||||
|
const signedAssertion = message + "." + signature;
|
||||||
|
aCallback(null, aCert + "~" + signedAssertion);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
/**
|
||||||
|
* Do an ECDH agreement between a public and private key,
|
||||||
|
* returning the derived encryption key as specced by
|
||||||
|
* JWA RFC.
|
||||||
|
* The raw ECDH secret is derived into a key using
|
||||||
|
* Concat KDF, as defined in Section 5.8.1 of [NIST.800-56A].
|
||||||
|
* @param {CryptoKey} privateKey
|
||||||
|
* @param {CryptoKey} publicKey
|
||||||
|
* @param {String[]} keyUsages See `SubtleCrypto.deriveKey` 5th paramater documentation.
|
||||||
|
* @returns {Promise<CryptoKey>}
|
||||||
|
*/
|
||||||
|
async function deriveECDHSharedAESKey(privateKey, publicKey, keyUsages) {
|
||||||
|
const params = {...ECDH_PARAMS, ...{public: publicKey}};
|
||||||
|
const sharedKey = await crypto.subtle.deriveKey(params, privateKey, AES_PARAMS, true, keyUsages);
|
||||||
|
// This is the NIST Concat KDF specialized to a specific set of parameters,
|
||||||
|
// which basically turn it into a single application of SHA256.
|
||||||
|
// The details are from the JWA RFC.
|
||||||
|
let sharedKeyBytes = await crypto.subtle.exportKey("raw", sharedKey);
|
||||||
|
sharedKeyBytes = new Uint8Array(sharedKeyBytes);
|
||||||
|
const info = [
|
||||||
|
"\x00\x00\x00\x07A256GCM", // 7-byte algorithm identifier
|
||||||
|
"\x00\x00\x00\x00", // empty PartyUInfo
|
||||||
|
"\x00\x00\x00\x00", // empty PartyVInfo
|
||||||
|
"\x00\x00\x01\x00", // keylen == 256
|
||||||
|
].join("");
|
||||||
|
const pkcs = `\x00\x00\x00\x01${String.fromCharCode.apply(null, sharedKeyBytes)}${info}`;
|
||||||
|
const pkcsBuf = Uint8Array.from(Array.prototype.map.call(pkcs, (c) => c.charCodeAt(0)));
|
||||||
|
const derivedKeyBytes = await crypto.subtle.digest({
|
||||||
|
name: "SHA-256",
|
||||||
|
}, pkcsBuf);
|
||||||
|
return crypto.subtle.importKey("raw", derivedKeyBytes, AES_PARAMS, false, keyUsages);
|
||||||
|
}
|
||||||
|
|
||||||
var jwcrypto = new jwcryptoClass();
|
const jwcrypto = new JWCrypto();
|
||||||
this.jwcrypto.ALGORITHMS = ALGORITHMS;
|
|
||||||
|
@ -7,9 +7,19 @@ var EXPORTED_SYMBOLS = ["CryptoUtils"];
|
|||||||
ChromeUtils.import("resource://services-common/observers.js");
|
ChromeUtils.import("resource://services-common/observers.js");
|
||||||
ChromeUtils.import("resource://services-common/utils.js");
|
ChromeUtils.import("resource://services-common/utils.js");
|
||||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
XPCOMUtils.defineLazyGlobalGetters(this, ["crypto"]);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "textEncoder",
|
||||||
|
function() { return new TextEncoder(); }
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A number of `Legacy` suffixed functions are exposed by CryptoUtils.
|
||||||
|
* They work with octet strings, which were used before Javascript
|
||||||
|
* got ArrayBuffer and friends.
|
||||||
|
*/
|
||||||
var CryptoUtils = {
|
var CryptoUtils = {
|
||||||
xor: function xor(a, b) {
|
xor(a, b) {
|
||||||
let bytes = [];
|
let bytes = [];
|
||||||
|
|
||||||
if (a.length != b.length) {
|
if (a.length != b.length) {
|
||||||
@ -25,19 +35,22 @@ var CryptoUtils = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a string of random bytes.
|
* Generate a string of random bytes.
|
||||||
|
* @returns {String} Octet string
|
||||||
*/
|
*/
|
||||||
generateRandomBytes: function generateRandomBytes(length) {
|
generateRandomBytesLegacy(length) {
|
||||||
let rng = Cc["@mozilla.org/security/random-generator;1"]
|
let bytes = CryptoUtils.generateRandomBytes(length);
|
||||||
.createInstance(Ci.nsIRandomGenerator);
|
return CommonUtils.arrayBufferToByteString(bytes);
|
||||||
let bytes = rng.generateRandomBytes(length);
|
},
|
||||||
return CommonUtils.byteArrayToString(bytes);
|
|
||||||
|
generateRandomBytes(length) {
|
||||||
|
return crypto.getRandomValues(new Uint8Array(length));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UTF8-encode a message and hash it with the given hasher. Returns a
|
* UTF8-encode a message and hash it with the given hasher. Returns a
|
||||||
* string containing bytes. The hasher is reset if it's an HMAC hasher.
|
* string containing bytes. The hasher is reset if it's an HMAC hasher.
|
||||||
*/
|
*/
|
||||||
digestUTF8: function digestUTF8(message, hasher) {
|
digestUTF8(message, hasher) {
|
||||||
let data = this._utf8Converter.convertToByteArray(message, {});
|
let data = this._utf8Converter.convertToByteArray(message, {});
|
||||||
hasher.update(data, data.length);
|
hasher.update(data, data.length);
|
||||||
let result = hasher.finish(false);
|
let result = hasher.finish(false);
|
||||||
@ -48,16 +61,18 @@ var CryptoUtils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Treat the given message as a bytes string and hash it with the given
|
* Treat the given message as a bytes string (if necessary) and hash it with
|
||||||
* hasher. Returns a string containing bytes. The hasher is reset if it's
|
* the given hasher. Returns a string containing bytes.
|
||||||
* an HMAC hasher.
|
* The hasher is reset if it's an HMAC hasher.
|
||||||
*/
|
*/
|
||||||
digestBytes: function digestBytes(message, hasher) {
|
digestBytes(bytes, hasher) {
|
||||||
// No UTF-8 encoding for you, sunshine.
|
if (typeof bytes == "string" || bytes instanceof String) {
|
||||||
let bytes = new Uint8Array(message.length);
|
bytes = CommonUtils.byteStringToArrayBuffer(bytes);
|
||||||
for (let i = 0; i < message.length; ++i) {
|
|
||||||
bytes[i] = message.charCodeAt(i) & 0xff;
|
|
||||||
}
|
}
|
||||||
|
return CryptoUtils.digestBytesArray(bytes, hasher);
|
||||||
|
},
|
||||||
|
|
||||||
|
digestBytesArray(bytes, hasher) {
|
||||||
hasher.update(bytes, bytes.length);
|
hasher.update(bytes, bytes.length);
|
||||||
let result = hasher.finish(false);
|
let result = hasher.finish(false);
|
||||||
if (hasher instanceof Ci.nsICryptoHMAC) {
|
if (hasher instanceof Ci.nsICryptoHMAC) {
|
||||||
@ -77,36 +92,6 @@ var CryptoUtils = {
|
|||||||
hasher.update(bytes, bytes.length);
|
hasher.update(bytes, bytes.length);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* UTF-8 encode a message and perform a SHA-1 over it.
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* (string) Buffer to perform operation on. Should be a JS string.
|
|
||||||
* It is possible to pass in a string representing an array
|
|
||||||
* of bytes. But, you probably don't want to UTF-8 encode
|
|
||||||
* such data and thus should not be using this function.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* Raw bytes constituting SHA-1 hash. Value is a JS string. Each
|
|
||||||
* character is the byte value for that offset. Returned string
|
|
||||||
* always has .length == 20.
|
|
||||||
*/
|
|
||||||
UTF8AndSHA1: function UTF8AndSHA1(message) {
|
|
||||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
|
||||||
.createInstance(Ci.nsICryptoHash);
|
|
||||||
hasher.init(hasher.SHA1);
|
|
||||||
|
|
||||||
return CryptoUtils.digestUTF8(message, hasher);
|
|
||||||
},
|
|
||||||
|
|
||||||
sha1: function sha1(message) {
|
|
||||||
return CommonUtils.bytesAsHex(CryptoUtils.UTF8AndSHA1(message));
|
|
||||||
},
|
|
||||||
|
|
||||||
sha1Base32: function sha1Base32(message) {
|
|
||||||
return CommonUtils.encodeBase32(CryptoUtils.UTF8AndSHA1(message));
|
|
||||||
},
|
|
||||||
|
|
||||||
sha256(message) {
|
sha256(message) {
|
||||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||||
.createInstance(Ci.nsICryptoHash);
|
.createInstance(Ci.nsICryptoHash);
|
||||||
@ -141,121 +126,84 @@ var CryptoUtils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HMAC-based Key Derivation (RFC 5869).
|
* @param {string} alg Hash algorithm (common values are SHA-1 or SHA-256)
|
||||||
|
* @param {string} key Key as an octet string.
|
||||||
|
* @param {string} data Data as an octet string.
|
||||||
*/
|
*/
|
||||||
hkdf: function hkdf(ikm, xts, info, len) {
|
async hmacLegacy(alg, key, data) {
|
||||||
let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
|
if (!key || !key.length) {
|
||||||
CryptoUtils.makeHMACKey(xts));
|
key = "\0";
|
||||||
let prk = CryptoUtils.digestBytes(ikm, h);
|
}
|
||||||
return CryptoUtils.hkdfExpand(prk, info, len);
|
data = CommonUtils.byteStringToArrayBuffer(data);
|
||||||
|
key = CommonUtils.byteStringToArrayBuffer(key);
|
||||||
|
const result = await CryptoUtils.hmac(alg, key, data);
|
||||||
|
return CommonUtils.arrayBufferToByteString(result);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HMAC-based Key Derivation Step 2 according to RFC 5869.
|
* @param {string} ikm IKM as an octet string.
|
||||||
|
* @param {string} salt Salt as an Hex string.
|
||||||
|
* @param {string} info Info as a regular string.
|
||||||
|
* @param {Number} len Desired output length in bytes.
|
||||||
*/
|
*/
|
||||||
hkdfExpand: function hkdfExpand(prk, info, len) {
|
async hkdfLegacy(ikm, xts, info, len) {
|
||||||
const BLOCKSIZE = 256 / 8;
|
ikm = CommonUtils.byteStringToArrayBuffer(ikm);
|
||||||
let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
|
xts = CommonUtils.byteStringToArrayBuffer(xts);
|
||||||
CryptoUtils.makeHMACKey(prk));
|
info = textEncoder.encode(info);
|
||||||
let T = "";
|
const okm = await CryptoUtils.hkdf(ikm, xts, info, len);
|
||||||
let Tn = "";
|
return CommonUtils.arrayBufferToByteString(okm);
|
||||||
let iterations = Math.ceil(len / BLOCKSIZE);
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
Tn = CryptoUtils.digestBytes(Tn + info + String.fromCharCode(i + 1), h);
|
|
||||||
T += Tn;
|
|
||||||
}
|
|
||||||
return T.slice(0, len);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PBKDF2 implementation in Javascript.
|
* @param {String} alg Hash algorithm (common values are SHA-1 or SHA-256)
|
||||||
*
|
* @param {ArrayBuffer} key
|
||||||
* The arguments to this function correspond to items in
|
* @param {ArrayBuffer} data
|
||||||
* PKCS #5, v2.0 pp. 9-10
|
* @param {Number} len Desired output length in bytes.
|
||||||
*
|
* @returns {Uint8Array}
|
||||||
* P: the passphrase, an octet string: e.g., "secret phrase"
|
|
||||||
* S: the salt, an octet string: e.g., "DNXPzPpiwn"
|
|
||||||
* c: the number of iterations, a positive integer: e.g., 4096
|
|
||||||
* dkLen: the length in octets of the destination
|
|
||||||
* key, a positive integer: e.g., 16
|
|
||||||
* hmacAlg: The algorithm to use for hmac
|
|
||||||
* hmacLen: The hmac length
|
|
||||||
*
|
|
||||||
* The default value of 20 for hmacLen is appropriate for SHA1. For SHA256,
|
|
||||||
* hmacLen should be 32.
|
|
||||||
*
|
|
||||||
* The output is an octet string of length dkLen, which you
|
|
||||||
* can encode as you wish.
|
|
||||||
*/
|
*/
|
||||||
pbkdf2Generate: function pbkdf2Generate(P, S, c, dkLen,
|
async hmac(alg, key, data) {
|
||||||
hmacAlg = Ci.nsICryptoHMAC.SHA1, hmacLen = 20) {
|
const hmacKey = await crypto.subtle.importKey("raw", key, {name: "HMAC", hash: alg}, false, ["sign"]);
|
||||||
|
const result = await crypto.subtle.sign("HMAC", hmacKey, data);
|
||||||
|
return new Uint8Array(result);
|
||||||
|
},
|
||||||
|
|
||||||
// We don't have a default in the algo itself, as NSS does.
|
/**
|
||||||
if (!dkLen) {
|
* @param {ArrayBuffer} ikm
|
||||||
throw new Error("dkLen should be defined");
|
* @param {ArrayBuffer} salt
|
||||||
}
|
* @param {ArrayBuffer} info
|
||||||
|
* @param {Number} len Desired output length in bytes.
|
||||||
|
* @returns {Uint8Array}
|
||||||
|
*/
|
||||||
|
async hkdf(ikm, salt, info, len) {
|
||||||
|
const key = await crypto.subtle.importKey("raw", ikm, {name: "HKDF"}, false, ["deriveBits"]);
|
||||||
|
const okm = await crypto.subtle.deriveBits({
|
||||||
|
name: "HKDF",
|
||||||
|
hash: "SHA-256",
|
||||||
|
salt,
|
||||||
|
info,
|
||||||
|
}, key, len * 8);
|
||||||
|
return new Uint8Array(okm);
|
||||||
|
},
|
||||||
|
|
||||||
function F(S, c, i, h) {
|
/**
|
||||||
|
* PBKDF2 password stretching with SHA-256 hmac.
|
||||||
function XOR(a, b, isA) {
|
*
|
||||||
if (a.length != b.length) {
|
* @param {string} passphrase Passphrase as an octet string.
|
||||||
return false;
|
* @param {string} salt Salt as an octet string.
|
||||||
}
|
* @param {string} iterations Number of iterations, a positive integer.
|
||||||
|
* @param {string} len Desired output length in bytes.
|
||||||
let val = [];
|
*/
|
||||||
for (let i = 0; i < a.length; i++) {
|
async pbkdf2Generate(passphrase, salt, iterations, len) {
|
||||||
if (isA) {
|
passphrase = CommonUtils.byteStringToArrayBuffer(passphrase);
|
||||||
val[i] = a[i] ^ b[i];
|
salt = CommonUtils.byteStringToArrayBuffer(salt);
|
||||||
} else {
|
const key = await crypto.subtle.importKey("raw", passphrase, {name: "PBKDF2"}, false, ["deriveBits"]);
|
||||||
val[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
|
const output = await crypto.subtle.deriveBits({
|
||||||
}
|
name: "PBKDF2",
|
||||||
}
|
hash: "SHA-256",
|
||||||
|
salt,
|
||||||
return val;
|
iterations,
|
||||||
}
|
}, key, len * 8);
|
||||||
|
return CommonUtils.arrayBufferToByteString(new Uint8Array(output));
|
||||||
let ret;
|
|
||||||
let U = [];
|
|
||||||
|
|
||||||
/* Encode i into 4 octets: _INT */
|
|
||||||
let I = [];
|
|
||||||
I[0] = String.fromCharCode((i >> 24) & 0xff);
|
|
||||||
I[1] = String.fromCharCode((i >> 16) & 0xff);
|
|
||||||
I[2] = String.fromCharCode((i >> 8) & 0xff);
|
|
||||||
I[3] = String.fromCharCode(i & 0xff);
|
|
||||||
|
|
||||||
U[0] = CryptoUtils.digestBytes(S + I.join(""), h);
|
|
||||||
for (let j = 1; j < c; j++) {
|
|
||||||
U[j] = CryptoUtils.digestBytes(U[j - 1], h);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = U[0];
|
|
||||||
for (let j = 1; j < c; j++) {
|
|
||||||
ret = CommonUtils.byteArrayToString(XOR(ret, U[j]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
let l = Math.ceil(dkLen / hmacLen);
|
|
||||||
let r = dkLen - ((l - 1) * hmacLen);
|
|
||||||
|
|
||||||
// Reuse the key and the hasher. Remaking them 4096 times is 'spensive.
|
|
||||||
let h = CryptoUtils.makeHMACHasher(hmacAlg,
|
|
||||||
CryptoUtils.makeHMACKey(P));
|
|
||||||
|
|
||||||
let T = [];
|
|
||||||
for (let i = 0; i < l;) {
|
|
||||||
T[i] = F(S, c, ++i, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ret = "";
|
|
||||||
for (let i = 0; i < l - 1;) {
|
|
||||||
ret += T[i++];
|
|
||||||
}
|
|
||||||
ret += T[l - 1].substr(0, r);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -295,15 +243,14 @@ var CryptoUtils = {
|
|||||||
* nonce - (string) Nonce value used.
|
* nonce - (string) Nonce value used.
|
||||||
* ts - (number) Integer seconds since Unix epoch that was used.
|
* ts - (number) Integer seconds since Unix epoch that was used.
|
||||||
*/
|
*/
|
||||||
computeHTTPMACSHA1: function computeHTTPMACSHA1(identifier, key, method,
|
async computeHTTPMACSHA1(identifier, key, method, uri, extra) {
|
||||||
uri, extra) {
|
|
||||||
let ts = (extra && extra.ts) ? extra.ts : Math.floor(Date.now() / 1000);
|
let ts = (extra && extra.ts) ? extra.ts : Math.floor(Date.now() / 1000);
|
||||||
let nonce_bytes = (extra && extra.nonce_bytes > 0) ? extra.nonce_bytes : 8;
|
let nonce_bytes = (extra && extra.nonce_bytes > 0) ? extra.nonce_bytes : 8;
|
||||||
|
|
||||||
// We are allowed to use more than the Base64 alphabet if we want.
|
// We are allowed to use more than the Base64 alphabet if we want.
|
||||||
let nonce = (extra && extra.nonce)
|
let nonce = (extra && extra.nonce)
|
||||||
? extra.nonce
|
? extra.nonce
|
||||||
: btoa(CryptoUtils.generateRandomBytes(nonce_bytes));
|
: btoa(CryptoUtils.generateRandomBytesLegacy(nonce_bytes));
|
||||||
|
|
||||||
let host = uri.asciiHost;
|
let host = uri.asciiHost;
|
||||||
let port;
|
let port;
|
||||||
@ -329,9 +276,7 @@ var CryptoUtils = {
|
|||||||
port + "\n" +
|
port + "\n" +
|
||||||
ext + "\n";
|
ext + "\n";
|
||||||
|
|
||||||
let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1,
|
const mac = await CryptoUtils.hmacLegacy("SHA-1", key, requestString);
|
||||||
CryptoUtils.makeHMACKey(key));
|
|
||||||
let mac = CryptoUtils.digestBytes(requestString, hasher);
|
|
||||||
|
|
||||||
function getHeader() {
|
function getHeader() {
|
||||||
return CryptoUtils.getHTTPMACSHA1Header(this.identifier, this.ts,
|
return CryptoUtils.getHTTPMACSHA1Header(this.identifier, this.ts,
|
||||||
@ -406,7 +351,6 @@ var CryptoUtils = {
|
|||||||
* All three keys are required:
|
* All three keys are required:
|
||||||
* id - (string) key identifier
|
* id - (string) key identifier
|
||||||
* key - (string) raw key bytes
|
* key - (string) raw key bytes
|
||||||
* algorithm - (string) which hash to use: "sha1" or "sha256"
|
|
||||||
* ext - (string) application-specific data, included in MAC
|
* ext - (string) application-specific data, included in MAC
|
||||||
* localtimeOffsetMsec - (number) local clock offset (vs server)
|
* localtimeOffsetMsec - (number) local clock offset (vs server)
|
||||||
* payload - (string) payload to include in hash, containing the
|
* payload - (string) payload to include in hash, containing the
|
||||||
@ -432,7 +376,7 @@ var CryptoUtils = {
|
|||||||
* for testing as this function will generate a
|
* for testing as this function will generate a
|
||||||
* cryptographically secure random one if not defined.
|
* cryptographically secure random one if not defined.
|
||||||
* @returns
|
* @returns
|
||||||
* (object) Contains results of operation. The object has the
|
* Promise<Object> Contains results of operation. The object has the
|
||||||
* following keys:
|
* following keys:
|
||||||
* field - (string) HAWK header, to use in Authorization: header
|
* field - (string) HAWK header, to use in Authorization: header
|
||||||
* artifacts - (object) other generated values:
|
* artifacts - (object) other generated values:
|
||||||
@ -446,23 +390,11 @@ var CryptoUtils = {
|
|||||||
* ext - (string) app-specific data
|
* ext - (string) app-specific data
|
||||||
* MAC - (string) request MAC (base64)
|
* MAC - (string) request MAC (base64)
|
||||||
*/
|
*/
|
||||||
computeHAWK(uri, method, options) {
|
async computeHAWK(uri, method, options) {
|
||||||
let credentials = options.credentials;
|
let credentials = options.credentials;
|
||||||
let ts = options.ts || Math.floor(((options.now || Date.now()) +
|
let ts = options.ts || Math.floor(((options.now || Date.now()) +
|
||||||
(options.localtimeOffsetMsec || 0))
|
(options.localtimeOffsetMsec || 0))
|
||||||
/ 1000);
|
/ 1000);
|
||||||
|
|
||||||
let hash_algo, hmac_algo;
|
|
||||||
if (credentials.algorithm == "sha1") {
|
|
||||||
hash_algo = Ci.nsICryptoHash.SHA1;
|
|
||||||
hmac_algo = Ci.nsICryptoHMAC.SHA1;
|
|
||||||
} else if (credentials.algorithm == "sha256") {
|
|
||||||
hash_algo = Ci.nsICryptoHash.SHA256;
|
|
||||||
hmac_algo = Ci.nsICryptoHMAC.SHA256;
|
|
||||||
} else {
|
|
||||||
throw new Error("Unsupported algorithm: " + credentials.algorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
let port;
|
let port;
|
||||||
if (uri.port != -1) {
|
if (uri.port != -1) {
|
||||||
port = uri.port;
|
port = uri.port;
|
||||||
@ -476,7 +408,7 @@ var CryptoUtils = {
|
|||||||
|
|
||||||
let artifacts = {
|
let artifacts = {
|
||||||
ts,
|
ts,
|
||||||
nonce: options.nonce || btoa(CryptoUtils.generateRandomBytes(8)),
|
nonce: options.nonce || btoa(CryptoUtils.generateRandomBytesLegacy(8)),
|
||||||
method: method.toUpperCase(),
|
method: method.toUpperCase(),
|
||||||
resource: uri.pathQueryRef, // This includes both path and search/queryarg.
|
resource: uri.pathQueryRef, // This includes both path and search/queryarg.
|
||||||
host: uri.asciiHost.toLowerCase(), // This includes punycoding.
|
host: uri.asciiHost.toLowerCase(), // This includes punycoding.
|
||||||
@ -489,18 +421,11 @@ var CryptoUtils = {
|
|||||||
|
|
||||||
if (!artifacts.hash && options.hasOwnProperty("payload")
|
if (!artifacts.hash && options.hasOwnProperty("payload")
|
||||||
&& options.payload) {
|
&& options.payload) {
|
||||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
const buffer = textEncoder.encode(`hawk.1.payload\n${contentType}\n${options.payload}\n`);
|
||||||
.createInstance(Ci.nsICryptoHash);
|
const hash = await crypto.subtle.digest("SHA-256", buffer);
|
||||||
hasher.init(hash_algo);
|
|
||||||
CryptoUtils.updateUTF8("hawk.1.payload\n", hasher);
|
|
||||||
CryptoUtils.updateUTF8(contentType + "\n", hasher);
|
|
||||||
CryptoUtils.updateUTF8(options.payload, hasher);
|
|
||||||
CryptoUtils.updateUTF8("\n", hasher);
|
|
||||||
let hash = hasher.finish(false);
|
|
||||||
// HAWK specifies this .hash to use +/ (not _-) and include the
|
// HAWK specifies this .hash to use +/ (not _-) and include the
|
||||||
// trailing "==" padding.
|
// trailing "==" padding.
|
||||||
let hash_b64 = btoa(hash);
|
artifacts.hash = ChromeUtils.base64URLEncode(hash, {pad: true}).replace(/-/g, "+").replace(/_/g, "/");
|
||||||
artifacts.hash = hash_b64;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let requestString = ("hawk.1.header\n" +
|
let requestString = ("hawk.1.header\n" +
|
||||||
@ -516,9 +441,8 @@ var CryptoUtils = {
|
|||||||
}
|
}
|
||||||
requestString += "\n";
|
requestString += "\n";
|
||||||
|
|
||||||
let hasher = CryptoUtils.makeHMACHasher(hmac_algo,
|
const hash = await CryptoUtils.hmacLegacy("SHA-256", credentials.key, requestString);
|
||||||
CryptoUtils.makeHMACKey(credentials.key));
|
artifacts.mac = btoa(hash);
|
||||||
artifacts.mac = btoa(CryptoUtils.digestBytes(requestString, hasher));
|
|
||||||
// The output MAC uses "+" and "/", and padded== .
|
// The output MAC uses "+" and "/", and padded== .
|
||||||
|
|
||||||
function escape(attribute) {
|
function escape(attribute) {
|
||||||
|
@ -11,6 +11,8 @@ XPCOMUtils.defineLazyServiceGetter(this,
|
|||||||
"@mozilla.org/identity/crypto-service;1",
|
"@mozilla.org/identity/crypto-service;1",
|
||||||
"nsIIdentityCryptoService");
|
"nsIIdentityCryptoService");
|
||||||
|
|
||||||
|
Cu.importGlobalProperties(["crypto"]);
|
||||||
|
|
||||||
const RP_ORIGIN = "http://123done.org";
|
const RP_ORIGIN = "http://123done.org";
|
||||||
const INTERNAL_ORIGIN = "browserid://";
|
const INTERNAL_ORIGIN = "browserid://";
|
||||||
|
|
||||||
@ -21,111 +23,99 @@ const HOUR_MS = MINUTE_MS * 60;
|
|||||||
// Enable logging from jwcrypto.jsm.
|
// Enable logging from jwcrypto.jsm.
|
||||||
Services.prefs.setCharPref("services.crypto.jwcrypto.log.level", "Debug");
|
Services.prefs.setCharPref("services.crypto.jwcrypto.log.level", "Debug");
|
||||||
|
|
||||||
function test_sanity() {
|
function promisify(fn) {
|
||||||
do_test_pending();
|
return (...args) => {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
jwcrypto.generateKeyPair("DS160", function(err, kp) {
|
fn(...args, (err, result) => {
|
||||||
Assert.equal(null, err);
|
err ? rej(err) : res(result);
|
||||||
|
|
||||||
do_test_finished();
|
|
||||||
run_next_test();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_generate() {
|
|
||||||
do_test_pending();
|
|
||||||
jwcrypto.generateKeyPair("DS160", function(err, kp) {
|
|
||||||
Assert.equal(null, err);
|
|
||||||
Assert.notEqual(kp, null);
|
|
||||||
|
|
||||||
do_test_finished();
|
|
||||||
run_next_test();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_get_assertion() {
|
|
||||||
do_test_pending();
|
|
||||||
|
|
||||||
jwcrypto.generateKeyPair(
|
|
||||||
"DS160",
|
|
||||||
function(err, kp) {
|
|
||||||
jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN, (err2, backedAssertion) => {
|
|
||||||
Assert.equal(null, err2);
|
|
||||||
|
|
||||||
Assert.equal(backedAssertion.split("~").length, 2);
|
|
||||||
Assert.equal(backedAssertion.split(".").length, 3);
|
|
||||||
|
|
||||||
do_test_finished();
|
|
||||||
run_next_test();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
const generateKeyPair = promisify(jwcrypto.generateKeyPair);
|
||||||
|
const generateAssertion = promisify(jwcrypto.generateAssertion);
|
||||||
|
|
||||||
function test_rsa() {
|
add_task(async function test_jwe_roundtrip_ecdh_es_encryption() {
|
||||||
do_test_pending();
|
const data = crypto.getRandomValues(new Uint8Array(123));
|
||||||
function checkRSA(err, kpo) {
|
const localEpk = await crypto.subtle.generateKey({
|
||||||
Assert.notEqual(kpo, undefined);
|
name: "ECDH",
|
||||||
info(kpo.serializedPublicKey);
|
namedCurve: "P-256",
|
||||||
let pk = JSON.parse(kpo.serializedPublicKey);
|
}, true, ["deriveKey"]);
|
||||||
Assert.equal(pk.algorithm, "RS");
|
const remoteEpk = await crypto.subtle.generateKey({
|
||||||
/* TODO
|
name: "ECDH",
|
||||||
do_check_neq(kpo.sign, null);
|
namedCurve: "P-256",
|
||||||
do_check_eq(typeof kpo.sign, "function");
|
}, true, ["deriveKey"]);
|
||||||
do_check_neq(kpo.userID, null);
|
const jwe = await jwcrypto._generateJWE(localEpk, remoteEpk.publicKey, data);
|
||||||
do_check_neq(kpo.url, null);
|
const decryptedJWE = await jwcrypto.decryptJWE(jwe, remoteEpk.privateKey);
|
||||||
do_check_eq(kpo.url, INTERNAL_ORIGIN);
|
Assert.deepEqual(data, decryptedJWE);
|
||||||
do_check_neq(kpo.exponent, null);
|
});
|
||||||
do_check_neq(kpo.modulus, null);
|
|
||||||
|
|
||||||
// TODO: should sign be async?
|
add_task(async function test_sanity() {
|
||||||
let sig = kpo.sign("This is a message to sign");
|
// Shouldn't reject.
|
||||||
|
await generateKeyPair("DS160");
|
||||||
|
});
|
||||||
|
|
||||||
do_check_neq(sig, null);
|
add_task(async function test_generate() {
|
||||||
do_check_eq(typeof sig, "string");
|
let kp = await generateKeyPair("DS160");
|
||||||
do_check_true(sig.length > 1);
|
Assert.notEqual(kp, null);
|
||||||
*/
|
});
|
||||||
do_test_finished();
|
|
||||||
run_next_test();
|
|
||||||
}
|
|
||||||
|
|
||||||
jwcrypto.generateKeyPair("RS256", checkRSA);
|
add_task(async function test_get_assertion() {
|
||||||
}
|
let kp = await generateKeyPair("DS160");
|
||||||
|
let backedAssertion = await generateAssertion("fake-cert", kp, RP_ORIGIN);
|
||||||
|
Assert.equal(backedAssertion.split("~").length, 2);
|
||||||
|
Assert.equal(backedAssertion.split(".").length, 3);
|
||||||
|
});
|
||||||
|
|
||||||
function test_dsa() {
|
add_task(async function test_rsa() {
|
||||||
do_test_pending();
|
let kpo = await generateKeyPair("RS256");
|
||||||
function checkDSA(err, kpo) {
|
Assert.notEqual(kpo, undefined);
|
||||||
Assert.notEqual(kpo, undefined);
|
info(kpo.serializedPublicKey);
|
||||||
info(kpo.serializedPublicKey);
|
let pk = JSON.parse(kpo.serializedPublicKey);
|
||||||
let pk = JSON.parse(kpo.serializedPublicKey);
|
Assert.equal(pk.algorithm, "RS");
|
||||||
Assert.equal(pk.algorithm, "DS");
|
/* TODO
|
||||||
/* TODO
|
do_check_neq(kpo.sign, null);
|
||||||
do_check_neq(kpo.sign, null);
|
do_check_eq(typeof kpo.sign, "function");
|
||||||
do_check_eq(typeof kpo.sign, "function");
|
do_check_neq(kpo.userID, null);
|
||||||
do_check_neq(kpo.userID, null);
|
do_check_neq(kpo.url, null);
|
||||||
do_check_neq(kpo.url, null);
|
do_check_eq(kpo.url, INTERNAL_ORIGIN);
|
||||||
do_check_eq(kpo.url, INTERNAL_ORIGIN);
|
do_check_neq(kpo.exponent, null);
|
||||||
do_check_neq(kpo.generator, null);
|
do_check_neq(kpo.modulus, null);
|
||||||
do_check_neq(kpo.prime, null);
|
|
||||||
do_check_neq(kpo.subPrime, null);
|
|
||||||
do_check_neq(kpo.publicValue, null);
|
|
||||||
|
|
||||||
let sig = kpo.sign("This is a message to sign");
|
// TODO: should sign be async?
|
||||||
|
let sig = kpo.sign("This is a message to sign");
|
||||||
|
|
||||||
do_check_neq(sig, null);
|
do_check_neq(sig, null);
|
||||||
do_check_eq(typeof sig, "string");
|
do_check_eq(typeof sig, "string");
|
||||||
do_check_true(sig.length > 1);
|
do_check_true(sig.length > 1);
|
||||||
*/
|
*/
|
||||||
do_test_finished();
|
});
|
||||||
run_next_test();
|
|
||||||
}
|
|
||||||
|
|
||||||
jwcrypto.generateKeyPair("DS160", checkDSA);
|
add_task(async function test_dsa() {
|
||||||
}
|
let kpo = await generateKeyPair("DS160");
|
||||||
|
info(kpo.serializedPublicKey);
|
||||||
|
let pk = JSON.parse(kpo.serializedPublicKey);
|
||||||
|
Assert.equal(pk.algorithm, "DS");
|
||||||
|
/* TODO
|
||||||
|
do_check_neq(kpo.sign, null);
|
||||||
|
do_check_eq(typeof kpo.sign, "function");
|
||||||
|
do_check_neq(kpo.userID, null);
|
||||||
|
do_check_neq(kpo.url, null);
|
||||||
|
do_check_eq(kpo.url, INTERNAL_ORIGIN);
|
||||||
|
do_check_neq(kpo.generator, null);
|
||||||
|
do_check_neq(kpo.prime, null);
|
||||||
|
do_check_neq(kpo.subPrime, null);
|
||||||
|
do_check_neq(kpo.publicValue, null);
|
||||||
|
|
||||||
function test_get_assertion_with_offset() {
|
let sig = kpo.sign("This is a message to sign");
|
||||||
do_test_pending();
|
|
||||||
|
|
||||||
|
do_check_neq(sig, null);
|
||||||
|
do_check_eq(typeof sig, "string");
|
||||||
|
do_check_true(sig.length > 1);
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_get_assertion_with_offset() {
|
||||||
// Use an arbitrary date in the past to ensure we don't accidentally pass
|
// Use an arbitrary date in the past to ensure we don't accidentally pass
|
||||||
// this test with current dates, missing offsets, etc.
|
// this test with current dates, missing offsets, etc.
|
||||||
let serverMsec = Date.parse("Tue Oct 31 2000 00:00:00 GMT-0800");
|
let serverMsec = Date.parse("Tue Oct 31 2000 00:00:00 GMT-0800");
|
||||||
@ -135,97 +125,56 @@ function test_get_assertion_with_offset() {
|
|||||||
let localtimeOffsetMsec = -1 * 12 * HOUR_MS;
|
let localtimeOffsetMsec = -1 * 12 * HOUR_MS;
|
||||||
let localMsec = serverMsec - localtimeOffsetMsec;
|
let localMsec = serverMsec - localtimeOffsetMsec;
|
||||||
|
|
||||||
jwcrypto.generateKeyPair(
|
let kp = await generateKeyPair("DS160");
|
||||||
"DS160",
|
let backedAssertion = await generateAssertion("fake-cert", kp, RP_ORIGIN,
|
||||||
function(err, kp) {
|
{
|
||||||
jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN,
|
duration: MINUTE_MS,
|
||||||
{ duration: MINUTE_MS,
|
localtimeOffsetMsec,
|
||||||
localtimeOffsetMsec,
|
now: localMsec,
|
||||||
now: localMsec},
|
|
||||||
function(err2, backedAssertion) {
|
|
||||||
Assert.equal(null, err2);
|
|
||||||
|
|
||||||
// properly formed
|
|
||||||
let cert;
|
|
||||||
let assertion;
|
|
||||||
[cert, assertion] = backedAssertion.split("~");
|
|
||||||
|
|
||||||
Assert.equal(cert, "fake-cert");
|
|
||||||
Assert.equal(assertion.split(".").length, 3);
|
|
||||||
|
|
||||||
let components = extractComponents(assertion);
|
|
||||||
|
|
||||||
// Expiry is within two minutes, corrected for skew
|
|
||||||
let exp = parseInt(components.payload.exp, 10);
|
|
||||||
Assert.ok(exp - serverMsec === MINUTE_MS);
|
|
||||||
|
|
||||||
do_test_finished();
|
|
||||||
run_next_test();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
// properly formed
|
||||||
|
let cert;
|
||||||
|
let assertion;
|
||||||
|
[cert, assertion] = backedAssertion.split("~");
|
||||||
|
|
||||||
function test_assertion_lifetime() {
|
Assert.equal(cert, "fake-cert");
|
||||||
do_test_pending();
|
Assert.equal(assertion.split(".").length, 3);
|
||||||
|
|
||||||
jwcrypto.generateKeyPair(
|
let components = extractComponents(assertion);
|
||||||
"DS160",
|
|
||||||
function(err, kp) {
|
|
||||||
jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN,
|
|
||||||
{duration: MINUTE_MS},
|
|
||||||
function(err2, backedAssertion) {
|
|
||||||
Assert.equal(null, err2);
|
|
||||||
|
|
||||||
// properly formed
|
// Expiry is within two minutes, corrected for skew
|
||||||
let cert;
|
let exp = parseInt(components.payload.exp, 10);
|
||||||
let assertion;
|
Assert.ok(exp - serverMsec === MINUTE_MS);
|
||||||
[cert, assertion] = backedAssertion.split("~");
|
});
|
||||||
|
|
||||||
Assert.equal(cert, "fake-cert");
|
add_task(async function test_assertion_lifetime() {
|
||||||
Assert.equal(assertion.split(".").length, 3);
|
let kp = await generateKeyPair("DS160");
|
||||||
|
let backedAssertion = await generateAssertion("fake-cert", kp, RP_ORIGIN, {duration: MINUTE_MS});
|
||||||
|
// properly formed
|
||||||
|
let cert;
|
||||||
|
let assertion;
|
||||||
|
[cert, assertion] = backedAssertion.split("~");
|
||||||
|
|
||||||
let components = extractComponents(assertion);
|
Assert.equal(cert, "fake-cert");
|
||||||
|
Assert.equal(assertion.split(".").length, 3);
|
||||||
|
|
||||||
// Expiry is within one minute, as we specified above
|
let components = extractComponents(assertion);
|
||||||
let exp = parseInt(components.payload.exp, 10);
|
|
||||||
Assert.ok(Math.abs(Date.now() - exp) > 50 * SECOND_MS);
|
|
||||||
Assert.ok(Math.abs(Date.now() - exp) <= MINUTE_MS);
|
|
||||||
|
|
||||||
do_test_finished();
|
// Expiry is within one minute, as we specified above
|
||||||
run_next_test();
|
let exp = parseInt(components.payload.exp, 10);
|
||||||
}
|
Assert.ok(Math.abs(Date.now() - exp) > 50 * SECOND_MS);
|
||||||
);
|
Assert.ok(Math.abs(Date.now() - exp) <= MINUTE_MS);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_audience_encoding_bug972582() {
|
add_task(async function test_audience_encoding_bug972582() {
|
||||||
let audience = "i-like-pie.com";
|
let audience = "i-like-pie.com";
|
||||||
|
let kp = await generateKeyPair("DS160");
|
||||||
jwcrypto.generateKeyPair(
|
let backedAssertion = await generateAssertion("fake-cert", kp, audience);
|
||||||
"DS160",
|
let [/* cert */, assertion] = backedAssertion.split("~");
|
||||||
function(err, kp) {
|
let components = extractComponents(assertion);
|
||||||
Assert.equal(null, err);
|
Assert.equal(components.payload.aud, audience);
|
||||||
jwcrypto.generateAssertion("fake-cert", kp, audience,
|
});
|
||||||
function(err2, backedAssertion) {
|
|
||||||
Assert.equal(null, err2);
|
|
||||||
|
|
||||||
let [/* cert */, assertion] = backedAssertion.split("~");
|
|
||||||
let components = extractComponents(assertion);
|
|
||||||
Assert.equal(components.payload.aud, audience);
|
|
||||||
|
|
||||||
do_test_finished();
|
|
||||||
run_next_test();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// End of tests
|
|
||||||
// Helper function follow
|
|
||||||
|
|
||||||
function extractComponents(signedObject) {
|
function extractComponents(signedObject) {
|
||||||
if (typeof(signedObject) != "string") {
|
if (typeof(signedObject) != "string") {
|
||||||
@ -259,16 +208,3 @@ function extractComponents(signedObject) {
|
|||||||
payloadSegment,
|
payloadSegment,
|
||||||
cryptoSegment};
|
cryptoSegment};
|
||||||
}
|
}
|
||||||
|
|
||||||
var TESTS = [
|
|
||||||
test_sanity,
|
|
||||||
test_generate,
|
|
||||||
test_get_assertion,
|
|
||||||
test_get_assertion_with_offset,
|
|
||||||
test_assertion_lifetime,
|
|
||||||
test_audience_encoding_bug972582,
|
|
||||||
];
|
|
||||||
|
|
||||||
TESTS = TESTS.concat([test_rsa, test_dsa]);
|
|
||||||
|
|
||||||
TESTS.forEach(f => add_test(f));
|
|
||||||
|
@ -11,54 +11,20 @@ function run_test() {
|
|||||||
run_next_test();
|
run_next_test();
|
||||||
}
|
}
|
||||||
|
|
||||||
add_test(function test_hawk() {
|
add_task(async function test_hawk() {
|
||||||
let compute = CryptoUtils.computeHAWK;
|
let compute = CryptoUtils.computeHAWK;
|
||||||
|
|
||||||
// vectors copied from the HAWK (node.js) tests
|
|
||||||
let credentials_sha1 = {
|
|
||||||
id: "123456",
|
|
||||||
key: "2983d45yun89q",
|
|
||||||
algorithm: "sha1",
|
|
||||||
};
|
|
||||||
|
|
||||||
let method = "POST";
|
let method = "POST";
|
||||||
let ts = 1353809207;
|
let ts = 1353809207;
|
||||||
let nonce = "Ygvqdz";
|
let nonce = "Ygvqdz";
|
||||||
let result;
|
|
||||||
|
|
||||||
let uri_http = CommonUtils.makeURI("http://example.net/somewhere/over/the/rainbow");
|
let credentials = {
|
||||||
let sha1_opts = { credentials: credentials_sha1,
|
|
||||||
ext: "Bazinga!",
|
|
||||||
ts,
|
|
||||||
nonce,
|
|
||||||
payload: "something to write about",
|
|
||||||
};
|
|
||||||
result = compute(uri_http, method, sha1_opts);
|
|
||||||
|
|
||||||
// The HAWK spec uses non-urlsafe base64 (+/) for its output MAC string.
|
|
||||||
Assert.equal(result.field,
|
|
||||||
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
|
||||||
'hash="bsvY3IfUllw6V5rvk4tStEvpBhE=", ext="Bazinga!", ' +
|
|
||||||
'mac="qbf1ZPG/r/e06F4ht+T77LXi5vw="'
|
|
||||||
);
|
|
||||||
Assert.equal(result.artifacts.ts, ts);
|
|
||||||
Assert.equal(result.artifacts.nonce, nonce);
|
|
||||||
Assert.equal(result.artifacts.method, method);
|
|
||||||
Assert.equal(result.artifacts.resource, "/somewhere/over/the/rainbow");
|
|
||||||
Assert.equal(result.artifacts.host, "example.net");
|
|
||||||
Assert.equal(result.artifacts.port, 80);
|
|
||||||
// artifacts.hash is the *payload* hash, not the overall request MAC.
|
|
||||||
Assert.equal(result.artifacts.hash, "bsvY3IfUllw6V5rvk4tStEvpBhE=");
|
|
||||||
Assert.equal(result.artifacts.ext, "Bazinga!");
|
|
||||||
|
|
||||||
let credentials_sha256 = {
|
|
||||||
id: "123456",
|
id: "123456",
|
||||||
key: "2983d45yun89q",
|
key: "2983d45yun89q",
|
||||||
algorithm: "sha256",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let uri_https = CommonUtils.makeURI("https://example.net/somewhere/over/the/rainbow");
|
let uri_https = CommonUtils.makeURI("https://example.net/somewhere/over/the/rainbow");
|
||||||
let sha256_opts = { credentials: credentials_sha256,
|
let opts = { credentials,
|
||||||
ext: "Bazinga!",
|
ext: "Bazinga!",
|
||||||
ts,
|
ts,
|
||||||
nonce,
|
nonce,
|
||||||
@ -66,7 +32,7 @@ add_test(function test_hawk() {
|
|||||||
contentType: "text/plain",
|
contentType: "text/plain",
|
||||||
};
|
};
|
||||||
|
|
||||||
result = compute(uri_https, method, sha256_opts);
|
let result = await compute(uri_https, method, opts);
|
||||||
Assert.equal(result.field,
|
Assert.equal(result.field,
|
||||||
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
||||||
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
||||||
@ -82,13 +48,13 @@ add_test(function test_hawk() {
|
|||||||
Assert.equal(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
|
Assert.equal(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
|
||||||
Assert.equal(result.artifacts.ext, "Bazinga!");
|
Assert.equal(result.artifacts.ext, "Bazinga!");
|
||||||
|
|
||||||
let sha256_opts_noext = { credentials: credentials_sha256,
|
let opts_noext = { credentials,
|
||||||
ts,
|
ts,
|
||||||
nonce,
|
nonce,
|
||||||
payload: "something to write about",
|
payload: "something to write about",
|
||||||
contentType: "text/plain",
|
contentType: "text/plain",
|
||||||
};
|
};
|
||||||
result = compute(uri_https, method, sha256_opts_noext);
|
result = await compute(uri_https, method, opts_noext);
|
||||||
Assert.equal(result.field,
|
Assert.equal(result.field,
|
||||||
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
||||||
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
||||||
@ -108,7 +74,7 @@ add_test(function test_hawk() {
|
|||||||
* Hawk id="123456", ts="1378764955", nonce="QkynqsrS44M=", mac="/C5NsoAs2fVn+d/I5wMfwe2Gr1MZyAJ6pFyDHG4Gf9U="
|
* Hawk id="123456", ts="1378764955", nonce="QkynqsrS44M=", mac="/C5NsoAs2fVn+d/I5wMfwe2Gr1MZyAJ6pFyDHG4Gf9U="
|
||||||
*/
|
*/
|
||||||
|
|
||||||
result = compute(uri_https, method, { credentials: credentials_sha256 });
|
result = await compute(uri_https, method, { credentials });
|
||||||
let fields = result.field.split(" ");
|
let fields = result.field.split(" ");
|
||||||
Assert.equal(fields[0], "Hawk");
|
Assert.equal(fields[0], "Hawk");
|
||||||
Assert.equal(fields[1], 'id="123456",'); // from creds.id
|
Assert.equal(fields[1], 'id="123456",'); // from creds.id
|
||||||
@ -122,13 +88,13 @@ add_test(function test_hawk() {
|
|||||||
Assert.equal(fields[3].length, ('nonce="12345678901=",').length);
|
Assert.equal(fields[3].length, ('nonce="12345678901=",').length);
|
||||||
Assert.equal(result.artifacts.nonce.length, ("12345678901=").length);
|
Assert.equal(result.artifacts.nonce.length, ("12345678901=").length);
|
||||||
|
|
||||||
let result2 = compute(uri_https, method, { credentials: credentials_sha256 });
|
let result2 = await compute(uri_https, method, { credentials });
|
||||||
Assert.notEqual(result.artifacts.nonce, result2.artifacts.nonce);
|
Assert.notEqual(result.artifacts.nonce, result2.artifacts.nonce);
|
||||||
|
|
||||||
/* Using an upper-case URI hostname shouldn't affect the hash. */
|
/* Using an upper-case URI hostname shouldn't affect the hash. */
|
||||||
|
|
||||||
let uri_https_upper = CommonUtils.makeURI("https://EXAMPLE.NET/somewhere/over/the/rainbow");
|
let uri_https_upper = CommonUtils.makeURI("https://EXAMPLE.NET/somewhere/over/the/rainbow");
|
||||||
result = compute(uri_https_upper, method, sha256_opts);
|
result = await compute(uri_https_upper, method, opts);
|
||||||
Assert.equal(result.field,
|
Assert.equal(result.field,
|
||||||
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
||||||
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
||||||
@ -137,7 +103,7 @@ add_test(function test_hawk() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
/* Using a lower-case method name shouldn't affect the hash. */
|
/* Using a lower-case method name shouldn't affect the hash. */
|
||||||
result = compute(uri_https_upper, method.toLowerCase(), sha256_opts);
|
result = await compute(uri_https_upper, method.toLowerCase(), opts);
|
||||||
Assert.equal(result.field,
|
Assert.equal(result.field,
|
||||||
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
||||||
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
||||||
@ -152,12 +118,12 @@ add_test(function test_hawk() {
|
|||||||
* Clients can remember this offset for a while.
|
* Clients can remember this offset for a while.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
result = compute(uri_https, method, { credentials: credentials_sha256,
|
result = await compute(uri_https, method, { credentials,
|
||||||
now: 1378848968650,
|
now: 1378848968650,
|
||||||
});
|
});
|
||||||
Assert.equal(result.artifacts.ts, 1378848968);
|
Assert.equal(result.artifacts.ts, 1378848968);
|
||||||
|
|
||||||
result = compute(uri_https, method, { credentials: credentials_sha256,
|
result = await compute(uri_https, method, { credentials,
|
||||||
now: 1378848968650,
|
now: 1378848968650,
|
||||||
localtimeOffsetMsec: 1000 * 1000,
|
localtimeOffsetMsec: 1000 * 1000,
|
||||||
});
|
});
|
||||||
@ -165,23 +131,23 @@ add_test(function test_hawk() {
|
|||||||
|
|
||||||
/* Search/query-args in URIs should be included in the hash. */
|
/* Search/query-args in URIs should be included in the hash. */
|
||||||
let makeURI = CommonUtils.makeURI;
|
let makeURI = CommonUtils.makeURI;
|
||||||
result = compute(makeURI("http://example.net/path"), method, sha256_opts);
|
result = await compute(makeURI("http://example.net/path"), method, opts);
|
||||||
Assert.equal(result.artifacts.resource, "/path");
|
Assert.equal(result.artifacts.resource, "/path");
|
||||||
Assert.equal(result.artifacts.mac, "WyKHJjWaeYt8aJD+H9UeCWc0Y9C+07ooTmrcrOW4MPI=");
|
Assert.equal(result.artifacts.mac, "WyKHJjWaeYt8aJD+H9UeCWc0Y9C+07ooTmrcrOW4MPI=");
|
||||||
|
|
||||||
result = compute(makeURI("http://example.net/path/"), method, sha256_opts);
|
result = await compute(makeURI("http://example.net/path/"), method, opts);
|
||||||
Assert.equal(result.artifacts.resource, "/path/");
|
Assert.equal(result.artifacts.resource, "/path/");
|
||||||
Assert.equal(result.artifacts.mac, "xAYp2MgZQFvTKJT9u8nsvMjshCRRkuaeYqQbYSFp9Qw=");
|
Assert.equal(result.artifacts.mac, "xAYp2MgZQFvTKJT9u8nsvMjshCRRkuaeYqQbYSFp9Qw=");
|
||||||
|
|
||||||
result = compute(makeURI("http://example.net/path?query=search"), method, sha256_opts);
|
result = await compute(makeURI("http://example.net/path?query=search"), method, opts);
|
||||||
Assert.equal(result.artifacts.resource, "/path?query=search");
|
Assert.equal(result.artifacts.resource, "/path?query=search");
|
||||||
Assert.equal(result.artifacts.mac, "C06a8pip2rA4QkBiosEmC32WcgFcW/R5SQC6kUWyqho=");
|
Assert.equal(result.artifacts.mac, "C06a8pip2rA4QkBiosEmC32WcgFcW/R5SQC6kUWyqho=");
|
||||||
|
|
||||||
/* Test handling of the payload, which is supposed to be a bytestring
|
/* Test handling of the payload, which is supposed to be a bytestring
|
||||||
(String with codepoints from U+0000 to U+00FF, pre-encoded). */
|
(String with codepoints from U+0000 to U+00FF, pre-encoded). */
|
||||||
|
|
||||||
result = compute(makeURI("http://example.net/path"), method,
|
result = await compute(makeURI("http://example.net/path"), method,
|
||||||
{ credentials: credentials_sha256,
|
{ credentials,
|
||||||
ts: 1353809207,
|
ts: 1353809207,
|
||||||
nonce: "Ygvqdz",
|
nonce: "Ygvqdz",
|
||||||
});
|
});
|
||||||
@ -189,8 +155,8 @@ add_test(function test_hawk() {
|
|||||||
Assert.equal(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
|
Assert.equal(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
|
||||||
|
|
||||||
// Empty payload changes nothing.
|
// Empty payload changes nothing.
|
||||||
result = compute(makeURI("http://example.net/path"), method,
|
result = await compute(makeURI("http://example.net/path"), method,
|
||||||
{ credentials: credentials_sha256,
|
{ credentials,
|
||||||
ts: 1353809207,
|
ts: 1353809207,
|
||||||
nonce: "Ygvqdz",
|
nonce: "Ygvqdz",
|
||||||
payload: null,
|
payload: null,
|
||||||
@ -198,8 +164,8 @@ add_test(function test_hawk() {
|
|||||||
Assert.equal(result.artifacts.hash, undefined);
|
Assert.equal(result.artifacts.hash, undefined);
|
||||||
Assert.equal(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
|
Assert.equal(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
|
||||||
|
|
||||||
result = compute(makeURI("http://example.net/path"), method,
|
result = await compute(makeURI("http://example.net/path"), method,
|
||||||
{ credentials: credentials_sha256,
|
{ credentials,
|
||||||
ts: 1353809207,
|
ts: 1353809207,
|
||||||
nonce: "Ygvqdz",
|
nonce: "Ygvqdz",
|
||||||
payload: "hello",
|
payload: "hello",
|
||||||
@ -208,8 +174,8 @@ add_test(function test_hawk() {
|
|||||||
Assert.equal(result.artifacts.mac, "pLsHHzngIn5CTJhWBtBr+BezUFvdd/IadpTp/FYVIRM=");
|
Assert.equal(result.artifacts.mac, "pLsHHzngIn5CTJhWBtBr+BezUFvdd/IadpTp/FYVIRM=");
|
||||||
|
|
||||||
// update, utf-8 payload
|
// update, utf-8 payload
|
||||||
result = compute(makeURI("http://example.net/path"), method,
|
result = await compute(makeURI("http://example.net/path"), method,
|
||||||
{ credentials: credentials_sha256,
|
{ credentials,
|
||||||
ts: 1353809207,
|
ts: 1353809207,
|
||||||
nonce: "Ygvqdz",
|
nonce: "Ygvqdz",
|
||||||
payload: "andré@example.org", // non-ASCII
|
payload: "andré@example.org", // non-ASCII
|
||||||
@ -218,8 +184,8 @@ add_test(function test_hawk() {
|
|||||||
Assert.equal(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
|
Assert.equal(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
|
||||||
|
|
||||||
/* If "hash" is provided, "payload" is ignored. */
|
/* If "hash" is provided, "payload" is ignored. */
|
||||||
result = compute(makeURI("http://example.net/path"), method,
|
result = await compute(makeURI("http://example.net/path"), method,
|
||||||
{ credentials: credentials_sha256,
|
{ credentials,
|
||||||
ts: 1353809207,
|
ts: 1353809207,
|
||||||
nonce: "Ygvqdz",
|
nonce: "Ygvqdz",
|
||||||
hash: "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=",
|
hash: "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=",
|
||||||
@ -229,8 +195,8 @@ add_test(function test_hawk() {
|
|||||||
Assert.equal(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
|
Assert.equal(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
|
||||||
|
|
||||||
// the payload "hash" is also non-urlsafe base64 (+/)
|
// the payload "hash" is also non-urlsafe base64 (+/)
|
||||||
result = compute(makeURI("http://example.net/path"), method,
|
result = await compute(makeURI("http://example.net/path"), method,
|
||||||
{ credentials: credentials_sha256,
|
{ credentials,
|
||||||
ts: 1353809207,
|
ts: 1353809207,
|
||||||
nonce: "Ygvqdz",
|
nonce: "Ygvqdz",
|
||||||
payload: "something else",
|
payload: "something else",
|
||||||
@ -243,16 +209,16 @@ add_test(function test_hawk() {
|
|||||||
* punycode was a bad joke that got out of the lab and into a spec.
|
* punycode was a bad joke that got out of the lab and into a spec.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
result = compute(makeURI("http://ëxample.net/path"), method,
|
result = await compute(makeURI("http://ëxample.net/path"), method,
|
||||||
{ credentials: credentials_sha256,
|
{ credentials,
|
||||||
ts: 1353809207,
|
ts: 1353809207,
|
||||||
nonce: "Ygvqdz",
|
nonce: "Ygvqdz",
|
||||||
});
|
});
|
||||||
Assert.equal(result.artifacts.mac, "pILiHl1q8bbNQIdaaLwAFyaFmDU70MGehFuCs3AA5M0=");
|
Assert.equal(result.artifacts.mac, "pILiHl1q8bbNQIdaaLwAFyaFmDU70MGehFuCs3AA5M0=");
|
||||||
Assert.equal(result.artifacts.host, "xn--xample-ova.net");
|
Assert.equal(result.artifacts.host, "xn--xample-ova.net");
|
||||||
|
|
||||||
result = compute(makeURI("http://example.net/path"), method,
|
result = await compute(makeURI("http://example.net/path"), method,
|
||||||
{ credentials: credentials_sha256,
|
{ credentials,
|
||||||
ts: 1353809207,
|
ts: 1353809207,
|
||||||
nonce: "Ygvqdz",
|
nonce: "Ygvqdz",
|
||||||
ext: "backslash=\\ quote=\" EOF",
|
ext: "backslash=\\ quote=\" EOF",
|
||||||
@ -260,8 +226,8 @@ add_test(function test_hawk() {
|
|||||||
Assert.equal(result.artifacts.mac, "BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc=");
|
Assert.equal(result.artifacts.mac, "BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc=");
|
||||||
Assert.equal(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="backslash=\\\\ quote=\\\" EOF", mac="BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc="');
|
Assert.equal(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="backslash=\\\\ quote=\\\" EOF", mac="BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc="');
|
||||||
|
|
||||||
result = compute(makeURI("http://example.net:1234/path"), method,
|
result = await compute(makeURI("http://example.net:1234/path"), method,
|
||||||
{ credentials: credentials_sha256,
|
{ credentials,
|
||||||
ts: 1353809207,
|
ts: 1353809207,
|
||||||
nonce: "Ygvqdz",
|
nonce: "Ygvqdz",
|
||||||
});
|
});
|
||||||
@ -276,15 +242,13 @@ add_test(function test_hawk() {
|
|||||||
* updated to do what we do here, so port="01234" should get the same hash
|
* updated to do what we do here, so port="01234" should get the same hash
|
||||||
* as port="1234".
|
* as port="1234".
|
||||||
*/
|
*/
|
||||||
result = compute(makeURI("http://example.net:01234/path"), method,
|
result = await compute(makeURI("http://example.net:01234/path"), method,
|
||||||
{ credentials: credentials_sha256,
|
{ credentials,
|
||||||
ts: 1353809207,
|
ts: 1353809207,
|
||||||
nonce: "Ygvqdz",
|
nonce: "Ygvqdz",
|
||||||
});
|
});
|
||||||
Assert.equal(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
|
Assert.equal(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
|
||||||
Assert.equal(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
|
Assert.equal(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
|
||||||
|
|
||||||
run_next_test();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,152 +0,0 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
|
||||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
||||||
|
|
||||||
ChromeUtils.import("resource://services-common/utils.js");
|
|
||||||
ChromeUtils.import("resource://services-crypto/utils.js");
|
|
||||||
|
|
||||||
// Test vectors from RFC 5869
|
|
||||||
|
|
||||||
// Test case 1
|
|
||||||
|
|
||||||
var tc1 = {
|
|
||||||
IKM: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
|
|
||||||
salt: "000102030405060708090a0b0c",
|
|
||||||
info: "f0f1f2f3f4f5f6f7f8f9",
|
|
||||||
L: 42,
|
|
||||||
PRK: "077709362c2e32df0ddc3f0dc47bba63" +
|
|
||||||
"90b6c73bb50f9c3122ec844ad7c2b3e5",
|
|
||||||
OKM: "3cb25f25faacd57a90434f64d0362f2a" +
|
|
||||||
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf" +
|
|
||||||
"34007208d5b887185865",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Test case 2
|
|
||||||
|
|
||||||
var tc2 = {
|
|
||||||
IKM: "000102030405060708090a0b0c0d0e0f" +
|
|
||||||
"101112131415161718191a1b1c1d1e1f" +
|
|
||||||
"202122232425262728292a2b2c2d2e2f" +
|
|
||||||
"303132333435363738393a3b3c3d3e3f" +
|
|
||||||
"404142434445464748494a4b4c4d4e4f",
|
|
||||||
salt: "606162636465666768696a6b6c6d6e6f" +
|
|
||||||
"707172737475767778797a7b7c7d7e7f" +
|
|
||||||
"808182838485868788898a8b8c8d8e8f" +
|
|
||||||
"909192939495969798999a9b9c9d9e9f" +
|
|
||||||
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
|
|
||||||
info: "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" +
|
|
||||||
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
|
|
||||||
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" +
|
|
||||||
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
|
|
||||||
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
|
|
||||||
L: 82,
|
|
||||||
PRK: "06a6b88c5853361a06104c9ceb35b45c" +
|
|
||||||
"ef760014904671014a193f40c15fc244",
|
|
||||||
OKM: "b11e398dc80327a1c8e7f78c596a4934" +
|
|
||||||
"4f012eda2d4efad8a050cc4c19afa97c" +
|
|
||||||
"59045a99cac7827271cb41c65e590e09" +
|
|
||||||
"da3275600c2f09b8367793a9aca3db71" +
|
|
||||||
"cc30c58179ec3e87c14c01d5c1f3434f" +
|
|
||||||
"1d87",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Test case 3
|
|
||||||
|
|
||||||
var tc3 = {
|
|
||||||
IKM: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
|
|
||||||
salt: "",
|
|
||||||
info: "",
|
|
||||||
L: 42,
|
|
||||||
PRK: "19ef24a32c717b167f33a91d6f648bdf" +
|
|
||||||
"96596776afdb6377ac434c1c293ccb04",
|
|
||||||
OKM: "8da4e775a563c18f715f802a063c5a31" +
|
|
||||||
"b8a11f5c5ee1879ec3454e5f3c738d2d" +
|
|
||||||
"9d201395faa4b61a96c8",
|
|
||||||
};
|
|
||||||
|
|
||||||
function sha256HMAC(message, key) {
|
|
||||||
let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
|
|
||||||
return CryptoUtils.digestBytes(message, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _hexToString(hex) {
|
|
||||||
let ret = "";
|
|
||||||
if (hex.length % 2 != 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < hex.length; i += 2) {
|
|
||||||
let cur = hex[i] + hex[i + 1];
|
|
||||||
ret += String.fromCharCode(parseInt(cur, 16));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extract_hex(salt, ikm) {
|
|
||||||
salt = _hexToString(salt);
|
|
||||||
ikm = _hexToString(ikm);
|
|
||||||
return CommonUtils.bytesAsHex(sha256HMAC(ikm, CryptoUtils.makeHMACKey(salt)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function expand_hex(prk, info, len) {
|
|
||||||
prk = _hexToString(prk);
|
|
||||||
info = _hexToString(info);
|
|
||||||
return CommonUtils.bytesAsHex(CryptoUtils.hkdfExpand(prk, info, len));
|
|
||||||
}
|
|
||||||
|
|
||||||
function hkdf_hex(ikm, salt, info, len) {
|
|
||||||
ikm = _hexToString(ikm);
|
|
||||||
if (salt)
|
|
||||||
salt = _hexToString(salt);
|
|
||||||
info = _hexToString(info);
|
|
||||||
return CommonUtils.bytesAsHex(CryptoUtils.hkdf(ikm, salt, info, len));
|
|
||||||
}
|
|
||||||
|
|
||||||
// In bug 1437416 we thought we supplied a default for the salt but
|
|
||||||
// actually ended up calling the platform c++ code with undefined as the
|
|
||||||
// salt - which still ended up doing the right thing. Let's try and
|
|
||||||
// codify that behaviour.
|
|
||||||
let hkdf_tc1 = {
|
|
||||||
ikm: "foo",
|
|
||||||
info: "bar",
|
|
||||||
salt: undefined,
|
|
||||||
len: 64,
|
|
||||||
// As all inputs are known, we can pre-calculate the expected result:
|
|
||||||
// >>> tokenlib.utils.HKDF("foo", None, "bar", 64).encode("hex")
|
|
||||||
// 'f037f3ab189f485d0d93249f432def681a0305e39ef85f810e2f0b74d2078861fbd34318934b49de822c6148c8bb0785613e4b01176b47634e25eecd5e94ff3b'
|
|
||||||
result: "f037f3ab189f485d0d93249f432def681a0305e39ef85f810e2f0b74d2078861fbd34318934b49de822c6148c8bb0785613e4b01176b47634e25eecd5e94ff3b",
|
|
||||||
};
|
|
||||||
|
|
||||||
// same inputs, but this time with the default salt explicitly defined.
|
|
||||||
// should give the same result.
|
|
||||||
let hkdf_tc2 = {
|
|
||||||
ikm: "foo",
|
|
||||||
info: "bar",
|
|
||||||
salt: String.fromCharCode(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
||||||
len: 64,
|
|
||||||
result: hkdf_tc1.result,
|
|
||||||
};
|
|
||||||
|
|
||||||
function run_test() {
|
|
||||||
_("Verifying Test Case 1");
|
|
||||||
Assert.equal(extract_hex(tc1.salt, tc1.IKM), tc1.PRK);
|
|
||||||
Assert.equal(expand_hex(tc1.PRK, tc1.info, tc1.L), tc1.OKM);
|
|
||||||
Assert.equal(hkdf_hex(tc1.IKM, tc1.salt, tc1.info, tc1.L), tc1.OKM);
|
|
||||||
|
|
||||||
_("Verifying Test Case 2");
|
|
||||||
Assert.equal(extract_hex(tc2.salt, tc2.IKM), tc2.PRK);
|
|
||||||
Assert.equal(expand_hex(tc2.PRK, tc2.info, tc2.L), tc2.OKM);
|
|
||||||
Assert.equal(hkdf_hex(tc2.IKM, tc2.salt, tc2.info, tc2.L), tc2.OKM);
|
|
||||||
|
|
||||||
_("Verifying Test Case 3");
|
|
||||||
Assert.equal(extract_hex(tc3.salt, tc3.IKM), tc3.PRK);
|
|
||||||
Assert.equal(expand_hex(tc3.PRK, tc3.info, tc3.L), tc3.OKM);
|
|
||||||
Assert.equal(hkdf_hex(tc3.IKM, tc3.salt, tc3.info, tc3.L), tc3.OKM);
|
|
||||||
Assert.equal(hkdf_hex(tc3.IKM, undefined, tc3.info, tc3.L), tc3.OKM);
|
|
||||||
|
|
||||||
_("Verifying hkdf semantics");
|
|
||||||
for (let tc of [hkdf_tc1, hkdf_tc2]) {
|
|
||||||
let result = CommonUtils.bytesAsHex(CryptoUtils.hkdf(tc.ikm, tc.salt, tc.info, tc.len));
|
|
||||||
Assert.equal(result, tc.result);
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,13 +5,12 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||||||
ChromeUtils.import("resource://services-common/utils.js");
|
ChromeUtils.import("resource://services-common/utils.js");
|
||||||
ChromeUtils.import("resource://services-crypto/utils.js");
|
ChromeUtils.import("resource://services-crypto/utils.js");
|
||||||
|
|
||||||
function run_test() {
|
add_test(function setup() {
|
||||||
initTestLogging();
|
initTestLogging();
|
||||||
|
|
||||||
run_next_test();
|
run_next_test();
|
||||||
}
|
});
|
||||||
|
|
||||||
add_test(function test_sha1() {
|
add_task(async function test_sha1() {
|
||||||
_("Ensure HTTP MAC SHA1 generation works as expected.");
|
_("Ensure HTTP MAC SHA1 generation works as expected.");
|
||||||
|
|
||||||
let id = "vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7";
|
let id = "vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7";
|
||||||
@ -21,7 +20,7 @@ add_test(function test_sha1() {
|
|||||||
let nonce = "wGX71";
|
let nonce = "wGX71";
|
||||||
let uri = CommonUtils.makeURI("http://10.250.2.176/alias/");
|
let uri = CommonUtils.makeURI("http://10.250.2.176/alias/");
|
||||||
|
|
||||||
let result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
|
let result = await CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
|
||||||
{ts, nonce});
|
{ts, nonce});
|
||||||
|
|
||||||
Assert.equal(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck=");
|
Assert.equal(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck=");
|
||||||
@ -32,18 +31,16 @@ add_test(function test_sha1() {
|
|||||||
|
|
||||||
let ext = "EXTRA DATA; foo,bar=1";
|
let ext = "EXTRA DATA; foo,bar=1";
|
||||||
|
|
||||||
result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
|
result = await CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
|
||||||
{ts, nonce, ext});
|
{ts, nonce, ext});
|
||||||
Assert.equal(btoa(result.mac), "bNf4Fnt5k6DnhmyipLPkuZroH68=");
|
Assert.equal(btoa(result.mac), "bNf4Fnt5k6DnhmyipLPkuZroH68=");
|
||||||
Assert.equal(result.getHeader(),
|
Assert.equal(result.getHeader(),
|
||||||
'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
|
'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
|
||||||
'ts="1329181221", nonce="wGX71", mac="bNf4Fnt5k6DnhmyipLPkuZroH68=", ' +
|
'ts="1329181221", nonce="wGX71", mac="bNf4Fnt5k6DnhmyipLPkuZroH68=", ' +
|
||||||
'ext="EXTRA DATA; foo,bar=1"');
|
'ext="EXTRA DATA; foo,bar=1"');
|
||||||
|
|
||||||
run_next_test();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
add_test(function test_nonce_length() {
|
add_task(async function test_nonce_length() {
|
||||||
_("Ensure custom nonce lengths are honoured.");
|
_("Ensure custom nonce lengths are honoured.");
|
||||||
|
|
||||||
function get_mac(length) {
|
function get_mac(length) {
|
||||||
@ -53,17 +50,15 @@ add_test(function test_nonce_length() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = get_mac(12);
|
let result = await get_mac(12);
|
||||||
Assert.equal(12, atob(result.nonce).length);
|
Assert.equal(12, atob(result.nonce).length);
|
||||||
|
|
||||||
result = get_mac(2);
|
result = await get_mac(2);
|
||||||
Assert.equal(2, atob(result.nonce).length);
|
Assert.equal(2, atob(result.nonce).length);
|
||||||
|
|
||||||
result = get_mac(0);
|
result = await get_mac(0);
|
||||||
Assert.equal(8, atob(result.nonce).length);
|
Assert.equal(8, atob(result.nonce).length);
|
||||||
|
|
||||||
result = get_mac(-1);
|
result = await get_mac(-1);
|
||||||
Assert.equal(8, atob(result.nonce).length);
|
Assert.equal(8, atob(result.nonce).length);
|
||||||
|
|
||||||
run_next_test();
|
|
||||||
});
|
});
|
||||||
|
@ -1,156 +0,0 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
|
||||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
||||||
|
|
||||||
ChromeUtils.import("resource://services-crypto/utils.js");
|
|
||||||
ChromeUtils.import("resource://services-common/utils.js");
|
|
||||||
|
|
||||||
var {bytesAsHex: b2h} = CommonUtils;
|
|
||||||
|
|
||||||
add_task(function test_pbkdf2() {
|
|
||||||
let symmKey16 = CryptoUtils.pbkdf2Generate("secret phrase", "DNXPzPpiwn", 4096, 16);
|
|
||||||
Assert.equal(symmKey16.length, 16);
|
|
||||||
Assert.equal(btoa(symmKey16), "d2zG0d2cBfXnRwMUGyMwyg==");
|
|
||||||
Assert.equal(CommonUtils.encodeBase32(symmKey16), "O5WMNUO5TQC7LZ2HAMKBWIZQZI======");
|
|
||||||
let symmKey32 = CryptoUtils.pbkdf2Generate("passphrase", "salt", 4096, 32);
|
|
||||||
Assert.equal(symmKey32.length, 32);
|
|
||||||
});
|
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc6070
|
|
||||||
// PBKDF2 HMAC-SHA1 Test Vectors
|
|
||||||
add_task(function test_pbkdf2_hmac_sha1() {
|
|
||||||
let pbkdf2 = CryptoUtils.pbkdf2Generate;
|
|
||||||
let vectors = [
|
|
||||||
{P: "password", // (8 octets)
|
|
||||||
S: "salt", // (4 octets)
|
|
||||||
c: 1,
|
|
||||||
dkLen: 20,
|
|
||||||
DK: h("0c 60 c8 0f 96 1f 0e 71" +
|
|
||||||
"f3 a9 b5 24 af 60 12 06" +
|
|
||||||
"2f e0 37 a6"), // (20 octets)
|
|
||||||
},
|
|
||||||
|
|
||||||
{P: "password", // (8 octets)
|
|
||||||
S: "salt", // (4 octets)
|
|
||||||
c: 2,
|
|
||||||
dkLen: 20,
|
|
||||||
DK: h("ea 6c 01 4d c7 2d 6f 8c" +
|
|
||||||
"cd 1e d9 2a ce 1d 41 f0" +
|
|
||||||
"d8 de 89 57"), // (20 octets)
|
|
||||||
},
|
|
||||||
|
|
||||||
{P: "password", // (8 octets)
|
|
||||||
S: "salt", // (4 octets)
|
|
||||||
c: 4096,
|
|
||||||
dkLen: 20,
|
|
||||||
DK: h("4b 00 79 01 b7 65 48 9a" +
|
|
||||||
"be ad 49 d9 26 f7 21 d0" +
|
|
||||||
"65 a4 29 c1"), // (20 octets)
|
|
||||||
},
|
|
||||||
|
|
||||||
// XXX Uncomment the following test after Bug 968567 lands
|
|
||||||
//
|
|
||||||
// XXX As it stands, I estimate that the CryptoUtils implementation will
|
|
||||||
// take approximately 16 hours in my 2.3GHz MacBook to perform this many
|
|
||||||
// rounds.
|
|
||||||
//
|
|
||||||
// {P: "password", // (8 octets)
|
|
||||||
// S: "salt" // (4 octets)
|
|
||||||
// c: 16777216,
|
|
||||||
// dkLen = 20,
|
|
||||||
// DK: h("ee fe 3d 61 cd 4d a4 e4"+
|
|
||||||
// "e9 94 5b 3d 6b a2 15 8c"+
|
|
||||||
// "26 34 e9 84"), // (20 octets)
|
|
||||||
// },
|
|
||||||
|
|
||||||
{P: "passwordPASSWORDpassword", // (24 octets)
|
|
||||||
S: "saltSALTsaltSALTsaltSALTsaltSALTsalt", // (36 octets)
|
|
||||||
c: 4096,
|
|
||||||
dkLen: 25,
|
|
||||||
DK: h("3d 2e ec 4f e4 1c 84 9b" +
|
|
||||||
"80 c8 d8 36 62 c0 e4 4a" +
|
|
||||||
"8b 29 1a 96 4c f2 f0 70" +
|
|
||||||
"38"), // (25 octets)
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
{P: "pass\0word", // (9 octets)
|
|
||||||
S: "sa\0lt", // (5 octets)
|
|
||||||
c: 4096,
|
|
||||||
dkLen: 16,
|
|
||||||
DK: h("56 fa 6a a7 55 48 09 9d" +
|
|
||||||
"cc 37 d7 f0 34 25 e0 c3"), // (16 octets)
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (let v of vectors) {
|
|
||||||
Assert.equal(v.DK, b2h(pbkdf2(v.P, v.S, v.c, v.dkLen)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// I can't find any normative ietf test vectors for pbkdf2 hmac-sha256.
|
|
||||||
// The following vectors are derived with the same inputs as above (the sha1
|
|
||||||
// test). Results verified by users here:
|
|
||||||
// https://stackoverflow.com/questions/5130513/pbkdf2-hmac-sha2-test-vectors
|
|
||||||
add_task(function test_pbkdf2_hmac_sha256() {
|
|
||||||
let pbkdf2 = CryptoUtils.pbkdf2Generate;
|
|
||||||
let vectors = [
|
|
||||||
{P: "password", // (8 octets)
|
|
||||||
S: "salt", // (4 octets)
|
|
||||||
c: 1,
|
|
||||||
dkLen: 32,
|
|
||||||
DK: h("12 0f b6 cf fc f8 b3 2c" +
|
|
||||||
"43 e7 22 52 56 c4 f8 37" +
|
|
||||||
"a8 65 48 c9 2c cc 35 48" +
|
|
||||||
"08 05 98 7c b7 0b e1 7b"), // (32 octets)
|
|
||||||
},
|
|
||||||
|
|
||||||
{P: "password", // (8 octets)
|
|
||||||
S: "salt", // (4 octets)
|
|
||||||
c: 2,
|
|
||||||
dkLen: 32,
|
|
||||||
DK: h("ae 4d 0c 95 af 6b 46 d3" +
|
|
||||||
"2d 0a df f9 28 f0 6d d0" +
|
|
||||||
"2a 30 3f 8e f3 c2 51 df" +
|
|
||||||
"d6 e2 d8 5a 95 47 4c 43"), // (32 octets)
|
|
||||||
},
|
|
||||||
|
|
||||||
{P: "password", // (8 octets)
|
|
||||||
S: "salt", // (4 octets)
|
|
||||||
c: 4096,
|
|
||||||
dkLen: 32,
|
|
||||||
DK: h("c5 e4 78 d5 92 88 c8 41" +
|
|
||||||
"aa 53 0d b6 84 5c 4c 8d" +
|
|
||||||
"96 28 93 a0 01 ce 4e 11" +
|
|
||||||
"a4 96 38 73 aa 98 13 4a"), // (32 octets)
|
|
||||||
},
|
|
||||||
|
|
||||||
{P: "passwordPASSWORDpassword", // (24 octets)
|
|
||||||
S: "saltSALTsaltSALTsaltSALTsaltSALTsalt", // (36 octets)
|
|
||||||
c: 4096,
|
|
||||||
dkLen: 40,
|
|
||||||
DK: h("34 8c 89 db cb d3 2b 2f" +
|
|
||||||
"32 d8 14 b8 11 6e 84 cf" +
|
|
||||||
"2b 17 34 7e bc 18 00 18" +
|
|
||||||
"1c 4e 2a 1f b8 dd 53 e1" +
|
|
||||||
"c6 35 51 8c 7d ac 47 e9"), // (40 octets)
|
|
||||||
},
|
|
||||||
|
|
||||||
{P: "pass\0word", // (9 octets)
|
|
||||||
S: "sa\0lt", // (5 octets)
|
|
||||||
c: 4096,
|
|
||||||
dkLen: 16,
|
|
||||||
DK: h("89 b6 9d 05 16 f8 29 89" +
|
|
||||||
"3c 69 62 26 65 0a 86 87"), // (16 octets)
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (let v of vectors) {
|
|
||||||
Assert.equal(v.DK,
|
|
||||||
b2h(pbkdf2(v.P, v.S, v.c, v.dkLen, Ci.nsICryptoHMAC.SHA256, 32)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// turn formatted test vectors into normal hex strings
|
|
||||||
function h(hexStr) {
|
|
||||||
return hexStr.replace(/\s+/g, "");
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
|
||||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
||||||
|
|
||||||
_("Make sure sha1 digests works with various messages");
|
|
||||||
|
|
||||||
ChromeUtils.import("resource://services-crypto/utils.js");
|
|
||||||
|
|
||||||
function run_test() {
|
|
||||||
let mes1 = "hello";
|
|
||||||
let mes2 = "world";
|
|
||||||
|
|
||||||
let dig0 = CryptoUtils.UTF8AndSHA1(mes1);
|
|
||||||
Assert.equal(dig0,
|
|
||||||
"\xaa\xf4\xc6\x1d\xdc\xc5\xe8\xa2\xda\xbe\xde\x0f\x3b\x48\x2c\xd9\xae\xa9\x43\x4d");
|
|
||||||
|
|
||||||
_("Make sure right sha1 digests are generated");
|
|
||||||
let dig1 = CryptoUtils.sha1(mes1);
|
|
||||||
Assert.equal(dig1, "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
|
|
||||||
let dig2 = CryptoUtils.sha1(mes2);
|
|
||||||
Assert.equal(dig2, "7c211433f02071597741e6ff5a8ea34789abbf43");
|
|
||||||
let dig12 = CryptoUtils.sha1(mes1 + mes2);
|
|
||||||
Assert.equal(dig12, "6adfb183a4a2c94a2f92dab5ade762a47889a5a1");
|
|
||||||
let dig21 = CryptoUtils.sha1(mes2 + mes1);
|
|
||||||
Assert.equal(dig21, "5715790a892990382d98858c4aa38d0617151575");
|
|
||||||
|
|
||||||
_("Repeated sha1s shouldn't change the digest");
|
|
||||||
Assert.equal(CryptoUtils.sha1(mes1), dig1);
|
|
||||||
Assert.equal(CryptoUtils.sha1(mes2), dig2);
|
|
||||||
Assert.equal(CryptoUtils.sha1(mes1 + mes2), dig12);
|
|
||||||
Assert.equal(CryptoUtils.sha1(mes2 + mes1), dig21);
|
|
||||||
|
|
||||||
_("Nested sha1 should work just fine");
|
|
||||||
let nest1 = CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(mes1)))));
|
|
||||||
Assert.equal(nest1, "23f340d0cff31e299158b3181b6bcc7e8c7f985a");
|
|
||||||
let nest2 = CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(mes2)))));
|
|
||||||
Assert.equal(nest2, "1f6453867e3fb9876ae429918a64cdb8dc5ff2d0");
|
|
||||||
}
|
|
@ -16,7 +16,4 @@ skip-if = (os == "android" || appname == 'thunderbird')
|
|||||||
skip-if = (os == "android" || appname == 'thunderbird')
|
skip-if = (os == "android" || appname == 'thunderbird')
|
||||||
|
|
||||||
[test_utils_hawk.js]
|
[test_utils_hawk.js]
|
||||||
[test_utils_hkdfExpand.js]
|
|
||||||
[test_utils_httpmac.js]
|
[test_utils_httpmac.js]
|
||||||
[test_utils_pbkdf2.js]
|
|
||||||
[test_utils_sha1.js]
|
|
||||||
|
@ -23,8 +23,6 @@ const PBKDF2_ROUNDS = 1000;
|
|||||||
const STRETCHED_PW_LENGTH_BYTES = 32;
|
const STRETCHED_PW_LENGTH_BYTES = 32;
|
||||||
const HKDF_SALT = CommonUtils.hexToBytes("00");
|
const HKDF_SALT = CommonUtils.hexToBytes("00");
|
||||||
const HKDF_LENGTH = 32;
|
const HKDF_LENGTH = 32;
|
||||||
const HMAC_ALGORITHM = Ci.nsICryptoHMAC.SHA256;
|
|
||||||
const HMAC_LENGTH = 32;
|
|
||||||
|
|
||||||
// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
|
// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
|
||||||
// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
|
// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
|
||||||
@ -52,8 +50,6 @@ var Credentials = Object.freeze({
|
|||||||
STRETCHED_PW_LENGTH_BYTES,
|
STRETCHED_PW_LENGTH_BYTES,
|
||||||
HKDF_SALT,
|
HKDF_SALT,
|
||||||
HKDF_LENGTH,
|
HKDF_LENGTH,
|
||||||
HMAC_ALGORITHM,
|
|
||||||
HMAC_LENGTH,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,8 +92,6 @@ var Credentials = Object.freeze({
|
|||||||
|
|
||||||
let hkdfSalt = options.hkdfSalt || HKDF_SALT;
|
let hkdfSalt = options.hkdfSalt || HKDF_SALT;
|
||||||
let hkdfLength = options.hkdfLength || HKDF_LENGTH;
|
let hkdfLength = options.hkdfLength || HKDF_LENGTH;
|
||||||
let hmacLength = options.hmacLength || HMAC_LENGTH;
|
|
||||||
let hmacAlgorithm = options.hmacAlgorithm || HMAC_ALGORITHM;
|
|
||||||
let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
|
let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
|
||||||
let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;
|
let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;
|
||||||
|
|
||||||
@ -106,18 +100,18 @@ var Credentials = Object.freeze({
|
|||||||
let password = CommonUtils.encodeUTF8(passwordInput);
|
let password = CommonUtils.encodeUTF8(passwordInput);
|
||||||
let salt = this.keyWordExtended("quickStretch", emailInput);
|
let salt = this.keyWordExtended("quickStretch", emailInput);
|
||||||
|
|
||||||
let runnable = () => {
|
let runnable = async () => {
|
||||||
let start = Date.now();
|
let start = Date.now();
|
||||||
let quickStretchedPW = CryptoUtils.pbkdf2Generate(
|
let quickStretchedPW = await CryptoUtils.pbkdf2Generate(
|
||||||
password, salt, pbkdf2Rounds, stretchedPWLength, hmacAlgorithm, hmacLength);
|
password, salt, pbkdf2Rounds, stretchedPWLength);
|
||||||
|
|
||||||
result.quickStretchedPW = quickStretchedPW;
|
result.quickStretchedPW = quickStretchedPW;
|
||||||
|
|
||||||
result.authPW =
|
result.authPW =
|
||||||
CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength);
|
await CryptoUtils.hkdfLegacy(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength);
|
||||||
|
|
||||||
result.unwrapBKey =
|
result.unwrapBKey =
|
||||||
CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength);
|
await CryptoUtils.hkdfLegacy(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength);
|
||||||
|
|
||||||
log.debug("Credentials set up after " + (Date.now() - start) + " ms");
|
log.debug("Credentials set up after " + (Date.now() - start) + " ms");
|
||||||
resolve(result);
|
resolve(result);
|
||||||
|
@ -971,7 +971,7 @@ FxAccountsInternal.prototype = {
|
|||||||
const {uid, kB} = userData;
|
const {uid, kB} = userData;
|
||||||
await this.updateUserAccountData({
|
await this.updateUserAccountData({
|
||||||
uid,
|
uid,
|
||||||
...this._deriveKeys(uid, CommonUtils.hexToBytes(kB)),
|
...(await this._deriveKeys(uid, CommonUtils.hexToBytes(kB))),
|
||||||
kA: null, // Remove kA and kB from storage.
|
kA: null, // Remove kA and kB from storage.
|
||||||
kB: null,
|
kB: null,
|
||||||
});
|
});
|
||||||
@ -1038,7 +1038,7 @@ FxAccountsInternal.prototype = {
|
|||||||
log.debug("kBbytes: " + kBbytes);
|
log.debug("kBbytes: " + kBbytes);
|
||||||
}
|
}
|
||||||
let updateData = {
|
let updateData = {
|
||||||
...this._deriveKeys(data.uid, kBbytes),
|
...(await this._deriveKeys(data.uid, kBbytes)),
|
||||||
keyFetchToken: null, // null values cause the item to be removed.
|
keyFetchToken: null, // null values cause the item to be removed.
|
||||||
unwrapBKey: null,
|
unwrapBKey: null,
|
||||||
};
|
};
|
||||||
@ -1062,11 +1062,11 @@ FxAccountsInternal.prototype = {
|
|||||||
return currentState.resolve(data);
|
return currentState.resolve(data);
|
||||||
},
|
},
|
||||||
|
|
||||||
_deriveKeys(uid, kBbytes) {
|
async _deriveKeys(uid, kBbytes) {
|
||||||
return {
|
return {
|
||||||
kSync: CommonUtils.bytesAsHex(this._deriveSyncKey(kBbytes)),
|
kSync: CommonUtils.bytesAsHex((await this._deriveSyncKey(kBbytes))),
|
||||||
kXCS: CommonUtils.bytesAsHex(this._deriveXClientState(kBbytes)),
|
kXCS: CommonUtils.bytesAsHex(this._deriveXClientState(kBbytes)),
|
||||||
kExtSync: CommonUtils.bytesAsHex(this._deriveWebExtSyncStoreKey(kBbytes)),
|
kExtSync: CommonUtils.bytesAsHex((await this._deriveWebExtSyncStoreKey(kBbytes))),
|
||||||
kExtKbHash: CommonUtils.bytesAsHex(this._deriveWebExtKbHash(uid, kBbytes)),
|
kExtKbHash: CommonUtils.bytesAsHex(this._deriveWebExtKbHash(uid, kBbytes)),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -1077,7 +1077,7 @@ FxAccountsInternal.prototype = {
|
|||||||
* @returns HKDF(kB, undefined, "identity.mozilla.com/picl/v1/oldsync", 64)
|
* @returns HKDF(kB, undefined, "identity.mozilla.com/picl/v1/oldsync", 64)
|
||||||
*/
|
*/
|
||||||
_deriveSyncKey(kBbytes) {
|
_deriveSyncKey(kBbytes) {
|
||||||
return CryptoUtils.hkdf(kBbytes, undefined,
|
return CryptoUtils.hkdfLegacy(kBbytes, undefined,
|
||||||
"identity.mozilla.com/picl/v1/oldsync", 2 * 32);
|
"identity.mozilla.com/picl/v1/oldsync", 2 * 32);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1087,7 +1087,7 @@ FxAccountsInternal.prototype = {
|
|||||||
* @returns HKDF(kB, undefined, "identity.mozilla.com/picl/v1/chrome.storage.sync", 64)
|
* @returns HKDF(kB, undefined, "identity.mozilla.com/picl/v1/chrome.storage.sync", 64)
|
||||||
*/
|
*/
|
||||||
_deriveWebExtSyncStoreKey(kBbytes) {
|
_deriveWebExtSyncStoreKey(kBbytes) {
|
||||||
return CryptoUtils.hkdf(kBbytes, undefined,
|
return CryptoUtils.hkdfLegacy(kBbytes, undefined,
|
||||||
"identity.mozilla.com/picl/v1/chrome.storage.sync",
|
"identity.mozilla.com/picl/v1/chrome.storage.sync",
|
||||||
2 * 32);
|
2 * 32);
|
||||||
},
|
},
|
||||||
|
@ -187,9 +187,9 @@ this.FxAccountsClient.prototype = {
|
|||||||
* @return Promise
|
* @return Promise
|
||||||
* Resolves with a boolean indicating if the session is still valid
|
* Resolves with a boolean indicating if the session is still valid
|
||||||
*/
|
*/
|
||||||
sessionStatus(sessionTokenHex) {
|
async sessionStatus(sessionTokenHex) {
|
||||||
return this._request("/session/status", "GET",
|
const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||||
deriveHawkCredentials(sessionTokenHex, "sessionToken")).then(
|
return this._request("/session/status", "GET", credentials).then(
|
||||||
() => Promise.resolve(true),
|
() => Promise.resolve(true),
|
||||||
error => {
|
error => {
|
||||||
if (isInvalidTokenError(error)) {
|
if (isInvalidTokenError(error)) {
|
||||||
@ -208,13 +208,13 @@ this.FxAccountsClient.prototype = {
|
|||||||
* The session token encoded in hex
|
* The session token encoded in hex
|
||||||
* @return Promise
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
signOut(sessionTokenHex, options = {}) {
|
async signOut(sessionTokenHex, options = {}) {
|
||||||
|
const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||||
let path = "/session/destroy";
|
let path = "/session/destroy";
|
||||||
if (options.service) {
|
if (options.service) {
|
||||||
path += "?service=" + encodeURIComponent(options.service);
|
path += "?service=" + encodeURIComponent(options.service);
|
||||||
}
|
}
|
||||||
return this._request(path, "POST",
|
return this._request(path, "POST", credentials);
|
||||||
deriveHawkCredentials(sessionTokenHex, "sessionToken"));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -224,14 +224,14 @@ this.FxAccountsClient.prototype = {
|
|||||||
* The current session token encoded in hex
|
* The current session token encoded in hex
|
||||||
* @return Promise
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
recoveryEmailStatus(sessionTokenHex, options = {}) {
|
async recoveryEmailStatus(sessionTokenHex, options = {}) {
|
||||||
|
const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||||
let path = "/recovery_email/status";
|
let path = "/recovery_email/status";
|
||||||
if (options.reason) {
|
if (options.reason) {
|
||||||
path += "?reason=" + encodeURIComponent(options.reason);
|
path += "?reason=" + encodeURIComponent(options.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._request(path, "GET",
|
return this._request(path, "GET", credentials);
|
||||||
deriveHawkCredentials(sessionTokenHex, "sessionToken"));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -241,9 +241,9 @@ this.FxAccountsClient.prototype = {
|
|||||||
* The current token encoded in hex
|
* The current token encoded in hex
|
||||||
* @return Promise
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
resendVerificationEmail(sessionTokenHex) {
|
async resendVerificationEmail(sessionTokenHex) {
|
||||||
return this._request("/recovery_email/resend_code", "POST",
|
const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||||
deriveHawkCredentials(sessionTokenHex, "sessionToken"));
|
return this._request("/recovery_email/resend_code", "POST", credentials);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -259,37 +259,36 @@ this.FxAccountsClient.prototype = {
|
|||||||
* user's password (bytes)
|
* user's password (bytes)
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
accountKeys(keyFetchTokenHex) {
|
async accountKeys(keyFetchTokenHex) {
|
||||||
let creds = deriveHawkCredentials(keyFetchTokenHex, "keyFetchToken");
|
let creds = await deriveHawkCredentials(keyFetchTokenHex, "keyFetchToken");
|
||||||
let keyRequestKey = creds.extra.slice(0, 32);
|
let keyRequestKey = creds.extra.slice(0, 32);
|
||||||
let morecreds = CryptoUtils.hkdf(keyRequestKey, undefined,
|
let morecreds = await CryptoUtils.hkdfLegacy(keyRequestKey, undefined,
|
||||||
Credentials.keyWord("account/keys"), 3 * 32);
|
Credentials.keyWord("account/keys"), 3 * 32);
|
||||||
let respHMACKey = morecreds.slice(0, 32);
|
let respHMACKey = morecreds.slice(0, 32);
|
||||||
let respXORKey = morecreds.slice(32, 96);
|
let respXORKey = morecreds.slice(32, 96);
|
||||||
|
|
||||||
return this._request("/account/keys", "GET", creds).then(resp => {
|
const resp = await this._request("/account/keys", "GET", creds);
|
||||||
if (!resp.bundle) {
|
if (!resp.bundle) {
|
||||||
throw new Error("failed to retrieve keys");
|
throw new Error("failed to retrieve keys");
|
||||||
}
|
}
|
||||||
|
|
||||||
let bundle = CommonUtils.hexToBytes(resp.bundle);
|
let bundle = CommonUtils.hexToBytes(resp.bundle);
|
||||||
let mac = bundle.slice(-32);
|
let mac = bundle.slice(-32);
|
||||||
|
|
||||||
let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
|
let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
|
||||||
CryptoUtils.makeHMACKey(respHMACKey));
|
CryptoUtils.makeHMACKey(respHMACKey));
|
||||||
|
|
||||||
let bundleMAC = CryptoUtils.digestBytes(bundle.slice(0, -32), hasher);
|
let bundleMAC = CryptoUtils.digestBytes(bundle.slice(0, -32), hasher);
|
||||||
if (mac !== bundleMAC) {
|
if (mac !== bundleMAC) {
|
||||||
throw new Error("error unbundling encryption keys");
|
throw new Error("error unbundling encryption keys");
|
||||||
}
|
}
|
||||||
|
|
||||||
let keyAWrapB = CryptoUtils.xor(respXORKey, bundle.slice(0, 64));
|
let keyAWrapB = CryptoUtils.xor(respXORKey, bundle.slice(0, 64));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kA: keyAWrapB.slice(0, 32),
|
kA: keyAWrapB.slice(0, 32),
|
||||||
wrapKB: keyAWrapB.slice(32),
|
wrapKB: keyAWrapB.slice(32),
|
||||||
};
|
};
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -308,8 +307,8 @@ this.FxAccountsClient.prototype = {
|
|||||||
* wrapping any of these HTTP code/errno pairs:
|
* wrapping any of these HTTP code/errno pairs:
|
||||||
* https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-12
|
* https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-12
|
||||||
*/
|
*/
|
||||||
signCertificate(sessionTokenHex, serializedPublicKey, lifetime) {
|
async signCertificate(sessionTokenHex, serializedPublicKey, lifetime) {
|
||||||
let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||||
|
|
||||||
let body = { publicKey: serializedPublicKey,
|
let body = { publicKey: serializedPublicKey,
|
||||||
duration: lifetime };
|
duration: lifetime };
|
||||||
@ -397,10 +396,10 @@ this.FxAccountsClient.prototype = {
|
|||||||
* type: Type of device (mobile|desktop)
|
* type: Type of device (mobile|desktop)
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
registerDevice(sessionTokenHex, name, type, options = {}) {
|
async registerDevice(sessionTokenHex, name, type, options = {}) {
|
||||||
let path = "/account/device";
|
let path = "/account/device";
|
||||||
|
|
||||||
let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||||
let body = { name, type };
|
let body = { name, type };
|
||||||
|
|
||||||
if (options.pushCallback) {
|
if (options.pushCallback) {
|
||||||
@ -432,7 +431,8 @@ this.FxAccountsClient.prototype = {
|
|||||||
* Resolves to an empty object:
|
* Resolves to an empty object:
|
||||||
* {}
|
* {}
|
||||||
*/
|
*/
|
||||||
notifyDevices(sessionTokenHex, deviceIds, excludedIds, payload, TTL = 0) {
|
async notifyDevices(sessionTokenHex, deviceIds, excludedIds, payload, TTL = 0) {
|
||||||
|
const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||||
if (deviceIds && excludedIds) {
|
if (deviceIds && excludedIds) {
|
||||||
throw new Error("You cannot specify excluded devices if deviceIds is set.");
|
throw new Error("You cannot specify excluded devices if deviceIds is set.");
|
||||||
}
|
}
|
||||||
@ -444,8 +444,7 @@ this.FxAccountsClient.prototype = {
|
|||||||
if (excludedIds) {
|
if (excludedIds) {
|
||||||
body.excluded = excludedIds;
|
body.excluded = excludedIds;
|
||||||
}
|
}
|
||||||
return this._request("/account/devices/notify", "POST",
|
return this._request("/account/devices/notify", "POST", credentials, body);
|
||||||
deriveHawkCredentials(sessionTokenHex, "sessionToken"), body);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -457,7 +456,8 @@ this.FxAccountsClient.prototype = {
|
|||||||
* had that index will be retrieved.
|
* had that index will be retrieved.
|
||||||
* @param [limit] - Maximum number of messages to retrieve.
|
* @param [limit] - Maximum number of messages to retrieve.
|
||||||
*/
|
*/
|
||||||
getCommands(sessionTokenHex, {index, limit}) {
|
async getCommands(sessionTokenHex, {index, limit}) {
|
||||||
|
const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (index != undefined) {
|
if (index != undefined) {
|
||||||
params.set("index", index);
|
params.set("index", index);
|
||||||
@ -466,8 +466,7 @@ this.FxAccountsClient.prototype = {
|
|||||||
params.set("limit", limit);
|
params.set("limit", limit);
|
||||||
}
|
}
|
||||||
const path = `/account/device/commands?${params.toString()}`;
|
const path = `/account/device/commands?${params.toString()}`;
|
||||||
return this._request(path, "GET",
|
return this._request(path, "GET", credentials);
|
||||||
deriveHawkCredentials(sessionTokenHex, "sessionToken"));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -481,14 +480,14 @@ this.FxAccountsClient.prototype = {
|
|||||||
* @return Promise
|
* @return Promise
|
||||||
* Resolves to the request's response, (which should be an empty object)
|
* Resolves to the request's response, (which should be an empty object)
|
||||||
*/
|
*/
|
||||||
invokeCommand(sessionTokenHex, command, target, payload) {
|
async invokeCommand(sessionTokenHex, command, target, payload) {
|
||||||
|
const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||||
const body = {
|
const body = {
|
||||||
command,
|
command,
|
||||||
target,
|
target,
|
||||||
payload,
|
payload,
|
||||||
};
|
};
|
||||||
return this._request("/account/devices/invoke_command", "POST",
|
return this._request("/account/devices/invoke_command", "POST", credentials, body);
|
||||||
deriveHawkCredentials(sessionTokenHex, "sessionToken"), body);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -518,10 +517,10 @@ this.FxAccountsClient.prototype = {
|
|||||||
* name: Device name
|
* name: Device name
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
updateDevice(sessionTokenHex, id, name, options = {}) {
|
async updateDevice(sessionTokenHex, id, name, options = {}) {
|
||||||
let path = "/account/device";
|
let path = "/account/device";
|
||||||
|
|
||||||
let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||||
let body = { id, name };
|
let body = { id, name };
|
||||||
if (options.pushCallback) {
|
if (options.pushCallback) {
|
||||||
body.pushCallback = options.pushCallback;
|
body.pushCallback = options.pushCallback;
|
||||||
@ -554,9 +553,9 @@ this.FxAccountsClient.prototype = {
|
|||||||
* ...
|
* ...
|
||||||
* ]
|
* ]
|
||||||
*/
|
*/
|
||||||
getDeviceList(sessionTokenHex) {
|
async getDeviceList(sessionTokenHex) {
|
||||||
let path = "/account/devices";
|
let path = "/account/devices";
|
||||||
let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||||
|
|
||||||
return this._request(path, "GET", creds, {});
|
return this._request(path, "GET", creds, {});
|
||||||
},
|
},
|
||||||
|
@ -1446,13 +1446,13 @@ add_task(async function test_checkVerificationStatusFailed() {
|
|||||||
Assert.equal(user.sessionToken, null);
|
Assert.equal(user.sessionToken, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_test(function test_deriveKeys() {
|
add_task(async function test_deriveKeys() {
|
||||||
let account = MakeFxAccounts();
|
let account = MakeFxAccounts();
|
||||||
let kBhex = "fd5c747806c07ce0b9d69dcfea144663e630b65ec4963596a22f24910d7dd15d";
|
let kBhex = "fd5c747806c07ce0b9d69dcfea144663e630b65ec4963596a22f24910d7dd15d";
|
||||||
let kB = CommonUtils.hexToBytes(kBhex);
|
let kB = CommonUtils.hexToBytes(kBhex);
|
||||||
const uid = "1ad7f502-4cc7-4ec1-a209-071fd2fae348";
|
const uid = "1ad7f502-4cc7-4ec1-a209-071fd2fae348";
|
||||||
|
|
||||||
const {kSync, kXCS, kExtSync, kExtKbHash} = account.internal._deriveKeys(uid, kB);
|
const {kSync, kXCS, kExtSync, kExtKbHash} = await account.internal._deriveKeys(uid, kB);
|
||||||
|
|
||||||
Assert.equal(kSync, "ad501a50561be52b008878b2e0d8a73357778a712255f7722f497b5d4df14b05" +
|
Assert.equal(kSync, "ad501a50561be52b008878b2e0d8a73357778a712255f7722f497b5d4df14b05" +
|
||||||
"dc06afb836e1521e882f521eb34691d172337accdbf6e2a5b968b05a7bbb9885");
|
"dc06afb836e1521e882f521eb34691d172337accdbf6e2a5b968b05a7bbb9885");
|
||||||
@ -1460,7 +1460,6 @@ add_test(function test_deriveKeys() {
|
|||||||
Assert.equal(kExtSync, "f5ccd9cfdefd9b1ac4d02c56964f59239d8dfa1ca326e63696982765c1352cdc" +
|
Assert.equal(kExtSync, "f5ccd9cfdefd9b1ac4d02c56964f59239d8dfa1ca326e63696982765c1352cdc" +
|
||||||
"5d78a5a9c633a6d25edfea0a6c221a3480332a49fd866f311c2e3508ddd07395");
|
"5d78a5a9c633a6d25edfea0a6c221a3480332a49fd866f311c2e3508ddd07395");
|
||||||
Assert.equal(kExtKbHash, "6192f1cc7dce95334455ba135fa1d8fca8f70e8f594ae318528de06f24ed0273");
|
Assert.equal(kExtKbHash, "6192f1cc7dce95334455ba135fa1d8fca8f70e8f594ae318528de06f24ed0273");
|
||||||
run_next_test();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -33,7 +33,7 @@ add_task(async function test_onepw_setup_credentials() {
|
|||||||
let password = CommonUtils.encodeUTF8("i like pie");
|
let password = CommonUtils.encodeUTF8("i like pie");
|
||||||
|
|
||||||
let pbkdf2 = CryptoUtils.pbkdf2Generate;
|
let pbkdf2 = CryptoUtils.pbkdf2Generate;
|
||||||
let hkdf = CryptoUtils.hkdf;
|
let hkdf = CryptoUtils.hkdfLegacy;
|
||||||
|
|
||||||
// quickStretch the email
|
// quickStretch the email
|
||||||
let saltyEmail = Credentials.keyWordExtended("quickStretch", email);
|
let saltyEmail = Credentials.keyWordExtended("quickStretch", email);
|
||||||
@ -43,7 +43,7 @@ add_task(async function test_onepw_setup_credentials() {
|
|||||||
let pbkdf2Rounds = 1000;
|
let pbkdf2Rounds = 1000;
|
||||||
let pbkdf2Len = 32;
|
let pbkdf2Len = 32;
|
||||||
|
|
||||||
let quickStretchedPW = pbkdf2(password, saltyEmail, pbkdf2Rounds, pbkdf2Len, Ci.nsICryptoHMAC.SHA256, 32);
|
let quickStretchedPW = await pbkdf2(password, saltyEmail, pbkdf2Rounds, pbkdf2Len);
|
||||||
let quickStretchedActual = "6b88094c1c73bbf133223f300d101ed70837af48d9d2c1b6e7d38804b20cdde4";
|
let quickStretchedActual = "6b88094c1c73bbf133223f300d101ed70837af48d9d2c1b6e7d38804b20cdde4";
|
||||||
Assert.equal(b2h(quickStretchedPW), quickStretchedActual);
|
Assert.equal(b2h(quickStretchedPW), quickStretchedActual);
|
||||||
|
|
||||||
@ -54,13 +54,13 @@ add_task(async function test_onepw_setup_credentials() {
|
|||||||
// derive auth password
|
// derive auth password
|
||||||
let hkdfSalt = h2b("00");
|
let hkdfSalt = h2b("00");
|
||||||
let hkdfLen = 32;
|
let hkdfLen = 32;
|
||||||
let authPW = hkdf(quickStretchedPW, hkdfSalt, authKeyInfo, hkdfLen);
|
let authPW = await hkdf(quickStretchedPW, hkdfSalt, authKeyInfo, hkdfLen);
|
||||||
|
|
||||||
Assert.equal(b2h(authPW), "4b8dec7f48e7852658163601ff766124c312f9392af6c3d4e1a247eb439be342");
|
Assert.equal(b2h(authPW), "4b8dec7f48e7852658163601ff766124c312f9392af6c3d4e1a247eb439be342");
|
||||||
|
|
||||||
// derive unwrap key
|
// derive unwrap key
|
||||||
let unwrapKeyInfo = Credentials.keyWord("unwrapBkey");
|
let unwrapKeyInfo = Credentials.keyWord("unwrapBkey");
|
||||||
let unwrapKey = hkdf(quickStretchedPW, hkdfSalt, unwrapKeyInfo, hkdfLen);
|
let unwrapKey = await hkdf(quickStretchedPW, hkdfSalt, unwrapKeyInfo, hkdfLen);
|
||||||
|
|
||||||
Assert.equal(b2h(unwrapKey), "8ff58975be391338e4ec5d7138b5ed7b65c7d1bfd1f3a4f93e05aa47d5b72be9");
|
Assert.equal(b2h(unwrapKey), "8ff58975be391338e4ec5d7138b5ed7b65c7d1bfd1f3a4f93e05aa47d5b72be9");
|
||||||
});
|
});
|
||||||
@ -79,8 +79,6 @@ add_task(async function test_client_stretch_kdf() {
|
|||||||
let options = {
|
let options = {
|
||||||
stretchedPassLength: 32,
|
stretchedPassLength: 32,
|
||||||
pbkdf2Rounds: 1000,
|
pbkdf2Rounds: 1000,
|
||||||
hmacAlgorithm: Ci.nsICryptoHMAC.SHA256,
|
|
||||||
hmacLength: 32,
|
|
||||||
hkdfSalt: h2b("00"),
|
hkdfSalt: h2b("00"),
|
||||||
hkdfLength: 32,
|
hkdfLength: 32,
|
||||||
};
|
};
|
||||||
|
@ -601,8 +601,7 @@ this.BrowserIDManager.prototype = {
|
|||||||
if (!this._token) {
|
if (!this._token) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let credentials = {algorithm: "sha256",
|
let credentials = {id: this._token.id,
|
||||||
id: this._token.id,
|
|
||||||
key: this._token.key,
|
key: this._token.key,
|
||||||
};
|
};
|
||||||
method = method || httpObject.method;
|
method = method || httpObject.method;
|
||||||
@ -615,7 +614,7 @@ this.BrowserIDManager.prototype = {
|
|||||||
credentials,
|
credentials,
|
||||||
};
|
};
|
||||||
|
|
||||||
let headerValue = CryptoUtils.computeHAWK(httpObject.uri, method, options);
|
let headerValue = await CryptoUtils.computeHAWK(httpObject.uri, method, options);
|
||||||
return {headers: {authorization: headerValue.field}};
|
return {headers: {authorization: headerValue.field}};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -55,12 +55,10 @@ class HMACMismatch extends Error {
|
|||||||
*/
|
*/
|
||||||
var Utils = {
|
var Utils = {
|
||||||
// Aliases from CryptoUtils.
|
// Aliases from CryptoUtils.
|
||||||
generateRandomBytes: CryptoUtils.generateRandomBytes,
|
generateRandomBytesLegacy: CryptoUtils.generateRandomBytesLegacy,
|
||||||
computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1,
|
computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1,
|
||||||
digestUTF8: CryptoUtils.digestUTF8,
|
digestUTF8: CryptoUtils.digestUTF8,
|
||||||
digestBytes: CryptoUtils.digestBytes,
|
digestBytes: CryptoUtils.digestBytes,
|
||||||
sha1: CryptoUtils.sha1,
|
|
||||||
sha1Base32: CryptoUtils.sha1Base32,
|
|
||||||
sha256: CryptoUtils.sha256,
|
sha256: CryptoUtils.sha256,
|
||||||
makeHMACKey: CryptoUtils.makeHMACKey,
|
makeHMACKey: CryptoUtils.makeHMACKey,
|
||||||
makeHMACHasher: CryptoUtils.makeHMACHasher,
|
makeHMACHasher: CryptoUtils.makeHMACHasher,
|
||||||
@ -192,7 +190,7 @@ var Utils = {
|
|||||||
* That makes them 12 characters long with 72 bits of entropy.
|
* That makes them 12 characters long with 72 bits of entropy.
|
||||||
*/
|
*/
|
||||||
makeGUID: function makeGUID() {
|
makeGUID: function makeGUID() {
|
||||||
return CommonUtils.encodeBase64URL(Utils.generateRandomBytes(9));
|
return CommonUtils.encodeBase64URL(Utils.generateRandomBytesLegacy(9));
|
||||||
},
|
},
|
||||||
|
|
||||||
_base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i,
|
_base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i,
|
||||||
|
@ -65,7 +65,7 @@ add_test(function test_set_invalid_values() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bundle.hmacKey = Utils.generateRandomBytes(15);
|
bundle.hmacKey = Utils.generateRandomBytesLegacy(15);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
thrown = true;
|
thrown = true;
|
||||||
Assert.equal(ex.message.indexOf("HMAC key must be at least 128"), 0);
|
Assert.equal(ex.message.indexOf("HMAC key must be at least 128"), 0);
|
||||||
@ -95,7 +95,7 @@ add_test(function test_set_invalid_values() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bundle.hmacKey = Utils.generateRandomBytes(15);
|
bundle.hmacKey = Utils.generateRandomBytesLegacy(15);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
thrown = true;
|
thrown = true;
|
||||||
Assert.equal(ex.message.indexOf("HMAC key must be at least 128"), 0);
|
Assert.equal(ex.message.indexOf("HMAC key must be at least 128"), 0);
|
||||||
|
@ -434,7 +434,7 @@ class CryptoCollection {
|
|||||||
* @returns {string} A base64-encoded string of the salt
|
* @returns {string} A base64-encoded string of the salt
|
||||||
*/
|
*/
|
||||||
getNewSalt() {
|
getNewSalt() {
|
||||||
return btoa(CryptoUtils.generateRandomBytes(STORAGE_SYNC_CRYPTO_SALT_LENGTH_BYTES));
|
return btoa(CryptoUtils.generateRandomBytesLegacy(STORAGE_SYNC_CRYPTO_SALT_LENGTH_BYTES));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,12 +57,15 @@ class StaticKeyEncryptionRemoteTransformer extends EncryptionRemoteTransformer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const BORING_KB = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
const BORING_KB = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||||
const STRETCHED_KEY = CryptoUtils.hkdf(BORING_KB, undefined, `testing storage.sync encryption`, 2 * 32);
|
let transformer;
|
||||||
const KEY_BUNDLE = {
|
add_task(async function setup() {
|
||||||
sha256HMACHasher: Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, Utils.makeHMACKey(STRETCHED_KEY.slice(0, 32))),
|
const STRETCHED_KEY = await CryptoUtils.hkdfLegacy(BORING_KB, undefined, `testing storage.sync encryption`, 2 * 32);
|
||||||
encryptionKeyB64: btoa(STRETCHED_KEY.slice(32, 64)),
|
const KEY_BUNDLE = {
|
||||||
};
|
sha256HMACHasher: Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, Utils.makeHMACKey(STRETCHED_KEY.slice(0, 32))),
|
||||||
const transformer = new StaticKeyEncryptionRemoteTransformer(KEY_BUNDLE);
|
encryptionKeyB64: btoa(STRETCHED_KEY.slice(32, 64)),
|
||||||
|
};
|
||||||
|
transformer = new StaticKeyEncryptionRemoteTransformer(KEY_BUNDLE);
|
||||||
|
});
|
||||||
|
|
||||||
add_task(async function test_encryption_transformer_roundtrip() {
|
add_task(async function test_encryption_transformer_roundtrip() {
|
||||||
const POSSIBLE_DATAS = [
|
const POSSIBLE_DATAS = [
|
||||||
|
Loading…
Reference in New Issue
Block a user