// 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 gMockPrompter = { promptPassword() { // 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" ); }); 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 primary 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(""); });