openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
109 lines (108 loc) • 4.44 kB
JavaScript
import crypto from "node:crypto";
//#region src/infra/ed25519-signature.ts
const ED25519_RAW_KEY_LENGTH = 32;
const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
const ED25519_PKCS8_PRIVATE_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
const MAX_BASE64URL_DECODE_INPUT_LENGTH = 4096;
/** Decode the existing permissive base64url wire shape. */
function base64UrlDecode(input) {
if (input.length > MAX_BASE64URL_DECODE_INPUT_LENGTH) throw new Error("base64url input exceeds the maximum allowed length");
const normalized = input.replaceAll("-", "+").replaceAll("_", "/");
const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
return Buffer.from(padded, "base64");
}
function pemEncode(label, der) {
return `-----BEGIN ${label}-----\n${der.toString("base64").match(/.{1,64}/g)?.join("\n") ?? ""}\n-----END ${label}-----\n`;
}
function decodeCanonicalPem(label, pem) {
const header = `-----BEGIN ${label}-----\n`;
const footer = `\n-----END ${label}-----\n`;
if (!pem.startsWith(header) || !pem.endsWith(footer)) throw new Error(`${label} must use canonical PEM framing`);
const body = pem.slice(header.length, -footer.length);
if (!body || !/^[A-Za-z0-9+/=\n]+$/.test(body)) throw new Error(`${label} contains invalid PEM body bytes`);
const der = Buffer.from(body.replaceAll("\n", ""), "base64");
if (pemEncode(label, der) !== pem) throw new Error(`${label} must use canonical PEM base64 encoding`);
return der;
}
function assertEd25519KeyType(key, label) {
if (key.asymmetricKeyType !== "ed25519") throw new Error(`${label} must be an Ed25519 key`);
}
function deriveRawKeyFromDer(params) {
const expectedLength = params.prefix.length + ED25519_RAW_KEY_LENGTH;
if (params.der.length !== expectedLength || !params.der.subarray(0, params.prefix.length).equals(params.prefix)) throw new Error(`${params.label} has a noncanonical Ed25519 encoding`);
return params.der.subarray(params.prefix.length);
}
function deriveCanonicalEd25519PublicKeyRaw(publicKeyPem) {
const spki = decodeCanonicalPem("PUBLIC KEY", publicKeyPem);
assertEd25519KeyType(crypto.createPublicKey({
key: spki,
type: "spki",
format: "der"
}), "public key");
return deriveRawKeyFromDer({
der: spki,
label: "public key",
prefix: ED25519_SPKI_PREFIX
});
}
function deriveCanonicalEd25519PrivateKeyRaw(privateKeyPem) {
const pkcs8 = decodeCanonicalPem("PRIVATE KEY", privateKeyPem);
assertEd25519KeyType(crypto.createPrivateKey({
key: pkcs8,
type: "pkcs8",
format: "der"
}), "private key");
return deriveRawKeyFromDer({
der: pkcs8,
label: "private key",
prefix: ED25519_PKCS8_PRIVATE_PREFIX
});
}
/** Parse any Node-compatible Ed25519 PEM and return its canonical raw public key. */
function deriveEd25519PublicKeyRaw(publicKeyPem) {
const key = crypto.createPublicKey(publicKeyPem);
assertEd25519KeyType(key, "public key");
return deriveRawKeyFromDer({
der: key.export({
type: "spki",
format: "der"
}),
label: "public key",
prefix: ED25519_SPKI_PREFIX
});
}
function publicKeyRawBase64UrlFromEd25519Pem(publicKeyPem) {
return deriveEd25519PublicKeyRaw(publicKeyPem).toString("base64url");
}
function normalizeEd25519PublicKeyBase64Url(publicKey) {
try {
const raw = publicKey.includes("BEGIN") ? deriveEd25519PublicKeyRaw(publicKey) : base64UrlDecode(publicKey);
if (raw.length === 0) return null;
return raw.toString("base64url");
} catch {
return null;
}
}
function signEd25519Payload(privateKeyPem, payload) {
const key = crypto.createPrivateKey(privateKeyPem);
return crypto.sign(null, Buffer.from(payload, "utf8"), key).toString("base64url");
}
function createEd25519PublicKey(publicKey) {
if (publicKey.includes("BEGIN")) return crypto.createPublicKey(publicKey);
return crypto.createPublicKey({
key: Buffer.concat([ED25519_SPKI_PREFIX, base64UrlDecode(publicKey)]),
type: "spki",
format: "der"
});
}
function verifyEd25519SignatureBytes(params) {
try {
const key = createEd25519PublicKey(params.publicKey);
const signature = base64UrlDecode(params.signatureBase64Url);
return crypto.verify(null, params.payload, key, signature);
} catch {
return false;
}
}
//#endregion
export { signEd25519Payload as a, publicKeyRawBase64UrlFromEd25519Pem as i, deriveCanonicalEd25519PublicKeyRaw as n, verifyEd25519SignatureBytes as o, normalizeEd25519PublicKeyBase64Url as r, deriveCanonicalEd25519PrivateKeyRaw as t };