Implemented challenge. Not all example HTML files ready
[CGIscriptor.git] / Private / manual.html
blobf5dc6c57954f8ace08ccf16e0c094312ec5bec26
1 <html>
2 <head>
3 <title>
4 Private data
5 </title>
6 <script type="text/javascript">
7 function createCookie(name,value,days,path) {
8 if (days) {
9 var date = new Date();
10 date.setTime(date.getTime()+(days*24*60*60*1000));
11 var expires = "; expires="+date.toGMTString();
13 else var expires = "";
14 var match = document.cookie.match('/('+name+'\=[^\;]*\);/');
15 if(match)
16 document.cookie = document.cookie.replace(match[1], name+"="+value);
17 else
18 document.cookie = name+"="+value+expires+"; path=/"+path;
21 function readCookie(name) {
22 var nameEQ = name + "=";
23 var ca = document.cookie.split(';');
24 for(var i=0;i < ca.length;i++) {
25 var c = ca[i];
26 while (c.charAt(0)==' ') c = c.substring(1,c.length);
27 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
29 return null;
32 function eraseCookie(name) {
33 createCookie(name,"",-1);
36 // Combine the PASSWORD with the site SERVERSALT and hash it
37 // Combine this Hash iwth the extra SERVERSALT, and hash them
38 function HashPassword(extsalt) {
39 var hash = HashSessionSeed(extsalt);
40 var password = document.getElementById('PASSWORD');
41 if(password){
42 password.value = hash;
43 } else {
44 alert("NO PASSWORD IN FORM");
45 return 0;
47 return hash;
50 // REMEMBER: Set the session cookie BEFORE you hash the password!!!
51 function SetSessionCookie() {
52 var seed = '<SCRIPT TYPE="text/ssperl">$LOGINTICKET</SCRIPT>';
53 var hash = HashSessionSeed(seed);
54 // Dom.storage.enabled must be set!
55 if (!sessionStorage || typeof(sessionStorage) == 'undefined' ) {
56 alert('Your browser does not support HTML5 sessionStorage. Set Dom.storage.enabled or try upgrading.');
58 else sessionStorage.setItem("CGIscriptorPRIVATE", hash);
59 return hash;
62 function HashSessionSeed(sessionseed) {
63 var hash1 = "";
64 var hash2 = "";
65 var passwordvalue = document.getElementById('PASSWORD');
66 var saltvalue = document.getElementById('SERVERSALT');
67 var username = document.getElementById('USERNAME');
68 hash1 = hex_sha1(saltvalue.value+passwordvalue.value+username.value);
69 if(sessionseed != "")
70 hash2 = hex_sha1(sessionseed+hash1);
71 else
72 hash2 = hash1;
73 return hash2;
76 // Remember to hash the repeat too! Or else it will be send in the clear
77 function HashNewPassword() {
78 var hash1 = "";
79 var newpassword = document.getElementById('NEWPASSWORD');
80 var newpasswordrep = document.getElementById('NEWPASSWORDREP');
81 var username = document.getElementById('USERNAME');
82 if(newpassword.value == "" ) {
83 newpassword.value = "";
84 return 0;
86 if(newpasswordrep && (newpasswordrep.value == ""|| newpassword.value != newpasswordrep.value)) {
87 newpassword.value = "";
88 newpasswordrep.value = "";
89 return 0;
91 var saltvalue = document.getElementById('SERVERSALT');
92 hash1 = hex_sha1(saltvalue.value+newpassword.value+username.value);
93 newpassword.value = hash1;
94 newpasswordrep.value = hash1;
95 return hash1;
98 function XOR_hex_strings(hex1, hex2) {
99 var resultHex = "";
100 for(var i=0; i < hex1.length; ++i) {
101 var d1 = parseInt(hex1.charAt(i),16);
102 var d2 = parseInt(hex2.charAt(i),16);
103 var resultD = d1^d2;
104 resultHex = resultHex+resultD.toString(16);
106 return resultHex;
109 function EncryptNewPassword() {
110 var password = document.getElementById('PASSWORD');
111 var saltvalue = document.getElementById('SERVERSALT');
112 var login = document.getElementById('LOGINTICKET');
113 var newpassword = document.getElementById('NEWPASSWORD');
114 var newpasswordrep = document.getElementById('NEWPASSWORDREP');
115 var username = document.getElementById('USERNAME');
117 // This hashes the newpassword field!
118 HashNewPassword();
119 hash = hex_sha1(saltvalue.value+password.value+username.value);
120 hash2 = hex_sha1(login.value+hash);
121 var encrypted = XOR_hex_strings(hash2, newpassword.value);
122 newpassword.value = encrypted;
123 newpasswordrep.value = encrypted;
124 return encrypted;
127 function add_cgiparam(elem, attr, param) {
128 var elems = document.getElementsByTagName(elem);
129 for (var i = 0; i < elems.length; i++)
131 var n=elems[i][attr].indexOf("?");
132 if(n<0)
133 elems[i][attr] = elems[i][attr] + "?" + param;
134 else
135 elems[i][attr] = elems[i][attr] + "&" + param;
139 function setSessionParameters() {
140 var sessionset = readCookie("CGIscriptorSESSION");
141 if(!(sessionset && sessionset.match(/[\S]/)))return false;
143 var sessionticket = "";
144 sessionticket = sessionStorage.getItem("CGIscriptorPRIVATE");
145 createCookie("CGIscriptorSESSION",sessionticket, 1, "Private");
147 // Without cookies, use this
148 // var sessionparm = document.getElementById('SESSIONTICKET');
149 // if(sessionparm) sessionparm.value = sessionticket;
150 // add_cgiparam('a', 'href', "SESSIONTICKET="+sessionticket);
151 // add_cgiparam('form', 'action', "SESSIONTICKET="+sessionticket);
152 return true;
154 function setChallengeParameters() {
155 var sessionset = readCookie("CGIscriptorCHALLENGE");
156 if(!(sessionset && sessionset.match(/[\S]/)))return false;
158 var sessionticket = "";
159 var sessionkey = sessionStorage.getItem("CGIscriptorPRIVATE");
160 sessionticket = hex_sha1(sessionset+sessionkey);
162 createCookie("CGIscriptorCHALLENGE",sessionticket, 1, "Private");
164 // Without cookies, use this
165 // var sessionparm = document.getElementById('CHALLENGETICKET');
166 // if(sessionparm) sessionparm.value = sessionticket;
167 // add_cgiparam('a', 'href', "CHALLENGETICKET="+sessionticket);
168 // add_cgiparam('form', 'action', "CHALLENGETICKET="+sessionticket);
169 return true;
172 window.onload = function() {
173 var challengeSet = setChallengeParameters();
174 if(! challengeSet)
175 setSessionParameters();
176 return true;
178 </script>
180 <script type="text/javascript">
182 * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
183 * in FIPS 180-1
184 * Version 2.2 Copyright Paul Johnston 2000 - 2009.
185 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
186 * Distributed under the BSD License
187 * See http://pajhome.org.uk/crypt/md5 for details.
191 * Configurable variables. You may need to tweak these to be compatible with
192 * the server-side, but the defaults work in most cases.
194 var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
195 var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
198 * These are the functions you'll usually want to call
199 * They take string arguments and return either hex or base-64 encoded strings
201 function hex_sha1(s) { return rstr2hex(rstr_sha1(str2rstr_utf8(s))); }
202 function b64_sha1(s) { return rstr2b64(rstr_sha1(str2rstr_utf8(s))); }
203 function any_sha1(s, e) { return rstr2any(rstr_sha1(str2rstr_utf8(s)), e); }
204 function hex_hmac_sha1(k, d)
205 { return rstr2hex(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
206 function b64_hmac_sha1(k, d)
207 { return rstr2b64(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d))); }
208 function any_hmac_sha1(k, d, e)
209 { return rstr2any(rstr_hmac_sha1(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
212 * Perform a simple self-test to see if the VM is working
214 function sha1_vm_test()
216 return hex_sha1("abc").toLowerCase() == "a9993e364706816aba3e25717850c26c9cd0d89d";
220 * Calculate the SHA1 of a raw string
222 function rstr_sha1(s)
224 return binb2rstr(binb_sha1(rstr2binb(s), s.length * 8));
228 * Calculate the HMAC-SHA1 of a key and some data (raw strings)
230 function rstr_hmac_sha1(key, data)
232 var bkey = rstr2binb(key);
233 if(bkey.length > 16) bkey = binb_sha1(bkey, key.length * 8);
235 var ipad = Array(16), opad = Array(16);
236 for(var i = 0; i < 16; i++)
238 ipad[i] = bkey[i] ^ 0x36363636;
239 opad[i] = bkey[i] ^ 0x5C5C5C5C;
242 var hash = binb_sha1(ipad.concat(rstr2binb(data)), 512 + data.length * 8);
243 return binb2rstr(binb_sha1(opad.concat(hash), 512 + 160));
247 * Convert a raw string to a hex string
249 function rstr2hex(input)
251 try { hexcase } catch(e) { hexcase=0; }
252 var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
253 var output = "";
254 var x;
255 for(var i = 0; i < input.length; i++)
257 x = input.charCodeAt(i);
258 output += hex_tab.charAt((x >>> 4) & 0x0F)
259 + hex_tab.charAt( x & 0x0F);
261 return output;
265 * Convert a raw string to a base-64 string
267 function rstr2b64(input)
269 try { b64pad } catch(e) { b64pad=''; }
270 var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
271 var output = "";
272 var len = input.length;
273 for(var i = 0; i < len; i += 3)
275 var triplet = (input.charCodeAt(i) << 16)
276 | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
277 | (i + 2 < len ? input.charCodeAt(i+2) : 0);
278 for(var j = 0; j < 4; j++)
280 if(i * 8 + j * 6 > input.length * 8) output += b64pad;
281 else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
284 return output;
288 * Convert a raw string to an arbitrary string encoding
290 function rstr2any(input, encoding)
292 var divisor = encoding.length;
293 var remainders = Array();
294 var i, q, x, quotient;
296 /* Convert to an array of 16-bit big-endian values, forming the dividend */
297 var dividend = Array(Math.ceil(input.length / 2));
298 for(i = 0; i < dividend.length; i++)
300 dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
304 * Repeatedly perform a long division. The binary array forms the dividend,
305 * the length of the encoding is the divisor. Once computed, the quotient
306 * forms the dividend for the next step. We stop when the dividend is zero.
307 * All remainders are stored for later use.
309 while(dividend.length > 0)
311 quotient = Array();
312 x = 0;
313 for(i = 0; i < dividend.length; i++)
315 x = (x << 16) + dividend[i];
316 q = Math.floor(x / divisor);
317 x -= q * divisor;
318 if(quotient.length > 0 || q > 0)
319 quotient[quotient.length] = q;
321 remainders[remainders.length] = x;
322 dividend = quotient;
325 /* Convert the remainders to the output string */
326 var output = "";
327 for(i = remainders.length - 1; i >= 0; i--)
328 output += encoding.charAt(remainders[i]);
330 /* Append leading zero equivalents */
331 var full_length = Math.ceil(input.length * 8 /
332 (Math.log(encoding.length) / Math.log(2)))
333 for(i = output.length; i < full_length; i++)
334 output = encoding[0] + output;
336 return output;
340 * Encode a string as utf-8.
341 * For efficiency, this assumes the input is valid utf-16.
343 function str2rstr_utf8(input)
345 var output = "";
346 var i = -1;
347 var x, y;
349 while(++i < input.length)
351 /* Decode utf-16 surrogate pairs */
352 x = input.charCodeAt(i);
353 y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
354 if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
356 x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
357 i++;
360 /* Encode output as utf-8 */
361 if(x <= 0x7F)
362 output += String.fromCharCode(x);
363 else if(x <= 0x7FF)
364 output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
365 0x80 | ( x & 0x3F));
366 else if(x <= 0xFFFF)
367 output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
368 0x80 | ((x >>> 6 ) & 0x3F),
369 0x80 | ( x & 0x3F));
370 else if(x <= 0x1FFFFF)
371 output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
372 0x80 | ((x >>> 12) & 0x3F),
373 0x80 | ((x >>> 6 ) & 0x3F),
374 0x80 | ( x & 0x3F));
376 return output;
380 * Encode a string as utf-16
382 function str2rstr_utf16le(input)
384 var output = "";
385 for(var i = 0; i < input.length; i++)
386 output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
387 (input.charCodeAt(i) >>> 8) & 0xFF);
388 return output;
391 function str2rstr_utf16be(input)
393 var output = "";
394 for(var i = 0; i < input.length; i++)
395 output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
396 input.charCodeAt(i) & 0xFF);
397 return output;
401 * Convert a raw string to an array of big-endian words
402 * Characters >255 have their high-byte silently ignored.
404 function rstr2binb(input)
406 var output = Array(input.length >> 2);
407 for(var i = 0; i < output.length; i++)
408 output[i] = 0;
409 for(var i = 0; i < input.length * 8; i += 8)
410 output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32);
411 return output;
415 * Convert an array of big-endian words to a string
417 function binb2rstr(input)
419 var output = "";
420 for(var i = 0; i < input.length * 32; i += 8)
421 output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF);
422 return output;
426 * Calculate the SHA-1 of an array of big-endian words, and a bit length
428 function binb_sha1(x, len)
430 /* append padding */
431 x[len >> 5] |= 0x80 << (24 - len % 32);
432 x[((len + 64 >> 9) << 4) + 15] = len;
434 var w = Array(80);
435 var a = 1732584193;
436 var b = -271733879;
437 var c = -1732584194;
438 var d = 271733878;
439 var e = -1009589776;
441 for(var i = 0; i < x.length; i += 16)
443 var olda = a;
444 var oldb = b;
445 var oldc = c;
446 var oldd = d;
447 var olde = e;
449 for(var j = 0; j < 80; j++)
451 if(j < 16) w[j] = x[i + j];
452 else w[j] = bit_rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
453 var t = safe_add(safe_add(bit_rol(a, 5), sha1_ft(j, b, c, d)),
454 safe_add(safe_add(e, w[j]), sha1_kt(j)));
455 e = d;
456 d = c;
457 c = bit_rol(b, 30);
458 b = a;
459 a = t;
462 a = safe_add(a, olda);
463 b = safe_add(b, oldb);
464 c = safe_add(c, oldc);
465 d = safe_add(d, oldd);
466 e = safe_add(e, olde);
468 return Array(a, b, c, d, e);
473 * Perform the appropriate triplet combination function for the current
474 * iteration
476 function sha1_ft(t, b, c, d)
478 if(t < 20) return (b & c) | ((~b) & d);
479 if(t < 40) return b ^ c ^ d;
480 if(t < 60) return (b & c) | (b & d) | (c & d);
481 return b ^ c ^ d;
485 * Determine the appropriate additive constant for the current iteration
487 function sha1_kt(t)
489 return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
490 (t < 60) ? -1894007588 : -899497514;
494 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
495 * to work around bugs in some JS interpreters.
497 function safe_add(x, y)
499 var lsw = (x & 0xFFFF) + (y & 0xFFFF);
500 var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
501 return (msw << 16) | (lsw & 0xFFFF);
505 * Bitwise rotate a 32-bit number to the left.
507 function bit_rol(num, cnt)
509 return (num << cnt) | (num >>> (32 - cnt));
511 </script>
512 </head>
513 <body>
514 <p ALIGN=RIGHT><a href="?LOGOUT">Logout</a></p>
515 <p ALIGN=RIGHT><a href="ChangePassword.html">Change Password</a></p>
516 <h1 align=CENTER>Manual</h1>
517 <p align=CENTER>Logged in from <script type="text/ssperl" CGI='$LOGINIPADDRESS="" $LOGINPATH=""'>"$LOGINIPADDRESS $LOGINPATH"</script></p>
518 <p align=CENTER><form method=GET action="index.html">
519 <p align=CENTER><input type="submit" value="Go back to home page" /></p>
520 <input type="hidden" name="SESSIONTICKET" id="SESSIONTICKET" value="" />
521 <input type="hidden" name="CHALLENGETICKET" id="CHALLENGETICKET" value="" /><br />
522 </form>
523 </p>
526 <A NAME="SESSIONTICKETS"><H2 ALIGN="CENTER">SERVER SIDE SESSIONS AND ACCESS CONTROL (LOGIN)</H2></A>
528 An infrastructure for user acount authorization and file access control
529 is available. Each request is matched against a list of URL path patterns.
530 If the request matches, a Session Ticket is required to access the URL.
531 This Session Ticket should be present as a CGI parameter or Cookie:
532 </p>
534 CGI: SESSIONTICKET=&lt;value&gt;<br />
535 Cookie: CGIscriptorSESSION=&lt;value&gt;</p>
537 The example implementation stores Session Tickets as files in a local
538 directory. To create Session Tickets, a Login request must be given
539 with a LOGIN=&lt;value&gt; CGI parameter, a user name and a (doubly hashed)
540 password. The user name and (singly hashed) password are stored in a
541 PASSWORD ticket with the same name as the user account (name cleaned up
542 for security).
543 </p>
545 The example session model implements 3 functions:
546 <ol>
547 <li>Login<br />
548 The password is hashed with the user name and server side salt, and then
549 hashed with a Random salt. Client and Server both perform these actions and the
550 Server only grants access if restults are the same. The server side only
551 stores the password hashed with the user name and
552 server side salt. Neither the plain password, nor the hashed password is
553 ever exchanged. Only values hashed with the one-time salt are exchanged.
554 </li>
555 <li>Session<br />
556 For every access to a restricted URL, the Session Ticket is checked before
557 access is granted. There are three session modes. The first uses a fixed
558 Session Ticket that is stored as a cookie value in the browser (actually,
559 as a sessionStorage value). The second uses only the IP address at login
560 to authenticate requests. The third
561 is a Challenge mode, where the client has to calculate the value of the
562 one-time Session Ticket from a value derived from the password and
563 a random string.
564 </li>
565 <li>Password Change<br />
566 A new password is hashed with the user name and server side salt, and
567 then encrypted (XORed)
568 with the old password hashed with the user name and salt. That value is
569 exchanged and XORed with the stored old hashed(salt+password+username).
570 Again, the stored password value is never exchanged unencrypted.
571 </li>
572 </ol>
573 </p>
574 <H3 ALIGN="CENTER">Implementation</H3>
576 The session authentication mechanism is based on the exchange of ticket
577 identifiers. A ticket identifier is just a string of characters, a name
578 or a random 40 character hexadecimal string. Ticket identifiers should be
579 "safe" filenames (except user names). There are four types of tickets:
580 <ul>
581 <li>PASSWORD: User account descriptors, including a user name and password</li>
582 <li>LOGIN: Temporary anonymous tickets used during login</li>
583 <li>SESSION: Reusable authetication tokens that allow access to specific URLs</li>
584 <li>IPADDRESS: authetication tokens that allow access based on the IP address of the request</li>
585 <li>CHALLENGE: One-time authetication tokens that allow access to specific URLs</li>
586 </ul>
587 All tickets can have an expiration date in the form of a time duration
588 from creation, in seconds, minutes, hours, or days (<em>+duration</em>[smhd]).
589 An absolute time can be given in seconds since the epoch of the server host.
590 </p>
592 A Login page should create a LOGIN ticket file locally and send a
593 server specific SALT, a Random salt, and a LOGIN ticket
594 identifier. The server side compares the username and hashed password,
595 actually hashed(Random salt+hashed(SALT+password)) from the client with
596 the values it calculates from the stored Random salt from the LOGIN
597 ticket and the hashed(SALT+password) from the PASSWORD ticket. If
598 successful, a new SESSION ticket is generated as a hash sum of the LOGIN
599 ticket and the stored password. This SESSION ticket should also be
600 generated by the client and stored as sessionStorage and cookie values
601 as needed. The Username, IP address and Path are available as
602 $LoginUsername, $LoginIPaddress, and $LoginPath, respectively.
603 </p>
605 In the current example implementation, all random values are created as
606 a full, 160 bit SHA1 hash (Hex strings) of 32 bytes read from
607 /dev/urandom.
608 </p>
610 <H3 ALIGN="CENTER">Security considerations with Session tickets</H3>
612 For strong security, please use end-to-end encryption. This can be
613 achieved using a VPN (Virtual Private Network), SSH tunnel, or a HTTPS
614 capable server with OpenSSL. The session ticket system of CGIscriptor.pl
615 is intended to be used as a simple authentication mechanism WITHOUT
616 END-TO-END ENCRYPTION. The authenticating mechanism tries to use some
617 simple means to protect the authentication process from eavesdropping.
618 For this it uses a secure hash function, SHA1. For all practial purposes,
619 it is impossible to "decrypt" a SHA1 sum. But this login scheme is
620 only as secure as your browser. Which, in general, is not secure.
621 </p>
623 Humans tend to reuse passwords. A compromise of a site running
624 CGIscriptor.pl could therefore lead to a compromise of user accounts at
625 other sites. Therefore, plain text passwords are never stored, used, or
626 even exchanged. Instead, a server site SALT value is "encrypted" with
627 the plain password and user name, actually, all are concatenated and hashed
628 with a one-way secure hash function (SHA1) into a single string.
629 Whenever the word "password" is used, this hash sum is meant.
630 </p>
632 For the authentication and a change of password, the (old) password
633 is used to "encrypt" a random one-time token or the new password,
634 respectively. For authentication, decryption is not needed, so a secure
635 hash function (SHA1) is used to create a one-way hash sum "encryption".
636 A new password must be decrypted. New passwords are encryped by XORing
637 them with the old password.
638 </p>
642 </body>
643 </html>