diff options
Diffstat (limited to '')
-rw-r--r-- | security/manager/ssl/tests/unit/test_sdr.js | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/security/manager/ssl/tests/unit/test_sdr.js b/security/manager/ssl/tests/unit/test_sdr.js new file mode 100644 index 0000000000..e9e477efc5 --- /dev/null +++ b/security/manager/ssl/tests/unit/test_sdr.js @@ -0,0 +1,272 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ +"use strict"; + +// Tests various aspects of the nsISecretDecoderRing implementation. + +do_get_profile(); + +let gSetPasswordShownCount = 0; + +// Mock implementation of nsITokenPasswordDialogs. +const gTokenPasswordDialogs = { + setPassword(ctx, tokenName) { + gSetPasswordShownCount++; + info(`setPassword() called; shown ${gSetPasswordShownCount} times`); + info(`tokenName: ${tokenName}`); + return false; // Returning false means "the user didn't cancel". + }, + + QueryInterface: ChromeUtils.generateQI(["nsITokenPasswordDialogs"]), +}; + +let gMockPrompter = { + promptPassword(dialogTitle, text, password, checkMsg, checkValue) { + // Returning false simulates the user canceling the password prompt. + return false; + }, + + QueryInterface: ChromeUtils.generateQI(["nsIPrompt"]), +}; + +// Mock nsIWindowWatcher. PSM calls getNewPrompter on this to get an nsIPrompt +// to call promptPassword. We return the mock one, above. +let gWindowWatcher = { + getNewPrompter: () => gMockPrompter, + QueryInterface: ChromeUtils.generateQI(["nsIWindowWatcher"]), +}; + +add_task(function setup() { + let windowWatcherCID = MockRegistrar.register( + "@mozilla.org/embedcomp/window-watcher;1", + gWindowWatcher + ); + registerCleanupFunction(() => { + MockRegistrar.unregister(windowWatcherCID); + }); +}); + +add_task(function testEncryptString() { + let sdr = Cc["@mozilla.org/security/sdr;1"].getService( + Ci.nsISecretDecoderRing + ); + + // Test valid inputs for encryptString() and decryptString(). + let inputs = [ + "", + " ", // First printable latin1 character (code point 32). + "foo", + "1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?", + "¡äöüÿ", // Misc + last printable latin1 character (code point 255). + "aaa 一二三", // Includes Unicode with code points outside [0, 255]. + ]; + for (let input of inputs) { + let converter = Cc[ + "@mozilla.org/intl/scriptableunicodeconverter" + ].createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + + let convertedInput = converter.ConvertFromUnicode(input); + convertedInput += converter.Finish(); + + let encrypted = sdr.encryptString(convertedInput); + + notEqual( + convertedInput, + encrypted, + "Encrypted input should not just be the input itself" + ); + + try { + atob(encrypted); + } catch (e) { + ok(false, `encryptString() should have returned Base64: ${e}`); + } + + equal( + convertedInput, + sdr.decryptString(encrypted), + "decryptString(encryptString(input)) should return input" + ); + } + + // Test invalid inputs for decryptString(). + throws( + () => sdr.decryptString("*"), + /NS_ERROR_ILLEGAL_VALUE/, + "decryptString() should throw if given non-Base64 input" + ); + + // Test calling changePassword() pops up the appropriate dialog. + // Note: On Android, nsITokenPasswordDialogs is apparently not implemented, + // which also seems to prevent us from mocking out the interface. + if (AppConstants.platform != "android") { + let tokenPasswordDialogsCID = MockRegistrar.register( + "@mozilla.org/nsTokenPasswordDialogs;1", + gTokenPasswordDialogs + ); + registerCleanupFunction(() => { + MockRegistrar.unregister(tokenPasswordDialogsCID); + }); + + equal( + gSetPasswordShownCount, + 0, + "changePassword() dialog should have been shown zero times" + ); + sdr.changePassword(); + equal( + gSetPasswordShownCount, + 1, + "changePassword() dialog should have been shown exactly once" + ); + } +}); + +add_task(async function testAsyncEncryptStrings() { + let sdr = Cc["@mozilla.org/security/sdr;1"].getService( + Ci.nsISecretDecoderRing + ); + + // Test valid inputs for encryptString() and decryptString(). + let inputs = [ + "", + " ", // First printable latin1 character (code point 32). + "foo", + "1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?", + "¡äöüÿ", // Misc + last printable latin1 character (code point 255). + "aaa 一二三", // Includes Unicode with code points outside [0, 255]. + ]; + + let encrypteds = await sdr.asyncEncryptStrings(inputs); + for (let i = 0; i < inputs.length; i++) { + let encrypted = encrypteds[i]; + let input = inputs[i]; + let converter = Cc[ + "@mozilla.org/intl/scriptableunicodeconverter" + ].createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + + let convertedInput = converter.ConvertFromUnicode(input); + convertedInput += converter.Finish(); + notEqual( + convertedInput, + encrypted, + "Encrypted input should not just be the input itself" + ); + + try { + atob(encrypted); + } catch (e) { + ok(false, `encryptString() should have returned Base64: ${e}`); + } + + equal( + convertedInput, + sdr.decryptString(encrypted), + "decryptString(encryptString(input)) should return input" + ); + } +}); + +add_task(async function testAsyncDecryptStrings() { + let sdr = Cc["@mozilla.org/security/sdr;1"].getService( + Ci.nsISecretDecoderRing + ); + + // Test valid inputs for encryptString() and decryptString(). + let testCases = [ + "", + " ", // First printable latin1 character (code point 32). + "foo", + "1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?", + "¡äöüÿ", // Misc + last printable latin1 character (code point 255). + "aaa 一二三", // Includes Unicode with code points outside [0, 255]. + ]; + + let convertedTestCases = testCases.map(tc => { + let converter = Cc[ + "@mozilla.org/intl/scriptableunicodeconverter" + ].createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + + let convertedInput = converter.ConvertFromUnicode(tc); + convertedInput += converter.Finish(); + return convertedInput; + }); + + let encryptedStrings = convertedTestCases.map(tc => sdr.encryptString(tc)); + let decrypteds = await sdr.asyncDecryptStrings(encryptedStrings); + for (let i = 0; i < encryptedStrings.length; i++) { + let decrypted = decrypteds[i]; + + equal( + decrypted, + testCases[i], + "decrypted string should match expected value" + ); + equal( + sdr.decryptString(encryptedStrings[i]), + convertedTestCases[i], + "decryptString(encryptString(input)) should return the initial decrypted string value" + ); + } +}); + +add_task(async function testAsyncDecryptInvalidStrings() { + let sdr = Cc["@mozilla.org/security/sdr;1"].getService( + Ci.nsISecretDecoderRing + ); + + // Test invalid inputs for sdr.asyncDecryptStrings + let testCases = [ + "~bmV0cGxheQ==", // invalid base64 encoding + "bmV0cGxheQ==", // valid base64 characters but not encrypted + "https://www.example.com", // website address from erroneous migration + ]; + + let decrypteds = await sdr.asyncDecryptStrings(testCases); + equal( + decrypteds.length, + testCases.length, + "each testcase should still return a response" + ); + for (let i = 0; i < decrypteds.length; i++) { + let decrypted = decrypteds[i]; + + equal( + decrypted, + "", + "decrypted string should be empty when trying to decrypt an invalid input with asyncDecryptStrings" + ); + + Assert.throws( + () => sdr.decryptString(testCases[i]), + /NS_ERROR_ILLEGAL_VALUE|NS_ERROR_FAILURE/, + `Check testcase would have thrown: ${testCases[i]}` + ); + } +}); + +add_task(async function testAsyncDecryptLoggedOut() { + // Set a master password. + let token = Cc["@mozilla.org/security/pk11tokendb;1"] + .getService(Ci.nsIPK11TokenDB) + .getInternalKeyToken(); + token.initPassword("password"); + token.logoutSimple(); + + let sdr = Cc["@mozilla.org/security/sdr;1"].getService( + Ci.nsISecretDecoderRing + ); + + await Assert.rejects( + sdr.asyncDecryptStrings(["irrelevant"]), + /NS_ERROR_NOT_AVAILABLE/, + "Check error is thrown instead of returning empty strings" + ); + + token.reset(); + token.initPassword(""); +}); |