@sexycoders/libauth.js
Version:
A full service for asymetric passwordless authentication.
650 lines (572 loc) • 22.6 kB
JavaScript
/*jslint browser: true, sloppy: true */
//adapted from https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-08#appendix-C
function base64urlEncode(arg) {
var s = window.btoa(arg); // Regular base64 encoder
s = s.split('=')[0]; // Remove any trailing '='s
s = s.replace(/\+/g, '-'); // 62nd char of encoding
s = s.replace(/\//g, '_'); // 63rd char of encoding
return s;
}
function base64urlDecode(s) {
s = s.replace(/-/g, '+'); // 62nd char of encoding
s = s.replace(/_/g, '/'); // 63rd char of encoding
switch (s.length % 4) { // Pad with trailing '='s
case 0: // No pad chars in this case
break;
case 2: // Two pad chars
s += "==";
break;
case 3: // One pad char
s += "=";
break;
default:
throw "Illegal base64url string!";
}
return window.atob(s); // Standard base64 decoder
}
/*jslint browser: true, devel: true, sloppy: true, vars: true*/
/*globals Uint8Array, Promise */
var extractable = true;
var encodePrivateKey, encodePublicKey;
function wrap(text, len) {
var length = len || 72, i, result = "";
for (i = 0; i < text.length; i += length) {
result += text.slice(i, i + length) + "\n";
}
return result;
}
function rsaPrivateKey(key) {
return window.btoa("-----BEGIN RSA PRIVATE KEY-----\n" + key + "-----END RSA PRIVATE KEY-----");
}
function arrayBufferToBase64(buffer) {
var binary = '', i;
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (i = 0; i < len; i += 1) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
function generateKeyPair(alg, size, name) {
return window.crypto.subtle.generateKey({
name: "RSASSA-PKCS1-v1_5",
modulusLength: 4096, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: { name: "SHA-256" } //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
},
extractable,
["sign", "verify"]
).then(function (key) {
var privateKey = window.crypto.subtle.exportKey(
"jwk",
key.privateKey
).then(encodePrivateKey).then(wrap).then(rsaPrivateKey);
var publicKey = window.crypto.subtle.exportKey(
"jwk",
key.publicKey
).then(function (jwk) {
return encodePublicKey(jwk, name);
});
return Promise.all([privateKey, publicKey]);
});
}
/*jslint browser: true, devel: true, bitwise: true, sloppy: true, vars: true*/
var base64urlDecode;
function arrayToString(a) {
return String.fromCharCode.apply(null, a);
}
function stringToArray(s) {
return s.split('').map(function (c) {
return c.charCodeAt();
});
}
function base64urlToArray(s) {
return stringToArray(base64urlDecode(s));
}
function pemToArray(pem) {
return stringToArray(window.atob(pem));
}
function arrayToPem(a) {
return window.btoa(a.map(function (c) {
return String.fromCharCode(c);
}).join(''));
}
function arrayToLen(a) {
var result = 0, i;
for (i = 0; i < a.length; i += 1) {
result = result * 256 + a[i];
}
return result;
}
function integerToOctet(n) {
var result = [];
for (true; n > 0; n = n >> 8) {
result.push(n & 0xFF);
}
return result.reverse();
}
function lenToArray(n) {
var oct = integerToOctet(n), i;
for (i = oct.length; i < 4; i += 1) {
oct.unshift(0);
}
return oct;
}
function decodePublicKey(s) {
var split = s.split(" ");
var prefix = split[0];
if (prefix !== "ssh-rsa") {
throw ("Unknown prefix:" + prefix);
}
var buffer = pemToArray(split[1]);
var nameLen = arrayToLen(buffer.splice(0, 4));
var type = arrayToString(buffer.splice(0, nameLen));
if (type !== "ssh-rsa") {
throw ("Unknown key type:" + type);
}
var exponentLen = arrayToLen(buffer.splice(0, 4));
var exponent = buffer.splice(0, exponentLen);
var keyLen = arrayToLen(buffer.splice(0, 4));
var key = buffer.splice(0, keyLen);
return {type: type, exponent: exponent, key: key, name: split[2]};
}
function checkHighestBit(v) {
if (v[0] >> 7 === 1) { // add leading zero if first bit is set
v.unshift(0);
}
return v;
}
function jwkToInternal(jwk) {
return {
type: "ssh-rsa",
exponent: checkHighestBit(stringToArray(base64urlDecode(jwk.e))),
name: "name",
key: checkHighestBit(stringToArray(base64urlDecode(jwk.n)))
};
}
function encodePublicKey(jwk, name) {
var k = jwkToInternal(jwk);
k.name = name;
var keyLenA = lenToArray(k.key.length);
var exponentLenA = lenToArray(k.exponent.length);
var typeLenA = lenToArray(k.type.length);
var array = [].concat(typeLenA, stringToArray(k.type), exponentLenA, k.exponent, keyLenA, k.key);
var encoding = arrayToPem(array);
return k.type + " " + encoding + " " + k.name;
}
function asnEncodeLen(n) {
var result = [];
if (n >> 7) {
result = integerToOctet(n);
result.unshift(0x80 + result.length);
} else {
result.push(n);
}
return result;
}
function encodePrivateKey(jwk) {
var order = ["n", "e", "d", "p", "q", "dp", "dq", "qi"];
var list = order.map(function (prop) {
var v = checkHighestBit(stringToArray(base64urlDecode(jwk[prop])));
var len = asnEncodeLen(v.length);
return [0x02].concat(len, v); // int tag is 0x02
});
var seq = [0x02, 0x01, 0x00]; // extra seq for SSH
seq = seq.concat.apply(seq, list);
var len = asnEncodeLen(seq.length);
var a = [0x30].concat(len, seq); // seq is 0x30
return arrayToPem(a);
}
function ssh_create(e,async_flag)
{
var ssh=new Object();
ssh.alg="RSASSA-PKCS1-v1_5";
ssh.size="4096";
ssh.name="username";
var keys=generateKeyPair(ssh.alg, ssh.size, ssh.name);
//returns array promise first cell is private and second is public
keys.then(function(keys){
var privateKey=atob(keys[0]);
var publicKey=keys[1];
//localStorage.setItem("SSH_Public",publicKey);
window.__auth_system._rsa=publicKey;
window.__auth_system.pRSA=privateKey;
//hashing password with md5 for IV
var md = forge.md.md5.create();
md.update(e.password);
var cipher = forge.cipher.createCipher('AES-CBC',md.digest().toHex());
delete md;
var md = forge.md.sha256.create();
md.update(e.password);
cipher.start({iv: md.digest().toHex().substring(0,16)});
cipher.update(forge.util.createBuffer(privateKey));
cipher.finish();
var encrypted = cipher.output;
delete md;
window.__auth_system._enc_prsa=btoa(encrypted.data);
//console.log(window.__auth_system._enc_prsa);
if(async_flag)
{
var t=new Event("SSH_CREATE_SIG");
window.dispatchEvent(t);
}
});
};
function retrieve_key(data)
{
var json_send=new Object();
json_send.command="request_private";
json_send.user=new Object();
json_send.user.id=data.email;
var send=btoa(JSON.stringify(json_send));
$.ajax({
type: 'POST',
headers: {"Access-Control-Allow-Origin":"http://auth-serve.localhost"},
url: window.__auth_system.auth_server,
data: send,
success:
function(response)
{
//console.log("server response: "+atob(response));
var res=JSON.parse(atob(response));
window.__auth_system.set_enc_prsa(res.pRSA);
window.__auth_system.KEY_SET=1;
},
async:false
});
}
function Login(k)
{
var data=new Object();
data.email=($('form').serializeArray()[0].value);
data.password=($('form').serializeArray()[1].value);
window.__auth_system.user=data.email;
if(!window.__auth_system.KEY_SET)
{
retrieve_key(data);
}
var encrypted_ssh=atob(window.__auth_system._enc_prsa);
var md = forge.md.md5.create();
md.update(data.password);
var decipher = forge.cipher.createDecipher('AES-CBC',md.digest().toHex());
delete md;
var md = forge.md.sha256.create();
md.update(data.password);
decipher.start({iv: md.digest().toHex().substring(0,16)});
decipher.update(forge.util.createBuffer(encrypted_ssh));
decipher.finish();
var decrypted = decipher.output;
window.__auth_system.setpRSA(decrypted.data);
//console.log("old key was:");
//console.log(decrypted);
delete md;
var seed=Math.floor(Math.random()*1000000000)+1;
//var signature=Sign(seed,decrypted.bytes());
var signature=Sign(seed,decrypted.bytes());
//will deprecate and move to custom class
var json_send=new Object();
json_send.command="request_connection";
json_send.user=new Object();
json_send.user.id=window.__auth_system.user;
json_send.user.seed=seed;
json_send.user.sign=btoa(signature);
json_send.update=new Object();
var ssh_pass=new Object();
ssh_pass.password=data.password;
//window.addEventListener("SSH_CREATE_SIG",function(){
json_send.update.pRSA=window.__auth_system._enc_prsa;
json_send.update.RSA=window.__auth_system._rsa;
//console.log("new key_is");
//console.log(window.__auth_system.pRSA);
console.log("Json Is: "+JSON.stringify(json_send));
var send=btoa(JSON.stringify(json_send));
$.ajax({
type: 'POST',
headers: {"Access-Control-Allow-Origin":"auth-serve.localhost"},
url: window.__auth_system.auth_server,
data: send,
success:
function(response)
{
console.log("server response: "+atob(response));
if(data.message=="connection_refused")
{
alert("Authentication failed!!! Please try again!!!\nThis incident will be reported!!!");
return 1;
}
window.__auth_system.INIT_FLAG=1;
//console.log(window.__auth_system);
if(window.__auth_system.REDIRECT_FLAG==true)
MOVE(window.__auth_system.parent,true);
else
MOVE(window.__auth_system.home,true);
},
async:false
});
//},{once:true});
//ssh_create(ssh_pass,true);
};
function refresh(user)
{
var data=new Object();
data.password=($('form').serializeArray()[0].value);
window.__auth_system.user=user;
//var data=new Object();
//data.email=($('form').serializeArray()[0].value);
//data.password=($('form').serializeArray()[1].value);
if(!window.__auth_system.KEY_SET)
{
retrieve_key(data);
}
var encrypted_ssh=atob(window.__auth_system._enc_prsa);
var t=new Object();
t.md = forge.md.md5.create();
t.md.update(data.password);
var decipher = forge.cipher.createDecipher('AES-CBC',t.md.digest().toHex());
delete t.md;
t.md = forge.md.sha256.create();
t.md.update(data.password);
decipher.start({iv: t.md.digest().toHex().substring(0,16)});
decipher.update(forge.util.createBuffer(encrypted_ssh));
decipher.finish();
var decrypted = decipher.output;
window.__auth_system.setpRSA(decipher.output.bytes());
console.log(decrypted);
delete t.md;
var seed=Math.floor(Math.random()*1000000000)+1;
//var signature=Sign(seed,decrypted.bytes());
var signature=Sign(seed,decrypted.bytes());
//will deprecate and move to custom class
var json_send=new Object();
json_send.command="refresh_connection";
json_send.user=new Object();
json_send.user.id=window.__auth_system.user;
json_send.user.seed=seed;
json_send.user.sign=btoa(signature);
window.__auth_system.user=data.email;
window.pRSA=decrypted.data;
console.log("Json Is: "+JSON.stringify(json_send));
var send=btoa(JSON.stringify(json_send));
$.ajax({
type: 'POST',
headers: {"Access-Control-Allow-Origin":"auth-serve.localhost"},
url: window.__auth_system.auth_server,
data: send,
success:
function(response)
{
console.log("server response: "+atob(response));
if(data.message=="connection_refused")
{
alert("Authentication failed!!! Please try again!!!\nThis incident will be reported!!!");
return 1;
}
},
async:false
});
}
function Register(k)
{
var data=new Object();
data.email=($('form').serializeArray()[0].value);
data.password=($('form').serializeArray()[1].value);
var json_send=new Object();
json_send.command="register_user";
json_send.user=new Object();
json_send.user.id=data.email;
console.log("Json Is: "+JSON.stringify(json_send));
json_send=btoa(JSON.stringify(json_send));
window.__auth_system.user=data.email;
//var ssh_pass=new Object();
//ssh_pass.password=data.password;
//ssh_create(ssh_pass);
var ssh_pass=new Object();
ssh_pass.password=data.password;
ssh_create(ssh_pass,false);
$.ajax({
type: 'POST',
headers: {"Access-Control-Allow-Origin":"auth-serve.localhost"},
url: window.__auth_system.auth_server,
data: json_send,
success:
function(response)
{
console.log("server response: "+atob(response));
var R=JSON.parse(atob(response));
if(R.message=="verify_email")
{
window.alert("A verification code has been sent to your email!\nPlease copy that into the box to complete your registration!");
}
},
async:false
});
};
function Verify()
{
var data=new Object();
data.code=($('form').serializeArray()[0].value);
console.log(data);
//var ssh_pass=new Object();
//ssh_pass.password=data.password;
//await ssh_create(ssh_pass);
//ssh_create(ssh_pass);
var json_send=new Object();
json_send.command="verify_email";
json_send.user=new Object();
json_send.user.id=window.__auth_system.user;
json_send.user.code=data.code;
json_send.user.RSA=window.__auth_system._rsa;
json_send.user.pRSA=window.__auth_system._enc_prsa;
console.log(json_send);
console.log("Json Is: "+JSON.stringify(json_send));
json_send=btoa(JSON.stringify(json_send));
$.ajax({
type: 'POST',
headers: {"Access-Control-Allow-Origin":"localhost:56083"},
url: window.__auth_system.auth_server,
data: json_send,
success:
function(response)
{
alert("Registration Complete!\nYou can now login!");
ResetToLogin();
},
async:false
});
}
function Sign(data,key)
{
var pki = forge.pki;
var pKey = pki.privateKeyFromPem(key);
var md = forge.md.sha256.create();
md.update(data,'utf8');
return pKey.sign(md);
}
function RenderRegister()
{
console.log("CHECK");
var t=document.getElementById("submit_b");
t.innerHTML="register";
t.removeAttribute("onclick");
t.setAttribute("onclick","event.preventDefault();Register();RenderVerify();return false;");
}
function RenderVerify()
{
//removing password field
var t_form=document.getElementById("main_form");
//have to create it again because browser force-autofills as email if not
document.getElementById("password-container").remove();
document.getElementById("redirect").remove();
var username=document.createElement("div");
username.classList.add("form-group");
username.setAttribute("id","email-container");
var username_in=document.createElement("input");
username_in.setAttribute("id","email-container-input");
username_in.setAttribute("type","text");
username_in.setAttribute("name","code");
username_in.classList.add("form-control");
username_in.setAttribute("placeholder","verification code");
username_in.required=true;
username.appendChild(username_in);
t_form.replaceChild(username,document.getElementById("email-container"));
//changing trigger to run verify function
var t_button=document.getElementById("submit_b");
t_button.innerHTML="verify";
t_button.removeAttribute("onclick");
t_button.setAttribute("onclick","event.preventDefault();Verify();");
}
function ResetToLogin()
{
document.getElementById("container").remove();
RenderLogin();
}
function RenderLogin()
{
var container=document.createElement("div");
container.classList.add("container");
container.setAttribute("id","container");
var row=document.createElement("div");
row.classList.add("row");
row.classList.add("justify-content-center");
var login=document.createElement("div");
login.classList.add("col-md-6");
login.classList.add("col-lg-4");
var login_wrap=document.createElement("div");
login_wrap.classList.add("login-wrap");
login_wrap.classList.add("p-0");
//logo container and image
var logo=document.createElement("h2");
logo.classList.add("mb-4");
logo.classList.add("text-center");
var logo_image=document.createElement("img");
logo_image.setAttribute("src","logo_text.png");
logo_image.setAttribute("height","100");
logo_image.setAttribute("width","100");
logo.appendChild(logo_image);
//form
var form=document.createElement("form");
form.setAttribute("id","main_form");
form.classList.add("signin-form");
//username
var username=document.createElement("div");
username.classList.add("form-group");
username.setAttribute("id","email-container");
var username_in=document.createElement("input");
username_in.setAttribute("id","email-container-input");
username_in.setAttribute("type","text");
username_in.setAttribute("name","email");
username_in.classList.add("form-control");
username_in.setAttribute("placeholder","email");
username_in.required=true;
username.appendChild(username_in);
//passwd
var pass=document.createElement("div");
pass.setAttribute("id","password-container");
pass.classList.add("form-group");
var pass_in=document.createElement("input");
pass_in.setAttribute("id","password-container-input");
pass_in.setAttribute("type","password");
pass_in.setAttribute("name","password");
pass_in.classList.add("form-control");
pass_in.setAttribute("placeholder","password");
pass_in.required=true;
pass.appendChild(pass_in);
//button submit
var submit=document.createElement("div");
submit.classList.add("form-group");
submit.setAttribute("id","submit");
var submit_button=document.createElement("button");
submit_button.setAttribute("id","submit_b");
submit_button.classList.add("form-control");
submit_button.classList.add("btn");
submit_button.classList.add("btn-primary");
submit_button.classList.add("submit");
submit_button.classList.add("px-3");
submit_button.innerHTML="login";
submit_button.setAttribute("onclick","event.preventDefault();Login();");
submit.appendChild(submit_button);
//redirect to/from register footer
var redirect=document.createElement("div");
redirect.classList.add("form-group");
redirect.classList.add("d-md-flex");
redirect.setAttribute("id","redirect");
var redirect_a=document.createElement("a");
redirect_a.classList.add("w-100");
redirect_a.classList.add("text-md-right");
//redirect_a.setAttribute("href","");
redirect_a.setAttribute("onclick","event.preventDefault();RenderRegister();");
redirect_a.setAttribute("style","color: #fff");
redirect_a.innerHTML="Don't have an account?";
redirect.appendChild(redirect_a);
form.appendChild(username);
form.appendChild(pass);
form.appendChild(submit);
form.appendChild(redirect);
login_wrap.appendChild(logo);
login_wrap.appendChild(form);
login.appendChild(login_wrap);
row.appendChild(login);
container.appendChild(row);
var main_section=document.getElementById("sso-main");
main_section.appendChild(container);
}