[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"> <component name="ChangeListManager">
<list default="true" id="bf317275-3039-49bb-a475-725a800a0cce" name="Changes" comment=""> <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$/.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> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -204,6 +207,8 @@
<workItem from="1757702942841" duration="32000" /> <workItem from="1757702942841" duration="32000" />
<workItem from="1757735249561" duration="5523000" /> <workItem from="1757735249561" duration="5523000" />
<workItem from="1757742881713" duration="2285000" /> <workItem from="1757742881713" duration="2285000" />
<workItem from="1757745929389" duration="93000" />
<workItem from="1757751423586" duration="2362000" />
</task> </task>
<servers /> <servers />
</component> </component>

View File

@@ -5,7 +5,7 @@ using Abyss.Components.Tools;
namespace Abyss.Components.Services; 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 Task? _executingTask;
private CancellationTokenSource? _cts; private CancellationTokenSource? _cts;
@@ -52,10 +52,9 @@ public class AbyssService(ILogger<AbyssService> logger, ConfigureService config)
private async Task ClientHandlerAsync(TcpClient client, CancellationToken cancellationToken) private async Task ClientHandlerAsync(TcpClient client, CancellationToken cancellationToken)
{ {
var stream = await client.GetAbyssStreamAsync(ct: cancellationToken);
try try
{ {
await using var stream = await client.GetAbyssStreamAsync(ct: cancellationToken, us: user);
var request = HttpHelper.Parse(await HttpReader.ReadHttpMessageAsync(stream, cancellationToken)); var request = HttpHelper.Parse(await HttpReader.ReadHttpMessageAsync(stream, cancellationToken));
var port = 80; var port = 80;
var sp = request.RequestUri?.ToString().Split(':') ?? []; var sp = request.RequestUri?.ToString().Split(':') ?? [];
@@ -137,7 +136,6 @@ public class AbyssService(ILogger<AbyssService> logger, ConfigureService config)
} }
finally finally
{ {
stream.Close();
client.Close(); client.Close();
client.Dispose(); 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; var algorithm = SignatureAlgorithm.Ed25519;
return algorithm.Verify(publicKey, data, signature); 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) public string CreateToken(string user, string ip, TimeSpan lifetime)
{ {

View File

@@ -9,15 +9,17 @@ using System.Data;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text;
using Abyss.Components.Services;
using Microsoft.AspNetCore.Authentication;
using NSec.Cryptography; using NSec.Cryptography;
using ChaCha20Poly1305 = System.Security.Cryptography.ChaCha20Poly1305; using ChaCha20Poly1305 = System.Security.Cryptography.ChaCha20Poly1305;
namespace Abyss.Components.Tools namespace Abyss.Components.Tools
{ {
// TODO: Since C25519 has already been used for user authentication, // TODO: (complete) 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) why not use that public key to verify user identity when establishing a secure channel here?
public sealed class AbyssStream : NetworkStream, IDisposable public sealed class AbyssStream : NetworkStream, IDisposable
{ {
private const int PublicKeyLength = 32; 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 /// 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. /// send/recv salts are assigned deterministically by lexicographic comparison of raw public keys.
/// </summary> /// </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)); if (client == null) throw new ArgumentNullException(nameof(client));
var socket = client.Client ?? throw new ArgumentException("TcpClient has no underlying socket"); 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); 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) // 3) Compute shared secret (X25519)
PublicKey remotePub; PublicKey remotePub;
try try
@@ -520,7 +543,7 @@ namespace Abyss.Components.Tools
public static class TcpClientAbyssExtensions public static class TcpClientAbyssExtensions
{ {
public static Task<AbyssStream> GetAbyssStreamAsync(this TcpClient client, byte[]? privateKeyRaw = null, CancellationToken ct = default) public static Task<AbyssStream> GetAbyssStreamAsync(this TcpClient client, UserService us, byte[]? privateKeyRaw = null, CancellationToken ct = default)
=> AbyssStream.CreateAsync(client, privateKeyRaw, ct); => AbyssStream.CreateAsync(client, us, privateKeyRaw, ct);
} }
} }