Advertisement
Guest User

Untitled

a guest
Sep 10th, 2017
136
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.71 KB | None | 0 0
  1. /*
  2.  
  3. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
  4.  
  5. * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  6. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  7. * Neither the name of Stanford University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
  8.  
  9. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  10.  
  11. */
  12. // A Firefox version of PwdHash.
  13. // Allows users to invisibly generate site-specific passwords.
  14. //
  15. // Author: Collin Jackson
  16. // Other contributors: Blake Ross, Nick Miyake, Dan Boneh, John Mitchell
  17. //
  18. // This version actually does the key masking trick (unlike my previous
  19. // Firefox prototype, which disabled JavaScript). It makes hashed
  20. // passwords that are as long as the user's original password (including
  21. // the prefix), which is information that we are leaking to the website
  22. // anyway. I'm also looking at the user's password for hints as to
  23. // whether nonalphanumerics are okay. Users who prefer the password key
  24. // (F2) will see a small character increase in password size when they
  25. // leave the field. Otherwise, I've stripped out all graphical user
  26. // interface; it's completely invisible unless it detects that something
  27. // goes wrong.
  28.  
  29. //
  30. // Major TODOs:
  31. // Collaborate with Mozilla Foundation to solve Flash keystroke stealing
  32. // Put in some better defenses for focus stealing attacks
  33. // Config file
  34.  
  35.  
  36. //////////////////////////////////////////////////////////////////////////////
  37. // Constants
  38.  
  39. const SPH_kPasswordKey = "DOM_VK_F2";
  40. const SPH_kPasswordPrefix = "@@";
  41. const SPH_kMinimumPasswordSize = 5; // Our defense against focus stealing
  42. const SPH_AddOnName = "PwdHash";
  43.  
  44. ////////////////////////////////////////////////////////////////////////////
  45. // Debug stuff
  46.  
  47. /**
  48. * Dump information to the console?
  49. */
  50. var SPH_debug = true;
  51.  
  52. /**
  53. * Sends data to the console if we're in debug mode
  54. * @param msg The string containing the message to display
  55. */
  56. function SPH_dump(msg) {
  57. if (SPH_debug)
  58. // console.log("|||||||||| SPH pwdhash: " + msg + "\n");
  59. console.log(SPH_AddOnName + ": " + msg);
  60. }
  61.  
  62. ////////////////////////////////////////////////////////////////////////////
  63. // "Major" objects/classes
  64.  
  65. /**
  66. * Password Key Monitor
  67. * Watches for the password prefix or password key
  68. */
  69. function SPH_PasswordKeyMonitor() {
  70. this.keystream = new Array();
  71. window.addEventListener("keydown", this, true);
  72. window.addEventListener("keypress", this, true);
  73. window.addEventListener("keyup", this, true);
  74. }
  75.  
  76. SPH_PasswordKeyMonitor.prototype = {
  77.  
  78. keystream: null,
  79.  
  80. protector: null,
  81.  
  82. handleEvent: function(evt) {
  83.  
  84. // Detect Password Key
  85. if (evt.keyCode == evt[SPH_kPasswordKey]) {
  86. evt.stopPropagation(); // Don't let user JavaScript see this event
  87. evt.preventDefault(); // Do not let the character hit the page
  88. if (evt.type == "keydown") {
  89. evt.pwdkey = true;
  90. if (evt.shiftKey) {
  91. // enable legacy mode on Shift-F2
  92. SPH_legacyUser = true
  93. SPH_legacy = true;
  94. SPH_dump("enabled legacy mode");
  95. SPH_controller.warnUser(SPH_strings["pwdhash.legacy"]);
  96. }
  97. if (this.attemptPasswordKeyLogin()) {
  98. SPH_dump("enabled pwdhash on field '" + evt.target.name + "'");
  99. } else {
  100. SPH_dump("failed to get pwd field");
  101. }
  102. }
  103. }
  104.  
  105. // Detect Password Prefix
  106. if (evt.type == "keypress") {
  107. var lastChar = String.fromCharCode(evt.charCode);
  108. this.keystream.push(lastChar);
  109.  
  110. if (this.keystream.length > SPH_kPasswordPrefix.length)
  111. this.keystream.shift();
  112.  
  113. if (!this.protector &&
  114. this.keystream.join('') == SPH_kPasswordPrefix) {
  115. evt.alreadyIntercepted = true; // Don't intercept again
  116. this.attemptPasswordPrefixLogin(lastChar);
  117.  
  118. }
  119. }
  120. },
  121.  
  122. /**
  123. * Create a password protector on an appropriate password field
  124. */
  125. attemptPasswordKeyLogin: function() {
  126. if(this.protector) { // Already protecting a password field
  127. this.protector.field.value = ""; // Clear field
  128. return this.protector;
  129. }
  130.  
  131. // Try the focused field, if it's a password field
  132. try {
  133. var element = document.activeElement;
  134. if (element) {
  135. if (element.nodeName == "INPUT" && element.type == "password") {
  136. element.value = ""; // clear field
  137. return new SPH_PasswordProtector(element, this);
  138. }
  139. }
  140. } catch(e) {
  141. SPH_dump(e.message);
  142. }
  143.  
  144. // Try to find a password field on the page and its frames
  145. try {
  146. var window = document.getElementById('content')
  147. .selectedBrowser.contentWindow;
  148. var passwordField = this.findPasswordField(window);
  149. if (passwordField) {
  150. passwordField.focus();
  151. return new SPH_PasswordProtector(passwordField, this);
  152. }
  153. } catch(e) {
  154. SPH_dump(e.message);
  155. }
  156.  
  157. try {
  158. var pwdfields = document.getElementsByTagName('INPUT');
  159. for (var i = 0; i < pwdfields.length; i++)
  160. if (pwdfields[i].type == "password") return new SPH_PasswordProtector(pwdfields[i], this);
  161. } catch(e) {
  162. SPH_dump(e.message);
  163. }
  164.  
  165.  
  166. // Yikes! Couldn't find any password fields
  167. var msg = SPH_strings["pwdhash.pwdkeywarn"];
  168. SPH_controller.warnUser(msg);
  169. return null;
  170. },
  171.  
  172. /**
  173. * Create a password protector on the focused field
  174. */
  175. attemptPasswordPrefixLogin: function(lastChar) {
  176. if (this.protector) { // Already protecting a password field
  177. return this.protector;
  178. }
  179.  
  180. try {
  181. var element = document.activeElement;
  182. if (element) {
  183. if (element.nodeName == "INPUT" && element.type == "password") {
  184. if (element.value + lastChar == SPH_kPasswordPrefix) {
  185. return new SPH_PasswordProtector(element, this);
  186. }
  187. }
  188. }
  189. } catch(e) {
  190. SPH_dump(e.message);
  191. }
  192.  
  193. var msg = SPH_strings["pwdhash.pwdprefixwarn"];
  194. SPH_controller.warnUser(msg); // Couldn't find any password fields
  195. return null;
  196. },
  197.  
  198. /*
  199. * Find a password field in the specified frame and return it,
  200. * or return null if no such password field exists
  201. */
  202. findPasswordField: function(frame) {
  203. var pwdfields = frame.document.getElementsByTagName('INPUT');
  204. for (var i = 0; i < pwdfields.length; i++)
  205. if (pwdfields[i].type == "password") return pwdfields[i];
  206. var result = null;
  207. if (frame.frames)
  208. for (var i = 0; i < frame.frames.length; i++)
  209. if (!result) result = this.findPasswordField(frame.frames[i]);
  210. return result;
  211. },
  212.  
  213. }
  214.  
  215. /**
  216. * Password Protector
  217. * Records and masks keystrokes while user is in password mode.
  218. * Triggers hashing when the user is done.
  219. */
  220. function SPH_PasswordProtector(field, monitor) {
  221.  
  222. // check for salt
  223. if (!SPH_legacy && (SPH_salt == "")) {
  224. SPH_controller.warnUser(SPH_strings["pwdhash.saltempty"]);
  225. field.value = ""
  226. return null;
  227. }
  228.  
  229. this.keyMap = new Array();
  230. this.nextAvail = this.firstAvail;
  231. this.field = field;
  232. this.field.setAttribute("secure","yes");
  233. this.borderstyle = this.field.style.border;
  234. this.field.style.border = "2px dashed " + (SPH_legacy ? "red" : "green");
  235. window.addEventListener("keydown", this, true);
  236. window.addEventListener("keyup", this, true);
  237. window.addEventListener("keypress", this, true);
  238. window.addEventListener("blur", this, true);
  239. window.addEventListener("focus", this, true);
  240. window.addEventListener("submit", this, true);
  241. monitor.protector = this;
  242. this._disable = function() {
  243. window.removeEventListener("keydown", this, true);
  244. window.removeEventListener("keyup", this, true);
  245. window.removeEventListener("keypress", this, true);
  246. window.removeEventListener("blur", this, true);
  247. window.removeEventListener("focus", this, true);
  248. window.removeEventListener("submit", this, true);
  249. monitor.protector = null;
  250. }
  251.  
  252. }
  253.  
  254. SPH_PasswordProtector.prototype = {
  255.  
  256. firstAvail: 'A'.charCodeAt(0), // First available mask character
  257.  
  258. lastAvail: 127, // Last available mask character (last printable)
  259.  
  260. nextAvail: null, // The next mask character this protector may use
  261.  
  262. keyMap: null, // A mapping from masked characters to originals
  263.  
  264. field: null, // The field we are protecting
  265.  
  266. borderstyle: null, // remember previous style
  267.  
  268. /**
  269. * Implementation of eventListener. Remembers keystrokes and watches for blur
  270. */
  271. handleEvent: function(evt) {
  272. if(!evt.pwdkey &&
  273. evt.originalTarget != this.field &&
  274. evt.originalTarget != this.field.form) {
  275. // We're confused; try to avoid messing things up further
  276. SPH_dump('Unexpected event ' + evt.type +
  277. ' on target ' + evt.originalTarget);
  278. this._disable();
  279. // TODO: Eventually we should be determining whether it is safe
  280. // to fail quietly or whether the unexpected event is putting the
  281. // user at risk
  282. }
  283.  
  284. if(evt.alreadyIntercepted) return; // Ignore self-generated keystrokes
  285.  
  286. // We need to make sure the user's printable key events don't leak
  287. if((evt.type == "keydown" || evt.type == "keyup") &&
  288. evt.keyCode >= evt.DOM_VK_0 && evt.keyCode <= evt.DOM_VK_DIVIDE) {
  289. evt.stopPropagation(); // Don't let user JavaScript see this event
  290. }
  291.  
  292. // Printable keystrokes should be masked
  293. if(evt.type == "keypress" && evt.keyCode == 0) {
  294. evt.stopPropagation(); // Don't let user JavaScript see this event
  295. evt.preventDefault(); // Do not let the character hit the page
  296. evt.originalTarget.value = evt.originalTarget.value + String.fromCharCode(this.mask(evt.charCode));
  297. }
  298.  
  299. if (evt.type == "blur" || evt.type == "submit") {
  300. this.finish();
  301. // TODO: Check for trusted blur event and call this._warnUntrusted();
  302. }
  303. },
  304.  
  305. _warnUntrusted: function() {
  306. this._disable();
  307. if (this.field) this.field.value = '';
  308. var msg = SPH_strings["pwdhash.trustedeventwarn"];
  309. SPH_dump(msg); // Ideally, use SPH_controller.warnUser(msg);
  310. },
  311.  
  312. /**
  313. * Translate the masked characters back to the originals
  314. */
  315. getPasswordFromMasked: function(masked) {
  316. var password = "";
  317. for (var i = 0; i < masked.length; i++) {
  318. var current = masked.charCodeAt(i);
  319. if (this.keyMap[current]) password += String.fromCharCode(this.keyMap[current]);
  320. else password += masked.substring(i, 1); // Keeps password from shrinking
  321. }
  322. return password;
  323. },
  324.  
  325. /**
  326. * Remember a keystroke and give me a mask I can use in its place
  327. */
  328. mask: function(charCode) {
  329. this.keyMap[this.nextAvail] = charCode;
  330. if (this.nextAvail > this.lastAvail) {
  331. var msg = SPH_strings["pwdhash.longpasswordwarn"];
  332. SPH_controller.warnUser(msg);
  333. this._disable();
  334. }
  335. return this.nextAvail++;
  336. },
  337.  
  338. /**
  339. * When a blur event occurs, we have to hash the field
  340. */
  341. finish: function() {
  342. this._disable();
  343.  
  344. var field = this.field
  345. var password = field.value;
  346. if (password == "") {
  347. field.secure = undefined; // User left field blank
  348. } else {
  349. // Trim the initial "@@" password prefix, if any
  350. var size = SPH_kPasswordPrefix.length;
  351. if(password.substring(0, size) == SPH_kPasswordPrefix)
  352. password = password.substring(size);
  353.  
  354. // Enforce minimum size requirement
  355. if(password.length < SPH_kMinimumPasswordSize) {
  356. var msg = SPH_strings["pwdhash.shortpasswordwarn"];
  357. SPH_controller.warnUser(msg);
  358. field.value = '';
  359. } else {
  360. // Obtain the hashed password
  361. var uri = new String(field.ownerDocument.location);
  362. var domain = (new SPH_DomainExtractor()).extractDomain(uri);
  363. var unmasked = this.getPasswordFromMasked(password);
  364.  
  365. // get password (and check for legacy mode)
  366. if (SPH_legacy) {
  367. field.value = (new SPH_HashedPassword_MD5(unmasked, domain));
  368. } else {
  369. field.value = (new SPH_HashedPassword(unmasked, domain, SPH_salt, SPH_iterations));
  370. }
  371.  
  372. // Clear the field if the user tries to edit the field
  373. var refocus = function() {
  374. field.removeEventListener("keydown", refocus, false);
  375. field.removeEventListener("focus", refocus, false);
  376. field.value = "";
  377. }
  378. field.addEventListener("keydown", refocus, false);
  379. field.addEventListener("focus", refocus, false);
  380. }
  381. }
  382.  
  383. // restore style
  384. this.field.style.border = this.borderstyle;
  385. // reset
  386. SPH_legacyUser = false
  387. },
  388.  
  389. }
  390.  
  391. /**
  392. * Master control object. Just kicks off the key monitor and
  393. * serves a global warning service
  394. */
  395. function SPH_Controller(model) {
  396. this._passwordKeyMonitor = new SPH_PasswordKeyMonitor();
  397. }
  398.  
  399. SPH_Controller.prototype = {
  400. warnUser: function(msg) {
  401. alert(SPH_AddOnName + ": " + msg);
  402. },
  403.  
  404. }
  405.  
  406. // What script would be complete without a couple of globals?
  407. var SPH_controller;
  408. var SPH_strings;
  409. var SPH_salt = "";
  410. var SPH_iterations;
  411. var SPH_legacy = true; // default to legacy mode
  412. var SPH_strings = {};
  413. var SPH_legacyUser = false // user requested legacy mode?
  414.  
  415. function SPH_getOptions() {
  416. function onError(error) {
  417. SPH_dump("Error: ${error}");
  418. }
  419.  
  420. function onGot(item) {
  421. SPH_salt = item.salt;
  422. SPH_iterations = item.iterations;
  423. if (!SPH_legacyUser) SPH_legacy = (item.legacy == null ? true : item.legacy)
  424. //SPH_dump(SPH_salt);
  425. //SPH_dump(SPH_iterations);
  426. if (SPH_legacy) SPH_dump("legacy = " + SPH_legacy);
  427. }
  428.  
  429. return browser.storage.local.get().then(onGot,onError);
  430.  
  431. }
  432.  
  433. function SPH_init() {
  434. SPH_controller = new SPH_Controller();
  435.  
  436. SPH_strings["pwdhash.pwddisplay"]="Hashed password for %s: %s";
  437. SPH_strings["pwdhash.warningtitle"]="PwdHash Warning";
  438. SPH_strings["pwdhash.pwdkeywarn"]="PwdHash could not find a password field on this page.\nIt is possible, though unlikely, that the site trying to steal your password.\nDo not enter your PwdHash password into this page.";
  439. SPH_strings["pwdhash.pwdprefixwarn"]="You typed the PwdHash password prefix, but you are not currently in a password field that starts with the password prefix.\nIt is possible, though unlikely, that the site trying to steal your password.\nDo not enter your PwdHash password into this page.";
  440. SPH_strings["pwdhash.trustedeventwarn"]="JavaScript on this page may be interfering with your ability to enter a password.\nAs a precaution, the password field has been cleared.\nIf the problem persists, you might not be able to use PwdHash on this page.";
  441. SPH_strings["pwdhash.longpasswordwarn"]="Your password is too long to protect.";
  442. SPH_strings["pwdhash.shortpasswordwarn"]="Your password is too short to protect.";
  443. SPH_strings["pwdhash.saltempty"]="No salt set, you have to set one in Add-On options.";
  444. SPH_strings["pwdhash.legacy"]="Legacy mode enabled manually, reload page to use mode configured in options";
  445.  
  446. // SPH_getOptions()
  447. }
  448.  
  449. // SPH_init();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement