change representation of public key in db to hex string + create room
This commit is contained in:
parent
da4f74b60c
commit
4d68e7a9f7
@ -1,4 +1,5 @@
|
||||
const { subtle } = require('node:crypto').webcrypto;
|
||||
const stringutils = require("./stringutils");
|
||||
|
||||
const sharedSecret = process.env.SHARED_SECRET;
|
||||
|
||||
@ -8,9 +9,9 @@ const authentication = {
|
||||
},
|
||||
verifySignature : async (msg, sig, publicKeys) => {
|
||||
try {
|
||||
for (const pemPubKey of publicKeys) {
|
||||
for (const hexKey of publicKeys) {
|
||||
try {
|
||||
const pubKey = await authentication.pemToKey(pemPubKey);
|
||||
const pubKey = await stringutils.hexToKey(hexKey);
|
||||
const verified = await subtle.verify(
|
||||
'Ed25519',
|
||||
pubKey,
|
||||
@ -18,31 +19,16 @@ const authentication = {
|
||||
msg
|
||||
);
|
||||
if (verified) {
|
||||
return pemPubKey;
|
||||
return hexKey;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Failed to verify signature with public key:', pemPubKey, err);
|
||||
console.log('Failed to verify signature with public key:', hexKey, err);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.error('Error verifying signature:', err);
|
||||
}
|
||||
},
|
||||
pemToKey: async (pemKey) => {
|
||||
const base64 = pemKey.replace(`-----BEGIN PUBLIC KEY-----`, '').replace(`-----END PUBLIC KEY-----`, '').trim();
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
const uint8Array = new Uint8Array(buffer);
|
||||
const publicKey = await subtle.importKey(
|
||||
"spki",
|
||||
uint8Array,
|
||||
{
|
||||
name: "Ed25519",
|
||||
},
|
||||
true,
|
||||
["verify"],
|
||||
);
|
||||
return publicKey;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
const crypto = require('crypto');
|
||||
const database = require("../db");
|
||||
const stringutils = require("../stringutils");
|
||||
const authentication = require("../authentication");
|
||||
const socket = require('../socket').default;
|
||||
|
||||
@ -10,9 +11,9 @@ const accountController = {
|
||||
if (!sharedSecret || !publicKey) {
|
||||
return res.status(400).json({ error: "Missing sharedSecret or publicKey" });
|
||||
}
|
||||
console.log('Received data:', { sharedSecret, publicKey });
|
||||
if (authentication.checkSharedSecret(sharedSecret)) {
|
||||
database.addUser(publicKey);
|
||||
let pubkey = stringutils.pemToHex(publicKey)
|
||||
database.addUser(pubkey);
|
||||
} else {
|
||||
return res.status(400).json({ error: "Wrong sharedSecret" });
|
||||
}
|
||||
|
@ -1 +1,20 @@
|
||||
const database = require("../db");
|
||||
const stringutils = require("../stringutils");
|
||||
|
||||
const chatController = {
|
||||
add: async (req, res) => {
|
||||
try {
|
||||
const { pubkey } = req.body;
|
||||
if (!pubkey) {
|
||||
return res.status(400).json({ error: "Missing publicKey" });
|
||||
}
|
||||
await database.createRoom(req.session.publicKey, stringutils.pemToHex(pubkey));
|
||||
return res.status(201).json({ message: "Room created successfully." });
|
||||
} catch (error) {
|
||||
console.error("Error creating the room:", error);
|
||||
return res.status(500).json({ error: "Failed to create the room" });
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = chatController;
|
||||
|
@ -1,39 +1,13 @@
|
||||
const path = require('path');
|
||||
const stringutils = require("../stringutils");
|
||||
|
||||
const mainController = {
|
||||
root: (req, res) => {
|
||||
let pubKey = req.session.publicKey;
|
||||
console.log(pubKey);
|
||||
let isLoggedIn = typeof pubKey !== 'undefined';
|
||||
let pubKeyHex = req.session.publicKey;
|
||||
let isLoggedIn = typeof pubKeyHex !== 'undefined';
|
||||
let pubKey = isLoggedIn ? stringutils.hexToPem(pubKeyHex).replaceAll('\n','') : null;
|
||||
res.render('index', {isLoggedIn, pubKey});
|
||||
},
|
||||
// style: (req, res) => {
|
||||
// res.sendFile(path.resolve(__dirname + '/../public/style.css'));
|
||||
// },
|
||||
// script: (req, res) => {
|
||||
// res.sendFile(path.resolve(__dirname + '/../public/script.js'));
|
||||
// },
|
||||
// ecc: (req, res) => {
|
||||
// res.sendFile(path.resolve(__dirname + '/../public/ecc.js'));
|
||||
// },
|
||||
// ecdh: (req, res) => {
|
||||
// res.sendFile(path.resolve(__dirname + '/../public/ecdh.js'));
|
||||
// },
|
||||
// popups: (req, res) => {
|
||||
// res.sendFile(path.resolve(__dirname + '/../public/popups.js'));
|
||||
// },
|
||||
// chat : (req, res) => {
|
||||
// res.sendFile(path.resolve(__dirname + '/../public/chat.js'));
|
||||
// },
|
||||
// register : (req, res) => {
|
||||
// res.sendFile(path.resolve(__dirname + '/../public/register.js'));
|
||||
// },
|
||||
// pubkey : (req, res) => {
|
||||
// res.sendFile(path.resolve(__dirname + '/../public/pubkey.js'));
|
||||
// },
|
||||
// registertext : (req, res) => {
|
||||
// res.sendFile(path.resolve(__dirname + '/../public/registertext.js'));
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = mainController;
|
||||
|
74
src/db.js
74
src/db.js
@ -35,40 +35,43 @@ const database = {
|
||||
createTables: () => {
|
||||
pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
uuid integer PRIMARY KEY,
|
||||
uuid SERIAL PRIMARY KEY,
|
||||
pubkey text
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "room" (
|
||||
uuid SERIAL PRIMARY KEY
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "room_members" (
|
||||
room_uuid INTEGER REFERENCES "room"(uuid),
|
||||
user_uuid INTEGER REFERENCES "users"(uuid),
|
||||
PRIMARY KEY (room_uuid, user_uuid)
|
||||
);
|
||||
`, (err, _) => {
|
||||
if (err) {
|
||||
console.error('Error creating users table', err);
|
||||
console.error('Error creating tables', err);
|
||||
return;
|
||||
}
|
||||
pool.query(`
|
||||
CREATE SEQUENCE IF NOT EXISTS uuid_sequence
|
||||
INCREMENT BY 1
|
||||
START WITH 1;
|
||||
`, (err, _) => {
|
||||
if (err) {
|
||||
console.error('Error creating sequence', err);
|
||||
} else {
|
||||
console.log("users table and sequence created successfully.");
|
||||
} else {
|
||||
console.log("tables created successfully.");
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
checkSchema: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
pool.query(`
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM pg_tables WHERE tablename = 'users'
|
||||
) AS table_exists;
|
||||
SELECT
|
||||
(EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users'))
|
||||
AND
|
||||
(EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'room'))
|
||||
AND
|
||||
(EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'room_members'))
|
||||
AS all_tables_exist;
|
||||
`, (err, res) => {
|
||||
if (err) {
|
||||
console.error('Error executing query', err);
|
||||
reject(err);
|
||||
} else {
|
||||
const tableExists = res.rows[0].table_exists;
|
||||
resolve(tableExists);
|
||||
const all_tables_exist = res.rows[0].all_tables_exist;
|
||||
resolve(all_tables_exist);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -90,17 +93,44 @@ const database = {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await pool.query('SELECT NEXTVAL(\'uuid_sequence\') AS next_uuid');
|
||||
const nextUuid = result.rows[0].next_uuid;
|
||||
await pool.query(
|
||||
'INSERT INTO "users" (uuid, pubkey) VALUES ($1, $2)',
|
||||
[nextUuid, pubkey]
|
||||
'INSERT INTO "users" (uuid, pubkey) VALUES (DEFAULT, $1)',
|
||||
[pubkey,]
|
||||
);
|
||||
console.log(`Added user with the public key ${pubkey} .`);
|
||||
} catch (err) {
|
||||
console.error('Error adding user:', err);
|
||||
}
|
||||
},
|
||||
createRoom: async (pubkey1, pubkey2) => {
|
||||
try {
|
||||
const userQuery = 'SELECT uuid FROM users WHERE pubkey = $1';
|
||||
const uuidRes1 = await pool.query(userQuery, [pubkey1]);
|
||||
const uuidRes2 = await pool.query(userQuery, [pubkey2]);
|
||||
|
||||
if (!uuidRes1.rows[0] || !uuidRes2.rows[0]) {
|
||||
throw new Error('One or both users not found');
|
||||
}
|
||||
|
||||
const uuid1 = uuidRes1.rows[0].uuid;
|
||||
const uuid2 = uuidRes2.rows[0].uuid;
|
||||
|
||||
const roomRes = await pool.query(
|
||||
'INSERT INTO room (uuid) VALUES (DEFAULT) RETURNING uuid'
|
||||
);
|
||||
const roomid = roomRes.rows[0].uuid;
|
||||
|
||||
await pool.query(
|
||||
`INSERT INTO room_members (room_uuid, user_uuid)
|
||||
VALUES ($1, $2), ($1, $3)`,
|
||||
[roomid, uuid1, uuid2]
|
||||
);
|
||||
return roomid;
|
||||
} catch (err) {
|
||||
console.error('Error creating the room:', err);
|
||||
throw err; // Re-throw to handle in calling code
|
||||
}
|
||||
},
|
||||
getPublicKeys: async () => {
|
||||
try {
|
||||
const result = await pool.query('SELECT pubkey FROM users');
|
||||
|
51
src/public/popups-logged.js
Normal file
51
src/public/popups-logged.js
Normal file
@ -0,0 +1,51 @@
|
||||
const currentUrl = window.location.href;
|
||||
|
||||
// handle key presses (close/confirm)
|
||||
document.addEventListener("keydown", async function(event) {
|
||||
if (event.isComposing || event.key === 'Escape') {
|
||||
Array.from(document.getElementsByClassName("popup")).forEach(function(x) {
|
||||
x.style.display = 'none';
|
||||
});
|
||||
document.getElementById("publickeyadd").innerText = "";
|
||||
} else if (event.key === 'Enter') {
|
||||
if (addPopup.style.display == 'flex') {
|
||||
await addConfirm();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// add popup
|
||||
document.getElementById("add").addEventListener("click", function () {
|
||||
addPopup.style.display = 'flex';
|
||||
});
|
||||
// cancel
|
||||
document.getElementById("addcancel").addEventListener("click", function () {
|
||||
addPopup.style.display = 'none';
|
||||
document.getElementById("publickeyadd").innerText = "";
|
||||
});
|
||||
//confirm
|
||||
document.getElementById("addconfirm").addEventListener("click", async () => {
|
||||
await addConfirm();
|
||||
});
|
||||
|
||||
export async function addConfirm() {
|
||||
const apiUrl = `${currentUrl}chat/add`;
|
||||
const inputFieldPublicKey = document.getElementById("publickeyadd");
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
pubkey: inputFieldPublicKey.value
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to add');
|
||||
} else {
|
||||
inputFieldPublicKey.value = '';
|
||||
location.reload();
|
||||
}
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
// const express = require("express");
|
||||
// const chatController = require("../controllers/chat");
|
||||
// const router = express.Router();
|
||||
//
|
||||
// module.exports = router;
|
||||
const express = require("express");
|
||||
const chatController = require("../controllers/chat");
|
||||
const router = express.Router();
|
||||
|
||||
router
|
||||
.route("/add")
|
||||
.post(chatController.add);
|
||||
|
||||
module.exports = router;
|
||||
|
@ -1,11 +1,11 @@
|
||||
const express = require("express");
|
||||
const rootRoutes = require('./root');
|
||||
const accountRoutes = require('./account.js');
|
||||
// const chatRoutes = require('./chat.js');
|
||||
const chatRoutes = require('./chat.js');
|
||||
const router = express.Router();
|
||||
|
||||
router.use("/", rootRoutes);
|
||||
router.use("/account", accountRoutes);
|
||||
// router.use("/chat", chatRoutes);
|
||||
router.use("/chat", chatRoutes);
|
||||
|
||||
module.exports = router;
|
||||
|
@ -1,9 +1,52 @@
|
||||
const { subtle } = require('node:crypto').webcrypto;
|
||||
|
||||
const stringutils = {
|
||||
hexToArray: (hexString) => {
|
||||
return Uint8Array.from(Buffer.from(hexString, 'hex'));
|
||||
},
|
||||
arrayToHex: (hex) => {
|
||||
return Buffer.from(hex).toString('hex');
|
||||
},
|
||||
pemToHex: (pemKey) => {
|
||||
const base64 = pemKey.replace(`-----BEGIN PUBLIC KEY-----`, '').replace(`-----END PUBLIC KEY-----`, '').trim().replaceAll('\n','');
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
const hex = buffer.toString('hex');
|
||||
return hex
|
||||
},
|
||||
hexToPem: (hexString) => {
|
||||
const buffer = Buffer.from(hexString, 'hex');
|
||||
const base64 = buffer.toString('base64');
|
||||
// split into 64 character long chunks for PEM format
|
||||
const base64Chunks = base64.match(/.{1,64}/g) || [];
|
||||
return `-----BEGIN PUBLIC KEY-----\n${base64Chunks.join('\n')}\n-----END PUBLIC KEY-----`;
|
||||
},
|
||||
hexToKey: async (hexString) => {
|
||||
const uint8Array = stringutils.hexToArray(hexString);
|
||||
const publicKey = await subtle.importKey(
|
||||
"spki",
|
||||
uint8Array,
|
||||
{
|
||||
name: "Ed25519",
|
||||
},
|
||||
true,
|
||||
["verify"],
|
||||
);
|
||||
return publicKey;
|
||||
},
|
||||
pemToKey: async (pemKey) => {
|
||||
const base64 = pemKey.replace(`-----BEGIN PUBLIC KEY-----`, '').replace(`-----END PUBLIC KEY-----`, '').trim();
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
const uint8Array = new Uint8Array(buffer);
|
||||
const publicKey = await subtle.importKey(
|
||||
"spki",
|
||||
uint8Array,
|
||||
{
|
||||
name: "Ed25519",
|
||||
},
|
||||
true,
|
||||
["verify"],
|
||||
);
|
||||
return publicKey;
|
||||
}
|
||||
}
|
||||
module.exports = stringutils;
|
||||
|
@ -13,6 +13,7 @@ html(lang="en-US")
|
||||
script(src="/pubkey.js", defer)
|
||||
script(src="/noble-curves.js", defer)
|
||||
script(type="module", src="/ecdh.js", defer)
|
||||
script(type="module", src="/popups-logged.js", defer)
|
||||
else
|
||||
script(type="module", src="/popups.js", defer)
|
||||
script(type="module", src="/register.js", defer)
|
||||
@ -47,7 +48,8 @@ html(lang="en-US")
|
||||
else
|
||||
.btn-toolbar.btn-group-sm(role="toolbar", aria-label="Toolbar")
|
||||
.btn-group.mr-2(role="group", aria-label="logout")
|
||||
a#logout.btn.btn-secondary(href="./account/logout") logout
|
||||
a#logout.btn.btn-secondary(href="/account/logout") logout
|
||||
button#add.btn.btn-secondary(type="button") add
|
||||
|
||||
|
||||
.d-flex.mb-3
|
||||
@ -60,5 +62,12 @@ html(lang="en-US")
|
||||
input#input(autocomplete="off")
|
||||
button Send
|
||||
|
||||
#addPopup.popup
|
||||
.popup-content
|
||||
.btn-group.mr-2.w-100(role="group", aria-label="Add group")
|
||||
input#publickeyadd.form-control.input-sm.w-50(type="text", placeholder="public key", required)
|
||||
button#addconfirm.btn.btn-secondary(type="button") confirm
|
||||
button#addcancel.btn.btn-secondary(type="button") cancel
|
||||
|
||||
|
||||
|
||||
|
@ -1,7 +1,36 @@
|
||||
const { subtle } = require('node:crypto').webcrypto;
|
||||
const authentication = require('../src/authentication');
|
||||
const stringutils = require("../src/stringutils");
|
||||
|
||||
describe('authentication module', () => {
|
||||
it('conversion between pem and hex', async () => {
|
||||
let pemKey = '-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEALrj4A3Vftz5TgWXEHi5KG+HD+uQLGB3bGc4TprDi9kE=\n-----END PUBLIC KEY-----';
|
||||
let hexKey = stringutils.pemToHex(pemKey);
|
||||
let pemKeyFromHex = stringutils.hexToPem(hexKey);
|
||||
expect(pemKeyFromHex).toBe(pemKey);
|
||||
});
|
||||
|
||||
it('should convert a pemkey to hex', async () => {
|
||||
const { publicKey, privateKey } = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: "Ed25519",
|
||||
},
|
||||
true,
|
||||
["sign", "verify"],
|
||||
);
|
||||
const exportedPubkey = await crypto.subtle.exportKey("spki", publicKey);
|
||||
const exportedBuffer = Buffer.from(exportedPubkey);
|
||||
const exportedAsBase64 = exportedBuffer.toString('base64');
|
||||
let pemKey = `-----BEGIN PUBLIC KEY-----\n${exportedAsBase64}\n-----END PUBLIC KEY-----`;
|
||||
let hexKey = stringutils.pemToHex(pemKey);
|
||||
let importedKey = await stringutils.hexToKey(hexKey);
|
||||
const exportedPubkeyAgain = await crypto.subtle.exportKey("spki", importedKey);
|
||||
const exportedBufferAgain = Buffer.from(exportedPubkeyAgain);
|
||||
const exportedAsBase64Again = exportedBufferAgain.toString('base64');
|
||||
let pemKeyAgain = `-----BEGIN PUBLIC KEY-----\n${exportedAsBase64Again}\n-----END PUBLIC KEY-----`;
|
||||
expect(pemKeyAgain).toBe(pemKey);
|
||||
});
|
||||
|
||||
it('should return the public key if the signature is verified successfully', async () => {
|
||||
const { publicKey, privateKey } = await crypto.subtle.generateKey(
|
||||
{
|
||||
@ -11,10 +40,11 @@ describe('authentication module', () => {
|
||||
["sign", "verify"],
|
||||
);
|
||||
const exportedPubkey = await crypto.subtle.exportKey("spki", publicKey);
|
||||
let exportedAsString = String.fromCharCode.apply(null, new Uint8Array(exportedPubkey));
|
||||
let exportedAsBase64 = Buffer.from(exportedAsString, 'binary').toString('base64');
|
||||
const exportedBuffer = Buffer.from(exportedPubkey);
|
||||
const exportedAsBase64 = exportedBuffer.toString('base64');
|
||||
let pemKey = `-----BEGIN PUBLIC KEY-----\n${exportedAsBase64}\n-----END PUBLIC KEY-----`;
|
||||
const publicKeys = [pemKey,];
|
||||
let hexKey = stringutils.pemToHex(pemKey);
|
||||
const publicKeys = [hexKey,];
|
||||
const msg = 'test message';
|
||||
const encoder = new TextEncoder();
|
||||
const encodedData = encoder.encode(msg);
|
||||
@ -26,10 +56,11 @@ describe('authentication module', () => {
|
||||
encodedData,
|
||||
);
|
||||
const result = await authentication.verifySignature(encodedData, signature, publicKeys);
|
||||
expect(result).toBe(pemKey);
|
||||
expect(result).toBe(hexKey);
|
||||
|
||||
let fakeKey = `-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAmrBLT6lyiFh/eUticsIFNY6AkjXuQPqj0Qvb99pCJJk=\n-----END PUBLIC KEY-----`
|
||||
const result2 = await authentication.verifySignature(encodedData, signature, [fakeKey,]);
|
||||
let fakeKeyHex = stringutils.pemToHex(fakeKey);
|
||||
const result2 = await authentication.verifySignature(encodedData, signature, [fakeKeyHex,]);
|
||||
expect(result2).toBe(null);
|
||||
});
|
||||
|
||||
@ -62,10 +93,11 @@ describe('authentication module', () => {
|
||||
|
||||
const sig = Buffer.from(signatureBase64, 'base64');
|
||||
|
||||
let pemKey2 = '-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEALrj4A3Vftz5TgWXEHi5KG+HD+uQLGB3bGc4TprDi9kE=\n-----END PUBLIC KEY-----'
|
||||
const publicKeys2 = [pemKey2,];
|
||||
let pemKey2 = '-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEALrj4A3Vftz5TgWXEHi5KG+HD+uQLGB3bGc4TprDi9kE=\n-----END PUBLIC KEY-----';
|
||||
let hexKey2 = stringutils.pemToHex(pemKey2);
|
||||
const publicKeys2 = [hexKey2,];
|
||||
const result3 = await authentication.verifySignature(encodedData2, sig, publicKeys2);
|
||||
expect(result3).toBe(pemKey2);
|
||||
expect(result3).toBe(hexKey2);
|
||||
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user