test_sdr.js (7982B)
1 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*- 2 // Any copyright is dedicated to the Public Domain. 3 // http://creativecommons.org/publicdomain/zero/1.0/ 4 "use strict"; 5 6 // Tests various aspects of the nsISecretDecoderRing implementation. 7 8 do_get_profile(); 9 10 let gSetPasswordShownCount = 0; 11 12 // Mock implementation of nsITokenPasswordDialogs. 13 const gTokenPasswordDialogs = { 14 setPassword(ctx, tokenName) { 15 gSetPasswordShownCount++; 16 info(`setPassword() called; shown ${gSetPasswordShownCount} times`); 17 info(`tokenName: ${tokenName}`); 18 return false; // Returning false means "the user didn't cancel". 19 }, 20 21 QueryInterface: ChromeUtils.generateQI(["nsITokenPasswordDialogs"]), 22 }; 23 24 let gMockPrompter = { 25 promptPassword() { 26 // Returning false simulates the user canceling the password prompt. 27 return false; 28 }, 29 30 QueryInterface: ChromeUtils.generateQI(["nsIPrompt"]), 31 }; 32 33 // Mock nsIWindowWatcher. PSM calls getNewPrompter on this to get an nsIPrompt 34 // to call promptPassword. We return the mock one, above. 35 let gWindowWatcher = { 36 getNewPrompter: () => gMockPrompter, 37 QueryInterface: ChromeUtils.generateQI(["nsIWindowWatcher"]), 38 }; 39 40 add_task(function setup() { 41 let windowWatcherCID = MockRegistrar.register( 42 "@mozilla.org/embedcomp/window-watcher;1", 43 gWindowWatcher 44 ); 45 registerCleanupFunction(() => { 46 MockRegistrar.unregister(windowWatcherCID); 47 }); 48 }); 49 50 add_task(function testEncryptString() { 51 let sdr = Cc["@mozilla.org/security/sdr;1"].getService( 52 Ci.nsISecretDecoderRing 53 ); 54 55 // Test valid inputs for encryptString() and decryptString(). 56 let inputs = [ 57 "", 58 " ", // First printable latin1 character (code point 32). 59 "foo", 60 "1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?", 61 "¡äöüÿ", // Misc + last printable latin1 character (code point 255). 62 "aaa 一二三", // Includes Unicode with code points outside [0, 255]. 63 ]; 64 for (let input of inputs) { 65 let converter = Cc[ 66 "@mozilla.org/intl/scriptableunicodeconverter" 67 ].createInstance(Ci.nsIScriptableUnicodeConverter); 68 converter.charset = "UTF-8"; 69 70 let convertedInput = converter.ConvertFromUnicode(input); 71 convertedInput += converter.Finish(); 72 73 let encrypted = sdr.encryptString(convertedInput); 74 75 notEqual( 76 convertedInput, 77 encrypted, 78 "Encrypted input should not just be the input itself" 79 ); 80 81 try { 82 atob(encrypted); 83 } catch (e) { 84 ok(false, `encryptString() should have returned Base64: ${e}`); 85 } 86 87 equal( 88 convertedInput, 89 sdr.decryptString(encrypted), 90 "decryptString(encryptString(input)) should return input" 91 ); 92 } 93 94 // Test invalid inputs for decryptString(). 95 throws( 96 () => sdr.decryptString("*"), 97 /NS_ERROR_ILLEGAL_VALUE/, 98 "decryptString() should throw if given non-Base64 input" 99 ); 100 101 // Test calling changePassword() pops up the appropriate dialog. 102 // Note: On Android, nsITokenPasswordDialogs is apparently not implemented, 103 // which also seems to prevent us from mocking out the interface. 104 if (AppConstants.platform != "android") { 105 let tokenPasswordDialogsCID = MockRegistrar.register( 106 "@mozilla.org/nsTokenPasswordDialogs;1", 107 gTokenPasswordDialogs 108 ); 109 registerCleanupFunction(() => { 110 MockRegistrar.unregister(tokenPasswordDialogsCID); 111 }); 112 113 equal( 114 gSetPasswordShownCount, 115 0, 116 "changePassword() dialog should have been shown zero times" 117 ); 118 sdr.changePassword(); 119 equal( 120 gSetPasswordShownCount, 121 1, 122 "changePassword() dialog should have been shown exactly once" 123 ); 124 } 125 }); 126 127 add_task(async function testAsyncEncryptStrings() { 128 let sdr = Cc["@mozilla.org/security/sdr;1"].getService( 129 Ci.nsISecretDecoderRing 130 ); 131 132 // Test valid inputs for encryptString() and decryptString(). 133 let inputs = [ 134 "", 135 " ", // First printable latin1 character (code point 32). 136 "foo", 137 "1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?", 138 "¡äöüÿ", // Misc + last printable latin1 character (code point 255). 139 "aaa 一二三", // Includes Unicode with code points outside [0, 255]. 140 ]; 141 142 let encrypteds = await sdr.asyncEncryptStrings(inputs); 143 for (let i = 0; i < inputs.length; i++) { 144 let encrypted = encrypteds[i]; 145 let input = inputs[i]; 146 let converter = Cc[ 147 "@mozilla.org/intl/scriptableunicodeconverter" 148 ].createInstance(Ci.nsIScriptableUnicodeConverter); 149 converter.charset = "UTF-8"; 150 151 let convertedInput = converter.ConvertFromUnicode(input); 152 convertedInput += converter.Finish(); 153 notEqual( 154 convertedInput, 155 encrypted, 156 "Encrypted input should not just be the input itself" 157 ); 158 159 try { 160 atob(encrypted); 161 } catch (e) { 162 ok(false, `encryptString() should have returned Base64: ${e}`); 163 } 164 165 equal( 166 convertedInput, 167 sdr.decryptString(encrypted), 168 "decryptString(encryptString(input)) should return input" 169 ); 170 } 171 }); 172 173 add_task(async function testAsyncDecryptStrings() { 174 let sdr = Cc["@mozilla.org/security/sdr;1"].getService( 175 Ci.nsISecretDecoderRing 176 ); 177 178 // Test valid inputs for encryptString() and decryptString(). 179 let testCases = [ 180 "", 181 " ", // First printable latin1 character (code point 32). 182 "foo", 183 "1234567890`~!@#$%^&*()-_=+{[}]|\\:;'\",<.>/?", 184 "¡äöüÿ", // Misc + last printable latin1 character (code point 255). 185 "aaa 一二三", // Includes Unicode with code points outside [0, 255]. 186 ]; 187 188 let convertedTestCases = testCases.map(tc => { 189 let converter = Cc[ 190 "@mozilla.org/intl/scriptableunicodeconverter" 191 ].createInstance(Ci.nsIScriptableUnicodeConverter); 192 converter.charset = "UTF-8"; 193 194 let convertedInput = converter.ConvertFromUnicode(tc); 195 convertedInput += converter.Finish(); 196 return convertedInput; 197 }); 198 199 let encryptedStrings = convertedTestCases.map(tc => sdr.encryptString(tc)); 200 let decrypteds = await sdr.asyncDecryptStrings(encryptedStrings); 201 for (let i = 0; i < encryptedStrings.length; i++) { 202 let decrypted = decrypteds[i]; 203 204 equal( 205 decrypted, 206 testCases[i], 207 "decrypted string should match expected value" 208 ); 209 equal( 210 sdr.decryptString(encryptedStrings[i]), 211 convertedTestCases[i], 212 "decryptString(encryptString(input)) should return the initial decrypted string value" 213 ); 214 } 215 }); 216 217 add_task(async function testAsyncDecryptInvalidStrings() { 218 let sdr = Cc["@mozilla.org/security/sdr;1"].getService( 219 Ci.nsISecretDecoderRing 220 ); 221 222 // Test invalid inputs for sdr.asyncDecryptStrings 223 let testCases = [ 224 "~bmV0cGxheQ==", // invalid base64 encoding 225 "bmV0cGxheQ==", // valid base64 characters but not encrypted 226 "https://www.example.com", // website address from erroneous migration 227 ]; 228 229 let decrypteds = await sdr.asyncDecryptStrings(testCases); 230 equal( 231 decrypteds.length, 232 testCases.length, 233 "each testcase should still return a response" 234 ); 235 for (let i = 0; i < decrypteds.length; i++) { 236 let decrypted = decrypteds[i]; 237 238 equal( 239 decrypted, 240 "", 241 "decrypted string should be empty when trying to decrypt an invalid input with asyncDecryptStrings" 242 ); 243 244 Assert.throws( 245 () => sdr.decryptString(testCases[i]), 246 /NS_ERROR_ILLEGAL_VALUE|NS_ERROR_FAILURE/, 247 `Check testcase would have thrown: ${testCases[i]}` 248 ); 249 } 250 }); 251 252 add_task(async function testAsyncDecryptLoggedOut() { 253 // Set a master password. 254 let token = Cc["@mozilla.org/security/pk11tokendb;1"] 255 .getService(Ci.nsIPK11TokenDB) 256 .getInternalKeyToken(); 257 token.initPassword("password"); 258 token.logoutSimple(); 259 260 let sdr = Cc["@mozilla.org/security/sdr;1"].getService( 261 Ci.nsISecretDecoderRing 262 ); 263 264 await Assert.rejects( 265 sdr.asyncDecryptStrings(["irrelevant"]), 266 /NS_ERROR_NOT_AVAILABLE/, 267 "Check error is thrown instead of returning empty strings" 268 ); 269 270 token.reset(); 271 token.initPassword(""); 272 });