Skip to content

Commit

Permalink
MV Auth - Early WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Skyedra committed Aug 11, 2024
1 parent 174e49c commit 5b8afd5
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 90 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<PackageVersion Include="ILReader.Core" Version="1.0.0.4" />
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.0" />
<PackageVersion Include="JWT" Version="10.1.1" />
<PackageVersion Include="Linguini.Bundle" Version="0.1.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1"/>
Expand Down
39 changes: 30 additions & 9 deletions Robust.Client/Console/Commands/LauncherAuthCommand.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#if TOOLS
using System;
using System.IO;
using System.Security.Cryptography;
using JWT.Algorithms;
using JWT.Builder;
using Microsoft.Data.Sqlite;
using Robust.Client.Utility;
using Robust.Shared.Console;
Expand All @@ -21,19 +24,20 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args)
var wantName = args.Length > 0 ? args[0] : null;

var basePath = Path.GetDirectoryName(UserDataDir.GetUserDataDir(_gameController))!;
var dbPath = Path.Combine(basePath, "launcher", "settings.db");
//var dbPath = Path.Combine(basePath, "launcher-ssmv", "settings.db");
var dbPath = Path.Combine(basePath, "Test61", "settings.db"); // TEMP

#if USE_SYSTEM_SQLITE
SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3());
#endif
using var con = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly");
con.Open();
using var cmd = con.CreateCommand();
cmd.CommandText = "SELECT UserId, UserName, Token FROM Login WHERE Expires > datetime('NOW')";
cmd.CommandText = "SELECT UserName, PublicKey, PrivateKey FROM LoginMV";

if (wantName != null)
{
cmd.CommandText += " AND UserName = @userName";
cmd.CommandText += " WHERE UserName = @userName";
cmd.Parameters.AddWithValue("@userName", wantName);
}

Expand All @@ -47,14 +51,31 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args)
return;
}

var userId = Guid.Parse(reader.GetString(0));
var userName = reader.GetString(1);
var token = reader.GetString(2);
var userName = reader.GetString(0);
var publicKeyString = reader.GetString(1);
var privateKeyString = reader.GetString(2);

_auth.Token = token;
_auth.UserId = new NetUserId(userId);
var publicKey = RSA.Create();
publicKey.ImportFromPem(publicKeyString);

shell.WriteLine($"Logged into account {userName}");
var privateKey = RSA.Create();
privateKey.ImportFromPem(privateKeyString);

// Create JWT
var token = JwtBuilder.Create()
.WithAlgorithm(new RS2048Algorithm(publicKey, privateKey))
.AddClaim("exp", DateTimeOffset.UtcNow.AddMinutes(5).ToUnixTimeSeconds()) // expiry
.AddClaim("nbf", DateTimeOffset.UtcNow.AddMinutes(-5).ToUnixTimeSeconds()) // not before
.AddClaim("iat", DateTimeOffset.UtcNow) // issued at
.AddClaim("jti", "TODO") // TODO
.AddClaim("aud", "TODO") // TODO
.AddClaim("preferredUserName", userName)
.Encode();

_auth.UserJWT = token;
_auth.UserPublicKey = publicKeyString;

shell.WriteLine($"Set auth parameters based on launcher keys for {userName}");
}
}
}
Expand Down
7 changes: 0 additions & 7 deletions Robust.Shared/CVars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -860,13 +860,6 @@ protected CVars()
public static readonly CVarDef<bool> AuthAllowLocal =
CVarDef.Create("auth.allowlocal", true, CVar.SERVERONLY);

// Only respected on server, client goes through IAuthManager for security.
/// <summary>
/// Authentication server address.
/// </summary>
public static readonly CVarDef<string> AuthServer =
CVarDef.Create("auth.server", AuthManager.DefaultAuthServer, CVar.SERVERONLY);

/*
* RENDERING
*/
Expand Down
33 changes: 12 additions & 21 deletions Robust.Shared/Network/AuthManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,34 @@ namespace Robust.Shared.Network
/// </summary>
internal interface IAuthManager
{
NetUserId? UserId { get; set; }
string? Server { get; set; }
string? Token { get; set; }
string? PubKey { get; set; }
string? ServerPublicKey { get; set; }
string? UserPublicKey { get; set; }
string? UserJWT { get; set; }

void LoadFromEnv();
}

internal sealed class AuthManager : IAuthManager
{
public const string DefaultAuthServer = "https://central.spacestation14.io/auth/";

public NetUserId? UserId { get; set; }
public string? Server { get; set; } = DefaultAuthServer;
public string? Token { get; set; }
public string? PubKey { get; set; }
public string? ServerPublicKey { get; set; }
public string? UserPublicKey { get; set; }
public string? UserJWT { get; set; }

public void LoadFromEnv()
{
if (TryGetVar("ROBUST_AUTH_SERVER", out var server))
{
Server = server;
}

if (TryGetVar("ROBUST_AUTH_USERID", out var userId))
if (TryGetVar("ROBUST_AUTH_PUBKEY", out var pubKey)) // Server's public key
{
UserId = new NetUserId(Guid.Parse(userId));
ServerPublicKey = pubKey;
}

if (TryGetVar("ROBUST_AUTH_PUBKEY", out var pubKey))
if (TryGetVar("ROBUST_USER_PUBLIC_KEY", out var userPublicKey)) // User's public key
{
PubKey = pubKey;
UserPublicKey = userPublicKey;
}

if (TryGetVar("ROBUST_AUTH_TOKEN", out var token))
if (TryGetVar("ROBUST_USER_JWT", out var userJWT))
{
Token = token;
UserJWT = userJWT;
}

static bool TryGetVar(string var, [NotNullWhen(true)] out string? val)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,26 @@ internal sealed class MsgEncryptionResponse : NetMessage

public override MsgGroups MsgGroup => MsgGroups.Core;

public Guid UserId;
public byte[] SealedData;
public string UserJWT;
public string UserPublicKey;

public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
UserId = buffer.ReadGuid();
var keyLength = buffer.ReadVariableInt32();
SealedData = buffer.ReadBytes(keyLength);

UserJWT = buffer.ReadString();
UserPublicKey = buffer.ReadString();
}

public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
buffer.Write(UserId);
buffer.WriteVariableInt32(SealedData.Length);
buffer.Write(SealedData);

buffer.Write(UserJWT);
buffer.Write(UserPublicKey);
}
}
}
17 changes: 11 additions & 6 deletions Robust.Shared/Network/Messages/Handshake/MsgLoginStart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,34 @@ internal sealed class MsgLoginStart : NetMessage

public override MsgGroups MsgGroup => MsgGroups.Core;

public string UserName;
/// <summary>
/// This is the username the player prefers -- however, the server may end up assigning a
/// derivative based on it.
/// </summary>
public string PreferredUserName;

public ImmutableArray<byte> HWId;
public bool CanAuth;
public bool NeedPubKey;
public bool NeedServerPublicKey;
public bool Encrypt;

public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
UserName = buffer.ReadString();
PreferredUserName = buffer.ReadString();
var length = buffer.ReadByte();
HWId = ImmutableArray.Create(buffer.ReadBytes(length));
CanAuth = buffer.ReadBoolean();
NeedPubKey = buffer.ReadBoolean();
NeedServerPublicKey = buffer.ReadBoolean();
Encrypt = buffer.ReadBoolean();
}

public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
buffer.Write(UserName);
buffer.Write(PreferredUserName);
buffer.Write((byte) HWId.Length);
buffer.Write(HWId.AsSpan());
buffer.Write(CanAuth);
buffer.Write(NeedPubKey);
buffer.Write(NeedServerPublicKey);
buffer.Write(Encrypt);
}
}
Expand Down
38 changes: 19 additions & 19 deletions Robust.Shared/Network/NetManager.ClientConnect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,19 @@ private async Task CCDoHandshake(NetPeerData peer, NetConnection connection, str
CancellationToken cancel)
{
var encrypt = _config.GetCVar(CVars.NetEncrypt);
var authToken = _authManager.Token;
var pubKey = _authManager.PubKey;
var authServer = _authManager.Server;
var userId = _authManager.UserId;
var serverPublicKey = _authManager.ServerPublicKey;
var userPublicKey = _authManager.UserPublicKey;
var userJWT = _authManager.UserJWT;

var hasPubKey = !string.IsNullOrEmpty(pubKey);
var authenticate = !string.IsNullOrEmpty(authToken);
var hasServerPublicKey = !string.IsNullOrEmpty(serverPublicKey);
var authenticate = !string.IsNullOrEmpty(userPublicKey) && !string.IsNullOrEmpty(userJWT);

var hwId = ImmutableArray.Create(HWId.Calc());
var msgLogin = new MsgLoginStart
{
UserName = userNameRequest,
PreferredUserName = userNameRequest,
CanAuth = authenticate,
NeedPubKey = !hasPubKey,
NeedServerPublicKey = !hasServerPublicKey,
HWId = hwId,
Encrypt = encrypt
};
Expand All @@ -163,10 +162,10 @@ private async Task CCDoHandshake(NetPeerData peer, NetConnection connection, str
encryption = new NetEncryption(sharedSecret, isServer: false);

byte[] keyBytes;
if (hasPubKey)
if (hasServerPublicKey)
{
// public key provided by launcher.
keyBytes = Convert.FromBase64String(pubKey!);
keyBytes = Convert.FromBase64String(serverPublicKey!);
}
else
{
Expand All @@ -188,21 +187,22 @@ private async Task CCDoHandshake(NetPeerData peer, NetConnection connection, str

var sealedData = CryptoBox.Seal(data, keyBytes);

var authHashBytes = MakeAuthHash(sharedSecret, keyBytes);
var authHash = Convert.ToBase64String(authHashBytes);
// var authHashBytes = MakeAuthHash(sharedSecret, keyBytes);
// var authHash = Convert.ToBase64String(authHashBytes);

var joinReq = new JoinRequest(authHash);
var request = new HttpRequestMessage(HttpMethod.Post, authServer + "api/session/join");
request.Content = JsonContent.Create(joinReq);
request.Headers.Authorization = new AuthenticationHeaderValue("SS14Auth", authToken);
var joinResp = await _http.Client.SendAsync(request, cancel);
// var joinReq = new JoinRequest(authHash);
// var request = new HttpRequestMessage(HttpMethod.Post, authServer + "api/session/join");
// request.Content = JsonContent.Create(joinReq);
// request.Headers.Authorization = new AuthenticationHeaderValue("SS14Auth", authToken);
// var joinResp = await _http.Client.SendAsync(request, cancel);

joinResp.EnsureSuccessStatusCode();
// joinResp.EnsureSuccessStatusCode();

var encryptionResponse = new MsgEncryptionResponse
{
SealedData = sealedData,
UserId = userId!.Value.UserId
UserJWT = _authManager.UserJWT,
UserPublicKey = _authManager.UserPublicKey
};

var outEncRespMsg = peer.Peer.CreateMessage();
Expand Down
Loading

0 comments on commit 5b8afd5

Please sign in to comment.