+ create user with public/private key
+ sign and verify votes and prevent unverified updates
This commit is contained in:
86
server/utils/crypto.ts
Normal file
86
server/utils/crypto.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { SignedData, VoteData } from "./types";
|
||||
/**
|
||||
* Gets the WebCrypto API regardless of environment (Node vs Browser)
|
||||
*/
|
||||
const getCrypto = () => {
|
||||
return (globalThis as any).crypto;
|
||||
};
|
||||
|
||||
export const verifyVote = async (data: any, signatureStr: string, publicKey: CryptoKey) => {
|
||||
const encoder = new TextEncoder();
|
||||
const encodedData = encoder.encode(JSON.stringify(data));
|
||||
|
||||
// Convert Base64 back to Uint8Array
|
||||
const signature = Uint8Array.from(atob(signatureStr), c => c.charCodeAt(0));
|
||||
|
||||
return await getCrypto().subtle.verify(
|
||||
"RSASSA-PKCS1-v1_5",
|
||||
publicKey,
|
||||
signature,
|
||||
encodedData
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies a specific vote within an array of votes by
|
||||
* reconstructing the "signed state" at that point in time.
|
||||
*/
|
||||
export const verifyChainedVote = async (
|
||||
voteData: SignedData<VoteData>[],
|
||||
index: number,
|
||||
pubKey: CryptoKey
|
||||
) => {
|
||||
const voteToVerify = voteData[index];
|
||||
console.log("Verifying vote: " + voteToVerify)
|
||||
if(voteToVerify) {
|
||||
// 1. Reconstruct the exact data state the user signed
|
||||
// We need the array exactly as it was when they pushed their vote
|
||||
const historicalState = voteData.slice(0, index + 1).map((v, i) => {
|
||||
if (i === index) {
|
||||
// For the current vote, the signature must be empty string
|
||||
// because it wasn't signed yet when passed to signVote
|
||||
return { ...v, signature: "" };
|
||||
}
|
||||
return v;
|
||||
});
|
||||
|
||||
try {
|
||||
// 3. Verify: Does this historicalState match the signature?
|
||||
return await verifyVote(historicalState, voteToVerify.signature, pubKey);
|
||||
} catch (err) {
|
||||
console.error("Verification failed")
|
||||
console.error(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
console.error("Vote is undefined or null");
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a Base64 string back into a usable CryptoKey object
|
||||
* @param keyStr The Base64 string (without PEM headers)
|
||||
* @param type 'public' or 'private'
|
||||
*/
|
||||
export const stringToCryptoKey = async (keyStr: string, type: 'public' | 'private'): Promise<CryptoKey> => {
|
||||
// 1. Convert Base64 string to a Uint8Array (binary)
|
||||
const bytes = Buffer.from(keyStr, 'base64');
|
||||
|
||||
// 2. Identify the format based on the key type
|
||||
// Public keys usually use 'spki', Private keys use 'pkcs8'
|
||||
const format = type === 'public' ? 'spki' : 'pkcs8';
|
||||
const usages: KeyUsage[] = type === 'public' ? ['verify'] : ['sign'];
|
||||
|
||||
// 3. Import the key
|
||||
return await getCrypto().subtle.importKey(
|
||||
format,
|
||||
bytes,
|
||||
{
|
||||
name: "RSASSA-PKCS1-v1_5",
|
||||
hash: "SHA-256",
|
||||
},
|
||||
true, // extractable (set to false if you want to lock it in memory)
|
||||
usages
|
||||
);
|
||||
};
|
||||
36
server/utils/types.ts
Normal file
36
server/utils/types.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export interface PollProps {
|
||||
userid: string | undefined,
|
||||
activePollId: string,
|
||||
pollData: PollData,
|
||||
addOption: (name: string) => void,
|
||||
vote: (optionName: string) => void
|
||||
}
|
||||
|
||||
export interface PollListProps {
|
||||
userid: string | undefined,
|
||||
}
|
||||
|
||||
export interface PollData extends Record<string, SignedData<VoteData>[]> {
|
||||
}
|
||||
|
||||
export interface SignedData<T> {
|
||||
data: T,
|
||||
signature: string
|
||||
}
|
||||
|
||||
export interface VoteData {
|
||||
userid: string,
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
export interface OptionData {
|
||||
userid: string,
|
||||
timestamp: string,
|
||||
optionName: string
|
||||
}
|
||||
|
||||
export interface UserData {
|
||||
userid: string,
|
||||
private_key: CryptoKey | undefined,
|
||||
public_key: CryptoKey | undefined
|
||||
}
|
||||
Reference in New Issue
Block a user