diff options
Diffstat (limited to 'src/routes')
-rw-r--r-- | src/routes/+page.svelte | 141 | ||||
-rw-r--r-- | src/routes/canaries/canaries.ts | 107 | ||||
-rw-r--r-- | src/routes/canaries/keystore.ts | 137 | ||||
-rw-r--r-- | src/routes/canary/+page.svelte | 7 | ||||
-rw-r--r-- | src/routes/shared.ts | 289 |
5 files changed, 354 insertions, 327 deletions
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index d96dda2..7bbd52c 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -13,11 +13,12 @@ */ --> <script lang="ts"> - import logo from "./boot-logo.png"; - import "./anim.css"; - import { architecture, hostname, tty, versions } from "./distro-info"; - import { login, ttyLines, type RenderBlock, type TTYText } from "./shared"; - import { onDestroy, onMount } from "svelte"; + import logo from './boot-logo.png'; + import './anim.css'; + import { architecture, hostname, tty, versions } from './distro-info'; + import { login, ttyLines, type RenderBlock, type TTYText } from './shared'; + import { onDestroy, onMount } from 'svelte'; + import { base } from '$app/paths'; const initTagLine = `line flex flex-row flex-wrap gap-3 justify-between`; @@ -36,8 +37,8 @@ skipAnimation?: boolean; } = $props(); onMount(() => { - if (skipAnimation && location.pathname !== "/skip-animation") - history.replaceState({}, "", "/skip-animation"); + if (skipAnimation && location.pathname !== base + '/skip-animation') + history.replaceState({}, '', base + '/skip-animation'); }); let isScripted = $state(false); @@ -53,7 +54,7 @@ <span class="text-[{colour}]">*</span> {/snippet} {#snippet okTagLeft()} - {@render tagLeft("#51f051")} + {@render tagLeft('#51f051')} {/snippet} {#snippet tagRight(innerText: string, innerColour: string)} <span class="text-[#5f5fff]">[</span> @@ -61,7 +62,7 @@ <span class="text-[#5f5fff]">]</span> {/snippet} {#snippet okTagRight()} - {@render tagRight("ok", "#51f051")} + {@render tagRight('ok', '#51f051')} {/snippet} {#snippet okLine(text: string, classes?: string, rightClass?: string)} <div class="{initTagLine}{classes ? ' ' + classes : ''}"> @@ -90,28 +91,30 @@ ? ` underline` : ' no-underline'} inline" >{#if section.raw}{@html section.value}{:else}{#each section.value - .split("\n") + .split('\n') .map( (l, i, a) => (i === a.length - 1 ? [0, l] : [1, l]), ) as [nl, l]}{l}{#if nl}<br />{/if}{/each}{/if}</span > {/snippet} {#snippet ttyTextMiddleRenderer( section: RenderBlock, - line: TTYText & { kind: "text" }, + line: TTYText & { kind: 'text' }, )} {#if section.url} - {#if typeof section.url === "string"} - {#if section.url.startsWith("newtab:")} + {#if typeof section.url === 'string'} + {#if section.url.startsWith('newtab:')} + {@const url = section.url.substring(7)} <a - href={section.url.substring(7)} + href={url.startsWith('/') ? base + url : url} target="_blank" rel="noopener noreferrer" class="no-underline text-inherit" download={section.dl}>{@render ttyTextInnerRenderer(section)}</a > - {:else if section.url.startsWith("currenttab:")} + {:else if section.url.startsWith('currenttab:')} + {@const url = section.url.substring(11)} <a - href={section.url.substring(11)} + href={url.startsWith('/') ? base + url : url} class="no-underline text-inherit" download={section.dl}>{@render ttyTextInnerRenderer(section)}</a > @@ -121,9 +124,9 @@ {:else} <span onclick={() => - typeof section.url !== "function" ? void 0 : section.url(line)} + typeof section.url !== 'function' ? void 0 : section.url(line)} onkeypress={() => - typeof section.url !== "function" ? void 0 : section.url(line)} + typeof section.url !== 'function' ? void 0 : section.url(line)} role="link" tabindex="0">{@render ttyTextInnerRenderer(section)}</span > @@ -133,9 +136,9 @@ {/if} {/snippet} {#snippet ttyText(line: TTYText)} - {#if line.kind === "text"} - {#if (line.renderrestriction ?? "everywhere") === "everywhere" || line.renderrestriction === "noscript" || (line.renderrestriction === "js-only" && isScripted)} - {#if line.renderrestriction === "noscript"} + {#if line.kind === 'text'} + {#if (line.renderrestriction ?? 'everywhere') === 'everywhere' || line.renderrestriction === 'noscript' || (line.renderrestriction === 'js-only' && isScripted)} + {#if line.renderrestriction === 'noscript'} <noscript class="inline-block max-w-[100%] {line.classes.join(' ')}" >{#each line.value as v}{@render ttyTextMiddleRenderer( v, @@ -155,7 +158,7 @@ {/snippet} <div class="hidden"> - {#each ["inline-block", "block", "flex", "inline-flex", "max-w-full", "font-thin", "font-extralight", "font-light", "font-normal", "font-medium", "font-semibold", "font-bold", "font-extrabold", "font-black"] as c} + {#each ['inline-block', 'block', 'flex', 'inline-flex', 'max-w-full', 'font-thin', 'font-extralight', 'font-light', 'font-normal', 'font-medium', 'font-semibold', 'font-bold', 'font-extrabold', 'font-black'] as c} <span class={c}></span> {/each} </div> @@ -257,51 +260,51 @@ > </div> <div class="line">​</div> - {@render okLine("/proc/ is already mounted")} - {@render okLine("Mounting /run ...")} - {@render okLine("/run/openrc: creating directory")} - {@render okLine("/run/lock: creating directory", "openrc-boot-step-1")} - {@render okLine("/run/lock: correcting owner", "openrc-boot-step-1")} - {@render okLine("Caching service dependencies ...", "openrc-boot-step-1")} - {@render okLine("Remounting devtmpfs on /dev ...", "openrc-boot-step-2")} - {@render okLine("Mounting /dev/mqueue ...", "openrc-boot-step-3")} - {@render okLine("Mounting security filesystem ...", "openrc-boot-step-4")} - {@render okLine("Mounting debug filesystem ...", "openrc-boot-step-5")} + {@render okLine('/proc/ is already mounted')} + {@render okLine('Mounting /run ...')} + {@render okLine('/run/openrc: creating directory')} + {@render okLine('/run/lock: creating directory', 'openrc-boot-step-1')} + {@render okLine('/run/lock: correcting owner', 'openrc-boot-step-1')} + {@render okLine('Caching service dependencies ...', 'openrc-boot-step-1')} + {@render okLine('Remounting devtmpfs on /dev ...', 'openrc-boot-step-2')} + {@render okLine('Mounting /dev/mqueue ...', 'openrc-boot-step-3')} + {@render okLine('Mounting security filesystem ...', 'openrc-boot-step-4')} + {@render okLine('Mounting debug filesystem ...', 'openrc-boot-step-5')} {@render okLine( - "Mounting persistent storage (pstore) filesystem ...", - "openrc-boot-step-5", + 'Mounting persistent storage (pstore) filesystem ...', + 'openrc-boot-step-5', )} - {@render okLine("Mounting efivarfs filesystem ...", "openrc-boot-step-5")} - {@render okLine("Starting busybox mdev ...", "openrc-boot-step-6")} - {@render okLine("Scanning hardware for mdev ...", "openrc-boot-step-6")} - {@render okLine("Loading hardware drivers ...", "openrc-boot-step-7")} - {@render okLine("Loading modules ...", "openrc-boot-step-8")} + {@render okLine('Mounting efivarfs filesystem ...', 'openrc-boot-step-5')} + {@render okLine('Starting busybox mdev ...', 'openrc-boot-step-6')} + {@render okLine('Scanning hardware for mdev ...', 'openrc-boot-step-6')} + {@render okLine('Loading hardware drivers ...', 'openrc-boot-step-7')} + {@render okLine('Loading modules ...', 'openrc-boot-step-8')} {@render okLine( - "Setting system clock using the hardware clock [UTC] ...", - "openrc-boot-step-9", + 'Setting system clock using the hardware clock [UTC] ...', + 'openrc-boot-step-9', )} - {@render okLine("Checking local filesystems ...", "openrc-boot-step-10")} + {@render okLine('Checking local filesystems ...', 'openrc-boot-step-10')} <div class="line openrc-boot-step-11"> /dev/mapper/bepis: clean, {fsckFileCount}/{fsckTotalCount} files, {blocksCount}/{blocksTotal} blocks </div> {@render okLine( - "Remounting root filesystem read/write ...", - "openrc-boot-step-12", + 'Remounting root filesystem read/write ...', + 'openrc-boot-step-12', )} - {@render okLine("Remounting filesystems ...", "openrc-boot-step-13")} - {@render okLine("Activating swap devices ...", "openrc-boot-step-14")} - {@render okLine("Mounting local filesystems ...", "openrc-boot-step-15")} + {@render okLine('Remounting filesystems ...', 'openrc-boot-step-13')} + {@render okLine('Activating swap devices ...', 'openrc-boot-step-14')} + {@render okLine('Mounting local filesystems ...', 'openrc-boot-step-15')} {@render okLine( - "Configuring kernel parameters ...", - "openrc-boot-step-16", + 'Configuring kernel parameters ...', + 'openrc-boot-step-16', )} - {@render okLine("Creating user login records ...", "openrc-boot-step-17")} - {@render okLine("Setting hostname ...", "openrc-boot-step-18")} - {@render okLine("Setting keymap ...", "openrc-boot-step-19")} - {@render okLine("Starting networking ...", "openrc-boot-step-20")} - {@render okLine("\xa0\xa0lo ...", "openrc-boot-step-21")} - {@render okLine("\xa0\xa0eth0 ...", "openrc-boot-step-22")} + {@render okLine('Creating user login records ...', 'openrc-boot-step-17')} + {@render okLine('Setting hostname ...', 'openrc-boot-step-18')} + {@render okLine('Setting keymap ...', 'openrc-boot-step-19')} + {@render okLine('Starting networking ...', 'openrc-boot-step-20')} + {@render okLine('\xa0\xa0lo ...', 'openrc-boot-step-21')} + {@render okLine('\xa0\xa0eth0 ...', 'openrc-boot-step-22')} <div class="line openrc-boot-step-22">udhcpd: started</div> <div class="line openrc-boot-step-23">udhcpd: broadcasting discover</div> <!-- TODO: configurable subnet uwu --> @@ -314,21 +317,21 @@ lease time 3600 </div> {@render okLine( - "Seeding random number generator ...", - "openrc-boot-step-26", + 'Seeding random number generator ...', + 'openrc-boot-step-26', )} {@render okLine( - "Seeding 256 bits without crediting", - "openrc-boot-step-26", + 'Seeding 256 bits without crediting', + 'openrc-boot-step-26', )} {@render okLine( - "Saving 256 bits of creditable seed for next boot", - "openrc-boot-step-26", + 'Saving 256 bits of creditable seed for next boot', + 'openrc-boot-step-26', )} - {@render okLine("Starting logbookd ...", "openrc-boot-step-27")} - {@render okLine("Starting busybox acpid ...", "openrc-boot-step-28")} - {@render okLine("Starting busybox crond ...", "openrc-boot-step-29")} - {@render okLine("Starting busybox ntpd ...", "openrc-boot-step-29")} + {@render okLine('Starting logbookd ...', 'openrc-boot-step-27')} + {@render okLine('Starting busybox acpid ...', 'openrc-boot-step-28')} + {@render okLine('Starting busybox crond ...', 'openrc-boot-step-29')} + {@render okLine('Starting busybox ntpd ...', 'openrc-boot-step-29')} <div class="line openrc-boot-step-30">​</div> <div class="line openrc-boot-step-30"> Welcome to Alpine Linux {versions.alpine @@ -341,7 +344,7 @@ <div class="line openrc-boot-step-30"> {hostname} login: <span class="openrc-username" >{#each login.username - .split("") + .split('') .map((v, i) => [v, i] as const) as [char, idx]}<span class="openrc-username-char openrc-username-char-{idx} inline-block" >{char}</span @@ -364,16 +367,16 @@ </span> </div> <div class="line ttylines-openrc font-mono text-[#070505] flex flex-col"> - {#each ttyLines.filter(((maxidx) => (_, i) => i < (maxidx === -1 ? Infinity : maxidx))(ttyLines.findIndex((v) => v.kind === "clear"))) as line} + {#each ttyLines.filter(((maxidx) => (_, i) => i < (maxidx === -1 ? Infinity : maxidx))(ttyLines.findIndex((v) => v.kind === 'clear'))) as line} {@render ttyText(line)} {/each} </div> </div> </div> - {#if ttyLines.find((v) => v.kind === "clear")} + {#if ttyLines.find((v) => v.kind === 'clear')} {#each ttyLines .map((v, i) => [v, i] as const) - .filter(([v]) => v.kind === "clear") + .filter(([v]) => v.kind === 'clear') .map((v, i, a) => [v[1], a[i + 1]?.[1], i]) as [idx, nextIdx, clearIdx]} <div id="tty-{clearIdx}" diff --git a/src/routes/canaries/canaries.ts b/src/routes/canaries/canaries.ts index 692abc2..dab2e68 100644 --- a/src/routes/canaries/canaries.ts +++ b/src/routes/canaries/canaries.ts @@ -1,25 +1,25 @@ -import { browser } from "$app/environment"; -import { validateSignature } from "./keystore"; +import { browser } from '$app/environment'; +import { base } from '$app/paths'; +import { validateSignature } from './keystore'; export const canaries: Canary[] = []; export interface CanaryInterface { - name: string, - description: string, - signer: string, - url: string, - keyIdentifier: string, - contentType: string, - upstream?: string + name: string; + description: string; + signer: string; + url: string; + keyIdentifier: string; + contentType: string; + upstream?: string; } -export interface Canary extends CanaryInterface { } +export interface Canary extends CanaryInterface {} type _PreCanaryInterfaceEntries = { - [T in keyof CanaryInterface]: [T, CanaryInterface[T]] -} -type CanaryInterfaceEntry = _PreCanaryInterfaceEntries[keyof _PreCanaryInterfaceEntries] + [T in keyof CanaryInterface]: [T, CanaryInterface[T]]; +}; +type CanaryInterfaceEntry = + _PreCanaryInterfaceEntries[keyof _PreCanaryInterfaceEntries]; export class Canary { - public constructor( - canary: CanaryInterface - ) { + public constructor(canary: CanaryInterface) { const entries = Object.entries(canary) as CanaryInterfaceEntry[]; for (const a of entries) { // @ts-ignore we know the value will match in type @@ -29,19 +29,22 @@ export class Canary { } public forceContentType = false; public async getRawText() { - if (!browser) throw new Error('This should only be done in a browser.') - const res = await fetch(this.url) + if (!browser) throw new Error('This should only be done in a browser.'); + const res = await fetch(this.url); if (res.ok) { - if (!this.forceContentType) this.contentType = res.headers.get('Content-Type') || this.contentType - const text = await res.text() - return text + if (!this.forceContentType) + this.contentType = res.headers.get('Content-Type') || this.contentType; + const text = await res.text(); + return text; } else { throw new Error(`Fetching canary failed with code ${res.status} (${res.statusText}): -${await res.text().catch(e => `Unknown (Unable to get text, ${JSON.stringify(e)})`)}`) +${await res.text().catch((e) => `Unknown (Unable to get text, ${JSON.stringify(e)})`)}`); } } - public async getValidatedText(rawText: string | Promise<string> = this.getRawText()) { - return await validateSignature(await rawText, this.keyIdentifier) + public async getValidatedText( + rawText: string | Promise<string> = this.getRawText(), + ) { + return await validateSignature(await rawText, this.keyIdentifier); } /** Returns downloadable data url if signature passes, otherwise returns null */ public async getUrl(rawText: string | Promise<string> = this.getRawText()) { @@ -54,34 +57,34 @@ ${await res.text().catch(e => `Unknown (Unable to get text, ${JSON.stringify(e)} return null; } return { - stripped: URL.createObjectURL(new Blob([stripped], { - type: this.contentType, - })), - signed: URL.createObjectURL(new Blob([t], { - type: this.contentType, - })), - } + stripped: URL.createObjectURL( + new Blob([stripped], { + type: this.contentType, + }), + ), + signed: URL.createObjectURL( + new Blob([t], { + type: this.contentType, + }), + ), + }; } } -new Canary( - { - name: 'estrogen.zone', - description: 'Services hosted around estrogen.zone and yuridick.gay', - signer: 'memdmp', - url: '/canaries/memdmp:estrogen.zone', - keyIdentifier: 'memdmp', - contentType: 'text/plain; charset=utf-8' - } -); +new Canary({ + name: 'estrogen.zone', + description: 'Services hosted around estrogen.zone and yuridick.gay', + signer: 'memdmp', + url: base + '/canaries/memdmp:estrogen.zone', + keyIdentifier: 'memdmp', + contentType: 'text/plain; charset=utf-8', +}); -new Canary( - { - name: 'kyun.host', - description: 'The VPS provider "kyun.host"', - signer: 'napatha', - url: '/canaries/napatha:kyun.host', - keyIdentifier: 'napatha', - contentType: 'text/plain; charset=utf-8', - upstream: 'https://files.kyun.host/canary.txt' - } -); +new Canary({ + name: 'kyun.host', + description: 'The VPS provider "kyun.host"', + signer: 'napatha', + url: base + '/canaries/napatha:kyun.host', + keyIdentifier: 'napatha', + contentType: 'text/plain; charset=utf-8', + upstream: 'https://files.kyun.host/canary.txt', +}); diff --git a/src/routes/canaries/keystore.ts b/src/routes/canaries/keystore.ts index 5b85201..ca8cb22 100644 --- a/src/routes/canaries/keystore.ts +++ b/src/routes/canaries/keystore.ts @@ -1,34 +1,49 @@ -import { dev } from "$app/environment"; -import { PublicKey, readCleartextMessage, readKey, verify } from "openpgp"; -import { fallbackKeys } from "./fallback-keys"; +import { dev } from '$app/environment'; +import { PublicKey, readCleartextMessage, readKey, verify } from 'openpgp'; +import { fallbackKeys } from './fallback-keys'; export const keyStore = new Map<string, PublicKey>(); const will_debug = true; -const debug = dev && will_debug ? (z: any, ...a: any[]) => console.debug(...[ - ...(typeof z === 'string' ? [`[keystore] ${z}`] : ['[keystore]', z]), - ...a -]) : () => void 0; +const debug = + dev && will_debug + ? (z: any, ...a: any[]) => + console.debug( + ...[ + ...(typeof z === 'string' + ? [`[keystore] ${z}`] + : ['[keystore]', z]), + ...a, + ], + ) + : () => void 0; const _validateSignature = async (message: string, id: string) => { id = id.toUpperCase(); - debug(`[validateSignature][status=lookup] looking up key by ID ${JSON.stringify(id)}`) - const key = keyStore.get(id) ?? keyStore.get(id.replace(/ /g, "")); - if (!key) throw new Error("Could not find key from keystore"); - debug(`[validateSignature][status=parse] parsing message`) + debug( + `[validateSignature][status=lookup] looking up key by ID ${JSON.stringify(id)}`, + ); + const key = keyStore.get(id) ?? keyStore.get(id.replace(/ /g, '')); + if (!key) throw new Error('Could not find key from keystore'); + debug(`[validateSignature][status=parse] parsing message`); const signedMessage = await readCleartextMessage({ cleartextMessage: message, }); - debug(`[validateSignature][status=verify] verifying message signature`) + debug(`[validateSignature][status=verify] verifying message signature`); const verificationResult = await verify({ message: signedMessage, verificationKeys: key, expectSigned: true, }); - debug(`[validateSignature][success] successfully validated message signature`) + debug( + `[validateSignature][success] successfully validated message signature`, + ); return verificationResult.data; -} -export const validateSignature: typeof _validateSignature = async (message, id) => { - debug(`[validateSignature][globalState] waiting on initKeystore`) +}; +export const validateSignature: typeof _validateSignature = async ( + message, + id, +) => { + debug(`[validateSignature][globalState] waiting on initKeystore`); await initKeystore; - return _validateSignature(message, id) + return _validateSignature(message, id); }; const pushKey = async ({ ids, @@ -47,34 +62,35 @@ const pushKey = async ({ }) => { ids = ids ?? []; if (is_url) { - const url = new URL(key, "https://keys.openpgp.org/vks/v1/by-fingerprint/"); - debug('[pushKey][info] Getting key with url\n\n', url) - key = await fetch( - url, - ).then((v) => v.text()).catch(e => { - if (fallbackKeys.has(key)) { - debug('[pushKey][warn] Failed with error', e, 'but found fallback key') - return fallbackKeys.get(key)! - } - else { - debug('[pushKey][error] Failed to fetch key, cannot find fallback') - throw e - } - }); - debug('[pushKey][success] Fetched key\n\n', JSON.stringify(key)) + const url = new URL(key, 'https://keys.openpgp.org/vks/v1/by-fingerprint/'); + debug('[pushKey][info] Getting key with url\n\n', url); + key = await fetch(url) + .then((v) => v.text()) + .catch((e) => { + if (fallbackKeys.has(key)) { + debug( + '[pushKey][warn] Failed with error', + e, + 'but found fallback key', + ); + return fallbackKeys.get(key)!; + } else { + debug('[pushKey][error] Failed to fetch key, cannot find fallback'); + throw e; + } + }); + debug('[pushKey][success] Fetched key\n\n', JSON.stringify(key)); } else { - debug('[pushKey][success] Found key\n\n', JSON.stringify(key)) + debug('[pushKey][success] Found key\n\n', JSON.stringify(key)); } - if (key === null) - throw new Error('Key is null.') - if (key === '') - throw new Error('Key is empty string.') + if (key === null) throw new Error('Key is null.'); + if (key === '') throw new Error('Key is empty string.'); if (typeof key !== 'string') - throw new Error(`Expected key with type string, got key of type ${key}`) + throw new Error(`Expected key with type string, got key of type ${key}`); if (signed_by) { - debug('[pushKey][validation][info] Key must be signed by', signed_by) + debug('[pushKey][validation][info] Key must be signed by', signed_by); key = await _validateSignature(key, signed_by); - debug('[pushKey][success] Validated signature') + debug('[pushKey][success] Validated signature'); } const parsedKey = await readKey({ armoredKey: key, @@ -86,22 +102,25 @@ const pushKey = async ({ if (missingUserIds.length) { throw new Error( `Key ${parsedKey.getFingerprint()} is missing User IDs: ${missingUserIds.join( - ", ", + ', ', )}`, ); } } - if (expect_fingerprint && parsedKey.getFingerprint().toUpperCase() !== expect_fingerprint.toUpperCase()) + if ( + expect_fingerprint && + parsedKey.getFingerprint().toUpperCase() !== + expect_fingerprint.toUpperCase() + ) throw new Error( `Key ${parsedKey.getFingerprint()} is not ${expect_fingerprint}`, ); else if (expect_fingerprint) - debug('[success] Fingerprint matches expected fingerprint') - else - debug('[warn] No expected fingerprint passed') + debug('[success] Fingerprint matches expected fingerprint'); + else debug('[warn] No expected fingerprint passed'); ids.push( - parsedKey.getKeyID().toHex().replace(/ /g, ""), - parsedKey.getFingerprint().replace(/ /g, ""), + parsedKey.getKeyID().toHex().replace(/ /g, ''), + parsedKey.getFingerprint().replace(/ /g, ''), ...(expect_user_ids ?? []), ); ids = ids.filter((v, i, a) => a.indexOf(v) === i).map((v) => v.toUpperCase()); @@ -111,15 +130,15 @@ const pushKey = async ({ debug('[success] Added key\n\n', { key: parsedKey, ids, - }) + }); }; export const initKeystore = (async () => { await pushKey({ key: 'B546778F06BBCC8EC167DB3CD919706487B8B6DE', - ids: ["memdmp"], + ids: ['memdmp'], expect_user_ids: [ - "memdmp <memdmp@estrogen.zone>", - "memdmp <memdmp@memeware.net>", + 'memdmp <memdmp@estrogen.zone>', + 'memdmp <memdmp@memeware.net>', ], expect_fingerprint: 'B546778F06BBCC8EC167DB3CD919706487B8B6DE', is_url: true, @@ -158,18 +177,18 @@ ZQ4KTbprMz8J4AD/bG33f9Kqg3AqehEyU2TldJs9U9Oni5AXGSGfKLJhmQc= =945T -----END PGP SIGNATURE----- `, - signed_by: "memdmp <memdmp@memeware.net>", - ids: ["canary-sigkey-signing"], - expect_fingerprint: '55D3582CAE78601990A8CA1DBFD0F9E61CB7D84E' + signed_by: 'memdmp <memdmp@memeware.net>', + ids: ['canary-sigkey-signing'], + expect_fingerprint: '55D3582CAE78601990A8CA1DBFD0F9E61CB7D84E', }); await pushKey({ - key: "https://git.estrogen.zone/mem-estrogen-zone.git/plain/static/keys/external/napatha.pgp.sig", - ids: ["napatha"], - expect_user_ids: ["chef naphtha <naphtha@kyun.host>"], + key: 'https://git.estrogen.zone/mem-estrogen-zone.git/plain/static/keys/external/napatha.pgp.sig', + ids: ['napatha'], + expect_user_ids: ['chef naphtha <naphtha@kyun.host>'], is_url: true, - signed_by: "canary-sigkey-signing", + signed_by: 'canary-sigkey-signing', }); - debug('[success] keystore initialization completed') + debug('[success] keystore initialization completed'); })(); export default keyStore; diff --git a/src/routes/canary/+page.svelte b/src/routes/canary/+page.svelte index 25c4f6c..2bb9142 100644 --- a/src/routes/canary/+page.svelte +++ b/src/routes/canary/+page.svelte @@ -1,9 +1,10 @@ <script lang="ts"> - import { goto } from "$app/navigation"; - import { onMount } from "svelte"; + import { goto } from '$app/navigation'; + import { base } from '$app/paths'; + import { onMount } from 'svelte'; onMount(() => - goto("/canaries/", { + goto(base + '/canaries/', { replaceState: true, }), ); diff --git a/src/routes/shared.ts b/src/routes/shared.ts index 56467a1..b40792a 100644 --- a/src/routes/shared.ts +++ b/src/routes/shared.ts @@ -7,6 +7,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ + export const biosSteps = 3; export const biosStepInterval = 1000; @@ -23,7 +24,7 @@ export const getDelay = (typingInfo: TypingSpeed) => (Math.random() * 2 - 1) * typingInfo.typingDeviation + typingInfo.typingSpeedAvg; export const login = { - username: "lain", + username: 'lain', passwordLength: 12, ...typingInfo(100), // one usually has muscle memory for well-known frequently-typed credentials, hence higher wpm }; @@ -38,34 +39,34 @@ export type RenderBlock = { url?: | `newtab:${string}` | `currenttab:${string}` - | ((textObj: TTYText & { kind: "text" }) => void); + | ((textObj: TTYText & { kind: 'text' }) => void); bg?: string; raw?: boolean; dl?: string; }; export type TTYText = | { - kind: "text"; - renderrestriction?: "everywhere" | "js-only" | "noscript"; + kind: 'text'; + renderrestriction?: 'everywhere' | 'js-only' | 'noscript'; value: RenderBlock[]; id: string; classes: string[]; } | { - kind: "removeNode"; + kind: 'removeNode'; removedId: string; removedItemClassList: string[]; } | { - kind: "time"; + kind: 'time'; delay: number; } | { - kind: "cursorVisibility"; + kind: 'cursorVisibility'; visible: boolean; } | { - kind: "clear"; + kind: 'clear'; }; export type Only<Obj, Keys extends keyof Obj> = { [k in Keys]: Obj[k]; @@ -90,15 +91,15 @@ export const ttyLines: TTYText[] = (() => { const byId = new Map<string, TTYText>(); let i = 69420; let defaultRenderRestriction: Only< - TTYText & { kind: "text" }, - "renderrestriction" - >["renderrestriction"] = "everywhere"; - type LimitedRenderBlock = Omit<RenderBlock, "value">; + TTYText & { kind: 'text' }, + 'renderrestriction' + >['renderrestriction'] = 'everywhere'; + type LimitedRenderBlock = Omit<RenderBlock, 'value'>; type RenderBlockArg = [text: string, options?: LimitedRenderBlock]; /** due to hellish css constraints, each text() call creates its own line - however, each block in a text() is joined on the same line. */ const text = ( globalOptions: - | Only<TTYText & { kind: "text" }, "renderrestriction"> + | Only<TTYText & { kind: 'text' }, 'renderrestriction'> | RenderBlockArg, ...blocks: RenderBlockArg[] ) => { @@ -109,12 +110,12 @@ export const ttyLines: TTYText[] = (() => { globalOptions = {}; } const txt = { - kind: "text", + kind: 'text', renderrestriction: globalOptions.renderrestriction ?? defaultRenderRestriction, value: blocks.map(([t, o]) => ({ value: t, - colour: "#b9b9b9", + colour: '#b9b9b9', ...o, })), id, @@ -126,34 +127,34 @@ export const ttyLines: TTYText[] = (() => { }; const wait = (time: number) => lines.push({ - kind: "time", + kind: 'time', delay: time, }); const del = (id: string | number) => { const removedId = - typeof id === "string" ? id : id >= 0 ? ids[id] : ids[ids.length + id]; + typeof id === 'string' ? id : id >= 0 ? ids[id] : ids[ids.length + id]; const r = byId.get(removedId); - if (r?.kind === "text") { + if (r?.kind === 'text') { lines.push({ - kind: "removeNode", + kind: 'removeNode', removedId, removedItemClassList: r.classes, }); - r.classes.push("ttytext-removed-after-done"); + r.classes.push('ttytext-removed-after-done'); } ids = ids.filter((v) => v !== removedId); }; const delSince = (id: string | number) => { const removedId = - typeof id === "string" ? id : id >= 0 ? ids[id] : ids[ids.length + id]; + typeof id === 'string' ? id : id >= 0 ? ids[id] : ids[ids.length + id]; const idIndex = ids.indexOf(removedId); for (let i = idIndex; i < ids.length; i++) { const removedId = ids[i]; const r = byId.get(removedId); - if (r?.kind === "text") { - r.classes.push("ttytext-removed-after-done"); + if (r?.kind === 'text') { + r.classes.push('ttytext-removed-after-done'); lines.push({ - kind: "removeNode", + kind: 'removeNode', removedId, removedItemClassList: r.classes, }); @@ -163,13 +164,13 @@ export const ttyLines: TTYText[] = (() => { }; const getLast = () => { const v = byId.get(ids[ids.length - 1]); - if (v?.kind == "text") { + if (v?.kind == 'text') { return v.value; } else return null; }; const overwriteLast = (...newValue: RenderBlockArg[]) => { const v = byId.get(ids[ids.length - 1]); - const l = v?.kind == "text" ? v.renderrestriction : "everywhere"; + const l = v?.kind == 'text' ? v.renderrestriction : 'everywhere'; del(-1); return text( { @@ -188,7 +189,7 @@ export const ttyLines: TTYText[] = (() => { [ v.value, Object.fromEntries( - Object.entries(v).filter(([k]) => k !== "value"), + Object.entries(v).filter(([k]) => k !== 'value'), ), ] as const, ), @@ -196,26 +197,26 @@ export const ttyLines: TTYText[] = (() => { ); const everyTTYClear: (() => void)[] = []; const clear = () => { - if (lines.find((v) => v.kind === "clear")) { - delSince(lines.findLastIndex((v) => v.kind === "clear")); + if (lines.find((v) => v.kind === 'clear')) { + delSince(lines.findLastIndex((v) => v.kind === 'clear')); } - lines.push({ kind: "clear" }); + lines.push({ kind: 'clear' }); everyTTYClear.forEach((v) => v()); }; everyTTYClear.push(() => { text( { - renderrestriction: "noscript", + renderrestriction: 'noscript', }, [ - "This browser does not support JS. Your experience may be degraded.", + 'This browser does not support JS. Your experience may be degraded.', { - bg: "#ff0000", - colour: "#dedede", + bg: '#ff0000', + colour: '#dedede', }, ], - ["\n", {}], + ['\n', {}], ); }); everyTTYClear.forEach((v) => v()); @@ -223,88 +224,88 @@ export const ttyLines: TTYText[] = (() => { // wait(300); - text({ renderrestriction: "js-only" }, [ + text({ renderrestriction: 'js-only' }, [ ((v) => v[Math.floor(Math.random() * v.length)])([ - "cool beats are stored in the balls", - "found xml documents in the firmware", + 'cool beats are stored in the balls', + 'found xml documents in the firmware', 'overhead: "I don\'t consent" "hey thats my line"', - "uwu", - "i regret making this hellhole of a codebase", + 'uwu', + 'i regret making this hellhole of a codebase', ]), { - colour: "#777777", + colour: '#777777', }, ]); wait(1000); clear(); text( [ - "lain", + 'lain', { - colour: "#FFFF53", + colour: '#FFFF53', weight: 700, }, ], - [" in "], + [' in '], [ - "mem.estrogen.zone", + 'mem.estrogen.zone', { - colour: "#17B117", + colour: '#17B117', weight: 700, }, ], - [" in "], + [' in '], [ - "~", + '~', { - colour: "#53FFFF", + colour: '#53FFFF', weight: 700, }, ], ); text( - ["❯ ", { colour: "#53FF53", weight: 600 }], - [""], - ["", { colour: "#777777" }], + ['❯ ', { colour: '#53FF53', weight: 600 }], + [''], + ['', { colour: '#777777' }], ); { - const targetstr = "/usr/bin/env wel"; + const targetstr = '/usr/bin/env wel'; const completions = [ '/bin/sh -c "$(/usr/bin/env curl -fsSL https://blahaj.estrogen.zone/)"', - "/usr/local/bin/become-estradiol", - "/usr/bin/shellutil ansi cheatsheet 24bit", - "/usr/bin/env sh", - "/usr/bin/env wc -l src/routes/anim.css", - "/usr/bin/env welcome -c ~/.local/share/welcome.toml", + '/usr/local/bin/become-estradiol', + '/usr/bin/shellutil ansi cheatsheet 24bit', + '/usr/bin/env sh', + '/usr/bin/env wc -l src/routes/anim.css', + '/usr/bin/env welcome -c ~/.local/share/welcome.toml', ]; - for (const c of [...targetstr.split(""), "COMPLETE"]) { + for (const c of [...targetstr.split(''), 'COMPLETE']) { replaceLast((l) => { const prompt = l[l.length - 2]; const suggestions = l[l.length - 1]; - if (c === "COMPLETE") { + if (c === 'COMPLETE') { prompt[0] += suggestions[0]; - suggestions[0] = ""; + suggestions[0] = ''; } else { prompt[0] += c; const completion = completions.find((v) => v.startsWith(prompt[0])); if (completion) { suggestions[0] = completion.substring(prompt[0].length); - } else suggestions[0] = ""; + } else suggestions[0] = ''; } return l; }); - if (c === "COMPLETE") { + if (c === 'COMPLETE') { wait(100); } else wait(50 + Math.random() * 100); } - text(["Preparing..."]); + text(['Preparing...']); wait(200); } wait(1000); clear(); - text(["Welcome to"], [""], [" the Estrogen Zone!\n\n"]); + text(['Welcome to'], [''], [' the Estrogen Zone!\n\n']); wait(300); - for (const c of " my corner of".split("")) { + for (const c of ' my corner of'.split('')) { replaceLast((l) => { l[1][0] += c; return l; @@ -312,95 +313,95 @@ export const ttyLines: TTYText[] = (() => { wait(70); } text([ - "Places you can find me:", + 'Places you can find me:', { - colour: "#7a7a7a", + colour: '#7a7a7a', }, ]); wait(300); text( [ - " - ", + ' - ', { - colour: "#7a7a7a", + colour: '#7a7a7a', }, ], - ["estrogen.zone git: ", { colour: "#cdcdcd" }], + ['estrogen.zone git: ', { colour: '#cdcdcd' }], ); wait(100); replaceLast((v) => [ ...v, [ - "git.estrogen.zone", + 'git.estrogen.zone', { - colour: "#99aaff", + colour: '#99aaff', underlined: true, weight: 700, - url: "newtab:https://git.estrogen.zone", + url: 'newtab:https://git.estrogen.zone', }, ], ]); wait(100); text( [ - " - ", + ' - ', { - colour: "#7a7a7a", + colour: '#7a7a7a', }, ], - ["On jsr: ", { colour: "#cdcdcd" }], + ['On jsr: ', { colour: '#cdcdcd' }], ); wait(600); replaceLast((v) => [ ...v, [ - "jsr.io/@memdmp", + 'jsr.io/@memdmp', { - colour: "#f7df23", + colour: '#f7df23', underlined: true, weight: 700, - url: "newtab:https://jsr.io/@memdmp", + url: 'newtab:https://jsr.io/@memdmp', }, ], ]); wait(200); text( [ - " - ", + ' - ', { - colour: "#7a7a7a", + colour: '#7a7a7a', }, ], - ["In random hackerspaces", { colour: "#aaaaaa" }], + ['In random hackerspaces', { colour: '#aaaaaa' }], ); wait(600); text([ - "\nYou may find these useful:", + '\nYou may find these useful:', { - colour: "#7a7a7a", + colour: '#7a7a7a', }, ]); wait(400); text( [ - " - ", + ' - ', { - colour: "#7a7a7a", + colour: '#7a7a7a', }, ], - ["GPG Root Key: ", { colour: "#cdcdcd" }], + ['GPG Root Key: ', { colour: '#cdcdcd' }], ); wait(1000); replaceLast((v) => [ ...v, [ - "B546778F06BBCC8EC167DB3CD919706487B8B6DE", + 'B546778F06BBCC8EC167DB3CD919706487B8B6DE', { - colour: "#58C7F3", + colour: '#58C7F3', underlined: true, weight: 700, - url: "currenttab:/keys/memdmp/primary.pgp", - dl: "memdmp-primary.pgp", + url: `currenttab:/keys/memdmp/primary.pgp`, + dl: 'memdmp-primary.pgp', }, ], ]); @@ -408,35 +409,35 @@ export const ttyLines: TTYText[] = (() => { replaceLast((v) => [ ...v, [ - " (root key)", + ' (root key)', { - colour: "#999999", + colour: '#999999', }, ], ]); wait(400); text([ - " - ", + ' - ', { - colour: "#7a7a7a", + colour: '#7a7a7a', }, ]); wait(70); replaceLast((v) => [ ...v, - ["GPG Git Key: ", { colour: "#cdcdcd" }], + ['GPG Git Key: ', { colour: '#cdcdcd' }], ]); wait(100); replaceLast((v) => [ ...v, [ - "5134F05BD8D9DB8C6C0E1515A9439D346AB6DF4E", + '5134F05BD8D9DB8C6C0E1515A9439D346AB6DF4E', { - colour: "#F0A3B3", + colour: '#F0A3B3', underlined: true, weight: 700, - url: "currenttab:/keys/memdmp/git.pgp", - dl: "memdmp-git.pgp", + url: `currenttab:/keys/memdmp/git.pgp`, + dl: 'memdmp-git.pgp', }, ], ]); @@ -444,37 +445,37 @@ export const ttyLines: TTYText[] = (() => { replaceLast((v) => [ ...v, [ - " (", + ' (', { - colour: "#999999", + colour: '#999999', }, ], [ - "signed", + 'signed', { - colour: "#F0A3B3", + colour: '#F0A3B3', underlined: true, weight: 500, - url: "currenttab:/keys/memdmp/git.pgp.sig", - dl: "memdmp-git.pgp.sig", + url: `currenttab:/keys/memdmp/git.pgp.sig`, + dl: 'memdmp-git.pgp.sig', }, ], - [")", { colour: "#999999" }], + [')', { colour: '#999999' }], ]); wait(45); text([ - " - ", + ' - ', { - colour: "#7a7a7a", + colour: '#7a7a7a', }, ]); wait(70); replaceLast((v) => [ ...v, [ - "GPG Release Signing Key: ", + 'GPG Release Signing Key: ', { - colour: "#cdcdcd", + colour: '#cdcdcd', }, ], ]); @@ -482,13 +483,13 @@ export const ttyLines: TTYText[] = (() => { replaceLast((v) => [ ...v, [ - "0D93102265071798C7B65A4C9F0739B9E0C8FD60", + '0D93102265071798C7B65A4C9F0739B9E0C8FD60', { - colour: "#ffffff", + colour: '#ffffff', underlined: true, weight: 700, - url: "currenttab:/keys/memdmp/release.pgp", - dl: "memdmp-release.pgp", + url: `currenttab:/keys/memdmp/release.pgp`, + dl: 'memdmp-release.pgp', }, ], ]); @@ -496,37 +497,37 @@ export const ttyLines: TTYText[] = (() => { replaceLast((v) => [ ...v, [ - " (", + ' (', { - colour: "#999999", + colour: '#999999', }, ], [ - "signed", + 'signed', { - colour: "#ffffff", + colour: '#ffffff', underlined: true, weight: 500, - url: "currenttab:/keys/memdmp/release.pgp.sig", - dl: "memdmp-release.pgp.sig", + url: `currenttab:/keys/memdmp/release.pgp.sig`, + dl: 'memdmp-release.pgp.sig', }, ], - [")", { colour: "#999999" }], + [')', { colour: '#999999' }], ]); wait(100); text([ - " - ", + ' - ', { - colour: "#7a7a7a", + colour: '#7a7a7a', }, ]); wait(35); replaceLast((v) => [ ...v, [ - "Source Code: ", + 'Source Code: ', { - colour: "#cdcdcd", + colour: '#cdcdcd', }, ], ]); @@ -534,29 +535,29 @@ export const ttyLines: TTYText[] = (() => { replaceLast((v) => [ ...v, [ - "git.estrogen.zone/mem-estrogen-zone", + 'git.estrogen.zone/mem-estrogen-zone', { - colour: "#F0A3B3", + colour: '#F0A3B3', underlined: true, weight: 700, - url: "newtab:/upstream/", + url: 'newtab:/upstream/', }, ], ]); wait(100); text([ - " - ", + ' - ', { - colour: "#7a7a7a", + colour: '#7a7a7a', }, ]); wait(35); replaceLast((v) => [ ...v, [ - "Canaries: ", + 'Canaries: ', { - colour: "#cdcdcd", + colour: '#cdcdcd', }, ], ]); @@ -564,12 +565,12 @@ export const ttyLines: TTYText[] = (() => { replaceLast((v) => [ ...v, [ - "/canaries/", + './canaries/', { - colour: "#58C7F3", + colour: '#58C7F3', underlined: true, weight: 700, - url: "currenttab:/canaries/", + url: `currenttab:/canaries/`, }, ], ]); @@ -578,23 +579,23 @@ export const ttyLines: TTYText[] = (() => { `<button style="padding: 12px 12px;background: #fff2;margin-top: 0.4rem;border-radius: 0.7rem;opacity:0.1;margin-top:3rem;" data-el="le funny button">have a button :3</button>`, { raw: true, - url: () => alert("i abused too much css for this i wanna cry now"), + url: () => alert('i abused too much css for this i wanna cry now'), }, ]); wait(200); text( - { renderrestriction: "js-only" }, + { renderrestriction: 'js-only' }, [ - "next time, you may want to ", + 'next time, you may want to ', { - colour: "#fff2", + colour: '#fff2', }, ], [ - `skip the animation<img style="opacity:0;position:fixed;top:0;left:0;width:0px;height:0px;pointer-events:none;" src='about:blank' onerror='setTimeout(()=>{if(location.pathname==="/skip-animation")document.querySelector(${JSON.stringify(`a[href=${JSON.stringify("/skip-animation")}]`)})?.parentElement?.remove();else document.querySelector(${JSON.stringify(`a[href=${JSON.stringify("about:blank")}]`)})?.remove();},1)' />`, + `skip the animation<img style="opacity:0;position:fixed;top:0;left:0;width:0px;height:0px;pointer-events:none;" src='about:blank' onerror='setTimeout(()=>{if(location.pathname===${JSON.stringify('/~mem/skip-animation')})document.querySelector(${JSON.stringify(`a[href=${JSON.stringify('/~mem/skip-animation')}]`)})?.parentElement?.remove();else document.querySelector(${JSON.stringify(`a[href=${JSON.stringify('about:blank')}]`)})?.remove();},1)' />`, { - url: "currenttab:/skip-animation", - colour: "#fff2", + url: `currenttab:/skip-animation`, + colour: '#fff2', underlined: true, raw: true, }, |