Public
Script
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
// SPDX-License-Identifier: 0BSD
import { createPacketReader, createSignedPacketReader } from "https://esm.town/v/vladimyr/libarmor";
import { bufferEquals } from "https://esm.town/v/vladimyr/libbuffer";
import { verifyAsync } from "npm:@noble/ed25519";
import { base64 } from "npm:multiformats/bases/base64";
const KEY_ALG_LENGTH = 2;
const KEY_ID_LENGTH = 8;
const REOP_PUBLIC_KEY_START = "-----BEGIN REOP PUBLIC KEY-----";
const REOP_PUBLIC_KEY_END = "-----END REOP PUBLIC KEY-----";
const parseArmoredKey = createPacketReader({
packetStartMarker: REOP_PUBLIC_KEY_START,
packetEndMarker: REOP_PUBLIC_KEY_END,
});
const REOP_MESSAGE_START = "-----BEGIN REOP SIGNED MESSAGE-----";
const REOP_SIGNATURE_START = "-----BEGIN REOP SIGNATURE-----";
const REOP_MESSAGE_END = "-----END REOP SIGNED MESSAGE-----";
const parseAmoredMessage = createSignedPacketReader({
packetStartMarker: REOP_MESSAGE_START,
packetEndMarker: REOP_MESSAGE_END,
signatureStartMarker: REOP_SIGNATURE_START,
});
type PublicKey = ReturnType<typeof parsePublicKey>;
export type SignedMessage = ReturnType<typeof parseSignedMessage>;
export type SigningKey = PublicKey["signingKey"];
export function parsePublicKey(input: string) {
const publicKey = parseArmoredKey(input);
const publicKeyEncoded = publicKey
.split(/\r?\n/g)
.filter(line => !line.startsWith("ident:"))
.join("")
.trim();
return decodePublicKey(publicKeyEncoded);
}
export function decodePublicKey(input: string) {
input = input.replace(/^reop/, "");
return readPublicKeyPacket(base64.baseDecode(input));
}
export function parseSignedMessage(input: string) {
const { content, signature } = parseAmoredMessage(input);
const signatureEncoded = signature
.split(/\r?\n/g)
.filter(line => !line.startsWith("ident:"))
.join("")
.trim();
return {
signature: readSignaturePacket(base64.baseDecode(signatureEncoded)),
message: {
text: content,
bytes: new TextEncoder().encode(content),
},
};
}
export async function verifyMessage(
signedMessage: string,
getKey: (message: string, signature: SignedMessage["signature"]) => SigningKey,
) {
const { message, signature } = parseSignedMessage(signedMessage);
const signingKey = getKey(message.text, signature);
if (signature.algorithm !== signingKey.algorithm) throw Error("error: algorithm mismatch");
if (signature.algorithm !== "Ed") throw Error("error: unsupported alogrithm");
if (!bufferEquals(signature.keyId, signingKey.id)) throw Error("error: key id mismatch");
const result = await verifyAsync(signature.bytes, message.bytes, signingKey.bytes);
if (!result) throw Error("error: verification failed");
return message.text;
}
function readPublicKeyPacket(publicKeyPacket: Uint8Array) {
const signingAlgorithm = new TextDecoder().decode(
new Uint8Array(publicKeyPacket.buffer, 0, KEY_ALG_LENGTH),
);
const encryptionAlgorithm = new TextDecoder().decode(
new Uint8Array(publicKeyPacket.buffer, KEY_ALG_LENGTH, KEY_ALG_LENGTH),
);
const keyId = new Uint8Array(publicKeyPacket.buffer, 2 * KEY_ALG_LENGTH, KEY_ID_LENGTH);
const keysOffset = 2 * KEY_ALG_LENGTH + KEY_ID_LENGTH;
const keyLength = new Uint8Array(publicKeyPacket.buffer, keysOffset).byteLength / 2;
const signingKeyBytes = new Uint8Array(
publicKeyPacket.buffer,
keysOffset,
keyLength,
);
const encryptionKeyBytes = new Uint8Array(
publicKeyPacket.buffer,
keysOffset + keyLength,
keyLength,
);
return {
keyId,
signingKey: {
id: keyId,
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Comments
Nobody has commented on this val yet: be the first!
March 6, 2024