implemented basic authentication

This commit is contained in:
Ludwig Lehnert 2025-06-13 10:13:31 +02:00
parent 0d4d348979
commit 568b8d1443
Signed by: ludwig
SSH Key Fingerprint: SHA256:4vshH9GJ8TLO1RS2fY6rDDLnq7+KVvSClCY+uEhYYRA
7 changed files with 59 additions and 26 deletions

View File

@ -48,7 +48,7 @@ function base64ToUint8(base64: string) {
export async function deriveKeyVault(secret: string) { export async function deriveKeyVault(secret: string) {
console.log(crypto); console.log(crypto);
let hash = strToUint8(secret), last = hash; let hash = strToUint8(secret), last = hash;
for (let i = 0; i < 10000; i++) { for (let i = 0; i < 10000; i++) {
const hashInput = Uint8Array.from([...strToUint8(SALT), ...hash]); const hashInput = Uint8Array.from([...strToUint8(SALT), ...hash]);
@ -57,14 +57,14 @@ export async function deriveKeyVault(secret: string) {
hash = new Uint8Array(hashBuffer); hash = new Uint8Array(hashBuffer);
} }
const symmetricKey = last.slice(0, 32); const symmKey = last.slice(0, 32);
const privateKey = hash.slice(0, 32); const privKey = hash.slice(0, 32);
const publicKey = ed.getPublicKey(privateKey); const pubKey = ed.getPublicKey(privKey);
return { return {
publicKey, pubKey,
privateKey, privKey,
symmetricKey, symmKey,
}; };
} }
@ -85,10 +85,10 @@ export function decodeKeyVault(encoded: string) {
} }
export async function encrypt(vault: KeyVault, data: Uint8Array) { export async function encrypt(symmKey: Uint8Array, data: Uint8Array) {
const key = await crypto.subtle.importKey( const key = await crypto.subtle.importKey(
'raw', 'raw',
vault.symmetricKey, symmKey,
{ name: 'AES-GCM' }, { name: 'AES-GCM' },
false, false,
['encrypt', 'decrypt'], ['encrypt', 'decrypt'],
@ -105,10 +105,10 @@ export async function encrypt(vault: KeyVault, data: Uint8Array) {
return Uint8Array.from([...iv, ...new Uint8Array(encryptedBuffer)]); return Uint8Array.from([...iv, ...new Uint8Array(encryptedBuffer)]);
} }
export async function decrypt(vault: KeyVault, cipher: Uint8Array) { export async function decrypt(symmKey: Uint8Array, cipher: Uint8Array) {
const key = await crypto.subtle.importKey( const key = await crypto.subtle.importKey(
'raw', 'raw',
vault.symmetricKey, symmKey,
{ name: 'AES-GCM' }, { name: 'AES-GCM' },
false, false,
['encrypt', 'decrypt'], ['encrypt', 'decrypt'],
@ -125,3 +125,15 @@ export async function decrypt(vault: KeyVault, cipher: Uint8Array) {
return new Uint8Array(dataBuffer); return new Uint8Array(dataBuffer);
} }
export async function sign(privKey: Uint8Array, data: Uint8Array): Promise<Uint8Array> {
return await ed.signAsync(data, privKey) as Uint8Array;
}
export async function verify(pubKey: Uint8Array, signature: Uint8Array, data: Uint8Array): Promise<boolean> {
try {
return await ed.verifyAsync(signature, data, pubKey);
} catch (_) {
return false;
}
}

View File

@ -15,7 +15,7 @@
} }
const vault = getKeyVault(); const vault = getKeyVault();
fetch(`/api/log?pubkey=${encodeURIComponent(uint8ToHex(vault!.publicKey))}`).then(async res => { fetch(`/api/log?pubkey=${encodeURIComponent(uint8ToHex(vault!.pubKey))}`).then(async res => {
if (!res.ok) return; if (!res.ok) return;
entryUrls = (await res.json() as any[]).sort((a, b) => b.date.localeCompare(a.date)); entryUrls = (await res.json() as any[]).sort((a, b) => b.date.localeCompare(a.date));
@ -35,7 +35,7 @@
console.log(cipherBuffer); console.log(cipherBuffer);
const vault = getKeyVault()!; const vault = getKeyVault()!;
const videoData = await decrypt(vault, new Uint8Array(cipherBuffer)); const videoData = await decrypt(vault.symmKey, new Uint8Array(cipherBuffer));
const blob = new Blob([videoData], { type: 'video/webm' }); const blob = new Blob([videoData], { type: 'video/webm' });
videoSrc = URL.createObjectURL(blob); videoSrc = URL.createObjectURL(blob);

View File

@ -3,7 +3,7 @@
import { hashBuffer } from "$lib/hash"; import { hashBuffer } from "$lib/hash";
import { dateDiff } from "$lib/date"; import { dateDiff } from "$lib/date";
import { getKeyVault, loggedIn } from "$lib/auth"; import { getKeyVault, loggedIn } from "$lib/auth";
import { encrypt, uint8ToHex } from "$lib/crypto"; import { encrypt, sign, uint8ToHex } from "$lib/crypto";
import { lang } from "$lib/lang"; import { lang } from "$lib/lang";
type StatusT = "pending" | "active" | "error"; type StatusT = "pending" | "active" | "error";
@ -59,7 +59,10 @@
recorder.addEventListener('dataavailable', async (e) => { recorder.addEventListener('dataavailable', async (e) => {
const inputBlob = new Blob([e.data], { type: 'video/mp4' }); const inputBlob = new Blob([e.data], { type: 'video/mp4' });
const convertRes = await fetch('/api/convert', { const vault = getKeyVault()!;
const signature = await sign(vault.privKey, new Uint8Array(await inputBlob.arrayBuffer()));
const convertRes = await fetch(`/api/convert?pubkey=${encodeURIComponent(uint8ToHex(vault.pubKey))}&signature=${encodeURIComponent(uint8ToHex(signature))}`, {
method: 'POST', method: 'POST',
body: inputBlob, body: inputBlob,
}); });
@ -87,13 +90,12 @@
const blob = await (await fetch(recordingURL)).blob(); const blob = await (await fetch(recordingURL)).blob();
const buffer = await blob.arrayBuffer(); const buffer = await blob.arrayBuffer();
console.log('buffer', buffer);
const vault = getKeyVault()!; const vault = getKeyVault()!;
const encrypted = await encrypt(vault, new Uint8Array(buffer)); const encrypted = await encrypt(vault.symmKey, new Uint8Array(buffer));
console.log('encrypted', encrypted); const signature = await sign(vault.privKey, encrypted);
const res = await fetch(`/api/today?pubkey=${uint8ToHex(vault.publicKey)}`, { const res = await fetch(`/api/today?pubkey=${uint8ToHex(vault.pubKey)}&signature=${uint8ToHex(signature)}`, {
method: 'POST', method: 'POST',
body: encrypted, body: encrypted,
headers: { headers: {

View File

@ -6,9 +6,9 @@
if (typeof window !== 'object') return; if (typeof window !== 'object') return;
document.body.style.setProperty('--vh', `${window.innerHeight * 0.01}px`); document.body.style.setProperty('--vh', `${window.innerHeight * 0.01}px`);
window.addEventListener('resize', () => { // window.addEventListener('resize', () => {
document.body.style.setProperty('--vh', `${window.innerHeight * 0.01}px`); // document.body.style.setProperty('--vh', `${window.innerHeight * 0.01}px`);
}); // });
}); });
</script> </script>

View File

@ -5,8 +5,6 @@
onMount(() => { onMount(() => {
if (typeof window !== 'object') return; if (typeof window !== 'object') return;
console.log('test', loggedIn());
if (loggedIn()) { if (loggedIn()) {
window.location.href = '/today'; window.location.href = '/today';
} }

View File

@ -1,10 +1,22 @@
import { convertVideo } from "$lib/ffmpeg.server"; import { convertVideo } from "$lib/ffmpeg.server";
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import type { RequestHandler } from "./$types"; import type { RequestHandler } from "./$types";
import { hexToUint8, verify } from "$lib/crypto";
export const POST: RequestHandler = async ({ request, url }) => {
const pubkeyHex = url.searchParams.get('pubkey')?.trim()?.toLowerCase();
if (!pubkeyHex || !/^[0-9a-f]{64}$/.test(pubkeyHex)) error(400);
const pubKey = hexToUint8(pubkeyHex);
const signatureHex = url.searchParams.get('signature')?.trim()?.toLowerCase();
if (!signatureHex || !/^[0-9a-f]+$/.test(signatureHex)) error(400);
const signature = hexToUint8(signatureHex);
export const POST: RequestHandler = async ({ request }) => {
const bodyBuffer = await request.arrayBuffer(); const bodyBuffer = await request.arrayBuffer();
const bodyUint8 = new Uint8Array(bodyBuffer); const bodyUint8 = new Uint8Array(bodyBuffer);
const valid = await verify(pubKey, signature, bodyUint8);
if (!valid) error(401);
const convertedUint8 = await convertVideo(bodyUint8); const convertedUint8 = await convertVideo(bodyUint8);
if (!convertedUint8) error(500); if (!convertedUint8) error(500);

View File

@ -4,17 +4,26 @@ import type { RequestHandler } from "./$types";
import { PutObjectCommand } from "@aws-sdk/client-s3"; import { PutObjectCommand } from "@aws-sdk/client-s3";
import { env } from "$env/dynamic/private"; import { env } from "$env/dynamic/private";
import { s3 } from "$lib/storage.server"; import { s3 } from "$lib/storage.server";
import { hexToUint8, verify } from "$lib/crypto";
// This route is currently vulnerable against replay attacks across multiple days
// -- possible fix: prepend date to signature verification payload
export const POST: RequestHandler = async ({ request, url }) => { export const POST: RequestHandler = async ({ request, url }) => {
const pubkeyHex = url.searchParams.get('pubkey')?.trim()?.toLowerCase(); const pubkeyHex = url.searchParams.get('pubkey')?.trim()?.toLowerCase();
if (!pubkeyHex || !/^[0-9a-f]{64}$/.test(pubkeyHex)) error(400); if (!pubkeyHex || !/^[0-9a-f]{64}$/.test(pubkeyHex)) error(400);
const pubKey = hexToUint8(pubkeyHex);
const signatureHex = url.searchParams.get('signature')?.trim()?.toLowerCase();
if (!signatureHex || !/^[0-9a-f]+$/.test(signatureHex)) error(400);
const signature = hexToUint8(signatureHex);
const today = new Date().toISOString().slice(0, 10); const today = new Date().toISOString().slice(0, 10);
const bodyBuffer = await request.arrayBuffer(); const bodyBuffer = await request.arrayBuffer();
const bodyUint8 = new Uint8Array(bodyBuffer); const bodyUint8 = new Uint8Array(bodyBuffer);
console.log(today, bodyUint8.byteLength); const valid = await verify(pubKey, signature, bodyUint8);
if (!valid) error(401);
const command = new PutObjectCommand({ const command = new PutObjectCommand({
Bucket: env.S3_BUCKET, Bucket: env.S3_BUCKET,