aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/canaries/keystore.ts
blob: ca8cb2299d83cc2a6d9bc7e321d49d780b1bf873 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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 _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@estrogen.zone>',
      'memdmp <memdmp@memeware.net>',
    ],
    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 <memdmp-key-for-signing-canary-related-keys-for-external-services@fakemail.uwu>
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 <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>'],
    is_url: true,
    signed_by: 'canary-sigkey-signing',
  });
  debug('[success] keystore initialization completed');
})();

export default keyStore;