Last active
June 18, 2017 13:34
-
-
Save gianluca-nitti/14e5d61d66d1f347330fa7f42f3db7da to your computer and use it in GitHub Desktop.
Proof-of-concept JS authentication client for Terasology FacadeServer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Terasology Web test</title> | |
<script src="http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js"></script> | |
<script src="http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn2.js"></script> | |
<script src="http://www-cs-students.stanford.edu/~tjw/jsbn/rsa.js"></script> | |
<script src="http://www-cs-students.stanford.edu/~tjw/jsbn/rsa2.js"></script> | |
<script src="http://kjur.github.io/jsrsasign/jsrsasign-latest-all-min.js"></script> | |
<script> | |
//function from https://stackoverflow.com/a/16245768 | |
function b64toBlob(b64Data, contentType, sliceSize) { | |
contentType = contentType || ''; | |
sliceSize = sliceSize || 512; | |
var byteCharacters = atob(b64Data); | |
var byteArrays = []; | |
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { | |
var slice = byteCharacters.slice(offset, offset + sliceSize); | |
var byteNumbers = new Array(slice.length); | |
for (var i = 0; i < slice.length; i++) { | |
byteNumbers[i] = slice.charCodeAt(i); | |
} | |
var byteArray = new Uint8Array(byteNumbers); | |
byteArrays.push(byteArray); | |
} | |
var blob = new Blob(byteArrays, {type: contentType}); | |
return blob; | |
} | |
//function from https://stackoverflow.com/a/39460727 | |
function base64toHEX(base64) { | |
var raw = atob(base64); | |
var HEX = ''; | |
for ( i = 0; i < raw.length; i++ ) { | |
var _hex = raw.charCodeAt(i).toString(16) | |
HEX += (_hex.length==2?_hex:'0'+_hex); | |
} | |
return HEX.toUpperCase(); | |
} | |
//function from https://stackoverflow.com/a/40031979 | |
function buf2hex(buffer) { // buffer is an ArrayBuffer | |
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); | |
} | |
//adapted from https://stackoverflow.com/a/30614192 | |
function HEXtobase64(HEX) { | |
var hexArray = HEX | |
.replace(/\r|\n/g, "") | |
.replace(/([\da-fA-F]{2}) ?/g, "0x$1 ") | |
.replace(/ +$/, "") | |
.split(" "); | |
var byteString = String.fromCharCode.apply(null, hexArray); | |
return btoa(byteString); | |
} | |
function concatHelloMessages(hello1, hello2) { | |
function certToBlob(cert) { | |
return new Blob([cert.id, b64toBlob(cert.modulus), b64toBlob(cert.exponent), b64toBlob(cert.signature)]); | |
} | |
function helloMessageToBlob(message) { | |
return new Blob([b64toBlob(message.random), certToBlob(message.certificate), b64toBlob(message.timestamp)]); | |
} | |
return new Blob([helloMessageToBlob(hello1), helloMessageToBlob(hello2)]); | |
} | |
function log(msg) { | |
document.getElementById('log').innerHTML += '<br/>' + msg; | |
} | |
function findClientId(clientIds, serverPublic) { | |
function predicate(element) { | |
return element.server.id === serverPublic.id; | |
} | |
return clientIds.find(predicate); | |
} | |
function connect() { | |
var waitingServerHello = true; | |
var clientIds = JSON.parse(document.getElementById('config').value).security.clientIdentities; | |
log('Succesfully parsed configuration'); | |
var socket = new WebSocket(document.getElementById('serverAddr').value); | |
log('WebSocket connecting...'); | |
function sendData() { | |
socket.send(document.getElementById('inputToSend').value); | |
} | |
document.getElementById('sendBtn').addEventListener("click", sendData); | |
socket.onopen = function (event) { | |
log('Connected! Initializing authentication handshake...'); | |
var data = JSON.stringify({messageType: 'AUTHENTICATION_REQUEST'}); | |
socket.send(data); | |
log('Raw sent data: ' + data); | |
}; | |
socket.onmessage = function (event) { | |
log('Raw received data: ' + event.data); | |
var receivedJsonObject = JSON.parse(event.data); | |
if (waitingServerHello && receivedJsonObject.messageType === 'ACTION_RESULT') { | |
waitingServerHello = false; | |
var serverHello = receivedJsonObject.data.data; | |
var serverPublic = serverHello.certificate; | |
log('Received and parsed server hello message'); | |
var identity = findClientId(clientIds, serverPublic); | |
if (!identity) { | |
log('Failed to find client identity for this server in the configuration'); | |
return; | |
} | |
var clientPublic = identity.clientPublic; | |
var clientHello = {random: "", certificate: identity.clientPublic, timestamp: ""}; | |
log('Built client hello message'); | |
log('Replying to server handshake...'); | |
var blobToSign = concatHelloMessages(serverHello, clientHello); | |
var sig = new KJUR.crypto.Signature({"alg": "SHA1withRSA"}); | |
var rsa = new RSAKey(); | |
rsa.setPrivate(base64toHEX(identity.clientPrivate.modulus), base64toHEX(identity.clientPublic.exponent), base64toHEX(identity.clientPrivate.exponent)); | |
sig.initSign(rsa); | |
sig.setAlgAndProvider('SHA1withRSA', 'cryptojs/jsrsa'); | |
var fileReader = new FileReader(); | |
fileReader.onload = function() { | |
sig.updateHex(buf2hex(this.result)); | |
var signedHexString = sig.sign(); | |
var signedBase64 = HEXtobase64(signedHexString); | |
socket.send(JSON.stringify({messageType: "AUTHENTICATION_DATA", data: {clientHello: clientHello, signature: signedBase64}})); | |
}; | |
fileReader.readAsArrayBuffer(blobToSign); | |
} else { | |
if (receivedJsonObject.messageType === 'ACTION_RESULT' && receivedJsonObject.data.status === 'OK') { | |
log('Authentication successful!'); | |
} | |
} | |
} | |
} | |
</script> | |
</head> | |
<body> | |
Server websocket address: <input type="text" id="serverAddr" value="ws://localhost:8080/ws"/><br/> | |
Configuration: <br/><textarea id="config" rows="20" cols="100">paste your configuration here...</textarea><br/> | |
<button onclick="connect()">Connect</button><br/> | |
Send: <br/><textarea id="inputToSend" rows="10" cols="100">Enter here JSON data to send to the server once connected</textarea><br/> | |
<button id="sendBtn">Send</button><br/> | |
<div id="log">Click Connect to start</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment