mirror of
https://github.com/MikuLeaks/MikuSB.git
synced 2026-06-04 05:23:59 +00:00
MikuSB Proxy
This commit is contained in:
124
Proxy/ProxyCertificateAuthority.cs
Normal file
124
Proxy/ProxyCertificateAuthority.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using MikuSB.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace MikuSB.Proxy;
|
||||
|
||||
public sealed class ProxyCertificateAuthority
|
||||
{
|
||||
private const string Password = "MikuSB.Proxy.LocalCA";
|
||||
private readonly ConcurrentDictionary<string, X509Certificate2> _serverCertificates = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly ILogger<ProxyCertificateAuthority> _logger;
|
||||
private readonly ProxyOptions _options;
|
||||
private readonly X509Certificate2 _rootCertificate;
|
||||
|
||||
public ProxyCertificateAuthority(IOptions<ProxyOptions> options, ILogger<ProxyCertificateAuthority> logger)
|
||||
{
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
_rootCertificate = LoadOrCreateRootCertificate();
|
||||
|
||||
if (_options.InstallRootCertificate)
|
||||
InstallRootCertificate();
|
||||
else
|
||||
_logger.LogWarning(
|
||||
"MikuSB proxy root certificate is not installed automatically. Import {CertificatePath} into CurrentUser Root to enable HTTPS interception.",
|
||||
RootCerPath);
|
||||
}
|
||||
|
||||
public string RootCerPath => Path.Combine(CertificateDirectory, "MikuSB.Proxy.Root.cer");
|
||||
|
||||
private static string CertificateDirectory => Path.Combine(AppContext.BaseDirectory, "proxy-certs");
|
||||
|
||||
public X509Certificate2 GetServerCertificate(string host)
|
||||
{
|
||||
host = host.Trim().TrimEnd('.').ToLowerInvariant();
|
||||
return _serverCertificates.GetOrAdd(host, CreateServerCertificate);
|
||||
}
|
||||
|
||||
private X509Certificate2 LoadOrCreateRootCertificate()
|
||||
{
|
||||
Directory.CreateDirectory(CertificateDirectory);
|
||||
var pfxPath = Path.Combine(CertificateDirectory, "MikuSB.Proxy.Root.pfx");
|
||||
|
||||
if (File.Exists(pfxPath))
|
||||
{
|
||||
var existing = new X509Certificate2(
|
||||
File.ReadAllBytes(pfxPath),
|
||||
Password,
|
||||
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet);
|
||||
|
||||
if (!File.Exists(RootCerPath))
|
||||
File.WriteAllBytes(RootCerPath, existing.Export(X509ContentType.Cert));
|
||||
|
||||
return existing;
|
||||
}
|
||||
|
||||
using var rsa = RSA.Create(4096);
|
||||
var request = new CertificateRequest(
|
||||
"CN=MikuSB Proxy Root CA",
|
||||
rsa,
|
||||
HashAlgorithmName.SHA256,
|
||||
RSASignaturePadding.Pkcs1);
|
||||
|
||||
request.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
|
||||
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign | X509KeyUsageFlags.DigitalSignature, true));
|
||||
request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
|
||||
|
||||
var root = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddYears(10));
|
||||
var exportable = new X509Certificate2(
|
||||
root.Export(X509ContentType.Pfx, Password),
|
||||
Password,
|
||||
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet);
|
||||
|
||||
File.WriteAllBytes(pfxPath, exportable.Export(X509ContentType.Pfx, Password));
|
||||
File.WriteAllBytes(RootCerPath, exportable.Export(X509ContentType.Cert));
|
||||
_logger.LogInformation("Created MikuSB proxy root certificate at {CertificatePath}", RootCerPath);
|
||||
return exportable;
|
||||
}
|
||||
|
||||
private X509Certificate2 CreateServerCertificate(string host)
|
||||
{
|
||||
using var rsa = RSA.Create(2048);
|
||||
var request = new CertificateRequest($"CN={host}", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
|
||||
var san = new SubjectAlternativeNameBuilder();
|
||||
if (IPAddress.TryParse(host, out var address))
|
||||
san.AddIpAddress(address);
|
||||
else
|
||||
san.AddDnsName(host);
|
||||
|
||||
request.CertificateExtensions.Add(san.Build());
|
||||
request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
|
||||
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, true));
|
||||
request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension([new Oid("1.3.6.1.5.5.7.3.1")], false));
|
||||
|
||||
var serial = RandomNumberGenerator.GetBytes(16);
|
||||
using var certificate = request.Create(
|
||||
_rootCertificate,
|
||||
DateTimeOffset.UtcNow.AddDays(-1),
|
||||
DateTimeOffset.UtcNow.AddYears(2),
|
||||
serial);
|
||||
|
||||
return new X509Certificate2(
|
||||
certificate.CopyWithPrivateKey(rsa).Export(X509ContentType.Pfx),
|
||||
(string?)null,
|
||||
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet);
|
||||
}
|
||||
|
||||
private void InstallRootCertificate()
|
||||
{
|
||||
using var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
var existing = store.Certificates.Find(X509FindType.FindByThumbprint, _rootCertificate.Thumbprint, false);
|
||||
if (existing.Count > 0)
|
||||
return;
|
||||
|
||||
store.Add(_rootCertificate);
|
||||
_logger.LogWarning("Installed MikuSB proxy root certificate into CurrentUser Root store. Thumbprint={Thumbprint}", _rootCertificate.Thumbprint);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user