Closed StefH closed 2 years ago
In case someone needs this information, see my working example code below:
using Certes;
using Certes.Acme;
using Certes.Acme.Resource;
Console.SetWindowSize(160, 40);
string currentDateTime = DateTime.Now.ToString("s").Replace(':', '_');
const int maxRetry = 30;
const int challengeRetryTimeInSeconds = 30;
const string path = @"C:\Users\Me\certes";
string accountPemPath = Path.Combine(path, "account.pem");
string passwordPath = Path.Combine(path, "password.txt");
string pemPath = Path.Combine(path, $"{currentDateTime}_chained.pem");
string pfxPath = Path.Combine(path, $"{currentDateTime}_pfx.pfx");
IAccountContext? account;
AcmeContext acme;
if (!File.Exists(accountPemPath))
{
// Create new
acme = new AcmeContext(WellKnownServers.LetsEncryptV2);
account = await acme.NewAccount("test@email.com", true);
var pemKey = acme.AccountKey.ToPem();
File.WriteAllText(accountPemPath, pemKey);
}
else
{
// Load the saved account key
var pemKey = File.ReadAllText(accountPemPath);
var accountKey = KeyFactory.FromPem(pemKey);
acme = new AcmeContext(WellKnownServers.LetsEncryptV2, accountKey);
account = await acme.Account();
}
var orderListContext = await account.Orders();
var orders = await orderListContext.Orders();
if (!orders.Any())
{
var sites = new[] { "example.com", "www.example.com" };
Console.WriteLine("Creating new Order for sites: {0}", string.Join(", ", sites));
var order = await acme.NewOrder(sites);
var authorizationContexts = await order.Authorizations();
var dnsChallenges = new Dictionary<string, IChallengeContext>();
foreach (var x in authorizationContexts.Select((authorizationContext, index) => new { authorizationContext, index }))
{
var dnsChallenge = await x.authorizationContext.Dns();
dnsChallenges.Add(sites[x.index], dnsChallenge);
var dnsTxt = acme.AccountKey.DnsTxt(dnsChallenge.Token);
Console.WriteLine("Add a DNS TXT record to _acme-challenge.{0} with dnsTxt value : {1}", sites[x.index], dnsTxt);
}
int retry = 0;
int ok;
do
{
await Delay(challengeRetryTimeInSeconds);
ok = 0;
foreach (var dnsChallenge in dnsChallenges)
{
var result = await dnsChallenge.Value.Validate();
Console.WriteLine("{0} ChallengeStatus for site {1} is {2}.", DateTime.Now, dnsChallenge.Key, result.Status);
ok += result.Status == ChallengeStatus.Valid ? 1 : 0;
}
retry++;
} while (retry < maxRetry && ok != sites.Length);
if (retry >= maxRetry)
{
Console.WriteLine("Unable to Validate after {0} retries. Exiting now.", retry);
}
// Download the certificate once validation is done
var privateKey = KeyFactory.NewKey(KeyAlgorithm.ES256);
var cert = await order.Generate(new CsrInfo
{
CountryName = "NL",
State = "...",
Locality = "...",
Organization = "Example",
OrganizationUnit = string.Empty
}, privateKey);
// Export full chain certification
var certPem = cert.ToPem();
await File.WriteAllTextAsync(pemPath, certPem);
// Export PFX
var pfxBuilder = cert.ToPfx(privateKey);
var pfx = pfxBuilder.Build($"{sites[0]}-certificate-{currentDateTime}", File.ReadAllText(passwordPath));
await File.WriteAllBytesAsync(pfxPath, pfx);
Console.WriteLine("All done");
}
static async Task Delay(int seconds)
{
Console.WriteLine("Waiting {0} second{1}...", seconds, seconds == 1 ? "" : "s");
await Task.Delay(TimeSpan.FromSeconds(seconds));
}
This is exactly what I needed. Good job!
I injected some of my own code to update my Azure DNS and I'm now off and running. Thanks 🙂
Example:
But when I successfully verify the first dns challenge (returns
ChallengeStatus.Valid
):Do I still need to call the Validate on the second challenge?
Because it seems that this fails: