110 lines
3.6 KiB
JavaScript
110 lines
3.6 KiB
JavaScript
function integerToBytes(integer, numBytes) {
|
|
const bytesArray = new Array(numBytes).fill(0);
|
|
for (let i = 0; i < numBytes; i++) {
|
|
bytesArray[numBytes - i - 1] = integer & 0xff;
|
|
integer >>= 8;
|
|
}
|
|
return Uint8Array.from(bytesArray);
|
|
}
|
|
|
|
function concatenateUInt8Arrays(array1, array2) {
|
|
const newArray = new Uint8Array(array1.length + array2.length);
|
|
newArray.set(array1);
|
|
newArray.set(array2, array1.length);
|
|
return newArray;
|
|
}
|
|
|
|
/*
|
|
Convert an ArrayBuffer into a string
|
|
from https://developer.chrome.com/blog/how-to-convert-arraybuffer-to-and-from-string/
|
|
*/
|
|
function ab2str(buf) {
|
|
return String.fromCharCode.apply(null, new Uint8Array(buf));
|
|
}
|
|
|
|
/*
|
|
Export the given key and write it into the "exported-key" space.
|
|
*/
|
|
async function exportCryptoKeyPem(key) {
|
|
const exported = await window.crypto.subtle.exportKey("spki", key);
|
|
const exportedAsString = ab2str(exported);
|
|
const exportedAsBase64 = btoa(exportedAsString);
|
|
return exportedAsBase64;
|
|
}
|
|
|
|
async function exportCryptoKey(key) {
|
|
const exported = await window.crypto.subtle.exportKey("jwk", key);
|
|
return JSON.stringify(exported, null, " ");
|
|
}
|
|
|
|
async function createKeyPair() {
|
|
const crypto = window.crypto.subtle
|
|
const keyPair = await window.crypto.subtle.generateKey(
|
|
{
|
|
name: "RSA-OAEP",
|
|
modulusLength: 2048,
|
|
publicExponent: new Uint8Array([4, 0, 1]),
|
|
hash: "SHA-256",
|
|
},
|
|
true,
|
|
["encrypt", "decrypt"]
|
|
);
|
|
return keyPair;
|
|
}
|
|
|
|
let keyPair = createKeyPair();
|
|
let loginForm = document.getElementById('login-form');
|
|
let loginButton = document.getElementById('login-button');
|
|
let nonce = null
|
|
loginButton.addEventListener('click', async evt => {
|
|
const publicKey = (await keyPair).publicKey;
|
|
const publicKeyPem = await exportCryptoKeyPem(publicKey);
|
|
fetch('api/login', {
|
|
method: 'POST',
|
|
headers: {
|
|
'public-key' : publicKeyPem,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ username: loginForm.username.value, password: loginForm.password.value})
|
|
}).then(async response => {
|
|
const nonceHeader = response.headers.get('nonce');
|
|
const encryptedNonce = atob(nonceHeader);
|
|
const privateKey = (await keyPair).privateKey;
|
|
const crypto = window.crypto.subtle;
|
|
const encryptedBuffer = Uint8Array.from(atob(nonceHeader), c => c.charCodeAt(0));
|
|
nonce = await crypto.decrypt({ name: "RSA-OAEP" }, privateKey, encryptedBuffer)
|
|
.then(it => new Uint8Array(it));
|
|
return response.text();
|
|
}).then(text => {
|
|
let paragraph = document.createElement('p');
|
|
paragraph.textContent = text;
|
|
document.body.appendChild(paragraph);
|
|
});
|
|
});
|
|
|
|
let button = document.createElement('button');
|
|
button.textContent = 'Press me'
|
|
document.body.appendChild(button);
|
|
|
|
button.addEventListener('click', async evt => {
|
|
let header = {};
|
|
if(nonce != null) {
|
|
const crypto = window.crypto.subtle;
|
|
const epochTick = Math.floor(new Date().getTime() / 10000)
|
|
const data = concatenateUInt8Arrays(nonce, integerToBytes(epochTick, 8))
|
|
const hash = new Uint8Array(await crypto.digest("SHA-256", data));
|
|
const token = btoa(Array.from(hash, byte => String.fromCharCode(byte)).join(''));
|
|
headers = {
|
|
'x-token': token
|
|
};
|
|
}
|
|
fetch('api/hello', {
|
|
method: 'GET',
|
|
headers
|
|
}).then(response => response.text()).then(text => {
|
|
let paragraph = document.createElement('p');
|
|
paragraph.textContent = text;
|
|
document.body.appendChild(paragraph);
|
|
});
|
|
});
|