Skip to content

Commit

Permalink
initial encryption commit, messages are now encrypted
Browse files Browse the repository at this point in the history
  • Loading branch information
ION606 committed Dec 25, 2023
1 parent 2cda2d8 commit 4cbbb5e
Show file tree
Hide file tree
Showing 21 changed files with 672 additions and 85 deletions.
2 changes: 2 additions & 0 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA==" crossorigin="anonymous" />
<script src="https://kit.fontawesome.com/728e740903.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/js/all.min.js" integrity="sha512-F5QTlBqZlvuBEs9LQPqc1iZv2UMxcVXezbHzomzS6Df4MZMClge/8+gXrKw2fl5ysdk4rWjR0vKS7NNkfymaBQ==" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/idb@8/build/umd.js"></script>
<script src="/scripts/encryption.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:400,400i,500,500i,700,700i&display=swap">
<title>proto-chat</title>

Expand Down
21 changes: 15 additions & 6 deletions client/join.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
<title>proto-chat</title>
<link rel="stylesheet" type="text/css" href="/CSS/login.css">
<script src="/scripts/initialize.js"></script>
<script src="/scripts/encryption.js"></script>
<script src="https://cdn.jsdelivr.net/npm/idb@8/build/umd.js"></script>

<script>
const ws = new WebSocket(createWSPath());
setInterval(() => { ws.send(JSON.stringify({code: 10})); }, 30000);

window.onload = () => {
function fakeWindowOnload() {
ws.addEventListener('error', (err) => {
alert('Uh oh!\nAn error occured!');
window.location.href = '/';
Expand All @@ -24,7 +26,9 @@

ws.addEventListener('message', (message) => {
const data = JSON.parse(message.data);
if (data.code != 0) return;

if (data.code == 7) createAndStoreKey(data);
else if (data.code != 0) return; // note this deals with the CODE
else if (data.op == 1) recieveCode(data);
else if (data.op == 2) recieveCodeResponse(data);
});
Expand All @@ -46,9 +50,7 @@
}
}));
}
</script>

<script>

function recieveCode(data) {
if (data.type == 1) return alert('email already exists!');
if (data.type == 2) return alert('username already exists!');
Expand Down Expand Up @@ -76,8 +78,15 @@
function recieveCodeResponse(data) {
if (data.type == 1) return alert('incorrect code!');
else if (data.type == 2) return alert('code expired!\nplease refresh the page and try again!');
window.location.href = '';

// create encryption
console.log(data);

window.location.href = '/';
}


fakeWindowOnload();
</script>
</head>
<body class="unselectable">
Expand Down
320 changes: 320 additions & 0 deletions client/scripts/encryption.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
const dbName = 'keyDatabase';
const storeName = 'keys';
const version = 1;
const prvtIDBKey = 'privt';
const symmEncIDBKey = 'symmenc';


function getDB() {
return new Promise(async (resolve, reject) => {
if (!('indexedDB' in window)) {
console.warn('IndexedDB not supported!');
return reject();
}

const db = await idb.openDB(dbName, version, {
upgrade(db, oldVersion, newVersion, transaction) {
const store = db.createObjectStore(storeName);
}
});

resolve(db);
});
}


async function writeKeyToIDB(cKey, isSymm = false) {
try {
const db = await getDB();
if (!db) return;

const tx = db.transaction(storeName, 'readwrite');
const store = await tx.objectStore(storeName);

const value = await store.put(cKey, (isSymm) ? symmEncIDBKey : prvtIDBKey);
await tx.done;

return true;
}
catch (err) {
console.error(err);
return false;
}
}

async function demo() {
const toSend = prompt("please enter message");
if (!toSend) return alert("NO TEXT FOUND TO SEND!")

const encMsg = await encryptMessage("other", toSend);
console.info("ENCRYPTED MESSAGE:", encMsg);
alert(await decryptMessage(encMsg));
}


// CRYPTO STUFF

// for simplicity and aesthetic
/**
* @param {Boolean} asJWK
* @returns {Object | CryptoKey}
*/
async function getPrivKey(asCrypto = false) {
const jwkKey = await (await getDB())?.transaction(storeName).objectStore(storeName).get(prvtIDBKey) || null;

if (!asCrypto) return jwkKey;

else return await crypto.subtle.importKey(
"jwk",
jwkKey,
{
name: "RSA-OAEP",
hash: { name: "SHA-256" },
},
true,
["decrypt"]
);
}

async function getSymmKey() {
const symmEncKeyEnc = await (await getDB())?.transaction(storeName).objectStore(storeName).get(symmEncIDBKey) || null;;
const prvKey = await getPrivKey(true);
if (!symmEncKeyEnc || !prvKey) return alert("Encryption Key Error!\nPlease try again later!");

// decrypt the symm key with the private key
const symmEncKey = await crypto.subtle.decrypt({ name: "RSA-OAEP" }, prvKey, base64ToArrayBuffer(symmEncKeyEnc));
return await crypto.subtle.importKey(
"raw",
symmEncKey,
{ name: "AES-GCM" },
true,
["encrypt", "decrypt"]
);
}


async function checkPassword(password, encryptedPrivateKeyData) {
const { salt, iv, encryptedPrivateKey } = encryptedPrivateKeyData;
if (!salt || !iv || !encryptedPrivateKey) return console.error("Missing data for password check!");

try {
const { keyFromPass } = await deriveKeyFromPass(password, salt);
const decryptedPrivateKey = await decryptPrivateKey(keyFromPass, encryptedPrivateKey, iv);

// password is correct and we get the private key
writeKeyToIDB(decryptedPrivateKey);

return true;
} catch (error) {
console.error("Incorrect password or decryption error:", error);
return false;
}
}



async function deriveKeyFromPass(password, saltInp = undefined) {
const salt = (saltInp != undefined) ? saltInp : window.crypto.getRandomValues(new Uint8Array(32));
const baseKey = await window.crypto.subtle.importKey(
"raw",
new TextEncoder().encode(password),
{ name: "PBKDF2" },
false,
["deriveKey"]
);

const derivedKey = await window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: new TextEncoder().encode(salt),
iterations: 100000,
hash: 'SHA-256'
},
baseKey,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);

return { keyFromPass: derivedKey, salt: salt, iv: window.crypto.getRandomValues(new Uint8Array(12)) };
}


async function decryptPrivateKey(derivedKey, encryptedPrivateKey, iv) {
const decryptedData = await window.crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv },
derivedKey,
encryptedPrivateKey
);

const decodedPrivateKey = new TextDecoder().decode(decryptedData);
return JSON.parse(decodedPrivateKey);
}


function bufferToHex(buffer) {
return Array.from(new Uint8Array(buffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}


async function generateSymmetricKey() {
try {
const key = await window.crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256, // Can be 128, 192, or 256 bits
},
true, // Whether the key is extractable (i.e., can be used in exportKey)
["encrypt", "decrypt"] // Specify the operations for which the key can be used
);

return key;
} catch (error) {
console.error("Error generating symmetric key:", error);
}
}


async function createAndStoreKey(data) {
try {
const keyPair = await window.crypto.subtle.generateKey({
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["encrypt", "decrypt"]
);

// encrypt
const encoder = new TextEncoder();

const publicKey = await crypto.subtle.exportKey("jwk", keyPair.publicKey);
const privateKey = await crypto.subtle.exportKey("jwk", keyPair.privateKey);

const { keyFromPass, salt, iv } = await deriveKeyFromPass(data.password);

// encrypt the private key using the key derived from their password
const encodedPrivateKey = encoder.encode(JSON.stringify(privateKey));
const encryptedPrivateKey = await window.crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv },
keyFromPass,
encodedPrivateKey
);

// not secure?
writeKeyToIDB(privateKey);

// encrypt the password using the private key
// DOES NOT WORK
// const passwordEncrypted = await window.crypto.subtle.encrypt(
// { name: "RSA-OAEP" },
// keyPair.privateKey,
// encoder.encode(data.password)
// );
const passwordEncrypted = data.password


// replace the unencrypted password
data['password'] = passwordEncrypted;

// new entries
data['keyPrvtEnc'] = bufferToHex(encryptedPrivateKey);
data['keyPub'] = JSON.stringify(publicKey);
data['iv'] = bufferToHex(iv);
data['salt'] = bufferToHex(salt);

// data already has code 7 and op 0
ws.send(JSON.stringify(data));

return true;
} catch (error) {
console.error("Error in storing key:", error);
return false;
}
}


function base64ToArrayBuffer(base64) {
const binaryString = window.atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}

function arrayBufferToBase64(buffer) {
let binary = '';
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}


async function importKey(base64Key) {
const arrayBufferKey = base64ToArrayBuffer(base64Key);
return await window.crypto.subtle.importKey(
"raw",
arrayBufferKey,
{ name: "AES-GCM" },
true,
["encrypt", "decrypt"]
);
}


async function encryptMsg(symmKey, data) {
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const encryptedData = await window.crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv },
symmKey,
new TextEncoder().encode(data)
);

const base64EncData = arrayBufferToBase64(encryptedData);
const base64IV = arrayBufferToBase64(iv);
return { base64EncData, base64IV };
}


async function decryptMsg(symmEncKey, dataObj) {
const { base64EncData, base64IV } = dataObj;

// Convert Base64 back to ArrayBuffer
const encryptedData = base64ToArrayBuffer(base64EncData);
const iv = base64ToArrayBuffer(base64IV);

// Decrypt the data
const decryptedData = await window.crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv },
symmEncKey,
encryptedData
);

// Convert the decrypted data back to a string
return new TextDecoder().decode(decryptedData);
}



// INITIAL THINGS
async function init() {
if (!("TextDecoder" in window)) return alert("Sorry, this browser does not support TextDecoder...");
if (!('indexedDB' in window)) return alert('IndexedDB not supported!');

if (!localStorage.getItem('sessionid')) return;
else if (!(await getPrivKey())) {
// LOG OUT HERE
logout();
}
};

init();
Loading

0 comments on commit 4cbbb5e

Please sign in to comment.