Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/workshop-4.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion __test__/config/jestSetup.ts
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@

14 changes: 7 additions & 7 deletions __test__/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"exclude": ["node_modules", "build"],
"compilerOptions": {
"outDir": "./dist", // Redirect compiled files to 'dist/'
"rootDir": "./src", // Keep TypeScript files inside 'src'
"target": "es2016",
"lib": ["es6"],
"module": "commonjs",
"baseUrl": "./",
"paths": {
"@/*": ["../src/*"]
},
"strict": true,
"resolveJsonModule": true,
"allowJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true,
"incremental": true
}
},
"include": ["src", "tests"],
"exclude": ["node_modules", "dist"]
}

Binary file added server_log.txt
Binary file not shown.
156 changes: 93 additions & 63 deletions src/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,140 +11,170 @@ function arrayBufferToBase64(buffer: ArrayBuffer): string {

// Function to convert Base64 string to ArrayBuffer
function base64ToArrayBuffer(base64: string): ArrayBuffer {
var buff = Buffer.from(base64, "base64");
const buff = Buffer.from(base64, "base64");
return buff.buffer.slice(buff.byteOffset, buff.byteOffset + buff.byteLength);
}

// ################
// ### RSA keys ###
// ### RSA Keys ###
// ################

// Generates a pair of private / public RSA keys
// Generates a pair of private/public RSA keys
type GenerateRsaKeyPair = {
publicKey: webcrypto.CryptoKey;
privateKey: webcrypto.CryptoKey;
};
export async function generateRsaKeyPair(): Promise<GenerateRsaKeyPair> {
// TODO implement this function using the crypto package to generate a public and private RSA key pair.
// the public key should be used for encryption and the private key for decryption. Make sure the
// keys are extractable.

// remove this
return { publicKey: {} as any, privateKey: {} as any };
const keyPair = await webcrypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true, // Extractable
["encrypt", "decrypt"]
);

return { publicKey: keyPair.publicKey, privateKey: keyPair.privateKey };
}

// Export a crypto public key to a base64 string format
// Export a crypto public key to a Base64 string format
export async function exportPubKey(key: webcrypto.CryptoKey): Promise<string> {
// TODO implement this function to return a base64 string version of a public key

// remove this
return "";
const exported = await webcrypto.subtle.exportKey("spki", key);
return arrayBufferToBase64(exported);
}

// Export a crypto private key to a base64 string format
// Export a crypto private key to a Base64 string format
export async function exportPrvKey(
key: webcrypto.CryptoKey | null
): Promise<string | null> {
// TODO implement this function to return a base64 string version of a private key

// remove this
return "";
if (!key) return null;
const exported = await webcrypto.subtle.exportKey("pkcs8", key);
return arrayBufferToBase64(exported);
}

// Import a base64 string public key to its native format
// Import a Base64 string public key to its native format
export async function importPubKey(
strKey: string
): Promise<webcrypto.CryptoKey> {
// TODO implement this function to go back from the result of the exportPubKey function to it's native crypto key object

// remove this
return {} as any;
const buffer = base64ToArrayBuffer(strKey);
return await webcrypto.subtle.importKey(
"spki",
buffer,
{ name: "RSA-OAEP", hash: "SHA-256" },
true,
["encrypt"]
);
}

// Import a base64 string private key to its native format
// Import a Base64 string private key to its native format
export async function importPrvKey(
strKey: string
): Promise<webcrypto.CryptoKey> {
// TODO implement this function to go back from the result of the exportPrvKey function to it's native crypto key object

// remove this
return {} as any;
const buffer = base64ToArrayBuffer(strKey);
return await webcrypto.subtle.importKey(
"pkcs8",
buffer,
{ name: "RSA-OAEP", hash: "SHA-256" },
true,
["decrypt"]
);
}

// Encrypt a message using an RSA public key
export async function rsaEncrypt(
b64Data: string,
strPublicKey: string
): Promise<string> {
// TODO implement this function to encrypt a base64 encoded message with a public key
// tip: use the provided base64ToArrayBuffer function

// remove this
return "";
const publicKey = await importPubKey(strPublicKey);
const encrypted = await webcrypto.subtle.encrypt(
{ name: "RSA-OAEP" },
publicKey,
Buffer.from(b64Data, "utf-8")
);
return arrayBufferToBase64(encrypted);
}

// Decrypts a message using an RSA private key
export async function rsaDecrypt(
data: string,
privateKey: webcrypto.CryptoKey
): Promise<string> {
// TODO implement this function to decrypt a base64 encoded message with a private key
// tip: use the provided base64ToArrayBuffer function

// remove this
return "";
const decrypted = await webcrypto.subtle.decrypt(
{ name: "RSA-OAEP" },
privateKey,
base64ToArrayBuffer(data)
);
return Buffer.from(decrypted).toString("utf-8");
}

// ######################
// ### Symmetric keys ###
// ### Symmetric Keys ###
// ######################

// Generates a random symmetric key
export async function createRandomSymmetricKey(): Promise<webcrypto.CryptoKey> {
// TODO implement this function using the crypto package to generate a symmetric key.
// the key should be used for both encryption and decryption. Make sure the
// keys are extractable.

// remove this
return {} as any;
return await webcrypto.subtle.generateKey(
{ name: "AES-CBC", length: 256 },
true,
["encrypt", "decrypt"]
);
}

// Export a crypto symmetric key to a base64 string format
// Export a crypto symmetric key to a Base64 string format
export async function exportSymKey(key: webcrypto.CryptoKey): Promise<string> {
// TODO implement this function to return a base64 string version of a symmetric key

// remove this
return "";
const exported = await webcrypto.subtle.exportKey("raw", key);
return arrayBufferToBase64(exported);
}

// Import a base64 string format to its crypto native format
// Import a Base64 string format to its crypto native format
export async function importSymKey(
strKey: string
): Promise<webcrypto.CryptoKey> {
// TODO implement this function to go back from the result of the exportSymKey function to it's native crypto key object

// remove this
return {} as any;
const buffer = base64ToArrayBuffer(strKey);
return await webcrypto.subtle.importKey(
"raw",
buffer,
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
}

// Encrypt a message using a symmetric key
export async function symEncrypt(
key: webcrypto.CryptoKey,
data: string
): Promise<string> {
// TODO implement this function to encrypt a base64 encoded message with a public key
// tip: encode the data to a uin8array with TextEncoder
const encoder = new TextEncoder();
const encodedData = encoder.encode(data);
const iv = webcrypto.getRandomValues(new Uint8Array(12));

const encrypted = await webcrypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
encodedData
);

return "";
return arrayBufferToBase64(iv) + ":" + arrayBufferToBase64(encrypted);
}

// Decrypt a message using a symmetric key
export async function symDecrypt(
strKey: string,
encryptedData: string
): Promise<string> {
// TODO implement this function to decrypt a base64 encoded message with a private key
// tip: use the provided base64ToArrayBuffer function and use TextDecode to go back to a string format

return "";
const [ivBase64, encryptedBase64] = encryptedData.split(":");
const iv = new Uint8Array(base64ToArrayBuffer(ivBase64));
const encryptedBuffer = base64ToArrayBuffer(encryptedBase64);

const key = await importSymKey(strKey);
const decrypted = await webcrypto.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
encryptedBuffer
);

return new TextDecoder().decode(decrypted);
}
2 changes: 2 additions & 0 deletions src/onionRouters/launchOnionRouters.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { simpleOnionRouter } from "./simpleOnionRouter";
import { BASE_ONION_ROUTER_PORT } from "../config";


export async function launchOnionRouters(n: number) {
const promises = [];
Expand Down
64 changes: 46 additions & 18 deletions src/onionRouters/simpleOnionRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,49 @@ import bodyParser from "body-parser";
import express from "express";
import { BASE_ONION_ROUTER_PORT } from "../config";

export async function simpleOnionRouter(nodeId: number) {
const onionRouter = express();
onionRouter.use(express.json());
onionRouter.use(bodyParser.json());

// TODO implement the status route
// onionRouter.get("/status", (req, res) => {});

const server = onionRouter.listen(BASE_ONION_ROUTER_PORT + nodeId, () => {
console.log(
`Onion router ${nodeId} is listening on port ${
BASE_ONION_ROUTER_PORT + nodeId
}`
);
});

return server;
}
export async function simpleOnionRouter(nodeId: number) {
const onionRouter = express();
onionRouter.use(express.json());
onionRouter.use(bodyParser.json());


onionRouter.get("/status", (req, res) => {
console.log(` Received request at node ${nodeId} for /status`);
res.send("live");
});

onionRouter.get("/getLastReceivedEncryptedMessage", (req, res) => {
console.log(`🔍 Node ${nodeId}: Returning empty encrypted message`);
res.json({ result: null });
});

onionRouter.get("/getLastReceivedDecryptedMessage", (req, res) => {
console.log(`🔍 Node ${nodeId}: Returning empty decrypted message`);
res.json({ result: null });
});

onionRouter.get("/getLastMessageDestination", (req, res) => {
console.log(`🔍 Node ${nodeId}: Returning null destination`);
res.json({ result: null });
});

// Define the port dynamically based on nodeId
const port = BASE_ONION_ROUTER_PORT + nodeId;

try {
const server = onionRouter.listen(port, () => {
console.log(` Onion Router ${nodeId} is successfully listening on port ${port}`);
});

// Catching server errors
server.on("error", (err) => {
console.error(` Error: Failed to start Onion Router ${nodeId} on port ${port}:`, err);
});

return server;
} catch (error) {
console.error(` Critical Error: Failed to start Onion Router ${nodeId}:`, error);
throw error;
}
}

Loading