/* * Copyright (c) 2009 Greg Stoll * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ var passwordhash = { SpecialCharMode: { allow: 0, force: 1, disallow: 2}, onLoad: function() { // initialization code this.initialized = true; this.strings = document.getElementById("passwordhash-strings"); /*document.getElementById("contentAreaContextMenu") .addEventListener("popupshowing", function(e) { this.showContextMenu(e); }, false);*/ }, popupNodeIsTextBox: function() { if (document.popupNode.localName.toUpperCase() == 'INPUT') { var inputType = document.popupNode.type; if (inputType.toLowerCase() == 'text' || inputType.toLowerCase() == 'password') { return true; } } return false; }, onMenuItemCommand: function(e) { var mainWindow = document.getElementById("passwordhash-mainWindow"); var ownerDocument = document.popupNode.ownerDocument; // See if we're http or https and set the icon accordingly. var isHttps = (ownerDocument.location.protocol.substr(0, 5) == 'https'); document.getElementById("passwordhash-icon-secure").setAttribute('hidden', !isHttps); document.getElementById("passwordhash-icon-notsecure").setAttribute('hidden', isHttps); var backgroundColor = isHttps ? '#FFFFAA' : '#EEEEEE'; document.getElementById('passwordhash-topbar').setAttribute('style', 'background-color: ' + backgroundColor); // See if we were popped up on a textbox or password box. // Save off the popupNode, since it's not valid later. this.originalPopupNode = document.popupNode; this.onTextBox = this.popupNodeIsTextBox(); document.getElementById('passwordhash-result-insert').setAttribute('disabled', !this.onTextBox); if (!this.onTextBox) { // Make sure the "fill in" isn't selected. document.getElementById('passwordhash-result-radiogroup').selectedIndex = 1; } else { // We could be fill in or copy to clipboard. Read the preference to // see what our default is. var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService); prefs = prefs.getBranch("extensions.passwordhash."); if (prefs.getPrefType('fillInTextField') == 0) { // This preference has not been created. Create it now and // default it to true. prefs.setBoolPref('fillInTextField', true); } var fillIn = prefs.getBoolPref('fillInTextField'); // Set the radio button document.getElementById('passwordhash-result-radiogroup').selectedIndex = fillIn ? 0 : 1; } // Fill in the domain var numDotsToTake = 3; var domainChunks = ownerDocument.domain.split('.'); var tld = domainChunks[domainChunks.length - 1]; if (tld == 'aero' || tld == 'asia' || tld == 'biz' || tld == 'cat' || tld == 'com' || tld == 'coop' || tld == 'edu' || tld == 'gov' || tld == 'info' || tld == 'int' || tld == 'jobs' || tld == 'mil' || tld == 'mobi' || tld == 'museum' || tld == 'name' || tld == 'net' || tld == 'org' || tld == 'pro' || tld == 'tel' || tld == 'travel') { numDotsToTake = 2; } // Common exceptions. if (domainChunks.length > 2 && (tld == 'org' && domainChunks[domainChunks.length - 2] == 'dyndns')) { numDotsToTake = 3; } var startIndex = domainChunks.length - numDotsToTake; if (startIndex < 0) { startIndex = 0; } document.getElementById('passwordhash-domainBox').value = domainChunks.slice(startIndex).join('.'); mainWindow.openPopup(document.popupNode, "after_pointer", 0, 0, false, false); document.getElementById('passwordhash-specialmode').selectedIndex = 0; document.getElementById('passwordhash-master-passwordbox').value = ''; document.getElementById('passwordhash-master-passwordbox').focus(); //window.openDialog('chrome://passwordhash/content/passwordWindow.xul', 'somename', 'chrome'); }, isSpecialChar: function(ch) { return ch == '+' || ch == '/'; }, computePassword: function(master, domain, numChars, specialMode) { // First, hash the master password. // // Note we're using the base 64 versions of these var masterHash = b64_sha1(master); // Now, concatenate the domain and hash that. var fullPassword = b64_sha1(masterHash + domain); // Finally, return the requested number of characters. var realPassword = fullPassword.substr(0, numChars); if (specialMode == this.SpecialCharMode.force) { var hexPassword = hex_sha1(masterHash + domain); var specialChars = '!@#$%^&*()~`\'<>?'; // Take characters from the end so hopefully we will be using // different data. var hexLen = hexPassword.length; var ch = parseInt(hexPassword.charAt(hexLen - 1), 16); var index = parseInt(hexPassword.substr(hexLen - 3, 2), 16) % realPassword.length; realPassword = realPassword.substr(0, index) + specialChars.charAt(ch) + realPassword.substr(index + 1); } else if (specialMode == this.SpecialCharMode.disallow) { var hexPassword = hex_sha1(masterHash + domain); var extraChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var nextExtraCharIndex = 0; var hexIndexToTry = hexPassword.length - 1; // Find all positions in realPassword that are special chars. for (var i = 0; i < realPassword.length; ++i) { if (this.isSpecialChar(realPassword.charAt(i))) { // Find the next hex index that's not special and copy it. // Take characters from the end so hopefully we will be using // different data. // THIS IS BROKEN but leaving for backwards-compat while (hexIndexToTry >= 0 || this.isSpecialChar(hexPassword.charAt(hexIndexToTry))) { hexIndexToTry--; } var nonSpecialChar; if (hexIndexToTry > 0) { // Copy this character to this position. nonSpecialChar = hexPassword.charAt(hexIndexToTry); hexIndexToTry--; } else { // Uh-oh, we're out of "extra" characters to pull. // just pull from a list. nonSpecialChar = extraChars.charAt(nextExtraCharIndex); nextExtraCharIndex++; } realPassword = realPassword.substr(0, i) + nonSpecialChar + realPassword.substr(i+1); } } } return realPassword; }, doPassword: function(numChars) { // First get the relevant data and calculate the password. var master = document.getElementById('passwordhash-master-passwordbox').value; var domain = document.getElementById('passwordhash-domainBox').value; var specialMode = parseInt(document.getElementById('passwordhash-specialmode').value); var result = this.computePassword(master, domain, numChars, specialMode); // Now copy or fill in the result. var doFillIn = document.getElementById('passwordhash-result-radiogroup').selectedIndex == 0; // Check and see if the popupNode is valid, because if it isn't we shouldn't // set the preference. if (this.onTextBox) { var prefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService); prefs = prefs.getBranch("extensions.passwordhash."); prefs.setBoolPref('fillInTextField', doFillIn); } if (doFillIn) { // Fill in // To be safe, make sure popupNode is still acceptable if (this.onTextBox) { this.originalPopupNode.value = result; } } else { // Copy to clipboard var clipboardService = Components.classes["@mozilla.org/widget/clipboardhelper;1"] .getService(Components.interfaces.nsIClipboardHelper); clipboardService.copyString(result); } // Close the popup var mainWindow = document.getElementById("passwordhash-mainWindow"); mainWindow.hidePopup(); }, }; window.addEventListener("load", function(e) { passwordhash.onLoad(e); }, false);