[feat] Abyss Protocol authentication
This commit is contained in:
5
.idea/.idea.Abyss/.idea/workspace.xml
generated
5
.idea/.idea.Abyss/.idea/workspace.xml
generated
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user