[feat] Abyss Protocol authentication

This commit is contained in:
acite
2025-09-13 17:01:12 +08:00
parent cad92f8fa5
commit e174238d3c
4 changed files with 64 additions and 11 deletions

View File

@@ -11,6 +11,9 @@
<component name="ChangeListManager">
<list default="true" id="bf317275-3039-49bb-a475-725a800a0cce" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/.idea.Abyss/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.Abyss/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Services/AbyssService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Services/AbyssService.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Services/UserService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Services/UserService.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Tools/AbyssStream.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Tools/AbyssStream.cs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -204,6 +207,8 @@
<workItem from="1757702942841" duration="32000" />
<workItem from="1757735249561" duration="5523000" />
<workItem from="1757742881713" duration="2285000" />
<workItem from="1757745929389" duration="93000" />
<workItem from="1757751423586" duration="2362000" />
</task>
<servers />
</component>

View File

@@ -5,7 +5,7 @@ using Abyss.Components.Tools;
namespace Abyss.Components.Services;
public class AbyssService(ILogger<AbyssService> logger, ConfigureService config) : IHostedService, IDisposable
public class AbyssService(ILogger<AbyssService> logger, ConfigureService config, UserService user) : IHostedService, IDisposable
{
private Task? _executingTask;
private CancellationTokenSource? _cts;
@@ -52,10 +52,9 @@ public class AbyssService(ILogger<AbyssService> 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<AbyssService> logger, ConfigureService config)
}
finally
{
stream.Close();
client.Close();
client.Dispose();
}

View File

@@ -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<bool> VerifyAny(byte[] data, byte[] signature)
{
var users = await _database.Table<User>().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)
{

View File

@@ -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.
/// </summary>
public static async Task<AbyssStream> CreateAsync(TcpClient client, byte[]? privateKeyRaw = null, CancellationToken cancellationToken = default)
public static async Task<AbyssStream> 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<byte>(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<byte>(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<AbyssStream> GetAbyssStreamAsync(this TcpClient client, byte[]? privateKeyRaw = null, CancellationToken ct = default)
=> AbyssStream.CreateAsync(client, privateKeyRaw, ct);
public static Task<AbyssStream> GetAbyssStreamAsync(this TcpClient client, UserService us, byte[]? privateKeyRaw = null, CancellationToken ct = default)
=> AbyssStream.CreateAsync(client, us, privateKeyRaw, ct);
}
}