From e174238d3cc9df5f2e5310a60b779ac0d6d835c4 Mon Sep 17 00:00:00 2001
From: acite <1498045907@qq.com>
Date: Sat, 13 Sep 2025 17:01:12 +0800
Subject: [PATCH] [feat] Abyss Protocol authentication
---
.idea/.idea.Abyss/.idea/workspace.xml | 5 ++++
Abyss/Components/Services/AbyssService.cs | 6 ++--
Abyss/Components/Services/UserService.cs | 29 ++++++++++++++++++-
Abyss/Components/Tools/AbyssStream.cs | 35 +++++++++++++++++++----
4 files changed, 64 insertions(+), 11 deletions(-)
diff --git a/.idea/.idea.Abyss/.idea/workspace.xml b/.idea/.idea.Abyss/.idea/workspace.xml
index 5a018f1..f885b75 100644
--- a/.idea/.idea.Abyss/.idea/workspace.xml
+++ b/.idea/.idea.Abyss/.idea/workspace.xml
@@ -11,6 +11,9 @@
+
+
+
@@ -204,6 +207,8 @@
+
+
diff --git a/Abyss/Components/Services/AbyssService.cs b/Abyss/Components/Services/AbyssService.cs
index 2107fcb..cfae61e 100644
--- a/Abyss/Components/Services/AbyssService.cs
+++ b/Abyss/Components/Services/AbyssService.cs
@@ -5,7 +5,7 @@ using Abyss.Components.Tools;
namespace Abyss.Components.Services;
-public class AbyssService(ILogger logger, ConfigureService config) : IHostedService, IDisposable
+public class AbyssService(ILogger logger, ConfigureService config, UserService user) : IHostedService, IDisposable
{
private Task? _executingTask;
private CancellationTokenSource? _cts;
@@ -52,10 +52,9 @@ public class AbyssService(ILogger logger, ConfigureService config)
private async Task ClientHandlerAsync(TcpClient client, CancellationToken cancellationToken)
{
- var stream = await client.GetAbyssStreamAsync(ct: cancellationToken);
-
try
{
+ await using var stream = await client.GetAbyssStreamAsync(ct: cancellationToken, us: user);
var request = HttpHelper.Parse(await HttpReader.ReadHttpMessageAsync(stream, cancellationToken));
var port = 80;
var sp = request.RequestUri?.ToString().Split(':') ?? [];
@@ -137,7 +136,6 @@ public class AbyssService(ILogger logger, ConfigureService config)
}
finally
{
- stream.Close();
client.Close();
client.Dispose();
}
diff --git a/Abyss/Components/Services/UserService.cs b/Abyss/Components/Services/UserService.cs
index 946d196..8050c32 100644
--- a/Abyss/Components/Services/UserService.cs
+++ b/Abyss/Components/Services/UserService.cs
@@ -174,11 +174,38 @@ public class UserService
}
}
- static bool VerifySignature(PublicKey publicKey, byte[] data, byte[] signature)
+ public static bool VerifySignature(PublicKey publicKey, byte[] data, byte[] signature)
{
var algorithm = SignatureAlgorithm.Ed25519;
return algorithm.Verify(publicKey, data, signature);
}
+
+ public async Task VerifyAny(byte[] data, byte[] signature)
+ {
+ var users = await _database.Table().ToListAsync();
+ foreach (var u in users)
+ {
+ try
+ {
+ var pubKeyBytes = Convert.FromBase64String(u.PublicKey);
+ var pubKey = PublicKey.Import(
+ SignatureAlgorithm.Ed25519,
+ pubKeyBytes,
+ KeyBlobFormat.RawPublicKey);
+
+ if (VerifySignature(pubKey, data, signature))
+ {
+ _logger.LogInformation($"Signature verified using user {u.Name}");
+ return true;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, $"Failed to import public key for {u.Name}");
+ }
+ }
+ return false;
+ }
public string CreateToken(string user, string ip, TimeSpan lifetime)
{
diff --git a/Abyss/Components/Tools/AbyssStream.cs b/Abyss/Components/Tools/AbyssStream.cs
index 566d015..347b927 100644
--- a/Abyss/Components/Tools/AbyssStream.cs
+++ b/Abyss/Components/Tools/AbyssStream.cs
@@ -9,15 +9,17 @@ using System.Data;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
-
+using System.Text;
+using Abyss.Components.Services;
+using Microsoft.AspNetCore.Authentication;
using NSec.Cryptography;
using ChaCha20Poly1305 = System.Security.Cryptography.ChaCha20Poly1305;
namespace Abyss.Components.Tools
{
- // TODO: Since C25519 has already been used for user authentication,
- // TODO: why not use that public key to verify user identity when establishing a secure channel here?
+ // TODO: (complete) Since C25519 has already been used for user authentication,
+ // TODO: (complete) why not use that public key to verify user identity when establishing a secure channel here?
public sealed class AbyssStream : NetworkStream, IDisposable
{
private const int PublicKeyLength = 32;
@@ -63,7 +65,7 @@ namespace Abyss.Components.Tools
/// Handshake: X25519 public exchange (raw) -> shared secret -> HKDF -> AEAD key + saltA + saltB
/// send/recv salts are assigned deterministically by lexicographic comparison of raw public keys.
///
- public static async Task CreateAsync(TcpClient client, byte[]? privateKeyRaw = null, CancellationToken cancellationToken = default)
+ public static async Task CreateAsync(TcpClient client, UserService us, byte[]? privateKeyRaw = null, CancellationToken cancellationToken = default)
{
if (client == null) throw new ArgumentNullException(nameof(client));
var socket = client.Client ?? throw new ArgumentException("TcpClient has no underlying socket");
@@ -104,6 +106,27 @@ namespace Abyss.Components.Tools
await ReadExactFromSocketAsync(socket, remotePublic, 0, PublicKeyLength, cancellationToken).ConfigureAwait(false);
+ var ch = Encoding.UTF8.GetBytes(UserService.GenerateRandomAsciiString(32));
+ sent = 0;
+ while (sent < ch.Length)
+ {
+ var toSend = new ReadOnlyMemory(ch, sent, ch.Length - sent);
+ sent += await socket.SendAsync(toSend, SocketFlags.None, cancellationToken).ConfigureAwait(false);
+ }
+
+ var rch = new byte[64];
+ await ReadExactFromSocketAsync(socket, rch, 0, 64, cancellationToken).ConfigureAwait(false);
+ bool rau = await us.VerifyAny(ch, rch);
+ if (!rau) throw new AuthenticationFailureException("");
+
+ var ack = Encoding.UTF8.GetBytes(UserService.GenerateRandomAsciiString(16));
+ sent = 0;
+ while (sent < ack.Length)
+ {
+ var toSend = new ReadOnlyMemory(ack, sent, ack.Length - sent);
+ sent += await socket.SendAsync(toSend, SocketFlags.None, cancellationToken).ConfigureAwait(false);
+ }
+
// 3) Compute shared secret (X25519)
PublicKey remotePub;
try
@@ -520,7 +543,7 @@ namespace Abyss.Components.Tools
public static class TcpClientAbyssExtensions
{
- public static Task GetAbyssStreamAsync(this TcpClient client, byte[]? privateKeyRaw = null, CancellationToken ct = default)
- => AbyssStream.CreateAsync(client, privateKeyRaw, ct);
+ public static Task GetAbyssStreamAsync(this TcpClient client, UserService us, byte[]? privateKeyRaw = null, CancellationToken ct = default)
+ => AbyssStream.CreateAsync(client, us, privateKeyRaw, ct);
}
}