Add Cloudflare signing key contract

This commit is contained in:
2026-05-07 23:58:36 -04:00
parent 90c5110d6c
commit 1013a22915
12 changed files with 1854 additions and 1 deletions
+4
View File
@@ -0,0 +1,4 @@
export interface Env {
ELY_KV: KVNamespace;
ELY_ENVIRONMENT: string;
}
+7
View File
@@ -0,0 +1,7 @@
import type { Env } from "./bindings.js";
export default {
fetch(_request: Request, _env: Env): Response {
return new Response("Not Found", { status: 404 });
},
};
+106
View File
@@ -0,0 +1,106 @@
const KEY_ID_PATTERN = /^[a-z0-9._-]{3,128}$/;
const PUBLIC_KEY_PATTERN = /^[a-f0-9]{64}$/;
const KV_NAMESPACE_PREFIX = "ely";
const PUBLIC_SIGNING_KEYS_NAMESPACE = "public_signing_keys";
export interface PublicSigningKey {
key_id: string;
public_key: string;
}
export interface PublicSigningKeysDocument {
version: 1;
keys: PublicSigningKey[];
}
export class SigningKeysSchemaError extends Error {
constructor(message: string) {
super(message);
this.name = "SigningKeysSchemaError";
}
}
export function publicSigningKeysKvKey(environment: string): string {
const normalizedEnvironment = normalizedEnvironmentName(environment);
return `${KV_NAMESPACE_PREFIX}:${normalizedEnvironment}:${PUBLIC_SIGNING_KEYS_NAMESPACE}`;
}
export function parsePublicSigningKeysDocument(value: string): PublicSigningKeysDocument {
let parsed: unknown;
try {
parsed = JSON.parse(value);
} catch {
throw new SigningKeysSchemaError("public signing keys document must be valid JSON");
}
if (!isRecord(parsed)) {
throw new SigningKeysSchemaError("public signing keys document must be an object");
}
if (parsed.version !== 1) {
throw new SigningKeysSchemaError("public signing keys document version must be 1");
}
if (!Array.isArray(parsed.keys) || parsed.keys.length === 0) {
throw new SigningKeysSchemaError("public signing keys document must contain keys");
}
return {
version: 1,
keys: parsePublicSigningKeys(parsed.keys),
};
}
function parsePublicSigningKeys(values: unknown[]): PublicSigningKey[] {
const keys: PublicSigningKey[] = [];
const seenKeyIds = new Set<string>();
for (const value of values) {
const key = parsePublicSigningKey(value);
if (seenKeyIds.has(key.key_id)) {
throw new SigningKeysSchemaError(`duplicate public signing key id: ${key.key_id}`);
}
seenKeyIds.add(key.key_id);
keys.push(key);
}
return keys;
}
function parsePublicSigningKey(value: unknown): PublicSigningKey {
if (!isRecord(value)) {
throw new SigningKeysSchemaError("public signing key must be an object");
}
const keyId = stringField(value, "key_id");
const publicKey = stringField(value, "public_key").toLowerCase();
if (!KEY_ID_PATTERN.test(keyId)) {
throw new SigningKeysSchemaError(`invalid public signing key id: ${keyId}`);
}
if (!PUBLIC_KEY_PATTERN.test(publicKey)) {
throw new SigningKeysSchemaError(`invalid public signing key value for ${keyId}`);
}
return { key_id: keyId, public_key: publicKey };
}
function normalizedEnvironmentName(value: string): string {
const environment = value.trim();
if (!/^[a-z0-9._-]{3,64}$/.test(environment)) {
throw new SigningKeysSchemaError(`invalid environment name: ${value}`);
}
return environment;
}
function stringField(value: Record<string, unknown>, field: string): string {
const fieldValue = value[field];
if (typeof fieldValue !== "string" || fieldValue.trim() === "") {
throw new SigningKeysSchemaError(`${field} must be a non-empty string`);
}
return fieldValue.trim();
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}