Open ParkerRedford opened 1 year ago
Some familiarity with ACME is required and expected in order for you to use this library to implement to ACME workflow. It may be worth reading through https://www.rfc-editor.org/rfc/rfc8555.html to understand what's expected to happen but the general flow is:
.Generate
does both of these steps I think (from memory) but you can split them out if you want to.Do you need to implement your own ACME workflow or could you use an established ACME client tool (https://certifytheweb.com etc) to achieve the same result?
I did managed to get the challenge validated by using a while loop to wait it out. I still get the same error message when I get to .Generate
I must be missing the connection between the download and the challenge.
I'm trying to implement my own ACME workflow using YARP because LettuceEncrypt doesn't work very well. I don't know why Microsoft insists on using LettuceEncrypt.
I'd imagine the author of that having been a member of their team has some considerable weight.
Ok, so once you have one challenge per identifier validated (so if your cert has domain.com and www.domain.com on it then you'd have two challenges to complete) you can move on to polling the Order until it's status is valid or ready, if you jump straight to Generate without checking the order first then it may not be ready yet (it can take a few seconds to transition from pending/valid to ready).
I don't see how to validate the order. It won't allow me to wait for the status change. The status shows WaitingForActivation
then gives me an error Can not find issuer 'C=US,O=(STAGING) Internet Security Research Group,CN=(STAGING) Pretend Pear X1' for certificate 'C=US,O=(STAGING) Internet Security Research Group,CN=(STAGING) Bogus Broccoli X2'
That error happens after I created the pfxBuilder, so it's not coming from the generate function.
WaitingForActivation means you forgot to await an async task and the object you have is the task, not the result.
The "pretend pear" issue is that you are testing with Let's Encrypt staging and Certes by default has never heard of this (fake) root certificate. To override that you need to use pfxBuilder.AddIssuers(<bytes of the root cert you want to trust>)
. To do that you can use:
using (TextReader textReader = new StringReader(certAsPemString))
{
var pemReader = new PemReader(textReader);
var pemObj = pemReader.ReadPemObject();
var certBytes = pemObj.Content;
_issuerCertCache.Add(certBytes);
}
where certAsPemString is the root cert in PEM format. You can grab them from https://github.com/letsencrypt/website/tree/master/static/certs/staging
I don't see an async task; I just see order.Resource().Result.Status
Other than that, it worked. I have successfully created the pfx file. The thing was I had to create a poll for validating the challenge. I was hitting .Generate
too early, which the docs never mentioned.
Cool, it's best practise to make your method async and await the task (order.Resource() is a task) rather than accessing .Result directly but if it works for you that's all that matters.
I am getting The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot
. The issuer is R3 and the subject is the domain name that I used.
The certutil command did give me some warnings though No key provider information
, Cannot find the certificate and private key for decryption.
, Private key is NOT plain text exportable
. I am not sure those are imported.
@webprofusion-chrisc do you have an example of the polling?
poll the challenge/order status to see how the CA checks for your challenges are going. This is necessary because challenges may take several seconds or even minutes to complete.
var order = await acme.NewOrder(new[] { "mytest.test.com" });
var authz = (await order.Authorizations()).First();
var dnsChallenge = await authz.Dns();
var dnsTxt = acme.AccountKey.DnsTxt(dnsChallenge.Token);
Console.WriteLine(dnsTxt);
var challengeResult = await dnsChallenge.Validate();
// Polling?
@HaroldH76 I use a loop to fetch the latest version of the challenge status and test the status to see if it's valid. There could be other better ways: https://github.com/webprofusion/certify/blob/development/src/Certify.Providers/ACME/Certes/CertesACMEProvider.cs#L1103
I have written an extension method for my http challenge:
Extension method:
public static async Task<Challenge> ValidateWithRetryAsync(this IChallengeContext httpChallenge)
{
var result = await httpChallenge.Validate();
var attempts = 5;
while (attempts > 0 && result.Status == ChallengeStatus.Pending || result.Status == ChallengeStatus.Processing)
{
Thread.Sleep(1000);
attempts--;
result = await httpChallenge.Resource();
}
return result;
}
Usage:
var authorizationContext = acme.Authorization(new Uri(**snip**));
var httpChallengeContext = await authorizationContext.Http();
var challengeResult = await httpChallengeContext.ValidateWithRetryAsync();
The link @webprofusion-chrisc posted seams to be broken. This should be the code he is referencing:
var attempts = 20;
while (attempts > 0 && (res.Status != AuthorizationStatus.Valid && res.Status != AuthorizationStatus.Invalid))
{
res = await authz.Resource();
attempts--;
// if status is not yet valid or invalid, wait a sec and try again
if (res.Status != AuthorizationStatus.Valid && res.Status != AuthorizationStatus.Invalid)
{
await Task.Delay(1000);
}
}
Sorry, can't we make a callback to the validate method to check the status?
The ACME certificate authority (such as let's Encrypt) is remote API, it doesn't have a connection to your machine to notify you of changes, you have to poll the authorization status to see if it is still pending (still being validated) or if it has failed or succeeded.
@webprofusion-chrisc
it can take a few seconds to transition from pending/valid to ready
What's the difference between the Pending
and Processing
states? Also: I see no Ready
state in v3.0.4.
Could you comment?
@webprofusion-chrisc
What's the difference between the Pending and Processing states?
I found the answer here:
I'm trying to follow the instructions in the documentation for the http challenge, but it fails at "await order.Generate". The error says "Order's status ("pending") is not acceptable for finalization".
I do have the endpoint ".well-known/acme-challenge/" open for file downloads. However, the docs doesn't say how the file should be formed nor how the file should be returned.
The httpChallenge.Validate() function does hit the endpoint with only the token value.
On my server, the file name is saved as "token.key" and the endpoint returns the file name as "token.key" as well, so the the endpoint does search for the key pair.
I don't know what I am missing to get this to work. The only thing I can think of is that the validation is being hit too soon, but I don't know if that's the case.