mirror of https://github.com/openclaw/openclaw.git
fix: tighten device identity helper coverage
This commit is contained in:
parent
8240fc519a
commit
e8c300c353
|
|
@ -0,0 +1,61 @@
|
||||||
|
import path from "node:path";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { withTempDir } from "../test-utils/temp-dir.js";
|
||||||
|
import {
|
||||||
|
deriveDeviceIdFromPublicKey,
|
||||||
|
loadOrCreateDeviceIdentity,
|
||||||
|
normalizeDevicePublicKeyBase64Url,
|
||||||
|
publicKeyRawBase64UrlFromPem,
|
||||||
|
signDevicePayload,
|
||||||
|
verifyDeviceSignature,
|
||||||
|
} from "./device-identity.js";
|
||||||
|
|
||||||
|
async function withIdentity(
|
||||||
|
run: (identity: ReturnType<typeof loadOrCreateDeviceIdentity>) => void,
|
||||||
|
) {
|
||||||
|
await withTempDir("openclaw-device-identity-", async (dir) => {
|
||||||
|
const identity = loadOrCreateDeviceIdentity(path.join(dir, "device.json"));
|
||||||
|
run(identity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("device identity crypto helpers", () => {
|
||||||
|
it("derives the same canonical raw key and device id from pem and encoded public keys", async () => {
|
||||||
|
await withIdentity((identity) => {
|
||||||
|
const publicKeyRaw = publicKeyRawBase64UrlFromPem(identity.publicKeyPem);
|
||||||
|
const paddedBase64 = `${publicKeyRaw.replaceAll("-", "+").replaceAll("_", "/")}==`;
|
||||||
|
|
||||||
|
expect(normalizeDevicePublicKeyBase64Url(identity.publicKeyPem)).toBe(publicKeyRaw);
|
||||||
|
expect(normalizeDevicePublicKeyBase64Url(paddedBase64)).toBe(publicKeyRaw);
|
||||||
|
expect(deriveDeviceIdFromPublicKey(identity.publicKeyPem)).toBe(identity.deviceId);
|
||||||
|
expect(deriveDeviceIdFromPublicKey(publicKeyRaw)).toBe(identity.deviceId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("signs payloads that verify against pem and raw public key forms", async () => {
|
||||||
|
await withIdentity((identity) => {
|
||||||
|
const payload = JSON.stringify({
|
||||||
|
action: "system.run",
|
||||||
|
ts: 1234,
|
||||||
|
});
|
||||||
|
const signature = signDevicePayload(identity.privateKeyPem, payload);
|
||||||
|
const publicKeyRaw = publicKeyRawBase64UrlFromPem(identity.publicKeyPem);
|
||||||
|
|
||||||
|
expect(verifyDeviceSignature(identity.publicKeyPem, payload, signature)).toBe(true);
|
||||||
|
expect(verifyDeviceSignature(publicKeyRaw, payload, signature)).toBe(true);
|
||||||
|
expect(verifyDeviceSignature(publicKeyRaw, `${payload}!`, signature)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fails closed for invalid public keys and signatures", async () => {
|
||||||
|
await withIdentity((identity) => {
|
||||||
|
const payload = "hello";
|
||||||
|
const signature = signDevicePayload(identity.privateKeyPem, payload);
|
||||||
|
|
||||||
|
expect(normalizeDevicePublicKeyBase64Url("-----BEGIN PUBLIC KEY-----broken")).toBeNull();
|
||||||
|
expect(deriveDeviceIdFromPublicKey("%%%")).toBeNull();
|
||||||
|
expect(verifyDeviceSignature("%%%invalid%%%", payload, signature)).toBe(false);
|
||||||
|
expect(verifyDeviceSignature(identity.publicKeyPem, payload, "%%%invalid%%%")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -134,6 +134,9 @@ export function normalizeDevicePublicKeyBase64Url(publicKey: string): string | n
|
||||||
return base64UrlEncode(derivePublicKeyRaw(publicKey));
|
return base64UrlEncode(derivePublicKeyRaw(publicKey));
|
||||||
}
|
}
|
||||||
const raw = base64UrlDecode(publicKey);
|
const raw = base64UrlDecode(publicKey);
|
||||||
|
if (raw.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return base64UrlEncode(raw);
|
return base64UrlEncode(raw);
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -145,6 +148,9 @@ export function deriveDeviceIdFromPublicKey(publicKey: string): string | null {
|
||||||
const raw = publicKey.includes("BEGIN")
|
const raw = publicKey.includes("BEGIN")
|
||||||
? derivePublicKeyRaw(publicKey)
|
? derivePublicKeyRaw(publicKey)
|
||||||
: base64UrlDecode(publicKey);
|
: base64UrlDecode(publicKey);
|
||||||
|
if (raw.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return crypto.createHash("sha256").update(raw).digest("hex");
|
return crypto.createHash("sha256").update(raw).digest("hex");
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue