Skip to content

Instantly share code, notes, and snippets.

@gianluca-nitti
Last active June 18, 2017 13:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gianluca-nitti/14e5d61d66d1f347330fa7f42f3db7da to your computer and use it in GitHub Desktop.
Save gianluca-nitti/14e5d61d66d1f347330fa7f42f3db7da to your computer and use it in GitHub Desktop.
Proof-of-concept JS authentication client for Terasology FacadeServer
<!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