import { dev } from '$app/environment'; import { PublicKey, readCleartextMessage, readKey, verify } from 'openpgp'; import { fallbackKeys } from './fallback-keys'; export const keyStore = new Map(); 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 _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`); const signedMessage = await readCleartextMessage({ cleartextMessage: message, }); debug(`[validateSignature][status=verify] verifying message signature`); const verificationResult = await verify({ message: signedMessage, verificationKeys: key, expectSigned: true, }); debug( `[validateSignature][success] successfully validated message signature`, ); return verificationResult.data; }; export const validateSignature: typeof _validateSignature = async ( message, id, ) => { debug(`[validateSignature][globalState] waiting on initKeystore`); await initKeystore; return _validateSignature(message, id); }; const pushKey = async ({ ids, key, is_url, expect_user_ids, expect_fingerprint, signed_by, }: { ids?: string[]; expect_user_ids?: string[]; expect_fingerprint?: string; key: string; is_url?: boolean; signed_by?: string; }) => { 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)); } else { 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 (typeof key !== 'string') 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); key = await _validateSignature(key, signed_by); debug('[pushKey][success] Validated signature'); } const parsedKey = await readKey({ armoredKey: key, }).then((v) => v.toPublic()); { const ids = parsedKey.getUserIDs(); const missingUserIds = expect_user_ids?.filter((v) => !ids.includes(v)) ?? []; if (missingUserIds.length) { throw new Error( `Key ${parsedKey.getFingerprint()} is missing User IDs: ${missingUserIds.join( ', ', )}`, ); } } 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'); ids.push( 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()); for (const id of ids) { keyStore.set(id, parsedKey); } debug('[success] Added key\n\n', { key: parsedKey, ids, }); }; export const initKeystore = (async () => { await pushKey({ key: 'B546778F06BBCC8EC167DB3CD919706487B8B6DE', ids: ['memdmp'], expect_user_ids: [ 'memdmp ', 'memdmp ', ], expect_fingerprint: 'B546778F06BBCC8EC167DB3CD919706487B8B6DE', is_url: true, }); await pushKey({ // TODO: when primary memdmp key rotates, or when this key expires, replace this inline string with a new one key: `-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 - -----BEGIN PGP PUBLIC KEY BLOCK----- Comment: User ID: memdmp canary keysig Comment: Valid from: 22 Nov 2024 18:31:11 Comment: Valid until: 22 Nov 2027 12:00:00 Comment: Type: 255-bit EdDSA (secret key available) Comment: Usage: Signing, Encryption, Certifying User IDs Comment: Fingerprint: 55D3582CAE78601990A8CA1DBFD0F9E61CB7D84E mDMEZ0C/3xYJKwYBBAHaRw8BAQdA5w4ET7V3FmasUc3h9sb0O0/y38LXp+IUV8Wf La95jm20ZG1lbWRtcCBjYW5hcnkga2V5c2lnIDxtZW1kbXAta2V5LWZvci1zaWdu aW5nLWNhbmFyeS1yZWxhdGVkLWtleXMtZm9yLWV4dGVybmFsLXNlcnZpY2VzQGZh a2VtYWlsLnV3dT6ImQQTFgoAQRYhBFXTWCyueGAZkKjKHb/Q+eYct9hOBQJnQL/f AhsDBQkFoz7RBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEL/Q+eYct9hO X68BAPPBy76J7EWb25+fj/QUD0rYyi/E2kLfGbW+PLhrB/AdAQDl5icCilAI/2xv X4jpGCH9KdJoClIV4g2AyKoEITKBDbg4BGdAv98SCisGAQQBl1UBBQEBB0CcYmml AWFCXVjIerJJrs/GA65EZDwoZowiVVTS99FvaQMBCAeIfgQYFgoAJhYhBFXTWCyu eGAZkKjKHb/Q+eYct9hOBQJnQL/fAhsMBQkFoz7RAAoJEL/Q+eYct9hOr2IA/22U 2rOPevvUoiObv/DeeQlP2mvaQcOCFHp1HVF+4oHrAQDWZiihBvdIESbqm5MH0zLe EkEE03+lW4Zbe25P6MHsBg== =5NPo - -----END PGP PUBLIC KEY BLOCK----- -----BEGIN PGP SIGNATURE----- iIoEARYKADIWIQS1RnePBrvMjsFn2zzZGXBkh7i23gUCZ0DABRQcbWVtZG1wQG1l bWV3YXJlLm5ldAAKCRDZGXBkh7i23vV5AP9K2Q6j6cOGovTVqsWlThK7qxA2Faz+ ZQ4KTbprMz8J4AD/bG33f9Kqg3AqehEyU2TldJs9U9Oni5AXGSGfKLJhmQc= =945T -----END PGP SIGNATURE----- `, signed_by: 'memdmp ', 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 '], is_url: true, signed_by: 'canary-sigkey-signing', }); debug('[success] keystore initialization completed'); })(); export default keyStore;