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([1, 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 => { if (!response.ok) { let paragraph = document.createElement('p'); paragraph.textContent = await response.text(); document.body.appendChild(paragraph); setTimeout(() => document.body.removeChild(paragraph), 10000); } 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)); }); }); async function computeToken(instant) { if(nonce != null) { const crypto = window.crypto.subtle; const epochTick = Math.floor(instant / 3000); 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('')); return token; } else { return null; } } const whoamiButton = document.getElementById('whoami-button'); const whoamiForm = document.getElementById('whoami-form'); const timeDriftLabel = whoamiForm.querySelector("label em"); const timeDriftSlider = whoamiForm['time-drift-slider']; timeDriftSlider.addEventListener('input', evt => { timeDriftLabel.textContent = parseInt(evt.target.value) / 1000 + ' s'; }); whoamiButton.addEventListener('click', async evt => { const drift = parseInt(timeDriftSlider.value); const instant = new Date().getTime() + drift; const token = await computeToken(instant); let headers = {}; if (token != null) { headers = { 'x-token': token }; } fetch('api/whoami', { method: 'GET', headers }).then(response => response.text()).then(text => { let paragraph = document.createElement('p'); paragraph.textContent = text; document.body.appendChild(paragraph); setTimeout(() => document.body.removeChild(paragraph), 10000); }); }); // const helloButton = document.createElement('button'); // helloButton.textContent = 'hello' // helloButton.addEventListener('click', async evt => { // const token = await computeToken(); // let headers = {}; // if (token != null) { // 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); // }); // });