chacha20 added

This commit is contained in:
2026-05-28 21:07:28 +08:00
parent 070fad058f
commit f274a7dd4b
9 changed files with 180 additions and 8 deletions
+17 -6
View File
@@ -167,12 +167,6 @@ final class AgentClient
private function handleInitialRequest(TcpConnection $connection, string $data): void
{
if (!$this->authenticated || $this->pop === null) {
$connection->send("ERR pop_not_connected\n");
$connection->close();
return;
}
$buffer = ($this->initialBuffers[$connection->id] ?? '') . $data;
$this->initialBuffers[$connection->id] = $buffer;
@@ -428,6 +422,11 @@ final class AgentClient
private function startPopSession(TcpConnection $connection, array $request, string $payloadBytes, string $ingressProtocol): void
{
if (!$this->authenticated || $this->pop === null) {
$this->failOpeningLocalClient($connection, $ingressProtocol, 'pop_not_connected');
return;
}
$sessionId = Uuid::v4();
$this->connectionSessionIds[$connection->id] = $sessionId;
$this->clients[$sessionId] = $connection;
@@ -448,6 +447,18 @@ final class AgentClient
]));
}
private function failOpeningLocalClient(TcpConnection $connection, string $ingressProtocol, string $reason): void
{
if ($ingressProtocol === 'socks5') {
$connection->send($this->socks5Reply(1));
} elseif (str_starts_with($ingressProtocol, 'http')) {
$connection->send("HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\nX-LayLink-Error: {$reason}\r\n\r\n");
} else {
$connection->send("ERR {$reason}\n");
}
$connection->close();
}
private function onSocks5UdpMessage(UdpConnection $connection, string $data): void
{
if (!$this->authenticated || $this->pop === null || strlen($data) < 10) {
+85 -2
View File
@@ -9,6 +9,34 @@ use InvalidArgumentException;
final class FrameCodec
{
public const MAX_FRAME_LENGTH = 16 * 1024 * 1024;
private const ENCRYPTION_NONE = 'none';
private const ENCRYPTION_CHACHA20 = 'chacha20';
private static string $encryption = self::ENCRYPTION_NONE;
private static ?string $key = null;
public static function configureEncryption(string $method, string $keyMaterial = ''): void
{
$method = strtolower(trim($method));
if ($method === '') {
$method = self::ENCRYPTION_NONE;
}
if (!in_array($method, [self::ENCRYPTION_NONE, self::ENCRYPTION_CHACHA20], true)) {
throw new InvalidArgumentException('unsupported_frame_encryption');
}
if ($method === self::ENCRYPTION_CHACHA20) {
if (!function_exists('sodium_crypto_stream_xchacha20_xor')) {
throw new InvalidArgumentException('chacha20_unavailable');
}
self::$key = self::normalizeKey($keyMaterial);
} else {
self::$key = null;
}
self::$encryption = $method;
}
public static function encode(Frame $frame): string
{
@@ -17,11 +45,14 @@ final class FrameCodec
throw new InvalidArgumentException('invalid_frame_payload');
}
return pack('N', strlen($json)) . $json;
$body = self::encrypt($json);
return pack('N', strlen($body)) . $body;
}
public static function decode(string $json): Frame
public static function decode(string $body): Frame
{
$json = self::decrypt($body);
$data = json_decode($json, true);
if (!is_array($data) || !isset($data['type']) || !is_string($data['type'])) {
throw new InvalidArgumentException('invalid_frame');
@@ -44,4 +75,56 @@ final class FrameCodec
(int)($data['version'] ?? 1),
);
}
private static function encrypt(string $plaintext): string
{
if (self::$encryption === self::ENCRYPTION_NONE) {
return $plaintext;
}
$nonce = random_bytes(SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES);
return $nonce . sodium_crypto_stream_xchacha20_xor($plaintext, $nonce, self::$key ?? '');
}
private static function decrypt(string $body): string
{
if (self::$encryption === self::ENCRYPTION_NONE) {
return $body;
}
$nonceLength = SODIUM_CRYPTO_STREAM_XCHACHA20_NONCEBYTES;
if (strlen($body) < $nonceLength) {
throw new InvalidArgumentException('invalid_encrypted_frame');
}
$nonce = substr($body, 0, $nonceLength);
$ciphertext = substr($body, $nonceLength);
return sodium_crypto_stream_xchacha20_xor($ciphertext, $nonce, self::$key ?? '');
}
private static function normalizeKey(string $keyMaterial): string
{
$keyMaterial = trim($keyMaterial);
if ($keyMaterial === '') {
throw new InvalidArgumentException('missing_frame_encryption_key');
}
if (str_starts_with($keyMaterial, 'hex:')) {
$decoded = hex2bin(substr($keyMaterial, 4));
if ($decoded !== false && strlen($decoded) === SODIUM_CRYPTO_STREAM_XCHACHA20_KEYBYTES) {
return $decoded;
}
throw new InvalidArgumentException('invalid_hex_frame_encryption_key');
}
if (str_starts_with($keyMaterial, 'base64:')) {
$decoded = base64_decode(substr($keyMaterial, 7), true);
if ($decoded !== false && strlen($decoded) === SODIUM_CRYPTO_STREAM_XCHACHA20_KEYBYTES) {
return $decoded;
}
throw new InvalidArgumentException('invalid_base64_frame_encryption_key');
}
return hash('sha256', $keyMaterial, true);
}
}