Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'win' && msix OR os == 'mac' && os_version == '11.20' && arch == 'aarch64'
- Manifest: security/manager/ssl/tests/unit/xpcshell.toml
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
// Any copyright is dedicated to the Public Domain.
"use strict";
// Tests the methods and attributes for interfacing with nsIOSKeyStore.
// Ensure that the appropriate initialization has happened.
do_get_profile();
const LABELS = ["mylabel1", "mylabel2", "mylabel3"];
async function delete_all_secrets() {
let keystore = Cc["@mozilla.org/security/oskeystore;1"].getService(
Ci.nsIOSKeyStore
);
for (let label of LABELS) {
if (await keystore.asyncSecretAvailable(label)) {
await keystore.asyncDeleteSecret(label);
ok(
!(await keystore.asyncSecretAvailable(label)),
label + " should be deleted now."
);
}
}
}
async function encrypt_decrypt_test() {
let keystore = Cc["@mozilla.org/security/oskeystore;1"].getService(
Ci.nsIOSKeyStore
);
ok(
!(await keystore.asyncSecretAvailable(LABELS[0])),
"The secret should not be available yet."
);
let recoveryPhrase = await keystore.asyncGenerateSecret(LABELS[0]);
ok(recoveryPhrase, "A recovery phrase should've been created.");
let recoveryPhrase2 = await keystore.asyncGenerateSecret(LABELS[1]);
ok(recoveryPhrase2, "A recovery phrase should've been created.");
let text = new Uint8Array([0x01, 0x00, 0x01]);
let ciphertext = "";
try {
ciphertext = await keystore.asyncEncryptBytes(LABELS[0], text);
ok(ciphertext, "We should have a ciphertext now.");
} catch (e) {
ok(false, "Error encrypting " + e);
}
// Decrypting should give us the plaintext bytes again.
try {
let plaintext = await keystore.asyncDecryptBytes(LABELS[0], ciphertext);
Assert.equal(
plaintext.toString(),
text.toString(),
"Decrypted plaintext should be the same as text."
);
} catch (e) {
ok(false, "Error decrypting ciphertext " + e);
}
// Decrypting with a wrong key should throw an error.
try {
await keystore.asyncDecryptBytes(LABELS[1], ciphertext);
ok(false, "Decrypting with the wrong key should fail.");
} catch (e) {
ok(true, "Decrypting with the wrong key should fail " + e);
}
}
add_task(async function () {
await delete_all_secrets();
await encrypt_decrypt_test();
await delete_all_secrets();
});
// Test that using a recovery phrase works.
add_task(async function () {
await delete_all_secrets();
let keystore = Cc["@mozilla.org/security/oskeystore;1"].getService(
Ci.nsIOSKeyStore
);
let recoveryPhrase = await keystore.asyncGenerateSecret(LABELS[0]);
ok(recoveryPhrase, "A recovery phrase should've been created.");
let text = new Uint8Array([0x01, 0x00, 0x01]);
let ciphertext = await keystore.asyncEncryptBytes(LABELS[0], text);
ok(ciphertext, "We should have a ciphertext now.");
await keystore.asyncDeleteSecret(LABELS[0]);
// Decrypting should fail after deleting the secret.
await keystore
.asyncDecryptBytes(LABELS[0], ciphertext)
.then(() =>
ok(false, "decrypting didn't throw as expected after deleting the secret")
)
.catch(() =>
ok(true, "decrypting threw as expected after deleting the secret")
);
await keystore.asyncRecoverSecret(LABELS[0], recoveryPhrase);
let plaintext = await keystore.asyncDecryptBytes(LABELS[0], ciphertext);
Assert.equal(
plaintext.toString(),
text.toString(),
"Decrypted plaintext should be the same as text."
);
await delete_all_secrets();
});
// Test that trying to use a non-base64 recovery phrase fails.
add_task(async function () {
await delete_all_secrets();
let keystore = Cc["@mozilla.org/security/oskeystore;1"].getService(
Ci.nsIOSKeyStore
);
await keystore
.asyncRecoverSecret(LABELS[0], "@##$^&*()#$^&*(@#%&*_")
.then(() =>
ok(false, "base64-decoding non-base64 should have failed but didn't")
)
.catch(() => ok(true, "base64-decoding non-base64 failed as expected"));
ok(
!(await keystore.asyncSecretAvailable(LABELS[0])),
"we didn't recover a secret, so the secret shouldn't be available"
);
let recoveryPhrase = await keystore.asyncGenerateSecret(LABELS[0]);
ok(
recoveryPhrase && !!recoveryPhrase.length,
"we should be able to re-use that label to generate a new secret"
);
await delete_all_secrets();
});
// Test that re-using a label overwrites any previously-stored secret.
add_task(async function () {
await delete_all_secrets();
let keystore = Cc["@mozilla.org/security/oskeystore;1"].getService(
Ci.nsIOSKeyStore
);
let recoveryPhrase = await keystore.asyncGenerateSecret(LABELS[0]);
ok(recoveryPhrase, "A recovery phrase should've been created.");
let text = new Uint8Array([0x66, 0x6f, 0x6f, 0x66]);
let ciphertext = await keystore.asyncEncryptBytes(LABELS[0], text);
ok(ciphertext, "We should have a ciphertext now.");
let newRecoveryPhrase = await keystore.asyncGenerateSecret(LABELS[0]);
ok(newRecoveryPhrase, "A new recovery phrase should've been created.");
// The new secret replaced the old one so we shouldn't be able to decrypt the ciphertext now.
await keystore
.asyncDecryptBytes(LABELS[0], ciphertext)
.then(() =>
ok(false, "decrypting without the original key should have failed")
)
.catch(() =>
ok(true, "decrypting without the original key failed as expected")
);
await keystore.asyncRecoverSecret(LABELS[0], recoveryPhrase);
let plaintext = await keystore.asyncDecryptBytes(LABELS[0], ciphertext);
Assert.equal(
plaintext.toString(),
text.toString(),
"Decrypted plaintext should be the same as text (once we have the original key again)."
);
await delete_all_secrets();
});
// Test that re-using a label (this time using a recovery phrase) overwrites any previously-stored
// secret.
add_task(async function () {
await delete_all_secrets();
let keystore = Cc["@mozilla.org/security/oskeystore;1"].getService(
Ci.nsIOSKeyStore
);
let recoveryPhrase = await keystore.asyncGenerateSecret(LABELS[0]);
ok(recoveryPhrase, "A recovery phrase should've been created.");
let newRecoveryPhrase = await keystore.asyncGenerateSecret(LABELS[0]);
ok(newRecoveryPhrase, "A new recovery phrase should've been created.");
let text = new Uint8Array([0x66, 0x6f, 0x6f, 0x66]);
let ciphertext = await keystore.asyncEncryptBytes(LABELS[0], text);
ok(ciphertext, "We should have a ciphertext now.");
await keystore.asyncRecoverSecret(LABELS[0], recoveryPhrase);
// We recovered the old secret, so decrypting ciphertext that had been encrypted with the newer
// key should fail.
await keystore
.asyncDecryptBytes(LABELS[0], ciphertext)
.then(() => ok(false, "decrypting without the new key should have failed"))
.catch(() => ok(true, "decrypting without the new key failed as expected"));
await keystore.asyncRecoverSecret(LABELS[0], newRecoveryPhrase);
let plaintext = await keystore.asyncDecryptBytes(LABELS[0], ciphertext);
Assert.equal(
plaintext.toString(),
text.toString(),
"Decrypted plaintext should be the same as text (once we have the new key again)."
);
await delete_all_secrets();
});
// Test that trying to use recovery phrases that are the wrong size fails.
add_task(async function () {
await delete_all_secrets();
let keystore = Cc["@mozilla.org/security/oskeystore;1"].getService(
Ci.nsIOSKeyStore
);
await keystore
.asyncRecoverSecret(LABELS[0], "")
.then(() => ok(false, "'recovering' with an empty key should have failed"))
.catch(() => ok(true, "'recovering' with an empty key failed as expected"));
ok(
!(await keystore.asyncSecretAvailable(LABELS[0])),
"we didn't recover a secret, so the secret shouldn't be available"
);
await keystore
.asyncRecoverSecret(LABELS[0], "AAAAAA")
.then(() =>
ok(false, "recovering with a key that is too short should have failed")
)
.catch(() =>
ok(true, "recovering with a key that is too short failed as expected")
);
ok(
!(await keystore.asyncSecretAvailable(LABELS[0])),
"we didn't recover a secret, so the secret shouldn't be available"
);
await keystore
.asyncRecoverSecret(
LABELS[0],
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
)
.then(() =>
ok(false, "recovering with a key that is too long should have failed")
)
.catch(() =>
ok(true, "recovering with a key that is too long failed as expected")
);
ok(
!(await keystore.asyncSecretAvailable(LABELS[0])),
"we didn't recover a secret, so the secret shouldn't be available"
);
let recoveryPhrase = await keystore.asyncGenerateSecret(LABELS[0]);
ok(
recoveryPhrase && !!recoveryPhrase.length,
"we should be able to use that label to generate a new secret"
);
ok(
await keystore.asyncSecretAvailable(LABELS[0]),
"the generated secret should now be available"
);
await delete_all_secrets();
});