mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-25 11:03:14 +01:00
src/Core/Cluster/Crypto implements the wire formats of the extension's cluster API v1 (xcvm_core ADR-002), in plain PHP with sodium and OpenSSL: - Enc: the lp()/u64 canonical encoding. - Box: XCVM-BOX-v1 session bodies. - Seal: XCVM-SEAL-v1 to an X25519 key; refuses the all-zero shared secret and binds purpose and context. - PanelSig: verifies panel signatures against the closed tag registry. - NodeSig: the node key's own signatures, under a separate domain string so they can never pass as panel records. - Canonical: the panel-owned request/response serialisation. It is the BOX context and the MAC input, with a ±90 s window and header parsing. - Ticket, RelayAuth, FileDigest: relay and file tickets, the child's per-connect relay proof, and owner-signed file digests. - ClusterCrypto: a facade over XC_VM::cluster_*. Refusals throw ClusterRefusedException with the extension's reason code. - ClusterCryptoFactory: fails closed (ClusterUnavailableException) without the extension, outside API 1–1, or without sodium/AES-GCM. No PHP fallback ships. Tests: - The panel matches the extension's shared vectors byte for byte (tests/Support/cluster_vectors.json, copied from xcvm_core): SEAL, BOX, panel signatures, the licence binding and CK_B, and the whole token chain an agent runs. - tests/Support/cluster_canonical_vectors.json freezes the canonical request/response bytes for the Go agent. - Also added: SignTagSeparationTest, ClusterTicketsTest, ClusterCryptoFailClosedTest (including a check that nothing under Core/Cluster signs as the panel or computes tokens) and ClusterCryptoBenchTest. - Timing: 64 KB is ~0.25 ms p99 against the 1 ms budget; 8 MB is ~41 ms here against a 40 ms target, guarded at 2x. The reference crypto that only xcvm_core or the Go agent computes (binding, token MAC, session keys, panel signing with a seed) lives in tests/Support and never ships. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Es8UhyPS8MNjuYTagqfGWi
53 lines
2.1 KiB
PHP
53 lines
2.1 KiB
PHP
<?php
|
|
|
|
namespace XcVm\Tests\Support;
|
|
|
|
use XcVm\Core\Cluster\Crypto\Enc;
|
|
use XcVm\Core\Cluster\Crypto\PanelSig;
|
|
|
|
/**
|
|
* Reference crypto for the tests: the parts of ADR-002 only `xcvm_core` (MAIN)
|
|
* or the Go agent compute in production: licence binding, CK_B, the token
|
|
* MAC, the session keys and panel signing with a known seed. It lives under
|
|
* tests/ so it never ships; CI rejects any archive that carries it.
|
|
*/
|
|
final class ClusterReference {
|
|
/** @return array<string, mixed> */
|
|
public static function vectors(): array {
|
|
return json_decode((string) file_get_contents(__DIR__ . '/cluster_vectors.json'), true);
|
|
}
|
|
|
|
public static function binding(string $rJti, string $rKeySha256): string {
|
|
return substr(hash('sha256', 'xcvm-lic-v1' . 'wl:' . Enc::lp($rJti) . $rKeySha256, true), 0, 16);
|
|
}
|
|
|
|
public static function ck(string $rPrk, string $rB): string {
|
|
return hash_hkdf('sha256', $rPrk, 32, 'xcvm/cluster/v1', $rB);
|
|
}
|
|
|
|
public static function tokenMsg(string $rUuid, int $rSid, int $rGen, string $rNodeSignPub, int $rEpoch, int $rNbf, int $rExp, string $rZ): string {
|
|
return 'xcvm-node-token' . "\x01" . Enc::lp($rUuid) . Enc::u64($rSid) . Enc::u64($rGen)
|
|
. hash('sha256', $rNodeSignPub, true) . Enc::u64($rEpoch) . Enc::u64($rNbf) . Enc::u64($rExp)
|
|
. hash('sha256', $rZ, true);
|
|
}
|
|
|
|
/** @return array{mac_up: string, mac_down: string, enc_up: string, enc_down: string} */
|
|
public static function sessionKeys(string $rT): array {
|
|
return [
|
|
'mac_up' => hash_hmac('sha256', 'xcvm/mac/up', $rT, true),
|
|
'mac_down' => hash_hmac('sha256', 'xcvm/mac/down', $rT, true),
|
|
'enc_up' => hash_hmac('sha256', 'xcvm/enc/up', $rT, true),
|
|
'enc_down' => hash_hmac('sha256', 'xcvm/enc/down', $rT, true),
|
|
];
|
|
}
|
|
|
|
public static function panelSign(string $rSeed, string $rTag, string $rPayload): string {
|
|
$rPair = sodium_crypto_sign_seed_keypair($rSeed);
|
|
return sodium_crypto_sign_detached(PanelSig::input($rTag, $rPayload), sodium_crypto_sign_secretkey($rPair));
|
|
}
|
|
|
|
public static function panelPub(string $rSeed): string {
|
|
return sodium_crypto_sign_publickey(sodium_crypto_sign_seed_keypair($rSeed));
|
|
}
|
|
}
|