= 0 ? t_cbor_head(0, $v) : t_cbor_head(1, -1 - $v); if (is_string($v) && !empty($v) && str_starts_with($v, "\0BIN")) { $b = substr($v, 4); return t_cbor_head(2, strlen($b)) . $b; } if (is_string($v)) return t_cbor_head(3, strlen($v)) . $v; if (is_array($v)) { $o = t_cbor_head(5, count($v)); foreach ($v as $k => $x) $o .= t_cbor($k) . t_cbor($x); return $o; } throw new RuntimeException('x'); } function t_cbor_head(int $m, int $n): string { if ($n < 24) return chr(($m << 5) | $n); if ($n < 256) return chr(($m << 5) | 24) . chr($n); if ($n < 65536) return chr(($m << 5) | 25) . pack('n', $n); return chr(($m << 5) | 26) . pack('N', $n); } class FakeKey { public $res; public string $kind; public string $credId; public int $count = 0; public function __construct(string $kind) { $this->kind = $kind; static $cnf = null; if ($cnf === null) { $cnf = sys_get_temp_dir() . '/deskhub-pk-' . bin2hex(random_bytes(4)) . '.cnf'; file_put_contents($cnf, "[req] distinguished_name=d [d] "); } $this->res = $kind === 'ec' ? openssl_pkey_new(['config' => $cnf, 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_EC, 'curve_name' => 'prime256v1']) : openssl_pkey_new(['config' => $cnf, 'private_key_type' => OPENSSL_KEYTYPE_RSA, 'private_key_bits' => 2048]); $this->credId = random_bytes(32); } public function cose(): string { $d = openssl_pkey_get_details($this->res); if ($this->kind === 'ec') return t_cbor([1 => 2, 3 => -7, -1 => 1, -2 => "\0BIN" . $d['ec']['x'], -3 => "\0BIN" . $d['ec']['y']]); return t_cbor([1 => 3, 3 => -257, -1 => "\0BIN" . $d['rsa']['n'], -2 => "\0BIN" . $d['rsa']['e']]); } } function t_client(string $type, string $challenge, ?string $origin = null): string { return json_encode(['type' => $type, 'challenge' => $challenge, 'origin' => $origin ?? $GLOBALS['fa_origin'], 'crossOrigin' => false]); } function t_register(FakeKey $k, array $opt, array $o = []): array { $rpId = $o['rpId'] ?? $GLOBALS['fa_rp']; $flags = $o['flags'] ?? 0x45; $ad = hash('sha256', $rpId, true) . chr($flags) . pack('N', $k->count) . str_repeat("\0", 16) . pack('n', strlen($k->credId)) . $k->credId . $k->cose(); $att = t_cbor(['fmt' => 'none', 'attStmt' => [], 'authData' => "\0BIN" . $ad]); $cd = t_client($o['type'] ?? 'webauthn.create', $o['challenge'] ?? $opt['challenge'], $o['origin'] ?? null); return ['id' => webauthn_b64u_encode($k->credId), 'response' => ['clientDataJSON' => webauthn_b64u_encode($cd), 'attestationObject' => webauthn_b64u_encode($att)]]; } function t_login(FakeKey $k, array $opt, int $staffId, array $o = []): array { $flags = $o['flags'] ?? 0x05; $k->count = $o['count'] ?? $k->count + 1; $ad = hash('sha256', $o['rpId'] ?? $GLOBALS['fa_rp'], true) . chr($flags) . pack('N', $k->count); $cd = t_client($o['type'] ?? 'webauthn.get', $o['challenge'] ?? $opt['challenge'], $o['origin'] ?? null); $sig = ''; openssl_sign($ad . hash('sha256', $cd, true), $sig, $o['signWith'] ?? $k->res, OPENSSL_ALGO_SHA256); return ['id' => webauthn_b64u_encode($k->credId), 'response' => [ 'clientDataJSON' => webauthn_b64u_encode($cd), 'authenticatorData' => webauthn_b64u_encode($ad), 'signature' => webauthn_b64u_encode($sig), 'userHandle' => $o['userHandle'] ?? webauthn_b64u_encode(webauthn_user_handle($staffId)), ]]; }