diff --git a/System.Application.SteamTools.Client.Desktop.Avalonia.App/Program.cs b/System.Application.SteamTools.Client.Desktop.Avalonia.App/Program.cs
index 5edf0161d..2ca64db37 100644
--- a/System.Application.SteamTools.Client.Desktop.Avalonia.App/Program.cs
+++ b/System.Application.SteamTools.Client.Desktop.Avalonia.App/Program.cs
@@ -5,7 +5,7 @@ using System.IO;
using System.Properties;
using System.Reflection;
using System.Threading.Tasks;
-using System.Windows;
+using LogLevel = Microsoft.Extensions.Logging.LogLevel;
[assembly: AssemblyTitle(ThisAssembly.AssemblyTrademark + " v" + ThisAssembly.Version)]
namespace System.Application.UI
@@ -36,7 +36,7 @@ namespace System.Application.UI
}
catch (Exception ex)
{
- AppHelper.SetNLoggerMinLevel(LogLevel.Trace);
+ AppHelper.TrySetLoggerMinLevel(LogLevel.Trace);
// NLog: catch any exception and log it.
logger.Error(ex, "Stopped program because of exception");
#if DEBUG
diff --git a/System.Application.SteamTools.Client.Desktop/UI/AppHelper.cs b/System.Application.SteamTools.Client.Desktop/UI/AppHelper.cs
index 96555e9fc..c72b1b2cb 100644
--- a/System.Application.SteamTools.Client.Desktop/UI/AppHelper.cs
+++ b/System.Application.SteamTools.Client.Desktop/UI/AppHelper.cs
@@ -65,12 +65,24 @@ namespace System.Application.UI
static void SetNLoggerMinLevel(LogLevel logLevel) => SetNLoggerMinLevel(ConvertLogLevel(logLevel));
- public static void SetNLoggerMinLevel(NLogLevel logLevel)
+ static void SetNLoggerMinLevel(NLogLevel logLevel)
{
NLogManager.GlobalThreshold = logLevel;
NInternalLogger.LogLevel = logLevel;
}
+ public static void TrySetLoggerMinLevel(LogLevel logLevel)
+ {
+ try
+ {
+ LoggerFilterOptions.MinLevel = logLevel;
+ }
+ catch
+ {
+ }
+ SetNLoggerMinLevel(ConvertLogLevel(logLevel));
+ }
+
///
/// 日志过滤选项
///
diff --git a/System.Application.SteamTools.Client/Models/AppSettings.cs b/System.Application.SteamTools.Client/Models/AppSettings.cs
index 5fa246ce8..753a352fc 100644
--- a/System.Application.SteamTools.Client/Models/AppSettings.cs
+++ b/System.Application.SteamTools.Client/Models/AppSettings.cs
@@ -52,5 +52,28 @@ namespace System.Application.Models
[N_JsonProperty("3")]
[S_JsonProperty("3")]
public string? AppSecretVisualStudioAppCenter { get; set; }
+
+ [MPKey(4)]
+ [N_JsonProperty("4")]
+ [S_JsonProperty("4")]
+ public string? RSASecret { get; set; }
+
+ RSA? rsa;
+
+ [MPIgnore]
+ [N_JsonIgnore]
+ [S_JsonIgnore]
+ public RSA RSA
+ {
+ get
+ {
+ if (rsa == null)
+ {
+ if (RSASecret == null) throw new ArgumentNullException(nameof(RSASecret));
+ rsa = RSAUtils.CreateFromJsonString(RSASecret);
+ }
+ return rsa;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/System.Application.SteamTools.Services.CloudService.Models/Models/ApiResponseCode.cs b/System.Application.SteamTools.Services.CloudService.Models/Models/ApiResponseCode.cs
index af06649f0..ac6407044 100644
--- a/System.Application.SteamTools.Services.CloudService.Models/Models/ApiResponseCode.cs
+++ b/System.Application.SteamTools.Services.CloudService.Models/Models/ApiResponseCode.cs
@@ -105,6 +105,8 @@ namespace System.Application.Models
EmptyUserAgent,
+ RequiredSecurityKey,
+
#endregion
}
}
\ No newline at end of file
diff --git a/System.Application.SteamTools.Services.CloudService.Models/Services.CloudService/Constants.cs b/System.Application.SteamTools.Services.CloudService.Models/Services.CloudService/Constants.cs
index 6ebf0c756..6fbf66457 100644
--- a/System.Application.SteamTools.Services.CloudService.Models/Services.CloudService/Constants.cs
+++ b/System.Application.SteamTools.Services.CloudService.Models/Services.CloudService/Constants.cs
@@ -10,8 +10,21 @@ namespace System.Application.Services.CloudService
public static string NetworkConnectionInterruption => SR.NetworkConnectionInterruption;
+ public static class Headers
+ {
+ public static class Request
+ {
+ public const string AppVersion = "App-Version";
+ public const string SecurityKey = "App-SKey";
+ }
+ }
+
public const string HeaderAppVersion = "App-Version";
+ public const string HeaderSecurityKey = "App-S-Key";
+
+ public const string HeaderRspCiphertext = "App-Rsp-Ciphertext";
+
///
/// 短信间隔,60秒
///
diff --git a/System.Application.SteamTools.Services.CloudService/HttpContentCompat.cs b/System.Application.SteamTools.Services.CloudService/HttpContentCompat.cs
new file mode 100644
index 000000000..051ba2a1f
--- /dev/null
+++ b/System.Application.SteamTools.Services.CloudService/HttpContentCompat.cs
@@ -0,0 +1,29 @@
+#if !NET5_0_OR_GREATER
+using System.IO;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+// ReSharper disable once CheckNamespace
+namespace System
+{
+ public static class HttpContentCompat
+ {
+ public static Task ReadAsByteArrayAsync(this HttpContent @this, CancellationToken _)
+ {
+ return @this.ReadAsByteArrayAsync();
+ }
+
+ public static Task ReadAsStreamAsync(this HttpContent @this, CancellationToken _)
+ {
+ return @this.ReadAsStreamAsync();
+ }
+
+ public static Task ReadAsStringAsync(this HttpContent @this, CancellationToken _)
+ {
+ return @this.ReadAsStringAsync();
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/System.Application.SteamTools.Services.CloudService/Models/ICloudServiceSettings.cs b/System.Application.SteamTools.Services.CloudService/Models/ICloudServiceSettings.cs
index 9d4272f41..dd2a51073 100644
--- a/System.Application.SteamTools.Services.CloudService/Models/ICloudServiceSettings.cs
+++ b/System.Application.SteamTools.Services.CloudService/Models/ICloudServiceSettings.cs
@@ -1,4 +1,4 @@
-using System.Application.Services.CloudService;
+using System.Security.Cryptography;
namespace System.Application.Models
{
@@ -7,5 +7,7 @@ namespace System.Application.Models
string? ApiBaseUrl { get; set; }
Guid AppVersion { get; set; }
+
+ RSA RSA { get; }
}
}
\ No newline at end of file
diff --git a/System.Application.SteamTools.Services.CloudService/ServiceCollectionExtensions.cs b/System.Application.SteamTools.Services.CloudService/ServiceCollectionExtensions.cs
index 8bf92992e..cdb08f528 100644
--- a/System.Application.SteamTools.Services.CloudService/ServiceCollectionExtensions.cs
+++ b/System.Application.SteamTools.Services.CloudService/ServiceCollectionExtensions.cs
@@ -2,6 +2,7 @@
using System;
using System.Application.Services;
using System.Application.Services.CloudService;
+using System.Net;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
@@ -23,7 +24,10 @@ namespace Microsoft.Extensions.DependencyInjection
var sc = s.GetRequiredService();
c.BaseAddress = new Uri(sc.ApiBaseUrl);
c.DefaultRequestHeaders.UserAgent.ParseAdd(sc.UserAgent);
- c.DefaultRequestHeaders.Add(Constants.HeaderAppVersion, sc.Settings.AppVersion.ToStringN());
+ c.DefaultRequestHeaders.Add(Constants.Headers.Request.AppVersion, sc.Settings.AppVersion.ToStringN());
+#if NET5_0_OR_GREATER
+ c.DefaultRequestVersion = HttpVersion.Version20;
+#endif
});
services.TryAddSingleton();
services.TryAddSingleton(s => s.GetRequiredService());
diff --git a/System.Application.SteamTools.Services.CloudService/Services/CloudService/ApiConnection.cs b/System.Application.SteamTools.Services.CloudService/Services/CloudService/ApiConnection.cs
index c20397dd5..88ca3f167 100644
--- a/System.Application.SteamTools.Services.CloudService/Services/CloudService/ApiConnection.cs
+++ b/System.Application.SteamTools.Services.CloudService/Services/CloudService/ApiConnection.cs
@@ -10,6 +10,7 @@ using System.IO.FileFormats;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
+using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -23,7 +24,7 @@ namespace System.Application.Services.CloudService
readonly ILogger logger;
readonly IHttpPlatformHelper http_helper;
readonly IApiConnectionPlatformHelper conn_helper;
- readonly Lazy jsonSerializer = new Lazy(() => new JsonSerializer());
+ readonly Lazy jsonSerializer = new(() => new JsonSerializer());
readonly IModelValidator validator;
public ApiConnection(
@@ -86,11 +87,15 @@ namespace System.Application.Services.CloudService
/// 请求模型
///
HttpContent? GetRequestContent(
- Serializable.ImplType serializableImplType,
- TRequestModel? request,
- CancellationToken cancellationToken)
+ bool isSecurity,
+ Aes? aes,
+ Serializable.ImplType serializableImplType,
+ TRequestModel? request,
+ CancellationToken cancellationToken)
{
if (request == null) return null;
+ if (isSecurity && aes == null) throw new ArgumentNullException(nameof(aes));
+
HttpContent? httpContent;
if (request is IUploadFileSource uploadFile) // 上传单个文件
{
@@ -102,6 +107,10 @@ namespace System.Application.Services.CloudService
}
else
{
+ if (isSecurity && serializableImplType != Serializable.ImplType.MessagePack)
+ {
+ serializableImplType = Serializable.ImplType.MessagePack;
+ }
switch (serializableImplType) // 序列化模型
{
case Serializable.ImplType.NewtonsoftJson:
@@ -110,6 +119,10 @@ namespace System.Application.Services.CloudService
case Serializable.ImplType.MessagePack:
var byteArray = Serializable.SMP(request, cancellationToken);
if (byteArray == null) return null;
+ if (isSecurity)
+ {
+ byteArray = aes.ThrowIsNull(nameof(aes)).Encrypt(byteArray);
+ }
httpContent = new ByteArrayContent(byteArray);
httpContent.Headers.ContentType = new MediaTypeHeaderValue(MediaTypeNames.MessagePack);
break;
@@ -343,7 +356,7 @@ namespace System.Application.Services.CloudService
{
get
{
- if (DI.Platform == Platform.Windows || DI.Platform == Platform.Linux || DI.Platform == Platform.Unknown) return true;
+ if (DI.Platform == Platform.Windows || (DI.Platform == Platform.Apple && DI.DeviceIdiom == DeviceIdiom.Desktop) || DI.Platform == Platform.Linux || DI.Platform == Platform.Unknown) return true;
return Connectivity.NetworkAccess == NetworkAccess.Internet;
}
}
@@ -375,7 +388,8 @@ namespace System.Application.Services.CloudService
HttpMethod method,
string requestUri,
TRequestModel? requestModel,
- bool responseContentMaybeNull)
+ bool responseContentMaybeNull,
+ bool isSecurity)
{
#region ModelValidator
@@ -396,15 +410,56 @@ namespace System.Application.Services.CloudService
}
IApiResponse responseResult;
+
+ Aes? aes = null;
+
try
{
+ if (isSecurity)
+ {
+ // 行业标准加密
+ aes = AESUtils.Create();
+ }
+
+ var serializableImplType = Serializable.ImplType.MessagePack;
+
using var request = new HttpRequestMessage(method, requestUri)
{
Content = GetRequestContent(
- Serializable.ImplType.MessagePack,
+ isSecurity,
+ aes,
+ serializableImplType,
requestModel,
cancellationToken),
};
+
+ switch (serializableImplType)
+ {
+ case Serializable.ImplType.NewtonsoftJson:
+ case Serializable.ImplType.SystemTextJson:
+ request.Headers.Accept.ParseAdd(MediaTypeNames.JSON);
+ break;
+ case Serializable.ImplType.MessagePack:
+ if (isSecurity)
+ {
+ request.Headers.Accept.ParseAdd(MediaTypeNames.Security);
+ }
+ else
+ {
+ request.Headers.Accept.ParseAdd(MediaTypeNames.MessagePack);
+ }
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(serializableImplType), serializableImplType, null);
+ }
+
+ if (isSecurity)
+ {
+ var skey_bytes = aes.ThrowIsNull(nameof(aes)).ToParamsByteArray();
+ var skey_str = conn_helper.RSA.EncryptToString(skey_bytes);
+ request.Headers.Add(Constants.Headers.Request.SecurityKey, skey_str);
+ }
+
await SetRequestHeaderAuthorization(request);
var client = conn_helper.CreateClient();
using var response = await client.SendAsync(request,
@@ -420,37 +475,47 @@ namespace System.Application.Services.CloudService
{
if (!isApi && typeof(TResponseModel) == typeof(byte[]))
{
- var bytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
+ var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
responseResult = ApiResponse.Code(code,
content: (TResponseModel)(object)bytes);
}
else if (!isApi && typeof(TResponseModel) == typeof(string))
{
- var str = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ var str = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
responseResult = ApiResponse.Code(code,
content: (TResponseModel)(object)str);
}
else
{
- var mime = response.Content.Headers.ContentType.MediaType;
+ var rspIsCiphertext = false;
+
+ var mime = response.Content.Headers.ContentType?.MediaType;
+
+ if (mime == MediaTypeNames.Security)
+ {
+ mime = MediaTypeNames.MessagePack;
+ rspIsCiphertext = true;
+ }
+
switch (mime)
{
case MediaTypeNames.JSON:
{
- using var stream = await response.Content
- .ReadAsStreamAsync().ConfigureAwait(false);
+ if (rspIsCiphertext)
+ {
+ throw new NotSupportedException("At present, JSON does not implement security on the server side, so this cannot happen.");
+ }
+ using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var reader = new StreamReader(stream, Encoding.UTF8);
using var json = new JsonTextReader(reader);
- responseResult = ApiResponse
- .Deserialize(jsonSerializer.Value, json);
+ responseResult = ApiResponse.Deserialize(jsonSerializer.Value, json);
}
break;
case MediaTypeNames.MessagePack:
{
- using var stream = await response.Content
- .ReadAsStreamAsync().ConfigureAwait(false);
- responseResult = await ApiResponse
- .DeserializeAsync(stream, cancellationToken);
+ using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ using CryptoStream? cryptoStream = rspIsCiphertext ? new CryptoStream(stream, aes.ThrowIsNull(nameof(aes)).CreateDecryptor(), CryptoStreamMode.Read) : null;
+ responseResult = await ApiResponse.DeserializeAsync(rspIsCiphertext ? cryptoStream.ThrowIsNull(nameof(cryptoStream)) : stream, cancellationToken);
}
break;
default:
@@ -465,6 +530,10 @@ namespace System.Application.Services.CloudService
logger.LogError(ex, nameof(ApiConnection));
responseResult = ApiResponse.Code(GetCodeByException(ex));
}
+ finally
+ {
+ aes?.Dispose();
+ }
await GlobalResponseIntercept(
isApi,
method,
@@ -564,7 +633,7 @@ namespace System.Application.Services.CloudService
return responseResult;
}
- public async Task> SendAsync(CancellationToken cancellationToken, HttpMethod method, string requestUri, TRequestModel? request, bool responseContentMaybeNull = false)
+ public async Task> SendAsync(CancellationToken cancellationToken, HttpMethod method, string requestUri, TRequestModel? request, bool responseContentMaybeNull, bool isSecurity)
{
var rsp = await SendCoreAsync(
isApi: true,
@@ -572,7 +641,8 @@ namespace System.Application.Services.CloudService
method,
requestUri,
requestModel: request,
- responseContentMaybeNull);
+ responseContentMaybeNull,
+ isSecurity);
return rsp;
}
@@ -584,7 +654,8 @@ namespace System.Application.Services.CloudService
HttpMethod.Get,
requestUri,
requestModel: null,
- responseContentMaybeNull: false);
+ responseContentMaybeNull: false,
+ isSecurity: false);
return rsp;
}
@@ -596,11 +667,12 @@ namespace System.Application.Services.CloudService
HttpMethod.Get,
requestUri,
requestModel: null,
- responseContentMaybeNull: false);
+ responseContentMaybeNull: false,
+ isSecurity: false);
return rsp;
}
- public async Task SendAsync(CancellationToken cancellationToken, HttpMethod method, string requestUri, TRequestModel? request)
+ public async Task SendAsync(CancellationToken cancellationToken, HttpMethod method, string requestUri, TRequestModel? request, bool isSecurity)
{
var rsp = await SendCoreAsync(
isApi: true,
@@ -608,7 +680,8 @@ namespace System.Application.Services.CloudService
method,
requestUri,
requestModel: request,
- responseContentMaybeNull: true);
+ responseContentMaybeNull: true,
+ isSecurity);
return rsp;
}
@@ -620,11 +693,12 @@ namespace System.Application.Services.CloudService
method,
requestUri,
requestModel: null,
- responseContentMaybeNull: true);
+ responseContentMaybeNull: true,
+ isSecurity: false);
return rsp;
}
- public async Task> SendAsync(CancellationToken cancellationToken, HttpMethod method, string requestUri, bool responseContentMaybeNull = false)
+ public async Task> SendAsync(CancellationToken cancellationToken, HttpMethod method, string requestUri, bool responseContentMaybeNull, bool isSecurity)
{
var rsp = await SendCoreAsync