Break window and document up in Options
[skeletonkey.git] / core.js
1 /* Copyright (c) 2012 Robert Sesek <http://robert.sesek.com>
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining a copy
4 * of this software and associated documentation files (the "Software"), to
5 * deal in the Software without restriction, including without limitation the
6 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 * sell copies of the Software, and to permit persons to whom the Software is
8 * furnished to do so, subject to the following conditions:
9 *
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 * DEALINGS IN THE SOFTWARE.
20 */
21
22 (function main() {
23 if (typeof chrome !== 'undefined') {
24 // TODO: load the extension JS
25 } else {
26 // TODO: load the hosted JS
27 }
28
29 document.addEventListener('DOMContentLoaded', function() {
30 var controller = new SkeletonKey(document);
31 });
32 })();
33
34 /**
35 * SkeletonKey is view controller for generating secure passwords.
36 *
37 * @param {HTMLDocument} doc The document on which to operate.
38 */
39 var SkeletonKey = SkeletonKey || function(doc) {
40 this._master = doc.getElementById('master');
41 this._sitekey = doc.getElementById('sitekey');
42 this._username = doc.getElementById('username');
43 this._password = doc.getElementById('password');
44 this._generateButton = doc.getElementById('generate');
45
46 // If this is an extension, use defaults until the Chrome settings are loaded.
47 var win = null;
48 if (!this._isChromeExtension())
49 win = window;
50 this._options = new SkeletonKeyOptions(null, win);
51
52
53 this._init();
54 };
55
56 /**
57 * The number of iterations to perform in PBKDF2.
58 * @const {int}
59 */
60 SkeletonKey.prototype.ITERATIONS = 1000;
61 /**
62 * The size of the key, in bytes.
63 * @const {int}
64 */
65 SkeletonKey.prototype.KEYSIZE = 256/32;
66
67 /**
68 * Initializes event handlers for the page.
69 * @private
70 */
71 SkeletonKey.prototype._init = function() {
72 this._generateButton.onclick = this._onGenerate.bind(this);
73
74 this._master.onkeyup = this._nextFieldInterceptor.bind(this);
75 this._sitekey.onkeyup = this._nextFieldInterceptor.bind(this);
76 this._username.onkeyup = this._nextFieldInterceptor.bind(this);
77
78 this._password.onclick = this._selectPassword.bind(this);
79 this._password.labels[0].onclick = this._selectPassword.bind(this);
80
81 this._initChromeExtension();
82
83 this._master.focus();
84 };
85
86 /**
87 * Event handler for generating a new password.
88 * @param {Event} e
89 * @private
90 */
91 SkeletonKey.prototype._onGenerate = function(e) {
92 var salt = this._username.value + '@' + this._sitekey.value;
93
94 // |key| is a WordArray of 32-bit words.
95 var key = CryptoJS.PBKDF2(this._master.value, salt,
96 {keySize: this.KEYSIZE, iterations: this.ITERATIONS});
97
98 var hexString = key.toString();
99 hexString = this._capitalizeKey(hexString);
100
101 var maxLength = this._options.getMaximumPasswordLength();
102 if (hexString.length > maxLength)
103 hexString = hexString.substr(0, maxLength);
104
105 this._password.value = hexString;
106 this._selectPassword();
107 };
108
109 /**
110 * Takes a HEX string and returns a mixed-case string.
111 * @param {string} key
112 * @return string
113 * @private
114 */
115 SkeletonKey.prototype._capitalizeKey = function(key) {
116 // |key| is too long for a decent password, so try and use the second half of
117 // it as the basis for capitalizing the key.
118 var capsSource = null;
119 var keyLength = key.length;
120 if (keyLength / 2 <= this._options.getMinimumPasswordLength()) {
121 capsSouce = key.substr(0, keyLength - this._options.getMinimumPasswordLength());
122 } else {
123 capsSource = key.substr(keyLength / 2);
124 }
125
126 if (!capsSource || capsSource.length < 1) {
127 return key;
128 }
129
130 key = key.substr(0, capsSource.length);
131 var capsSourceLength = capsSource.length;
132
133 var j = 0;
134 var newKey = "";
135 for (var i = 0; i < key.length; i++) {
136 var c = key.charCodeAt(i);
137 // If this is not a lowercase letter or there's no more source, skip.
138 if (c < 0x61 || c > 0x7A || j >= capsSourceLength) {
139 newKey += key[i];
140 continue;
141 }
142
143 var makeCap = capsSource.charCodeAt(j++) % 2;
144 if (makeCap)
145 newKey += String.fromCharCode(c - 0x20);
146 else
147 newKey += key[i];
148 }
149
150 return newKey;
151 };
152
153 /**
154 * Checks if the given key event is from the enter key and moves onto the next
155 * field or generates the password.
156 * @param {Event} e
157 * @private
158 */
159 SkeletonKey.prototype._nextFieldInterceptor = function(e) {
160 if (e.keyCode != 0xD)
161 return;
162
163 if (this._master.value == "") {
164 this._master.focus();
165 } else if (this._sitekey.value == "") {
166 this._sitekey.focus();
167 } else if (this._username.value == "") {
168 this._username.focus();
169 } else {
170 this._generateButton.click();
171 }
172 };
173
174 /**
175 * Selects the contents of the generated password.
176 * @private
177 */
178 SkeletonKey.prototype._selectPassword = function() {
179 this._password.focus();
180 this._password.select();
181 };
182
183 /**
184 * Initalizes the Chrome extension pieces if running inside chrome.
185 * @private
186 */
187 SkeletonKey.prototype._initChromeExtension = function() {
188 return;
189
190 // getCurrent is undefined for backround pages. Need content script.
191 chrome.tabs.getCurrent(function (tab) {
192 if (tab == null)
193 return;
194
195 var url = tab.url;
196 if (url == null || url == "")
197 return;
198
199 var siteKey = url.search(/https?:\/\/(www.?|login|accounts?)\.(.*)\.(com?|net|org|edu|biz|info)?.*/);
200 console.log(siteKey);
201 });
202 };
203
204 /**
205 * Checks if SkeletonKey is running as a Chrome extension.
206 * @returns {bool}
207 * @private
208 */
209 SkeletonKey.prototype._isChromeExtension = function() {
210 return typeof chrome != 'undefined' && typeof chrome.extension != 'undefined';
211 };