diff options
Diffstat (limited to 'src/routes/canaries')
-rw-r--r-- | src/routes/canaries/canaries.ts | 107 | ||||
-rw-r--r-- | src/routes/canaries/keystore.ts | 137 |
2 files changed, 133 insertions, 111 deletions
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; |