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); }); });