Unable to generate certificate from .Net with production url


#1

Getting exception while creating an account with c# for production environment “ACME operation not supported.”


#2

@jmorahan can you please check it for me.


#3

Hi @Jagjit

what tool or code do you use? The output looks like you use a library, not your own code.


#4

@JuergenAuer we are using c#.net to generate the code let me past code here.

            string letsEncryptAccountEmail = "test@gmail.com";

            LetsEncryptCertificateResponseViewModel certificateResponse = new LetsEncryptCertificateResponseViewModel();

            certificateResponse.Domains = domains;
            certificateResponse.AccountEmail = letsEncryptAccountEmail;

            var acme = new AcmeContext(WellKnownServers.LetsEncryptV2);
            var account = await acme.NewAccount(letsEncryptAccountEmail, true);

            // Save the account key for later use
            var accountPemKey = acme.AccountKey.ToPem();

            //FOR WILD CARD WE NEED TO PUT THE TXT RECORD 
            //==>Add a DNS TXT record to _acme-challenge.your.domain.name with dnsTxt value.
            //var order = await acme.NewOrder(new[] { "*.your.domain.name" });
            //var dnsChallenge = await authz.Dns();
            //var dnsTxt = acme.AccountKey.DnsTxt(dnsChallenge.Token);

            //FOR VALIDATING DOMAIN WITH HTTP METHOD WE WILL USE S3 BLOB TO
            //UPLOAD THE FILE THERE AND VALIDATE HERE
            //var order = await acme.NewOrder(new[] { domains.ToArray()});
            var order = await acme.NewOrder(domains);
            var authz = (await order.Authorizations()).First();
            var httpChallenge = await authz.Http();
            var keyAuthz = httpChallenge.KeyAuthz;
            
            var validationFileURL = AwsFileHelper.UploadLetsEncryptDomainValidationFileOnS3(httpChallenge.Token, keyAuthz);

            var status = await httpChallenge.Validate();

            var privateKey = KeyFactory.NewKey(KeyAlgorithm.RS256);
            var certificateChain = await order.Generate(new CsrInfo
            {
                CountryName = "US",
                State = "PA",
                Locality = "Jenkintown",
                Organization = "Certes",
                OrganizationUnit = "IT",
                CommonName = domains[0],
            }, privateKey); -- we are getting error here 

            var certificateBody = certificateChain.Certificate.ToPem();
            var privateKeyInPemFormat = privateKey.ToPem();

#5

@JuergenAuer below is the error that we are getting

Fail to load resource from ‘https://acme-v02.api.letsencrypt.org/acme/finalize/47104985/206634481’.
urn:ietf:params:acme:error:unauthorized: Error finalizing order :: authorizations for these names not found or expired: local2.mytestdomain.com


#6

You should check your status before you create a Certificate Signing Request.

The status may be invalide, so a CSR is not possible.


#7

@JuergenAuer the current status is valid that is pending “Certes.Acme.Resource.ChallengeStatus.Pending”

This is the only case when i put 2 domains for single domain it is working fine


#8

The status of all challenges must be “valid”, the status of your order must be “ready” before you can create a CSR.


#9

@JuergenAuer So how much time it will take for it to change from “Pending” to “Ready”?
Do we need to wait for it ?. This will cause too delay as our users have to wait alot on front end


#10

@JuergenAuer also I see that this was working fine me with “Pending” status if I am doing with one domain. Problem is only in case i add 2 domains


#11

You must wait until your order has the “ready” status. Letsencrypt checks a lot of domains, so that may need one or much more seconds.

If your code works with one domain, but not with two, your code is wrong.

Query your order url, check it, if pending / processing etc. (not ready or invalid), wait 5 seconds, then try it again.


#12

@JuergenAuer I waited more than 2 minutes there but the status was not changed for me


#13

@JuergenAuer I waited there more than 5 minutes and still getting this error. The error is only in case there are more than one domain. For single domain it is working fine please check my code at the top I pasted.

Fail to load resource from ‘https://acme-v02.api.letsencrypt.org/acme/challenge/lkhM-4S0wFu9O9weLB73nhanxj75XkKUIyOndpNtoI8/9986883623’.
urn:ietf:params:acme:error:badNonce: JWS has an invalid anti-replay nonce: “U0YJ_iyfv_9yr9qBCrjWlkMyWOtci7X1ksvtUVBeKXc”


#14

Your link doesn’t show the order, only one challenge. Share your order url.

This is a different error. You need a nonce, but if you wait too long, your old nonce is invalide, so query a new nonce.

Read the specification:

https://tools.ietf.org/html/draft-ietf-acme-acme-16


#15

@JuergenAuer Below is the url for validation

https://acme-v02.api.letsencrypt.org/acme/challenge/6yoD4UK5lMIePPi4wBpTcHXLAtXxdnBvQF4ko5IjBOo/9989609709

And below is the error on creating csr

Fail to load resource from ‘https://acme-v02.api.letsencrypt.org/acme/finalize/47111125/206782550’.
urn:ietf:params:acme:error:unauthorized: Error finalizing order :: authorizations for these names not found or expired:


#16

Hi @Jagjit

I think @JuergenAuer was specifically looking for your order URL. This is a challenge URL. Thankfully from your finalize URL we can work backwards to the order URL:

https://acme-v02.api.letsencrypt.org/acme/order/47111125/206782550

I think it would be easier to spot the problem if you share the full error, in this case:

Error finalizing order :: authorizations for these names not found or expired: local2.hiremytrainer.com

The problem is as the error says: You created an order for two DNS identifiers (local3.hiremytrainer.com and local2.hiremytrainer.com) but you only have a valid authorization from completing a challenge for one of them (local3.hiremytrainer.com).

Looking at the server-side logs I can see that your client POSTed a challenge from one of the two authorizations in the pending order. It was the challenge URL you shared (https://acme-v02.api.letsencrypt.org/acme/challenge/6yoD4UK5lMIePPi4wBpTcHXLAtXxdnBvQF4ko5IjBOo/9989609709) and once you completed the HTTP-01 validation successfully it resulted in a valid authorization (https://acme-v02.api.letsencrypt.org/acme/authz/6yoD4UK5lMIePPi4wBpTcHXLAtXxdnBvQF4ko5IjBOo) for local3.hiremytrainer.com.

Your client does not POST any challenges from the other pending authorization in the order for local2.hiremytrainer.com. That means when you try to finalize the order with a CSR you’re missing authorization for one of the two names in the order.

I’m not familiar with the programming language or ACME library you’re using but this isn’t correct for an order with more than one identifier. You need to loop through all of the order’s authorizations, completing a challenge for each, before finalizing the order.

If possible you should also check the status of the order before trying to finalize it by sending a CSR to the finalization endpoint. In this case the order is always in status “pending” when your code tries to complete the order because one of the authorizations is still pending. An order can only ever be finalized when its status is “ready” so there’s no point in trying unless you’re sure the order is ready! :slight_smile:

I second @JuergenAuer’s pointer to reading the ACME draft specification. Unless you wanted to use a higher level ACME client (something like Certbot or CertifyTheWeb) you will benefit from understanding the low level details. In particular the status changes that are possible between resources like orders and authorizations.

Hope this helps!


#17

Thanks @cpu - yep, this is the url I need.

@Jagjit : There you see:

https://acme-v02.api.letsencrypt.org/acme/authz/nHusxEFFEAtiSo5JSY_kAO5VD5Jkq7rv4G9oo3T64oA

is pending, so Letsencrypt waits, that you send a confirmation.

But if you send a finalize-request before, your complete order fails.

So you have to start with a new order.


#18

hi @cpu and @JuergenAuer I found the issue that was i was only sending the verification for one record i changed my code to send it for every domain as below and that worked for me. Now I want more thing from you guys. We are building a highly scalable system by supporting unlimited custom domains with SSL and for this we are using AWS CF that will support 100 CNAMES by default and suppprt one SSL certificate per cloud front. We know that aws is also providing the the free ssl but that requires over head on user end to verify the domains with dns enteries that’s why we are using Let’s encrypt for SSL. So with Let’s encrypt I have below queries . Please check it and answer me.’

  1. Suppose first time our user enters one domain (example.com)then we will get ssl certificate with that domain and link the certificate with CF.
  2. On the next time if they want to add another domain (anotherexample.com) then we will get a new certificate from Let’s encrypt by including the previous domain with the new one(example.com, anotherexample.com) and so on.
  3. Now do we need to create the challenge for the previous one and the new one. Is there is a way that I can upload only single file that will validate all the domains in one shot? Check the code at below as I am creating challenge for all the domains.
  4. Also I wan the SSL to work with domain like starting with/without “www” like(www.example.com and example.com) so do i need to add 2 enteries in let’s encrypt.
  5. I need all the error codes returned by the let’s encrypt c# api.
  6. Also how we can autorenew the certificates? As we will have more than 10 thousand domains in the coming time. So getting new certificates again for the existing domains on expiration will be challenge for us. We need a simple autorenewl process with .net atuomatically.

Below is the altered code that worked for me:

var order = await acme.NewOrder(domains);
var authorizationDetails = (await order.Authorizations()).ToList();

            foreach(var authz in authorizationDetails)
            {
                var httpChallenge = await authz.Http();
                var keyAuthz = httpChallenge.KeyAuthz;                    
                AwsFileHelper.UploadLetsEncryptDomainValidationFileOnS3(httpChallenge.Token, keyAuthz);
                var status = await httpChallenge.Validate();                    
            }

#19

@cpu @JuergenAuer Please check the above thread for me


#20

When you need to add new domains to the certificate you will need to start a new order, which will then be a new certificate. Authorizations are cached for 30 days, so anything over that will be all new authorizations/challenges. Using http validation every authorization is a new file (or challenge response at least).

To support www/non-www you need both variations on the certificate.

Let’s Encrypt doesn’t have an official C# API client, you are using Certes so look at it’s code on github to determine error responses.

Auto renewal is just performing the entire new order again before the certificate expires, so you need to track all your expiry dates and decide when to do that.

As mentioned already there are other .net clients such as my own GUI (https://certifytheweb), win-acme cli, certes cli, POSH-Acme etc, so the only real benefit to writing your own is to have a lot of control over the process and you will then need to cope with all the error conditions etc.

Regarding waiting for validation to complete, this is currently what Certify does (also using Certes), you could optionally add Task.Delay(500) between validation status checks:

                    Challenge result = await challenge.Validate();

                    int attempts = 10;

                    while (attempts > 0 && result.Status == ChallengeStatus.Pending || result.Status == ChallengeStatus.Processing)
                    {
                        result = await challenge.Resource();
                    }