mirror of
https://github.com/lkddi/Xboard.git
synced 2026-04-23 11:27:30 +08:00
feat: machine mode, ECH subscriptions, batch ops & security hardening
This commit is contained in:
@@ -73,25 +73,36 @@ class ManageController extends Controller
|
||||
Log::error($e);
|
||||
return $this->fail([500, '创建失败']);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
$params = $request->validate([
|
||||
'id' => 'required|integer',
|
||||
'show' => 'integer',
|
||||
'show' => 'nullable|integer',
|
||||
'machine_id' => 'nullable|integer',
|
||||
'enabled' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
$server = Server::find($request->id);
|
||||
if (!$server) {
|
||||
return $this->fail([400202, '服务器不存在']);
|
||||
}
|
||||
$server->show = (int) $request->show;
|
||||
|
||||
if (array_key_exists('show', $params)) {
|
||||
$server->show = (int) $params['show'];
|
||||
}
|
||||
if (array_key_exists('machine_id', $params)) {
|
||||
$server->machine_id = $params['machine_id'] ?: null;
|
||||
}
|
||||
if (array_key_exists('enabled', $params)) {
|
||||
$server->enabled = (bool) $params['enabled'];
|
||||
}
|
||||
|
||||
if (!$server->save()) {
|
||||
return $this->fail([500, '保存失败']);
|
||||
}
|
||||
|
||||
return $this->success(true);
|
||||
}
|
||||
|
||||
@@ -105,9 +116,14 @@ class ManageController extends Controller
|
||||
$request->validate([
|
||||
'id' => 'required|integer',
|
||||
]);
|
||||
if (Server::where('id', $request->id)->delete() === false) {
|
||||
$server = Server::find($request->id);
|
||||
if (!$server) {
|
||||
return $this->fail([400202, '服务器不存在']);
|
||||
}
|
||||
if ($server->delete() === false) {
|
||||
return $this->fail([500, '删除失败']);
|
||||
}
|
||||
|
||||
return $this->success(true);
|
||||
}
|
||||
|
||||
@@ -200,6 +216,40 @@ class ManageController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新节点属性(show等)
|
||||
*/
|
||||
public function batchUpdate(Request $request)
|
||||
{
|
||||
$params = $request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
'show' => 'nullable|integer|in:0,1',
|
||||
]);
|
||||
|
||||
$ids = $params['ids'];
|
||||
if (empty($ids)) {
|
||||
return $this->fail([400, '请选择要更新的节点']);
|
||||
}
|
||||
|
||||
$update = [];
|
||||
if (array_key_exists('show', $params) && $params['show'] !== null) {
|
||||
$update['show'] = (int) $params['show'];
|
||||
}
|
||||
|
||||
if (empty($update)) {
|
||||
return $this->fail([400, '没有可更新的字段']);
|
||||
}
|
||||
|
||||
try {
|
||||
Server::whereIn('id', $ids)->update($update);
|
||||
return $this->success(true);
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e);
|
||||
return $this->fail([500, '批量更新失败']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制节点
|
||||
* @param \Illuminate\Http\Request $request
|
||||
@@ -221,4 +271,57 @@ class ManageController extends Controller
|
||||
|
||||
return $this->success(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate ECH (Encrypted Client Hello) key pair.
|
||||
* Returns PEM-encoded ECH key (server-side) and ECH config (client-side).
|
||||
*/
|
||||
public function generateEchKey(Request $request)
|
||||
{
|
||||
$publicName = $request->input('public_name', 'ech.example.com');
|
||||
if (strlen($publicName) < 1 || strlen($publicName) > 253) {
|
||||
throw new ApiException('public_name must be a valid domain (1-253 bytes)');
|
||||
}
|
||||
|
||||
// Generate X25519 key pair
|
||||
$privateKey = random_bytes(32);
|
||||
$publicKey = sodium_crypto_scalarmult_base($privateKey);
|
||||
|
||||
$configId = random_int(0, 255);
|
||||
|
||||
// Build ECHConfigContents (draft-ietf-tls-esni-18)
|
||||
$contents = '';
|
||||
$contents .= pack('C', $configId); // config_id
|
||||
$contents .= pack('n', 0x0020); // kem_id: DHKEM(X25519)
|
||||
$contents .= pack('n', 32) . $publicKey; // public_key (length-prefixed)
|
||||
// cipher_suites: 2 suites × 4 bytes = 8 bytes
|
||||
$contents .= pack('n', 8); // cipher_suites byte length
|
||||
$contents .= pack('nn', 0x0001, 0x0001); // HKDF-SHA256 + AES-128-GCM
|
||||
$contents .= pack('nn', 0x0001, 0x0003); // HKDF-SHA256 + ChaCha20Poly1305
|
||||
$contents .= pack('C', 0); // max_name_length
|
||||
$contents .= pack('C', strlen($publicName)) . $publicName;
|
||||
$contents .= pack('n', 0); // extensions: empty
|
||||
|
||||
// ECHConfig = version(2) + length(2) + contents
|
||||
$echConfig = pack('n', 0xfe0d) . pack('n', strlen($contents)) . $contents;
|
||||
|
||||
// ECHConfigList = total_length(2) + configs
|
||||
$echConfigList = pack('n', strlen($echConfig)) . $echConfig;
|
||||
|
||||
// ECH Keys = private_key_len(2) + key(32) + config_len(2) + config
|
||||
$echKeysPayload = pack('n', 32) . $privateKey . pack('n', strlen($echConfig)) . $echConfig;
|
||||
|
||||
$keyPem = "-----BEGIN ECH KEYS-----\n"
|
||||
. chunk_split(base64_encode($echKeysPayload), 64, "\n")
|
||||
. "-----END ECH KEYS-----";
|
||||
|
||||
$configPem = "-----BEGIN ECH CONFIGS-----\n"
|
||||
. chunk_split(base64_encode($echConfigList), 64, "\n")
|
||||
. "-----END ECH CONFIGS-----";
|
||||
|
||||
return $this->success([
|
||||
'key' => $keyPem,
|
||||
'config' => $configPem,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user