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 = false; const debug = dev && will_debug ? console.debug : () => void 0; const _validateSignature = async (message: string, id: string) => { id = id.toUpperCase(); const key = keyStore.get(id) ?? keyStore.get(id.replace(/ /g, "")); if (!key) throw new Error("Could not find key from keystore"); const signedMessage = await readCleartextMessage({ cleartextMessage: message, }); const verificationResult = await verify({ message: signedMessage, verificationKeys: key, expectSigned: true, }); return verificationResult.data; } export const validateSignature: typeof _validateSignature = async (message, id) => { 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('getting key with url', url) key = await fetch( url, ).then((v) => v.text()).catch(e => { if (fallbackKeys.has(key)) { debug('failed with error', e, 'but found fallback key') return fallbackKeys.get(key)! } else { debug('failed to fetch key, cannot find fallback') throw e } }); debug('fetched key', key) } else { debug('found key', 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('key must be signed by', signed_by) key = await _validateSignature(key, signed_by); debug('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('fingerprint matches expected fingerprint') else debug('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('added key', parsedKey, 'with ids', ids, 'to keystore') }; 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", }); })(); export default keyStore;