Testing more garbage encryption

This commit is contained in:
gamer147
2024-09-07 22:14:24 -04:00
parent f7657c2ec4
commit 7e4bce9ac5
32 changed files with 783 additions and 51 deletions

View File

@@ -58,6 +58,7 @@ public class DCGEDbContext : DbContext
IEnumerable<string> pendingMigrations = Database.GetPendingMigrations(); IEnumerable<string> pendingMigrations = Database.GetPendingMigrations();
if (!pendingMigrations.Any()) if (!pendingMigrations.Any())
{ {
_logger.LogDebug("No pending migrations found, continuing.");
return; return;
} }

View File

@@ -0,0 +1,12 @@
namespace SVSim.Database.Enums;
public enum SocialAccountType
{
None,
GooglePlay,
GameCenter,
Facebook,
Dmm,
Steam,
AppleId
}

View File

@@ -0,0 +1,171 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SVSim.Database;
#nullable disable
namespace SVSim.Database.Migrations
{
[DbContext(typeof(SVSimDbContext))]
[Migration("20240907191709_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.8");
modelBuilder.Entity("DCGEngine.Database.Models.CardEntry", b =>
{
b.Property<long>("Id")
.HasColumnType("INTEGER");
b.Property<int?>("Attack")
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime?>("DateUpdated")
.HasColumnType("TEXT");
b.Property<int?>("Defense")
.HasColumnType("INTEGER");
b.Property<string>("Discriminator")
.IsRequired()
.HasMaxLength(21)
.HasColumnType("TEXT");
b.Property<string>("InternalName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("PrimaryResourceCost")
.HasColumnType("INTEGER");
b.Property<long?>("ShadowverseDeckEntryId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ShadowverseDeckEntryId");
b.ToTable("CardEntry");
b.HasDiscriminator().HasValue("CardEntry");
b.UseTphMappingStrategy();
});
modelBuilder.Entity("SVSim.Database.Models.ShadowverseDeckEntry", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime?>("DateUpdated")
.HasColumnType("TEXT");
b.Property<string>("InternalName")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("ShadowverseDeckEntry");
});
modelBuilder.Entity("SVSim.Database.Models.SocialAccountConnection", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<ulong>("AccountId")
.HasColumnType("INTEGER");
b.Property<int>("AccountType")
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime?>("DateUpdated")
.HasColumnType("TEXT");
b.Property<ulong>("ViewerId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ViewerId");
b.ToTable("SocialAccountConnection");
});
modelBuilder.Entity("SVSim.Database.Models.Viewer", b =>
{
b.Property<ulong>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime?>("DateUpdated")
.HasColumnType("TEXT");
b.Property<string>("DisplayName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<ulong>("ShortUdid")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Viewer");
});
modelBuilder.Entity("SVSim.Database.Models.ShadowverseCardEntry", b =>
{
b.HasBaseType("DCGEngine.Database.Models.CardEntry");
b.HasDiscriminator().HasValue("ShadowverseCardEntry");
});
modelBuilder.Entity("DCGEngine.Database.Models.CardEntry", b =>
{
b.HasOne("SVSim.Database.Models.ShadowverseDeckEntry", null)
.WithMany("Cards")
.HasForeignKey("ShadowverseDeckEntryId");
});
modelBuilder.Entity("SVSim.Database.Models.SocialAccountConnection", b =>
{
b.HasOne("SVSim.Database.Models.Viewer", "Viewer")
.WithMany()
.HasForeignKey("ViewerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Viewer");
});
modelBuilder.Entity("SVSim.Database.Models.ShadowverseDeckEntry", b =>
{
b.Navigation("Cards");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,118 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SVSim.Database.Migrations
{
/// <inheritdoc />
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ShadowverseDeckEntry",
columns: table => new
{
Id = table.Column<long>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: true),
DateUpdated = table.Column<DateTime>(type: "TEXT", nullable: true),
InternalName = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ShadowverseDeckEntry", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Viewer",
columns: table => new
{
Id = table.Column<ulong>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DisplayName = table.Column<string>(type: "TEXT", nullable: false),
ShortUdid = table.Column<ulong>(type: "INTEGER", nullable: false),
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: true),
DateUpdated = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Viewer", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CardEntry",
columns: table => new
{
Id = table.Column<long>(type: "INTEGER", nullable: false),
InternalName = table.Column<string>(type: "TEXT", nullable: false),
Attack = table.Column<int>(type: "INTEGER", nullable: true),
Defense = table.Column<int>(type: "INTEGER", nullable: true),
PrimaryResourceCost = table.Column<int>(type: "INTEGER", nullable: true),
Discriminator = table.Column<string>(type: "TEXT", maxLength: 21, nullable: false),
ShadowverseDeckEntryId = table.Column<long>(type: "INTEGER", nullable: true),
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: true),
DateUpdated = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_CardEntry", x => x.Id);
table.ForeignKey(
name: "FK_CardEntry_ShadowverseDeckEntry_ShadowverseDeckEntryId",
column: x => x.ShadowverseDeckEntryId,
principalTable: "ShadowverseDeckEntry",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "SocialAccountConnection",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
AccountType = table.Column<int>(type: "INTEGER", nullable: false),
AccountId = table.Column<ulong>(type: "INTEGER", nullable: false),
ViewerId = table.Column<ulong>(type: "INTEGER", nullable: false),
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: true),
DateUpdated = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_SocialAccountConnection", x => x.Id);
table.ForeignKey(
name: "FK_SocialAccountConnection_Viewer_ViewerId",
column: x => x.ViewerId,
principalTable: "Viewer",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_CardEntry_ShadowverseDeckEntryId",
table: "CardEntry",
column: "ShadowverseDeckEntryId");
migrationBuilder.CreateIndex(
name: "IX_SocialAccountConnection_ViewerId",
table: "SocialAccountConnection",
column: "ViewerId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CardEntry");
migrationBuilder.DropTable(
name: "SocialAccountConnection");
migrationBuilder.DropTable(
name: "ShadowverseDeckEntry");
migrationBuilder.DropTable(
name: "Viewer");
}
}
}

View File

@@ -0,0 +1,168 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SVSim.Database;
#nullable disable
namespace SVSim.Database.Migrations
{
[DbContext(typeof(SVSimDbContext))]
partial class SVSimDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.8");
modelBuilder.Entity("DCGEngine.Database.Models.CardEntry", b =>
{
b.Property<long>("Id")
.HasColumnType("INTEGER");
b.Property<int?>("Attack")
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime?>("DateUpdated")
.HasColumnType("TEXT");
b.Property<int?>("Defense")
.HasColumnType("INTEGER");
b.Property<string>("Discriminator")
.IsRequired()
.HasMaxLength(21)
.HasColumnType("TEXT");
b.Property<string>("InternalName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("PrimaryResourceCost")
.HasColumnType("INTEGER");
b.Property<long?>("ShadowverseDeckEntryId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ShadowverseDeckEntryId");
b.ToTable("CardEntry");
b.HasDiscriminator().HasValue("CardEntry");
b.UseTphMappingStrategy();
});
modelBuilder.Entity("SVSim.Database.Models.ShadowverseDeckEntry", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime?>("DateUpdated")
.HasColumnType("TEXT");
b.Property<string>("InternalName")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("ShadowverseDeckEntry");
});
modelBuilder.Entity("SVSim.Database.Models.SocialAccountConnection", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<ulong>("AccountId")
.HasColumnType("INTEGER");
b.Property<int>("AccountType")
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime?>("DateUpdated")
.HasColumnType("TEXT");
b.Property<ulong>("ViewerId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ViewerId");
b.ToTable("SocialAccountConnection");
});
modelBuilder.Entity("SVSim.Database.Models.Viewer", b =>
{
b.Property<ulong>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime?>("DateUpdated")
.HasColumnType("TEXT");
b.Property<string>("DisplayName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<ulong>("ShortUdid")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Viewer");
});
modelBuilder.Entity("SVSim.Database.Models.ShadowverseCardEntry", b =>
{
b.HasBaseType("DCGEngine.Database.Models.CardEntry");
b.HasDiscriminator().HasValue("ShadowverseCardEntry");
});
modelBuilder.Entity("DCGEngine.Database.Models.CardEntry", b =>
{
b.HasOne("SVSim.Database.Models.ShadowverseDeckEntry", null)
.WithMany("Cards")
.HasForeignKey("ShadowverseDeckEntryId");
});
modelBuilder.Entity("SVSim.Database.Models.SocialAccountConnection", b =>
{
b.HasOne("SVSim.Database.Models.Viewer", "Viewer")
.WithMany()
.HasForeignKey("ViewerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Viewer");
});
modelBuilder.Entity("SVSim.Database.Models.ShadowverseDeckEntry", b =>
{
b.Navigation("Cards");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,25 @@
using DCGEngine.Database.Models;
using SVSim.Database.Enums;
namespace SVSim.Database.Models;
/// <summary>
/// A connection between a social account (ie facebook) and a viewer.
/// </summary>
public class SocialAccountConnection : BaseEntity<Guid>
{
/// <summary>
/// The type of the social account.
/// </summary>
public SocialAccountType AccountType { get; set; }
/// <summary>
/// The identifier of the social account.
/// </summary>
public ulong AccountId { get; set; }
/// <summary>
/// The viewer connected.
/// </summary>
public Viewer Viewer { get; set; }
}

View File

@@ -1,15 +0,0 @@
using DCGEngine.Database.Models;
namespace SVSim.Database.Models;
/// <summary>
/// A user within the game system.
/// </summary>
public class User : BaseEntity<long>
{
public string ViewerId { get; set; }
public ulong SteamId { get; set; }
public string DisplayName { get; set; }
}

View File

@@ -0,0 +1,19 @@
using DCGEngine.Database.Models;
namespace SVSim.Database.Models;
/// <summary>
/// A user within the game system.
/// </summary>
public class Viewer : BaseEntity<ulong>
{
/// <summary>
/// This user's name displayed in game.
/// </summary>
public string DisplayName { get; set; }
/// <summary>
/// This user's short identifier.
/// </summary>
public ulong ShortUdid { get; set; }
}

View File

@@ -0,0 +1,8 @@
using SVSim.Database.Enums;
namespace SVSim.Database.Repositories.Viewer;
public interface IViewerRepository
{
Task<Models.Viewer?> GetViewerBySocialConnection(SocialAccountType accountType, ulong socialId);
}

View File

@@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore;
using SVSim.Database.Enums;
using SVSim.Database.Models;
namespace SVSim.Database.Repositories.Viewer;
public class ViewerRepository : IViewerRepository
{
protected readonly SVSimDbContext _dbContext;
public ViewerRepository(SVSimDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<Models.Viewer?> GetViewerBySocialConnection(SocialAccountType accountType, ulong socialId)
{
return _dbContext.Set<SocialAccountConnection>()
.AsNoTracking()
.Include(sac => sac.Viewer)
.FirstOrDefault(sac => sac.AccountType == accountType && sac.AccountId == socialId)
?.Viewer;
}
}

View File

@@ -0,0 +1,8 @@
namespace SVSim.EmulatedEntrypoint.Constants;
public static class NetworkConstants
{
public const string UdidHeaderName = "UDID";
public const string SessionIdHeaderName = "SID";
public const string ShortUdidHeaderName = "SHORT_UDID";
}

View File

@@ -0,0 +1,7 @@
namespace SVSim.EmulatedEntrypoint.Constants;
public static class ShadowverseClaimTypes
{
public const string ShortUdidClaim = "ShortUdid";
public const string ViewerIdClaim = "ViewerId";
}

View File

@@ -1,6 +1,7 @@
using System.Buffers.Text; using System.Buffers.Text;
using System.Text; using System.Text;
using MessagePack; using MessagePack;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -22,6 +23,7 @@ namespace SVSim.EmulatedEntrypoint.Controllers
_logger = logger; _logger = logger;
} }
[AllowAnonymous]
[HttpPost("special_title")] [HttpPost("special_title")]
public async Task<DataWrapper<SpecialTitleCheckResponse>> SpecialTitleCheck(SpecialTitleCheckRequest request) public async Task<DataWrapper<SpecialTitleCheckResponse>> SpecialTitleCheck(SpecialTitleCheckRequest request)
{ {

View File

@@ -1,6 +1,8 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using SVSim.EmulatedEntrypoint.Security; using SVSim.EmulatedEntrypoint.Security;
using SVSim.EmulatedEntrypoint.Security.SteamSessionAuthentication;
namespace SVSim.EmulatedEntrypoint.Controllers namespace SVSim.EmulatedEntrypoint.Controllers
{ {
@@ -9,11 +11,8 @@ namespace SVSim.EmulatedEntrypoint.Controllers
/// </summary> /// </summary>
[Route("api/[controller]")] [Route("api/[controller]")]
[ApiController] [ApiController]
[Authorize(AuthenticationSchemes = SteamAuthenticationConstants.SchemeName)]
public abstract class SVSimController : ControllerBase public abstract class SVSimController : ControllerBase
{ {
/// <summary>
/// Returns the UdId of the user making the request. Can be null or empty, as only certain requests will send it. Known requests to send this value are: SignUp, CheckSpecialTitle, CheckiCloudUser, MigrateiCloudUser
/// </summary>
public string? UdId => Encryption.Decode(Request.Headers["UDID"]);
} }
} }

View File

@@ -0,0 +1,30 @@
using SVSim.EmulatedEntrypoint.Constants;
using SVSim.EmulatedEntrypoint.Security;
using SVSim.EmulatedEntrypoint.Services;
namespace SVSim.EmulatedEntrypoint.Middlewares;
/// <summary>
/// Maps an incoming request's session id to a udid if both are present.
/// </summary>
public class SessionidMappingMiddleware : IMiddleware
{
private readonly ShadowverseSessionService _shadowverseSessionService;
public SessionidMappingMiddleware(ShadowverseSessionService shadowverseSessionService)
{
_shadowverseSessionService = shadowverseSessionService;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
bool hasSessionId = context.Request.Headers.TryGetValue(NetworkConstants.UdidHeaderName, out var udid);
bool hasUdid = context.Request.Headers.TryGetValue(NetworkConstants.SessionIdHeaderName, out var sid);
if (hasSessionId && hasUdid)
{
_shadowverseSessionService.StoreUdidForSessionId(sid.FirstOrDefault(), Guid.Parse(Encryption.Decode(udid.FirstOrDefault())));
}
await next.Invoke(context);
}
}

View File

@@ -4,7 +4,9 @@ using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Newtonsoft.Json; using Newtonsoft.Json;
using SVSim.EmulatedEntrypoint.Constants;
using SVSim.EmulatedEntrypoint.Security; using SVSim.EmulatedEntrypoint.Security;
using SVSim.EmulatedEntrypoint.Services;
namespace SVSim.EmulatedEntrypoint.Middlewares; namespace SVSim.EmulatedEntrypoint.Middlewares;
@@ -14,10 +16,12 @@ namespace SVSim.EmulatedEntrypoint.Middlewares;
public class ShadowverseTranslationMiddleware : IMiddleware public class ShadowverseTranslationMiddleware : IMiddleware
{ {
private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
private readonly ShadowverseSessionService _sessionService;
public ShadowverseTranslationMiddleware(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) public ShadowverseTranslationMiddleware(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, ShadowverseSessionService sessionService)
{ {
_actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
_sessionService = sessionService;
} }
public async Task InvokeAsync(HttpContext context, RequestDelegate next) public async Task InvokeAsync(HttpContext context, RequestDelegate next)
@@ -32,21 +36,33 @@ public class ShadowverseTranslationMiddleware : IMiddleware
await next.Invoke(context); await next.Invoke(context);
return; return;
} }
using var requestBytesStream = new MemoryStream();
// Replace response body stream to re-access it.
using var tempResponseBody = new MemoryStream(); using var tempResponseBody = new MemoryStream();
var originalResponsebody = context.Response.Body; var originalResponsebody = context.Response.Body;
context.Response.Body = tempResponseBody; context.Response.Body = tempResponseBody;
// Pull out the request bytes into a stream
using var requestBytesStream = new MemoryStream();
await context.Request.Body.CopyToAsync(requestBytesStream); await context.Request.Body.CopyToAsync(requestBytesStream);
byte[] requestBytes = requestBytesStream.ToArray(); byte[] requestBytes = requestBytesStream.ToArray();
// Decrypt incoming data. Placeholder.
requestBytes = Encryption.Decrypt(requestBytes, Encryption.Decode(context.Request.Headers["UDID"])); // Get encryption values for this request
string sid = context.Request.Headers[NetworkConstants.SessionIdHeaderName];
string udid = _sessionService.GetUdidFromSessionId(sid).GetValueOrDefault().ToString();
// Decrypt incoming data.
requestBytes = Encryption.Decrypt(requestBytes, udid);
object? data = MessagePackSerializer.Deserialize(endpointDescriptor.Parameters.FirstOrDefault().ParameterType, object? data = MessagePackSerializer.Deserialize(endpointDescriptor.Parameters.FirstOrDefault().ParameterType,
requestBytes); requestBytes);
var json = JsonConvert.SerializeObject(data); var json = JsonConvert.SerializeObject(data);
var newStream = new StringContent(json, Encoding.UTF8, "application/json"); var newStream = new StringContent(json, Encoding.UTF8, "application/json");
context.Request.Body = newStream.ReadAsStream(); context.Request.Body = newStream.ReadAsStream();
context.Request.Headers.ContentType = new StringValues("application/json"); context.Request.Headers.ContentType = new StringValues("application/json");
await next.Invoke(context); await next.Invoke(context);
// Convert the response into a messagepack, encrypt it
var responseType = ((ControllerActionDescriptor)endpointDescriptor).MethodInfo.ReturnType; var responseType = ((ControllerActionDescriptor)endpointDescriptor).MethodInfo.ReturnType;
if (responseType.IsGenericType && responseType.GetGenericTypeDefinition() == typeof(Task<>)) if (responseType.IsGenericType && responseType.GetGenericTypeDefinition() == typeof(Task<>))
{ {
@@ -58,7 +74,7 @@ public class ShadowverseTranslationMiddleware : IMiddleware
var responseBytes = responseBytesStream.ToArray(); var responseBytes = responseBytesStream.ToArray();
var responseData = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(responseBytes), responseType); var responseData = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(responseBytes), responseType);
var packedData = MessagePackSerializer.Serialize(responseType, responseData); var packedData = MessagePackSerializer.Serialize(responseType, responseData);
packedData = Encryption.Encrypt(packedData, Encryption.Decode(context.Request.Headers["UDID"])); packedData = Encryption.Encrypt(packedData, udid);
await originalResponsebody.WriteAsync(Encoding.UTF8.GetBytes(Convert.ToBase64String(packedData))); await originalResponsebody.WriteAsync(Encoding.UTF8.GetBytes(Convert.ToBase64String(packedData)));
context.Response.Body = originalResponsebody; context.Response.Body = originalResponsebody;
} }

View File

@@ -3,12 +3,12 @@ using MessagePack;
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests; namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
[MessagePackObject] [MessagePackObject]
public abstract class BaseRequest public class BaseRequest
{ {
[Key("viewer_id")] [Key("viewer_id")]
public string ViewerId { get; set; } public string ViewerId { get; set; }
[Key("steam_id")] [Key("steam_id")]
public long SteamId { get; set; } public ulong SteamId { get; set; }
[Key("steam_session_ticket")] [Key("steam_session_ticket")]
public string SteamSessionTicket { get; set; } public string SteamSessionTicket { get; set; }
} }

View File

@@ -7,8 +7,10 @@ public class TransitionAccountData
{ {
[Key("social_account_id")] [Key("social_account_id")]
public string SocialAccountId { get; set; } public string SocialAccountId { get; set; }
[Key("social_account_type")] [Key("social_account_type")]
public string SocialAccountType { get; set; } public string SocialAccountType { get; set; }
[Key("connected_viewer_id")] [Key("connected_viewer_id")]
public string ConnectedViewerId { get; set; } public string ConnectedViewerId { get; set; }
} }

View File

@@ -4,7 +4,10 @@ using DCGEngine.Database.Configuration;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using SVSim.Database; using SVSim.Database;
using SVSim.Database.Models; using SVSim.Database.Models;
using SVSim.Database.Repositories.Viewer;
using SVSim.EmulatedEntrypoint.Middlewares; using SVSim.EmulatedEntrypoint.Middlewares;
using SVSim.EmulatedEntrypoint.Security.SteamSessionAuthentication;
using SVSim.EmulatedEntrypoint.Services;
namespace SVSim.EmulatedEntrypoint; namespace SVSim.EmulatedEntrypoint;
@@ -24,18 +27,42 @@ public class Program
{ {
}); });
#region Database Services
builder.Services.AddDbContext<SVSimDbContext>(opt => builder.Services.AddDbContext<SVSimDbContext>(opt =>
{ {
opt.UseSqlite(); opt.UseSqlite(builder.Configuration.GetConnectionString("Sqlite"));
}); });
builder.Services.AddTransient<IViewerRepository, ViewerRepository>();
#endregion
builder.Services.AddTransient<ShadowverseTranslationMiddleware>(); builder.Services.AddTransient<ShadowverseTranslationMiddleware>();
builder.Services.AddTransient<SessionidMappingMiddleware>();
builder.Services.Configure<DCGEDatabaseConfiguration>(opt => builder.Services.Configure<DCGEDatabaseConfiguration>(opt =>
{ {
opt.DbSetSearchAssemblies = new List<Assembly> { Assembly.GetAssembly(typeof(SVSimDbContext)) }; opt.DbSetSearchAssemblies = new List<Assembly> { Assembly.GetAssembly(typeof(SVSimDbContext)) };
}); });
builder.Services.AddSingleton<ShadowverseSessionService>();
builder.Services.AddSingleton<SteamSessionService>();
builder.Services.AddAuthentication()
.AddScheme<SteamAuthenticationHandlerOptions, SteamSessionAuthenticationHandler>(
SteamAuthenticationConstants.SchemeName,
opt =>
{
});
var app = builder.Build(); var app = builder.Build();
// Update database
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
dbContext.UpdateDatabase();
}
app.UseHttpLogging(); app.UseHttpLogging();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
@@ -47,8 +74,12 @@ public class Program
//app.UseHttpsRedirection(); //app.UseHttpsRedirection();
app.UseMiddleware<SessionidMappingMiddleware>();
app.UseMiddleware<ShadowverseTranslationMiddleware>(); app.UseMiddleware<ShadowverseTranslationMiddleware>();
app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();

View File

@@ -32,4 +32,29 @@
<ProjectReference Include="..\SVSim.Database\SVSim.Database.csproj" /> <ProjectReference Include="..\SVSim.Database\SVSim.Database.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="lib\libsteam_api.so">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="lib\libsteam_api.so.meta">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="lib\steam_api.dll">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="lib\steam_api.lib">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="lib\steam_api64.dll">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="lib\steam_api64.lib">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="COPY &quot;$(ProjectDir)\lib\*&quot; &quot;$(TargetDir)&quot;" />
</Target>
</Project> </Project>

View File

@@ -58,29 +58,31 @@ public static class Encryption
/// <returns>the decrypted bytes</returns> /// <returns>the decrypted bytes</returns>
public static byte[] Decrypt(byte[] encryptedData, string udId) public static byte[] Decrypt(byte[] encryptedData, string udId)
{ {
using (var rj = Aes.Create()) using (var rj = new RijndaelManaged())
{ {
rj.KeySize = EncryptionKeySize; rj.KeySize = EncryptionKeySize;
rj.Mode = EncryptionMode; rj.Mode = EncryptionMode;
rj.BlockSize = EncryptionBlockSize; rj.BlockSize = EncryptionBlockSize;
//rj.Padding = PaddingMode.None;
byte[] rgbIv = Encoding.UTF8.GetBytes(udId.Replace("-", string.Empty).Substring(0, UdIdKeySize)); byte[] rgbIv = Encoding.UTF8.GetBytes(udId.Replace("-", string.Empty).Substring(0, UdIdKeySize));
byte[] keyBytes = new byte[KeyStringSize]; byte[] keyBytes = new byte[KeyStringSize];
byte[] encryptedValueBytes = new byte[encryptedData.Length - KeyStringSize]; byte[] encryptedValueBytes = new byte[encryptedData.Length - KeyStringSize];
Array.Copy(encryptedData, encryptedData.Length - keyBytes.Length, keyBytes, 0, keyBytes.Length); Array.Copy(encryptedData, encryptedData.Length - keyBytes.Length, keyBytes, 0, keyBytes.Length);
Array.Copy(encryptedData, 0, encryptedValueBytes, 0, encryptedValueBytes.Length); Array.Copy(encryptedData, 0, encryptedValueBytes, 0, encryptedValueBytes.Length);
ICryptoTransform transform = rj.CreateDecryptor(keyBytes, rgbIv); ICryptoTransform transform = rj.CreateDecryptor(keyBytes, rgbIv);
byte[] decryptedValueBytes = new byte[encryptedValueBytes.Length];
using (MemoryStream ms = new MemoryStream(encryptedValueBytes)) using (MemoryStream ms = new MemoryStream(encryptedValueBytes))
{ {
using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Read)) using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Read))
{ {
byte[] decryptedValueBytes = new byte[encryptedValueBytes.Length]; cs.CopyTo(decryptedValueBytes);
cs.Read(decryptedValueBytes, 0, encryptedValueBytes.Length); cs.Flush();
cs.FlushFinalBlock(); ms.Flush();
}
}
return decryptedValueBytes; return decryptedValueBytes;
} }
} }
}
}
public static string Encode(string sourceData) public static string Encode(string sourceData)
{ {

View File

@@ -0,0 +1,7 @@
namespace SVSim.EmulatedEntrypoint.Security.SteamSessionAuthentication;
public static class SteamAuthenticationConstants
{
public const string SchemeName = "SteamAuthentication";
public const string SteamIdClaim = "SteamId";
}

View File

@@ -1,21 +1,74 @@
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using SVSim.Database.Enums;
using SVSim.Database.Models;
using SVSim.Database.Repositories.Viewer;
using SVSim.EmulatedEntrypoint.Constants;
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
using SVSim.EmulatedEntrypoint.Services;
namespace SVSim.EmulatedEntrypoint.Security.SteamSessionAuthentication; namespace SVSim.EmulatedEntrypoint.Security.SteamSessionAuthentication;
public class SteamSessionAuthenticationHandler : AuthenticationHandler<SteamAuthenticationHandlerOptions> public class SteamSessionAuthenticationHandler : AuthenticationHandler<SteamAuthenticationHandlerOptions>
{ {
public SteamSessionAuthenticationHandler(IOptionsMonitor<SteamAuthenticationHandlerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) private readonly SteamSessionService _sessionService;
{ private readonly IViewerRepository _viewerRepository;
} public SteamSessionAuthenticationHandler(IOptionsMonitor<SteamAuthenticationHandlerOptions> options, ILoggerFactory logger, UrlEncoder encoder, SteamSessionService sessionService, IViewerRepository viewerRepository) : base(options, logger, encoder)
public SteamSessionAuthenticationHandler(IOptionsMonitor<SteamAuthenticationHandlerOptions> options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder)
{ {
_sessionService = sessionService;
_viewerRepository = viewerRepository;
} }
protected async override Task<AuthenticateResult> HandleAuthenticateAsync() protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
{ {
return AuthenticateResult.Fail("Not implemented"); byte[] requestBytes;
using (var requestBytesStream = new MemoryStream())
{
await Request.Body.CopyToAsync(requestBytesStream);
requestBytes = requestBytesStream.ToArray();
}
// Convert bytes to json
string requestString = Encoding.UTF8.GetString(requestBytes);
BaseRequest? requestJson = JsonConvert.DeserializeObject<BaseRequest>(requestString);
// Reset request stream
Request.Body.Seek(0, SeekOrigin.Begin);
if (requestJson is null)
{
return AuthenticateResult.Fail("Invalid request body.");
}
// Check steam session validity
bool sessionIsValid = _sessionService.IsTicketValidForUser(requestJson.SteamSessionTicket, requestJson.SteamId);
if (!sessionIsValid)
{
return AuthenticateResult.Fail("Invalid ticket.");
}
Viewer? viewer =
await _viewerRepository.GetViewerBySocialConnection(SocialAccountType.Steam, requestJson.SteamId);
if (viewer is null)
{
return AuthenticateResult.Fail("User not found.");
}
// Build identity
ClaimsIdentity identity = new ClaimsIdentity();
identity.AddClaim(new Claim(ClaimTypes.Name, viewer.DisplayName));
identity.AddClaim(new Claim(ShadowverseClaimTypes.ShortUdidClaim, viewer.ShortUdid.ToString()));
identity.AddClaim(new Claim(ShadowverseClaimTypes.ViewerIdClaim, viewer.Id.ToString()));
identity.AddClaim(new Claim(SteamAuthenticationConstants.SteamIdClaim, requestJson.SteamId.ToString()));
// Build and return final ticket
AuthenticationTicket ticket =
new AuthenticationTicket(new ClaimsPrincipal(), SteamAuthenticationConstants.SchemeName);
return AuthenticateResult.Success(ticket);
} }
} }

View File

@@ -0,0 +1,28 @@
using System.Collections.Concurrent;
namespace SVSim.EmulatedEntrypoint.Services;
public class ShadowverseSessionService
{
private readonly ConcurrentDictionary<string, Guid> _sessionIdToUdid;
public ShadowverseSessionService()
{
_sessionIdToUdid = new();
}
public Guid? GetUdidFromSessionId(string sid)
{
if (_sessionIdToUdid.TryGetValue(sid, out var udid))
{
return udid;
}
return null;
}
public void StoreUdidForSessionId(string sid, Guid udid)
{
_sessionIdToUdid.AddOrUpdate(sid, _ => udid, (_, _) => udid);
}
}

View File

@@ -1,12 +0,0 @@
namespace SVSim.EmulatedEntrypoint;
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}

View File

@@ -6,5 +6,8 @@
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information" "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
} }
}, },
"ConnectionStrings": {
"Sqlite": "Data Source=test_db"
},
"AllowedHosts": "*" "AllowedHosts": "*"
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -16,6 +16,6 @@ public class Tests
"140000005ee7d30c1263e214e133a10001001001e07cd866180000000100000002000000b8526bb7b8946cd27c214574f1000000b20000003200000004000000e133a1000100100168eb0600488cc2443101a8c0000000008165d4660115f06601005c7e010000000000cad61456a2b83d39595c3e3749b96b4537ebde88d048103a6f6c7b2b81ee68711378836872a11422f5bd16fad803f81122c5ae98d986b693bbbc00ac7d30a8f85af2c1a7dce57751eb2c7f21130284aa8d9ee787246c8ccc138f05936bacb1ba4baba5fa5fbf6158002cf7207ae25a6f6ee8e3fc8edbb84903d346a249179637"; "140000005ee7d30c1263e214e133a10001001001e07cd866180000000100000002000000b8526bb7b8946cd27c214574f1000000b20000003200000004000000e133a1000100100168eb0600488cc2443101a8c0000000008165d4660115f06601005c7e010000000000cad61456a2b83d39595c3e3749b96b4537ebde88d048103a6f6c7b2b81ee68711378836872a11422f5bd16fad803f81122c5ae98d986b693bbbc00ac7d30a8f85af2c1a7dce57751eb2c7f21130284aa8d9ee787246c8ccc138f05936bacb1ba4baba5fa5fbf6158002cf7207ae25a6f6ee8e3fc8edbb84903d346a249179637";
using var steamService = new SteamSessionService(); using var steamService = new SteamSessionService();
bool validTicket = steamService.IsTicketValidForUser(ticket, 76561197970830305); bool validTicket = steamService.IsTicketValidForUser(ticket, 76561197970830305);
Assert.AreEqual(true, validTicket); Assert.That(validTicket, Is.EqualTo(true));
} }
} }