| 1 | import * as openpgp from 'openpgp'; |
| 2 | |
| 3 | export interface PublicKeyDetails |
| 4 | /** Long key id of the primary key, lowercase hex. */ |
| 5 | keyId: string; |
| 6 | fingerprint: string; |
| 7 | /** Addresses the key's user ids claim, lowercased. */ |
| 8 | emails: string[] |
| 9 | expired: boolean; |
| 10 | revoked: boolean; |
| 11 | } |
| 12 | |
| 13 | /** Reads an armored public key, or throws when the armor is not one. */ |
| 14 | export async function readPublicKey |
| 15 | armoredKey: string, |
| 16 | ): Promise<PublicKeyDetails |
| 17 | const key = |
| 18 | if (key.isPrivate |
| 19 | throw new Error( |
| 20 | } |
| 21 | |
| 22 | const emails = |
| 23 | .getUserIDs() |
| 24 | .map((id) |
| 25 | .filter((email) |
| 26 | |
| 27 | // `null` means "never expires"; `Infinity` comes back for a key whose expiry cannot be read at all, and neither is an expired key. |
| 28 | const expiresAt = |
| 29 | |
| 30 | return { |
| 31 | keyId: key.getKeyID |
| 32 | fingerprint: key.getFingerprint |
| 33 | emails: [...new |
| 34 | expired: expiresAt instanceof |
| 35 | revoked: await key |
| 36 | }; |
| 37 | } |
| 38 | |
| 39 | /** Long key ids a signature says could have made it, lowercase hex. */ |
| 40 | export async function signingKeyIds |
| 41 | armoredSignature: string, |
| 42 | ): Promise<string |
| 43 | const signature = |
| 44 | return signature |
| 45 | .getSigningKeyIDs() |
| 46 | .map((keyId) |
| 47 | } |
| 48 | |
| 49 | /** |
| 50 | * Whether `armoredKey` made this signature over this payload. |
| 51 | * |
| 52 | * The payload is verified as binary: git detach-signs the commit object with no canonicalisation, so anything that rewrote line endings would verify a different set of bytes than the ones git hashed. |
| 53 | */ |
| 54 | export async function verifySignature |
| 55 | payload, |
| 56 | armoredSignature, |
| 57 | armoredKey, |
| 58 | }: { |
| 59 | payload: string |
| 60 | armoredSignature: string |
| 61 | armoredKey: string |
| 62 | }): Promise<boolean |
| 63 | try { |
| 64 | const result = |
| 65 | message: await openpgp |
| 66 | binary: new Uint8Array |
| 67 | }), |
| 68 | signature: await openpgp |
| 69 | verificationKeys: await openpgp |
| 70 | // An expired key that was valid when it signed is still proof of who signed, which is what a commit badge claims. |
| 71 | expectSigned: false, |
| 72 | config: { allowInsecureVerificationWithReformattedKeys: false |
| 73 | }); |
| 74 | |
| 75 | await result.signatures |
| 76 | return result.signatures |
| 77 | } catch { |
| 78 | return false; |
| 79 | } |
| 80 | } |