c# ACME client (補(bu)充)
上一篇 c# ACME client 漏了一部(bu)分內容,今天補(bu)上
除(chu)了之前介紹(shao)的(de)在 asp.net core 使(shi)用方式,還可以單獨在代碼中使(shi)用client
簡化用法
如果已經(jing)集成(cheng)好完(wan)全的(de)自動證書申請驗證,就(jiu)可以使用已經(jing)封裝(zhuang)好的(de)代碼(ma)進行簡單使用
舉(ju)例(li)在asp.net core提(ti)供 一個api 可以(yi)根據參數申(shen)請(qing)證書
starup
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddAcmeChallengeCore(config: c =>
{
c.HttpClientConfig = new VKProxy.Config.HttpClientConfig()
{
DangerousAcceptAnyServerCertificate = true
};
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
api
using Microsoft.AspNetCore.Mvc;
using VKProxy.ACME.AspNetCore;
using VKProxy.Core.Extensions;
namespace WithApi.Controllers;
[ApiController]
[Route("[controller]")]
public class CertController : ControllerBase
{
private readonly IAcmeStateIniter initer;
public CertController(IAcmeStateIniter initer)
{
this.initer = initer;
}
[HttpGet]
public async Task<string> Get([FromQuery] string domain)
{
// 證書配置
var o = new AcmeChallengeOptions()
{
AllowedChallengeTypes = VKProxy.ACME.AspNetCore.ChallengeType.Http01,
Server = new Uri("//127.0.0.1:14000/dir"),
DomainNames = new[] { domain },
AdditionalIssuers = new[] { """
-----BEGIN CERTIFICATE-----
MIIDGzCCAgOgAwIBAgIIUPFry5qBu34wDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
AxMVUGViYmxlIFJvb3QgQ0EgMjFjNjY3MCAXDTI1MDcyMjAxMTA0OVoYDzIwNTUw
NzIyMDExMDQ5WjAgMR4wHAYDVQQDExVQZWJibGUgUm9vdCBDQSAyMWM2NjcwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxNKa4y93OFYaSx8bcbuWsHHnW
mpfsobK5Elf7GE02mi/cDrMP+wR1l53BuucrW04OyoewkBsJNZoxEy1DkCjxv4+g
Q+HgGCR5R14ex17ZdFxpcl42H8QnRB3IqVBlJiz0JyGZwiaOamOkUTVEYTGDeuxu
PglpvboGeatsWQe0MJJfBN8OxLVUmi6Y/enbzlIdv3tvgQujfPNiS8MLDMBuIiMs
ixhu8YAzUqvVKZoQVK7GwbD9WrVBKub8w86StKFmU14aSXahidt8IENdpLO2OT3J
y1nt25QDsAmtS1/wGnTDPeefLGsM7kGYNesQkSW0w8Um4p9KLWKnKyOvzPZrAgMB
AAGjVzBVMA4GA1UdDwEB/wQEAwIChDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRoXcwo6c5J8jMweiHKPw4OlcWIQzANBgkq
hkiG9w0BAQsFAAOCAQEAad9XT4sN1KserYtCxBKmoPhPAHInHYgG/Z2gd6KqdsK9
biIgEbKo84tClLqA6XCN/yN1bMQL2ZMbWBF8oHv/A5o0atpTpd+Ho+punHYRIpqv
akUX21Zsu6NdAuH7g7m9t9h/lc6tgiqaAf2HwpC3NrXmUlPRqLay7/t+BFQU6dBa
E+qzmL7lHZQf1UArfb+QDYH2XsFCk9Pjv0xdP+PGwf8HqHhfPLctvus5JL+LXp0X
68eWKQCs1CrL8cUMwcELlW/mR1lKnJL1WgM1Bns9ZF1ha6egG539ruzQjItF6MHB
xAEt55nXfs+mjV1p7qrcmR8jIdByR9C36T21r+8pKA==
-----END CERTIFICATE-----
"""
}
};
// 申請全新account
o.NewAccount(new string[] { "mailto:test11@xxx.com" });
// 執行全套流程
var cert = await initer.CreateCertificateAsync(o);
return cert.ExportPem();
}
}
默認情況下(xia)三(san)種驗證方式:
http
如在 asp.net core 中使用,默認已經添加了 app.Map("/.well-known/acme-challenge" 路由處理, 如要申請公網上權威認證的證書,請將 /.well-known/acme-challenge 路由(you)暴露(lu)在(zai)公(gong)網(wang)讓acme服(fu)務器可以(yi)訪問
其次由于默認實現沒有持久化和分布式處理驗證信息,重啟和多實例都會有問題,如有需求可以替換IHttpChallengeResponseStore實現以達到效果
public interface IHttpChallengeResponseStore
{
Task AddChallengeResponseAsync(string token, string keyAuth, CancellationToken cancellationToken);
Task<string> GetChallengeResponse(string token, CancellationToken cancellationToken);
Task RemoveChallengeResponseAsync(string token, CancellationToken cancellationToken);
}
dns
由于不同服務商有各自的api,所以默認沒有實現,該功能其實無效,如需使用,請實現 IDnsChallengeStore
public interface IDnsChallengeStore
{
Task AddTxtRecordAsync(string acmeDomain, string dnsTxt, CancellationToken cancellationToken);
Task RemoveTxtRecordAsync(string acmeDomain, string dnsTxt, CancellationToken cancellationToken);
}
tls
個(ge)人并不推(tui)薦使用tls驗證(zheng)(zheng)方式(shi),其(qi)由于驗證(zheng)(zheng)自簽證(zheng)(zheng)書(shu)和正式(shi)證(zheng)(zheng)書(shu)都會在 tls 層,運行時不停機重新申請對于tls管(guan)理還是(shi)有(you)些挑戰的(de)
如想嘗試可以實現ITlsAlpnChallengeStore (默(mo)認在 asp.net core 的實現并不能支持過濾驗證自簽證書只用于acme服務器請求)
public interface ITlsAlpnChallengeStore
{
Task AddChallengeAsync(string domainName, X509Certificate2 cert, CancellationToken cancellationToken);
Task RemoveChallengeAsync(string domainName, X509Certificate2 cert, CancellationToken cancellationToken);
}
底層 client
如需(xu)直接使用原始 acme 協(xie)議client,可參考(kao)如下
starup
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddACME(c =>
{
c.HttpClientConfig = new VKProxy.Config.HttpClientConfig()
{
DangerousAcceptAnyServerCertificate = true
};
});
use
var context = services.BuildServiceProvider().GetRequiredService<IAcmeContext>();
await context.InitAsync(new Uri("//127.0.0.1:14000/dir"), cancellationToken);
var account = await context.NewAccountAsync(new string[] { "mailto:xxx@xxx.com" }, true, KeyAlgorithm.RS256.NewKey());
var order = await context.NewOrderAsync(new string[] { "test.com" });
var aus = order.GetAuthorizationsAsync().ToBlockingEnumerable().ToArray();
var a = aus.First();
var b = await a.HttpAsync();
var c = await b.ValidateAsync();
Key privateKey = KeyAlgorithm.RS256.NewKey();
var csrInfo = new CsrInfo
{
CommonName = "test.com",
};
order = await context.FinalizeAsync(csr, key, cancellationToken);
var acmeCert = await order.DownloadAsync();
var pfxBuilder = acmeCert.ToPfx(privateKey);
if (!string.IsNullOrWhiteSpace(Args.AdditionalIssuer) && File.Exists(Args.AdditionalIssuer))
{
pfxBuilder.AddIssuer(File.ReadAllBytes(Args.AdditionalIssuer));
}
var pfx = pfxBuilder.Build("HTTPS Cert - " + Args.Domain, string.Empty);
var r = X509CertificateLoader.LoadPkcs12(pfx, string.Empty, X509KeyStorageFlags.Exportable);
ui
在 VKProxy管理站點的(de) ui sni 里(li)面添(tian)加了 簡單的(de) http 驗證方式的(de)acme證書界面配置 如(ru)下圖(tu)
(當然使用前提得(de)是 暴露 xxx域名/.well-known/acme-challenge 接口(kou)到公網,這樣公網acme 才能(neng)驗證)

專職ACME管理程序
其實(shi)對更(geng)多(duo)人來說(shuo),ACME都是低頻率使用,只是現在(zai)免費證(zheng)書大多(duo)90天(tian),所(suo)以才(cai)期望有個工具(ju)幫自(zi)己干活
現成的很多,比如
- 腳本工具,稍顯復雜
- 目前只支持 dns 驗證,不過dns服務商/通知/證書部署都支持非常全面
所以珠玉在前,大家可以直接盡情使用(yong)
至于用c#再做一個(ge),多半(ban)沒有(you)啥人關注,不信,和大家打個(ge)賭:評論留言(yan)說期望(wang)有(you)個(ge)c#版的超過 30 條(tiao)(tiao),就(jiu)搞一個(ge) (一條(tiao)(tiao)評論都(dou)沒有(you),信不信,哈哈哈)
是使用c#開發的基于(yu) Kestrel 實(shi)現 L4/L7的代理(感興趣的同學煩請點個小贊(zan)(zan)贊(zan)(zan)呢)
