mirror of
https://github.com/opengram-server/opengram.git
synced 2026-09-09 20:14:15 +03:00
Initial commit
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
namespace MyTelegram.AuthServer.BackgroundServices;
|
||||
|
||||
public class MyTelegramAuthServerBackgroundService(
|
||||
ILogger<MyTelegramAuthServerBackgroundService> logger,
|
||||
IHandlerHelper handlerHelper,
|
||||
IFingerprintHelper fingerprintHelper
|
||||
) : BackgroundService
|
||||
{
|
||||
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
handlerHelper.InitAllHandlers();
|
||||
logger.LogInformation("MyTelegram auth server started");
|
||||
const long defaultFingerprint = -3591632762792723036;
|
||||
var fingerprint = fingerprintHelper.GetFingerprint();
|
||||
if (fingerprint == defaultFingerprint)
|
||||
{
|
||||
logger.LogWarning("You are currently using the default private key, which anyone can obtain from the mytelegram open source project. For security reasons, please use your own private key and replace the client's public key.");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||
|
||||
# This stage is used when running from VS in fast mode (Default for Debug configuration)
|
||||
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base
|
||||
USER $APP_UID
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
# This stage is used to build the service project
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
COPY ["Directory.Packages.props", "."]
|
||||
COPY ["Directory.Build.props", "."]
|
||||
COPY ["nuget.config", "."]
|
||||
COPY ["src/MyTelegram.AuthServer/MyTelegram.AuthServer.csproj", "src/MyTelegram.AuthServer/"]
|
||||
COPY ["src/MyTelegram.Caching.Redis/MyTelegram.Caching.Redis.csproj", "src/MyTelegram.Caching.Redis/"]
|
||||
COPY ["src/MyTelegram.Core/MyTelegram.Core.csproj", "src/MyTelegram.Core/"]
|
||||
COPY ["src/MyTelegram.Abstractions/MyTelegram.Abstractions.csproj", "src/MyTelegram.Abstractions/"]
|
||||
COPY ["src/MyTelegram.Domain.Shared/MyTelegram.Domain.Shared.csproj", "src/MyTelegram.Domain.Shared/"]
|
||||
COPY ["src/MyTelegram.Schema/MyTelegram.Schema.csproj", "src/MyTelegram.Schema/"]
|
||||
COPY ["src/MyTelegram.EventBus.Rebus/MyTelegram.EventBus.Rebus.csproj", "src/MyTelegram.EventBus.Rebus/"]
|
||||
COPY ["src/MyTelegram.EventBus/MyTelegram.EventBus.csproj", "src/MyTelegram.EventBus/"]
|
||||
COPY ["src/MyTelegram.Services/MyTelegram.Services.csproj", "src/MyTelegram.Services/"]
|
||||
COPY ["src/MyTelegram.EventFlow/MyTelegram.EventFlow.csproj", "src/MyTelegram.EventFlow/"]
|
||||
RUN dotnet restore "./src/MyTelegram.AuthServer/MyTelegram.AuthServer.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/src/MyTelegram.AuthServer"
|
||||
RUN dotnet build "./MyTelegram.AuthServer.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
# This stage is used to publish the service project to be copied to the final stage
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "./MyTelegram.AuthServer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "MyTelegram.AuthServer.dll"]
|
||||
@@ -0,0 +1,92 @@
|
||||
namespace MyTelegram.AuthServer.EventHandlers;
|
||||
|
||||
public class UnencryptedMessageHandler(
|
||||
ILogger<UnencryptedMessageHandler> logger,
|
||||
IHandlerHelper handlerHelper,
|
||||
IEventBus eventBus
|
||||
) : IEventHandler<UnencryptedMessage>, ITransientDependency
|
||||
{
|
||||
public async Task HandleEventAsync(UnencryptedMessage eventData)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!handlerHelper.TryGetHandler(eventData.ObjectId, out var handler))
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Cannot find a handler with objectId {ObjectId:x2}, connectionId: {ConnectionId}",
|
||||
eventData.ObjectId,
|
||||
eventData.ConnectionId
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (logger.IsEnabled(LogLevel.Trace))
|
||||
{
|
||||
logger.LogTrace(
|
||||
"Processing unencrypted message, connectionId: {ConnectionId}, [objectId]: {ObjectId:x2}, handler: {Handler}, reqMsgId: {ReqMsgId}",
|
||||
eventData.ConnectionId,
|
||||
eventData.ObjectId,
|
||||
handler.GetType().Name,
|
||||
eventData.MessageId
|
||||
);
|
||||
}
|
||||
|
||||
var obj = eventData.MessageData.ToTObject<IObject>();
|
||||
|
||||
var r = await handler.HandleAsync(
|
||||
new RequestInput(
|
||||
eventData.ConnectionId,
|
||||
eventData.RequestId,
|
||||
eventData.ObjectId,
|
||||
eventData.MessageId,
|
||||
0,
|
||||
0,
|
||||
eventData.AuthKeyId,
|
||||
eventData.AuthKeyId,
|
||||
0,
|
||||
eventData.Date,
|
||||
DeviceType.Unknown,
|
||||
eventData.ClientIp,
|
||||
0,
|
||||
0
|
||||
),
|
||||
obj
|
||||
);
|
||||
|
||||
if (r != null!)
|
||||
{
|
||||
using var writer = new ArrayPoolBufferWriter<byte>();
|
||||
r.Serialize(writer);
|
||||
var unencryptedResponse = new UnencryptedMessageResponse(
|
||||
eventData.AuthKeyId,
|
||||
writer.WrittenMemory,
|
||||
eventData.ConnectionId,
|
||||
eventData.MessageId
|
||||
);
|
||||
await eventBus.PublishAsync(unencryptedResponse);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(
|
||||
"Process request failed, connectionId: {ConnectionId}, reqMsgId: {ReqMsgId}, error: {Error}",
|
||||
eventData.ConnectionId,
|
||||
eventData.MessageId,
|
||||
ex
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
eventData.MemoryOwner?.Dispose();
|
||||
}
|
||||
|
||||
if (logger.IsEnabled(LogLevel.Trace))
|
||||
{
|
||||
logger.LogTrace(
|
||||
"Process unencrypted message completed, reqMsgId: {ReqMsgId}",
|
||||
eventData.MessageId
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using MyTelegram.EventBus.Extensions;
|
||||
|
||||
namespace MyTelegram.AuthServer.Extensions;
|
||||
|
||||
public static class MyTelegramAuthServerExtensions
|
||||
{
|
||||
public static IServiceCollection AddAuthServer(this IServiceCollection services)
|
||||
{
|
||||
services.RegisterServices();
|
||||
|
||||
services.AddMyTelegramHandlerServices();
|
||||
|
||||
services.AddEventHandlers();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static void AddEventHandlers(this IServiceCollection services)
|
||||
{
|
||||
services.AddSubscription<UnencryptedMessage, UnencryptedMessageHandler>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||
<xs:element name="Weavers">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,25 @@
|
||||
global using Microsoft.Extensions.Configuration;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.Hosting;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
global using Microsoft.Extensions.Options;
|
||||
global using MyTelegram.Abstractions;
|
||||
global using MyTelegram.AuthServer;
|
||||
global using MyTelegram.AuthServer.BackgroundServices;
|
||||
global using MyTelegram.AuthServer.EventHandlers;
|
||||
global using MyTelegram.AuthServer.Extensions;
|
||||
global using MyTelegram.AuthServer.Services;
|
||||
global using MyTelegram.Caching.Redis;
|
||||
global using MyTelegram.Core;
|
||||
global using MyTelegram.EventBus;
|
||||
global using MyTelegram.EventBus.RabbitMQ;
|
||||
global using MyTelegram.Schema;
|
||||
global using MyTelegram.Schema.Extensions;
|
||||
global using MyTelegram.Services.Extensions;
|
||||
global using MyTelegram.Services.NativeAot;
|
||||
global using MyTelegram.Services.Services;
|
||||
global using Serilog;
|
||||
global using Serilog.Sinks.SystemConsole.Themes;
|
||||
global using System.Buffers;
|
||||
global using System.Numerics;
|
||||
global using System.Security.Cryptography;
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace MyTelegram.AuthServer.Handlers;
|
||||
|
||||
public interface IMsgsAckHandler : IObjectHandler
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace MyTelegram.AuthServer.Handlers;
|
||||
|
||||
public interface IReqDhParamsHandler : IObjectHandler
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace MyTelegram.AuthServer.Handlers;
|
||||
|
||||
public interface IReqPqHandler : IObjectHandler
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace MyTelegram.AuthServer.Handlers;
|
||||
|
||||
public interface IReqPqMultiHandler : IObjectHandler
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace MyTelegram.AuthServer.Handlers;
|
||||
|
||||
public interface ISetClientDhParamsHandler : IObjectHandler
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace MyTelegram.AuthServer.Handlers;
|
||||
|
||||
public class MsgsAckHandler : BaseObjectHandler<TMsgsAck, IObject>, IMsgsAckHandler
|
||||
{
|
||||
protected override Task<IObject> HandleCoreAsync(IRequestInput input, TMsgsAck obj)
|
||||
{
|
||||
return Task.FromResult<IObject>(null!);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace MyTelegram.AuthServer.Handlers;
|
||||
|
||||
public class ReqDhParamsHandler(IStep2Helper step2ServerHelper, ILogger<ReqDhParamsHandler> logger)
|
||||
: BaseObjectHandler<RequestReqDHParams, IServerDHParams>,
|
||||
IReqDhParamsHandler
|
||||
{
|
||||
protected override async Task<IServerDHParams> HandleCoreAsync(
|
||||
IRequestInput input,
|
||||
RequestReqDHParams obj
|
||||
)
|
||||
{
|
||||
var dto = await step2ServerHelper.GetServerDhParamsAsync(obj);
|
||||
logger.LogInformation(
|
||||
"[Step2] ReqDhParamsHandler, connectionId: {ConnectionId}, reqMsgId: {ReqMsgId}",
|
||||
input.ConnectionId,
|
||||
input.ReqMsgId
|
||||
);
|
||||
|
||||
return dto.ServerDhParams;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace MyTelegram.AuthServer.Handlers;
|
||||
|
||||
public class ReqPqHandler(
|
||||
IStep1Helper step1ServerHelper,
|
||||
ILogger<ReqPqHandler> logger,
|
||||
ICacheManager<AuthCacheItem> cacheManager
|
||||
) : BaseObjectHandler<RequestReqPq, IResPQ>, IReqPqHandler
|
||||
{
|
||||
protected override async Task<IResPQ> HandleCoreAsync(IRequestInput input, RequestReqPq obj)
|
||||
{
|
||||
var dto = step1ServerHelper.GetResponse(obj.Nonce);
|
||||
var authCacheItem = new AuthCacheItem(obj.Nonce, dto.ServerNonce, dto.P, dto.Q, false);
|
||||
|
||||
var key = AuthCacheItem.GetCacheKey(dto.ServerNonce);
|
||||
|
||||
await cacheManager.SetAsync(
|
||||
key,
|
||||
authCacheItem,
|
||||
MyTelegramConsts.AuthKeyExpireSeconds
|
||||
);
|
||||
logger.LogInformation(
|
||||
"[Step1] ReqPqHandler, connectionId: {ConnectionId}, reqMsgId: {ReqMsgId}",
|
||||
input.ConnectionId,
|
||||
input.ReqMsgId
|
||||
);
|
||||
|
||||
return dto.ResPq;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MyTelegram.AuthServer.Handlers;
|
||||
|
||||
public class ReqPqMultiHandler(
|
||||
IStep1Helper step1ServerHelper,
|
||||
ILogger<ReqPqMultiHandler> logger,
|
||||
ICacheManager<AuthCacheItem> cacheManager
|
||||
) : BaseObjectHandler<RequestReqPqMulti, IResPQ>, IReqPqMultiHandler
|
||||
{
|
||||
protected override async Task<IResPQ> HandleCoreAsync(
|
||||
IRequestInput input,
|
||||
RequestReqPqMulti obj
|
||||
)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var dto = step1ServerHelper.GetResponse(obj.Nonce);
|
||||
|
||||
var authCacheItem = new AuthCacheItem(obj.Nonce, dto.ServerNonce, dto.P, dto.Q, false);
|
||||
var key = AuthCacheItem.GetCacheKey(dto.ServerNonce);
|
||||
await cacheManager.SetAsync(
|
||||
key,
|
||||
authCacheItem,
|
||||
MyTelegramConsts.AuthKeyExpireSeconds
|
||||
);
|
||||
sw.Stop();
|
||||
logger.LogInformation(
|
||||
"[Step1] ReqPqMultiHandler, connectionId={ConnectionId}, nonce: {Nonce} reqMsgId: {ReqMsgId}, authKeyId: {AuthKeyId} {TimeSpan}ms",
|
||||
input.ConnectionId,
|
||||
obj.Nonce.ToHexString(),
|
||||
input.ReqMsgId,
|
||||
input.AuthKeyId,
|
||||
sw.Elapsed.TotalMilliseconds
|
||||
);
|
||||
|
||||
return dto.ResPq;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
namespace MyTelegram.AuthServer.Handlers;
|
||||
|
||||
public class SetClientDhParamsHandler(
|
||||
IStep3Helper step3ServerHelper,
|
||||
ILogger<SetClientDhParamsHandler> logger,
|
||||
ICacheManager<AuthKeyCacheItem> cacheManager,
|
||||
IEventBus eventBus
|
||||
) : BaseObjectHandler<RequestSetClientDHParams, ISetClientDHParamsAnswer>, ISetClientDhParamsHandler
|
||||
{
|
||||
protected override async Task<ISetClientDHParamsAnswer> HandleCoreAsync(
|
||||
IRequestInput input,
|
||||
RequestSetClientDHParams obj
|
||||
)
|
||||
{
|
||||
var dto = await step3ServerHelper.SetClientDhParamsAnswerAsync(obj);
|
||||
logger.LogInformation(
|
||||
"[Step3] [{IsPerm}] authKey created successfully, connectionId: {ConnectionId}, authKeyId: {AuthKeyId:x2}, reqMsgId: {ReqMsgId}",
|
||||
input.ConnectionId,
|
||||
dto.IsPermanent ? "Perm" : "Temp",
|
||||
dto.AuthKeyId,
|
||||
input.ReqMsgId
|
||||
);
|
||||
|
||||
// Cached authentication data expires in 120 seconds
|
||||
var cacheKey = AuthKeyCacheItem.GetCacheKey(dto.AuthKeyId);
|
||||
await cacheManager.SetAsync(
|
||||
cacheKey,
|
||||
new AuthKeyCacheItem(dto.AuthKey, dto.ServerSalt, dto.IsPermanent),
|
||||
120
|
||||
);
|
||||
await eventBus.PublishAsync(
|
||||
new AuthKeyCreatedIntegrationEvent(
|
||||
input.ConnectionId,
|
||||
input.ReqMsgId,
|
||||
dto.AuthKey,
|
||||
dto.ServerSalt,
|
||||
dto.IsPermanent,
|
||||
dto.SetClientDhParamsAnswer.ToBytes(),
|
||||
dto.DcId
|
||||
)
|
||||
);
|
||||
|
||||
// The session server will send SetClientDhParamsAnswer to client if the perm auth key created on session server
|
||||
if (!dto.IsPermanent)
|
||||
{
|
||||
return dto.SetClientDhParamsAnswer;
|
||||
}
|
||||
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<Title>MyTelegram auth server</Title>
|
||||
<Description>MyTelegram auth server</Description>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<DockerfileContext>..\..</DockerfileContext>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<TrimmableAssembly Include="HashedWheelTimer" />
|
||||
<TrimmableAssembly Include="SharpZipLib" />
|
||||
<TrimmableAssembly Include="MyTelegram.Schema" />
|
||||
<TrimmableAssembly Include="MyTelegram.Core" />
|
||||
<TrimmableAssembly Include="EventFlow" />
|
||||
<TrimmableAssembly Include="MyTelegram.Domain.Shared" />
|
||||
<TrimmableAssembly Include="MyTelegram.Services" />
|
||||
<TrimmableAssembly Include="System.Private.CoreLib" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<TrimmerRootDescriptor Include="TrimmerRoots.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<RdXmlFile Include="rd.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="appsettings.Development.json" />
|
||||
<None Remove="appsettings.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="appsettings.Development.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MyTelegram.Caching.Redis\MyTelegram.Caching.Redis.csproj" />
|
||||
<ProjectReference Include="..\MyTelegram.EventBus.RabbitMQ\MyTelegram.EventBus.RabbitMQ.csproj" />
|
||||
<!--<ProjectReference Include="..\MyTelegram.EventBus.RabbitMQ\MyTelegram.EventBus.RabbitMQ.csproj" />-->
|
||||
<ProjectReference Include="..\MyTelegram.Services\MyTelegram.Services.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="private.pkcs8.key">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Fody">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace MyTelegram.AuthServer;
|
||||
|
||||
public class MyTelegramAuthServerOptions
|
||||
{
|
||||
public string PrivateKeyFilePath { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using MyTelegram.EventBus.RabbitMQ.Extensions;
|
||||
using MyTelegramConsts = MyTelegram.MyTelegramConsts;
|
||||
|
||||
Console.Title = "MyTelegram auth server";
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Async(c => c.Console(theme: AnsiConsoleTheme.Code))
|
||||
.WriteTo.Async(c => c.File("Logs/startup-log.txt"))
|
||||
.CreateLogger();
|
||||
|
||||
Log.Information(
|
||||
"{Info} {Version}",
|
||||
"MyTelegram Auth Server",
|
||||
typeof(Program).Assembly.GetName().Version
|
||||
);
|
||||
Log.Information(
|
||||
"{Description} {Url}",
|
||||
"For more information, please visit",
|
||||
MyTelegramConsts.RepositoryUrl
|
||||
);
|
||||
|
||||
Log.Information("MyTelegram authentication server starting...");
|
||||
|
||||
//Console.ReadLine();
|
||||
var builder = Host.CreateDefaultBuilder(args);
|
||||
builder.ConfigureAppConfiguration(options =>
|
||||
{
|
||||
options.AddEnvironmentVariables();
|
||||
options.AddCommandLine(args);
|
||||
});
|
||||
|
||||
builder.UseSerilog(
|
||||
(context, configuration) => { configuration.ReadFrom.Configuration(context.Configuration); }
|
||||
);
|
||||
builder.ConfigureServices(
|
||||
(context, services) =>
|
||||
{
|
||||
services.Configure<MyTelegramAuthServerOptions>(
|
||||
context.Configuration.GetRequiredSection("App")
|
||||
);
|
||||
services.Configure<EventBusRabbitMqOptions>(
|
||||
context.Configuration.GetRequiredSection("RabbitMQ:EventBus")
|
||||
);
|
||||
services.Configure<RabbitMqOptions>(
|
||||
context.Configuration.GetRequiredSection("RabbitMQ:Connections:Default")
|
||||
);
|
||||
services.AddHostedService<MyTelegramAuthServerBackgroundService>();
|
||||
services.AddAuthServer();
|
||||
services.AddMyTelegramStackExchangeRedisCache(options =>
|
||||
{
|
||||
options.Configuration = context.Configuration.GetValue<string>("Redis:Configuration");
|
||||
});
|
||||
services.AddCacheJsonSerializer(options =>
|
||||
{
|
||||
options.TypeInfoResolverChain.Add(MyJsonSerializeContext.Default);
|
||||
});
|
||||
|
||||
services.AddMyTelegramRabbitMqEventBus();
|
||||
|
||||
//services.AddRebusEventBus(options =>
|
||||
//{
|
||||
// var eventBusOptions = context
|
||||
// .Configuration.GetRequiredSection("RabbitMQ:EventBus")
|
||||
// .Get<EventBusRabbitMqOptions>();
|
||||
// var rabbitMqOptions = context
|
||||
// .Configuration.GetRequiredSection("RabbitMQ:Connections:Default")
|
||||
// .Get<RabbitMqOptions>();
|
||||
|
||||
// options.Transport(t =>
|
||||
// {
|
||||
// t.UseRabbitMq(
|
||||
// $"amqp://{rabbitMqOptions!.UserName}:{rabbitMqOptions.Password}@{rabbitMqOptions.HostName}:{rabbitMqOptions.Port}",
|
||||
// eventBusOptions!.ClientName
|
||||
// )
|
||||
// .ExchangeNames(
|
||||
// eventBusOptions.ExchangeName,
|
||||
// eventBusOptions.TopicExchangeName ?? "RebusTopics"
|
||||
// );
|
||||
// });
|
||||
// options.AddSystemTextJson(jsonOptions =>
|
||||
// {
|
||||
// jsonOptions.TypeInfoResolverChain.Add(MyJsonSerializeContext.Default);
|
||||
// });
|
||||
//});
|
||||
}
|
||||
);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
await app.RunAsync();
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:52769/",
|
||||
"sslPort": 44398
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"MyTelegram.AuthServer": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000"
|
||||
},
|
||||
"Docker": {
|
||||
"commandName": "Docker"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public class FingerprintHelper(IRsaKeyProvider rsaKeyProvider, IMyRsaHelper rsaHelper)
|
||||
: IFingerprintHelper,
|
||||
ISingletonDependency
|
||||
{
|
||||
private long _fingerprint;
|
||||
|
||||
public long GetFingerprint()
|
||||
{
|
||||
if (_fingerprint == 0)
|
||||
{
|
||||
_fingerprint = rsaHelper.GetFingerprintFromPrivateKey(
|
||||
rsaKeyProvider.GetRsaPrivateKey()
|
||||
);
|
||||
}
|
||||
|
||||
return _fingerprint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public interface IFingerprintHelper
|
||||
{
|
||||
long GetFingerprint();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public interface IStep1Helper
|
||||
{
|
||||
Step1Output GetResponse(byte[] nonce);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public interface IStep2Helper
|
||||
{
|
||||
Task<Step2Output> GetServerDhParamsAsync(RequestReqDHParams req);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public interface IStep3Helper
|
||||
{
|
||||
Task<Step3Output> SetClientDhParamsAnswerAsync(RequestSetClientDHParams req);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public class RsaKeyProvider(IOptions<MyTelegramAuthServerOptions> options)
|
||||
: IRsaKeyProvider,
|
||||
ISingletonDependency
|
||||
{
|
||||
private string? _privateKey;
|
||||
|
||||
public string GetRsaPrivateKey()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_privateKey))
|
||||
{
|
||||
return _privateKey;
|
||||
}
|
||||
|
||||
if (!File.Exists(options.Value.PrivateKeyFilePath))
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
"Private key not exists",
|
||||
options.Value.PrivateKeyFilePath
|
||||
);
|
||||
}
|
||||
|
||||
_privateKey = File.ReadAllText(options.Value.PrivateKeyFilePath);
|
||||
|
||||
if (string.IsNullOrEmpty(_privateKey))
|
||||
{
|
||||
throw new InvalidOperationException("Private key can not be null");
|
||||
}
|
||||
|
||||
return _privateKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public class Step1Helper(IFingerprintHelper fingerprintHelper) : IStep1Helper, ISingletonDependency
|
||||
{
|
||||
public Step1Output GetResponse(byte[] nonce)
|
||||
{
|
||||
var p = AuthConsts.P;
|
||||
var q = AuthConsts.Q;
|
||||
|
||||
var serverNonce = RandomNumberGenerator.GetBytes(16);
|
||||
|
||||
var publicKeyFingerprint = fingerprintHelper.GetFingerprint();
|
||||
var pq = AuthConsts.Pq;
|
||||
var resPq = new TResPQ
|
||||
{
|
||||
Nonce = nonce,
|
||||
ServerNonce = serverNonce,
|
||||
Pq = pq,
|
||||
ServerPublicKeyFingerprints = new TVector<long> { publicKeyFingerprint }
|
||||
};
|
||||
|
||||
return new Step1Output(p, q, serverNonce, resPq);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public record Step1Output(byte[] P, byte[] Q, byte[] ServerNonce, TResPQ ResPq);
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public class Step1To3Helper
|
||||
{
|
||||
private static readonly BigInteger TwoPowOf2048Sub64 = BigInteger.Pow(2, 2048 - 64);
|
||||
|
||||
protected void CheckRequestData(
|
||||
Span<byte> expected,
|
||||
Span<byte> actual,
|
||||
[CallerArgumentExpression(nameof(actual))]
|
||||
string? message = null
|
||||
)
|
||||
{
|
||||
if (!expected.SequenceEqual(actual))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Invalid {message}, expected: {expected.ToHexString()} actual: {actual.ToHexString()}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected string GetAuthCacheKey(byte[] serverNonce)
|
||||
{
|
||||
return AuthCacheItem.GetCacheKey(serverNonce);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://corefork.telegram.org/mtproto/auth_key#6-server-responds-with
|
||||
/// </summary>
|
||||
/// <param name="gaOrGb"></param>
|
||||
/// <param name="dhPrime"></param>
|
||||
/// <returns></returns>
|
||||
protected bool IsGoodGaOrGb(BigInteger gaOrGb, BigInteger dhPrime)
|
||||
{
|
||||
var dhPrimeSubTowPowOf2048Sub64 = dhPrime - TwoPowOf2048Sub64;
|
||||
var isGoodGaOrGb =
|
||||
gaOrGb > 1
|
||||
&& gaOrGb < dhPrime - 1
|
||||
&& gaOrGb > TwoPowOf2048Sub64
|
||||
&& gaOrGb < dhPrimeSubTowPowOf2048Sub64;
|
||||
|
||||
return isGoodGaOrGb;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public class Step2Helper(
|
||||
ILogger<Step2Helper> logger,
|
||||
IAesHelper aesHelper,
|
||||
IMtpHelper mtpHelper,
|
||||
IMyRsaHelper myRsaHelper,
|
||||
ICacheManager<AuthCacheItem> cacheManager,
|
||||
IRsaKeyProvider rsaKeyProvider
|
||||
) : Step1To3Helper, IStep2Helper, ISingletonDependency
|
||||
{
|
||||
public async Task<Step2Output> GetServerDhParamsAsync(RequestReqDHParams req)
|
||||
{
|
||||
var cacheKey = GetAuthCacheKey(req.ServerNonce);
|
||||
var cachedAuthKey = await cacheManager.GetAsync(cacheKey);
|
||||
if (cachedAuthKey == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"GetServerDhParamsAsync: can not find cached auth key info, nonce={req.Nonce.ToHexString()}"
|
||||
);
|
||||
}
|
||||
|
||||
#region check request
|
||||
|
||||
CheckRequestData(cachedAuthKey.Nonce, req.Nonce);
|
||||
CheckRequestData(cachedAuthKey.ServerNonce, req.ServerNonce);
|
||||
CheckRequestData(cachedAuthKey.P, req.P);
|
||||
CheckRequestData(cachedAuthKey.Q, req.Q);
|
||||
|
||||
var tInnerData = DeserializeRequestTpqInnerData(req, rsaKeyProvider.GetRsaPrivateKey());
|
||||
CheckRequestData(cachedAuthKey.P, tInnerData.P);
|
||||
CheckRequestData(cachedAuthKey.Q, tInnerData.Q);
|
||||
CheckRequestData(cachedAuthKey.ServerNonce, tInnerData.ServerNonce);
|
||||
CheckRequestData(cachedAuthKey.Nonce, tInnerData.Nonce);
|
||||
|
||||
#endregion check request
|
||||
|
||||
var isPermanentAuthKey = false;
|
||||
int? dcId = null;
|
||||
switch (tInnerData)
|
||||
{
|
||||
case TPQInnerData:
|
||||
isPermanentAuthKey = true;
|
||||
break;
|
||||
case TPQInnerDataDc:
|
||||
isPermanentAuthKey = true;
|
||||
break;
|
||||
case TPQInnerDataTemp:
|
||||
|
||||
break;
|
||||
case TPQInnerDataTempDc pqInnerDataTempDc:
|
||||
dcId = pqInnerDataTempDc.Dc;
|
||||
break;
|
||||
}
|
||||
|
||||
var dh2048P = AuthConsts.Dh2048P;
|
||||
var g = AuthConsts.G;
|
||||
var aAndGa = GenerateAAndGa();
|
||||
|
||||
var newCachedAuthKey = cachedAuthKey with
|
||||
{
|
||||
IsPermanent = isPermanentAuthKey,
|
||||
NewNonce = tInnerData.NewNonce,
|
||||
A = aAndGa.a,
|
||||
Ga = aAndGa.ga,
|
||||
DcId = dcId
|
||||
};
|
||||
|
||||
var serverDhInnerData = new TServerDHInnerData
|
||||
{
|
||||
DhPrime = dh2048P,
|
||||
G = g[0],
|
||||
GA = aAndGa.ga,
|
||||
Nonce = cachedAuthKey.Nonce,
|
||||
ServerNonce = cachedAuthKey.ServerNonce,
|
||||
ServerTime = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
};
|
||||
|
||||
await cacheManager.SetAsync(cacheKey, newCachedAuthKey, 600);
|
||||
|
||||
var serverDhParams = SerializeResponse(tInnerData, serverDhInnerData);
|
||||
|
||||
return new Step2Output(tInnerData.NewNonce, serverDhParams);
|
||||
}
|
||||
|
||||
private IPQInnerData DeserializeRequestTpqInnerData(
|
||||
RequestReqDHParams reqDhParams,
|
||||
string privateKey
|
||||
)
|
||||
{
|
||||
// It needs to be converted into a 256-byte array.
|
||||
// sometimes the auth key data length is only 255, and 0 needs to be added to the first position.
|
||||
var innerDataWithHash = myRsaHelper.Decrypt(reqDhParams.EncryptedData, privateKey);
|
||||
if (innerDataWithHash.Length == 256)
|
||||
{
|
||||
return ParsePqInnerData(innerDataWithHash);
|
||||
}
|
||||
|
||||
return ParsePqInnerDataOld(innerDataWithHash);
|
||||
}
|
||||
|
||||
private IPQInnerData ParsePqInnerDataOld(byte[] innerDataWithHash)
|
||||
{
|
||||
var span = innerDataWithHash.AsSpan();
|
||||
var shaHash = span[..20];
|
||||
var innerData = span[20..];
|
||||
ReadOnlyMemory<byte> buffer = innerDataWithHash.AsMemory(20, innerDataWithHash.Length - 20);
|
||||
var oldLength = buffer.Length;
|
||||
var tPqInnerData = buffer.Read<IPQInnerData>();
|
||||
var length = oldLength - buffer.Length;
|
||||
var realInnerData = innerData[..length];
|
||||
|
||||
Span<byte> calcHash = stackalloc byte[20];
|
||||
SHA1.HashData(realInnerData, calcHash);
|
||||
if (!shaHash.SequenceEqual(calcHash))
|
||||
{
|
||||
logger.LogWarning("PQInnerData SHA1 hash mismatch");
|
||||
}
|
||||
|
||||
return tPqInnerData;
|
||||
}
|
||||
|
||||
private (byte[] a, byte[] ga) GenerateAAndGa()
|
||||
{
|
||||
var g = AuthConsts.G.ToBigEndianBigInteger();
|
||||
var dhPrime = AuthConsts.DhPrime;
|
||||
while (true)
|
||||
{
|
||||
var aBytes = RandomNumberGenerator.GetBytes(256);
|
||||
var a = aBytes.ToBigEndianBigInteger();
|
||||
|
||||
var ga = BigInteger.ModPow(g, a, dhPrime);
|
||||
if (IsGoodGaOrGb(ga, dhPrime))
|
||||
{
|
||||
return (aBytes, ga.ToByteArray(true, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IPQInnerData ParsePqInnerData(ReadOnlySpan<byte> keyAesEncryptedBytes)
|
||||
{
|
||||
const int tempKeyLength = 32;
|
||||
var tempBytes = ArrayPool<byte>.Shared.Rent(keyAesEncryptedBytes.Length + 32 + 32);
|
||||
|
||||
try
|
||||
{
|
||||
var tempSpan = tempBytes.AsSpan(0, keyAesEncryptedBytes.Length + 32 + 32 + 32);
|
||||
var startIndex = keyAesEncryptedBytes.Length - tempKeyLength;
|
||||
var dataWithHash = tempSpan[..(keyAesEncryptedBytes.Length - tempKeyLength)];
|
||||
|
||||
var aesEncryptedSha256Hash = tempSpan.Slice(startIndex, 32);
|
||||
var calculatedHash = tempSpan.Slice(startIndex + 32, 32);
|
||||
var aesEncrypted = keyAesEncryptedBytes[tempKeyLength..];
|
||||
|
||||
var tempKeyXor = keyAesEncryptedBytes[..tempKeyLength];
|
||||
SHA256.HashData(aesEncrypted, aesEncryptedSha256Hash);
|
||||
var tempKey = Xor(tempKeyXor, aesEncryptedSha256Hash);
|
||||
Span<byte> tempIv1 = stackalloc byte[32];
|
||||
aesHelper.DecryptIge(aesEncrypted, tempKey, tempIv1, dataWithHash);
|
||||
|
||||
var dataPaddingReversed = dataWithHash[..^32];
|
||||
var hash = dataWithHash[^32..];
|
||||
dataPaddingReversed.Reverse();
|
||||
var dataWithPadding = dataPaddingReversed;
|
||||
using var hasher = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
|
||||
hasher.AppendData(tempKey);
|
||||
hasher.AppendData(dataWithPadding);
|
||||
hasher.GetHashAndReset(calculatedHash);
|
||||
|
||||
if (!hash.SequenceEqual(calculatedHash))
|
||||
{
|
||||
logger.LogWarning("PQInnerData hash mismatch");
|
||||
|
||||
throw new ArgumentException("PQInnerData hash mismatch");
|
||||
}
|
||||
|
||||
var tPqInnerData = tempBytes.ToTObject<IPQInnerData>();
|
||||
|
||||
return tPqInnerData;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(tempBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private TServerDHParamsOk SerializeResponse(
|
||||
IPQInnerData pqInnerData,
|
||||
TServerDHInnerData dhInnerData
|
||||
)
|
||||
{
|
||||
return SerializeResponse(
|
||||
pqInnerData.Nonce,
|
||||
pqInnerData.NewNonce,
|
||||
pqInnerData.ServerNonce,
|
||||
dhInnerData
|
||||
);
|
||||
}
|
||||
|
||||
private TServerDHParamsOk SerializeResponse(
|
||||
byte[] nonce,
|
||||
byte[] newNonce,
|
||||
byte[] serverNonce,
|
||||
TServerDHInnerData dhInnerData
|
||||
)
|
||||
{
|
||||
using var writer = new ArrayPoolBufferWriter<byte>();
|
||||
dhInnerData.Serialize(writer);
|
||||
|
||||
var writtenCount = writer.WrittenCount;
|
||||
var totalLength = writtenCount + 20;// 20=SHA1 hash length
|
||||
var tempBytes = ArrayPool<byte>.Shared.Rent(totalLength + 32 + 16);
|
||||
var tempSpan = tempBytes.AsSpan();
|
||||
try
|
||||
{
|
||||
var sha1Hash = tempSpan.Slice(0, 20);
|
||||
var answerWithHashLength = writtenCount + 20;
|
||||
if (answerWithHashLength % 16 != 0)
|
||||
{
|
||||
answerWithHashLength += 16 - (answerWithHashLength % 16);
|
||||
}
|
||||
var answerWithHashSpan = tempSpan.Slice(0, answerWithHashLength);
|
||||
SHA1.HashData(writer.WrittenSpan, sha1Hash);
|
||||
sha1Hash.CopyTo(answerWithHashSpan);
|
||||
writer.WrittenSpan.CopyTo(answerWithHashSpan.Slice(20));
|
||||
var aesKey = new byte[32];
|
||||
Span<byte> aesIv = stackalloc byte[32];
|
||||
mtpHelper.CalcTempAesKeyData(newNonce, serverNonce, aesKey, aesIv);
|
||||
|
||||
aesHelper.EncryptIge(answerWithHashSpan, aesKey, aesIv, answerWithHashSpan);
|
||||
|
||||
return new TServerDHParamsOk
|
||||
{
|
||||
EncryptedAnswer = answerWithHashSpan.ToArray(),
|
||||
Nonce = nonce,
|
||||
ServerNonce = serverNonce
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(tempBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] Xor(ReadOnlySpan<byte> src, ReadOnlySpan<byte> dest)
|
||||
{
|
||||
var bytes = new byte[src.Length];
|
||||
for (var i = 0; i < src.Length; i++)
|
||||
{
|
||||
bytes[i] = (byte)(src[i] ^ dest[i]);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public record Step2Output(byte[] NewNonce, IServerDHParams ServerDhParams);
|
||||
@@ -0,0 +1,151 @@
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public class Step3Helper(
|
||||
IAesHelper aesHelper,
|
||||
IHashHelper hashHelper,
|
||||
IMtpHelper mtpHelper,
|
||||
ILogger<Step3Helper> logger,
|
||||
IAuthKeyIdHelper authKeyIdHelper,
|
||||
ICacheManager<AuthCacheItem> cacheManager
|
||||
) : Step1To3Helper, IStep3Helper, ISingletonDependency
|
||||
{
|
||||
public async Task<Step3Output> SetClientDhParamsAnswerAsync(RequestSetClientDHParams req)
|
||||
{
|
||||
var cacheKey = GetAuthCacheKey(req.ServerNonce);
|
||||
var cachedAuthKey = await cacheManager.GetAsync(cacheKey);
|
||||
if (cachedAuthKey?.A == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot find cached auth key info, nonce: {req.Nonce.ToHexString()}"
|
||||
);
|
||||
}
|
||||
|
||||
if (cachedAuthKey.NewNonce == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cachedAuthKey.NewNonce));
|
||||
}
|
||||
|
||||
CheckRequestData(cachedAuthKey.Nonce, req.Nonce, "Nonce");
|
||||
CheckRequestData(cachedAuthKey.ServerNonce, req.ServerNonce, "ServerNonce");
|
||||
|
||||
var aesKey = new byte[32];
|
||||
Span<byte> aesIv = stackalloc byte[32];
|
||||
mtpHelper.CalcTempAesKeyData(
|
||||
cachedAuthKey.NewNonce,
|
||||
cachedAuthKey.ServerNonce,
|
||||
aesKey,
|
||||
aesIv
|
||||
);
|
||||
var dhInnerData = DeserializeRequest(req, aesKey, aesIv);
|
||||
|
||||
CheckRequestData(cachedAuthKey.Nonce, dhInnerData.Nonce, "Nonce");
|
||||
CheckRequestData(cachedAuthKey.ServerNonce, dhInnerData.ServerNonce, "ServerNonce");
|
||||
var a = cachedAuthKey.A;
|
||||
var gb = dhInnerData.GB;
|
||||
|
||||
var authKeyBytes = BigInteger
|
||||
.ModPow(gb.ToBigEndianBigInteger(), a.ToBigEndianBigInteger(), AuthConsts.DhPrime)
|
||||
.ToByteArray(true, true)
|
||||
.ToBytes256();
|
||||
|
||||
var dto = new Step3Output(
|
||||
authKeyIdHelper.GetAuthKeyId(authKeyBytes),
|
||||
authKeyBytes,
|
||||
mtpHelper.ComputeSalt(cachedAuthKey.NewNonce, dhInnerData.ServerNonce),
|
||||
cachedAuthKey.IsPermanent,
|
||||
CreateDhGenOkAnswer(req, cachedAuthKey.NewNonce, authKeyBytes),
|
||||
cachedAuthKey.DcId
|
||||
);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
private TClientDHInnerData DeserializeRequest(
|
||||
RequestSetClientDHParams serverDhParams,
|
||||
byte[] key,
|
||||
ReadOnlySpan<byte> iv
|
||||
)
|
||||
{
|
||||
var tempBytes = ArrayPool<byte>.Shared.Rent(serverDhParams.EncryptedData.Length + 20);
|
||||
var tempSpan = tempBytes.AsSpan(0, serverDhParams.EncryptedData.Length + 20);
|
||||
var answerWithHash = tempSpan.Slice(0, serverDhParams.EncryptedData.Length);
|
||||
try
|
||||
{
|
||||
aesHelper.DecryptIge(
|
||||
serverDhParams.EncryptedData,
|
||||
key,
|
||||
iv,
|
||||
answerWithHash
|
||||
);
|
||||
|
||||
var hash = answerWithHash[..20];
|
||||
var answer = answerWithHash[20..];
|
||||
ReadOnlyMemory<byte> buffer = tempBytes.AsMemory(20, answerWithHash.Length - 20);
|
||||
var oldLength = buffer.Length;
|
||||
var obj = buffer.Read<TClientDHInnerData>();
|
||||
var consumed = oldLength-buffer.Length;
|
||||
var paddingCount = (int)(answer.Length - consumed);
|
||||
var data = answer[..^paddingCount];
|
||||
var calcHash = tempSpan[^20..];
|
||||
SHA1.HashData(data, calcHash);
|
||||
if (!hash.SequenceEqual(calcHash))
|
||||
{
|
||||
logger.LogWarning("Answer sha1 hash mismatch.");
|
||||
|
||||
throw new ArgumentException($"Answer sha1 hash mismatch.");
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(tempBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private TDhGenOk CreateDhGenOkAnswer(
|
||||
RequestSetClientDHParams req,
|
||||
byte[] newNonce,
|
||||
byte[] authKey
|
||||
)
|
||||
{
|
||||
var newNonceHash1 = CreateNewNonceHash(newNonce, authKey, 1);
|
||||
|
||||
return new TDhGenOk
|
||||
{
|
||||
Nonce = req.Nonce,
|
||||
ServerNonce = req.ServerNonce,
|
||||
NewNonceHash1 = newNonceHash1
|
||||
};
|
||||
}
|
||||
|
||||
//private TDhGenRetry CreateDhGenRetryRetryAnswer(RequestSetClientDHParams req, byte[] newNonce, byte[] authKey)
|
||||
//{
|
||||
// var newNonceHash2 = CreateNewNonceHash(newNonce, authKey, 2);
|
||||
|
||||
// return new TDhGenRetry
|
||||
// {
|
||||
// Nonce = req.Nonce,
|
||||
// ServerNonce = req.ServerNonce,
|
||||
// NewNonceHash2 = newNonceHash2
|
||||
// };
|
||||
//}
|
||||
|
||||
private byte[] CreateNewNonceHash(byte[] newNonce, byte[] authKey, byte n)
|
||||
{
|
||||
// https://core.telegram.org/mtproto/auth_key#9-server-responds-in-one-of-three-ways
|
||||
// new_nonce_hash1, new_nonce_hash2, and new_nonce_hash3 are obtained as the 128 lower - order bits of SHA1 of
|
||||
// the byte string derived from the new_nonce string by adding a single byte with the value of 1, 2, or 3, and followed
|
||||
// by another 8 bytes with auth_key_aux_hash.Different values are required to prevent an intruder from changing server
|
||||
// response dh_gen_ok into dh_gen_retry.
|
||||
|
||||
var authKeyAuxHash = SHA1.HashData(authKey).AsSpan(0, 8);
|
||||
Span<byte> newNonceWithAuxHashBytes = stackalloc byte[newNonce.Length + 1 + 8];
|
||||
newNonce.CopyTo(newNonceWithAuxHashBytes);
|
||||
newNonceWithAuxHashBytes[newNonce.Length] = n;
|
||||
authKeyAuxHash.CopyTo(newNonceWithAuxHashBytes[(newNonce.Length + 1)..]);
|
||||
var newNonceHashN = hashHelper.Sha1(newNonceWithAuxHashBytes);
|
||||
|
||||
return newNonceHashN.AsSpan(4).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace MyTelegram.AuthServer.Services;
|
||||
|
||||
public record Step3Output(
|
||||
long AuthKeyId,
|
||||
byte[] AuthKey,
|
||||
long ServerSalt,
|
||||
bool IsPermanent,
|
||||
ISetClientDHParamsAnswer SetClientDhParamsAnswer,
|
||||
int? DcId = null
|
||||
);
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"Serilog": {
|
||||
"Using": [
|
||||
"Serilog.Sinks.Console",
|
||||
"Serilog.Sinks.File",
|
||||
"Serilog.Sinks.Async"
|
||||
],
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning"
|
||||
}
|
||||
},
|
||||
"Properties": {
|
||||
"Application": "AuthServer"
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Async",
|
||||
"Args": {
|
||||
"configure": [
|
||||
{
|
||||
"Name": "Console",
|
||||
"Args": {
|
||||
"MinimumLevel": "Information",
|
||||
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss:ffffff} [{Level:u3}] {Message}{NewLine}{Exception}",
|
||||
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Async",
|
||||
"Args": {
|
||||
"configure": [
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"rollingInterval": "Day",
|
||||
//"buffered": true,
|
||||
"path": "./Logs/log-.txt",
|
||||
"outputTemplate": "{Timestamp:o} [{Level:u3}] {Message}{NewLine}{Exception}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"App": {
|
||||
"PrivateKeyFilePath": "private.pkcs8.key"
|
||||
},
|
||||
"RabbitMQ": {
|
||||
"Connections": {
|
||||
"Default": {
|
||||
"HostName": "localhost",
|
||||
"Port": 5672,
|
||||
"UserName": "guest",
|
||||
"Password": "guest"
|
||||
}
|
||||
},
|
||||
"EventBus": {
|
||||
"ClientName": "MyTelegramAuthServer",
|
||||
"ExchangeName": "MyTelegramExchange",
|
||||
}
|
||||
},
|
||||
"Redis": {
|
||||
"Configuration": "redis:6379,password=CHANGE_ME"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user