#1 2025-06-06 07:08:13

testgary
Member
Registered: 2025-02-06
Posts: 25

ACME + TMVCApplication

procedure ACMEStart;
var
  F: TFileName;
  ES: TAcmeLetsEncryptServer;
  I: integer;
  Status: TAcmeStatus;
  Cert, PrivateKey: RawUtf8;
begin
  RegisterOpenSsl;
  F := IncludeTrailingPathDelimiter(GetCurrentDir) + 'sslFolder';

  ES := TAcmeLetsEncryptServer.Create(TSynLog, F,
    ACME_LETSENCRYPT_DEBUG_URL,
    'x509-es256', 'jf93v83');
  try
    ES.LoadFromKeyStoreFolder;

    for I := Low(ES.Client) to High(ES.Client) do
      with ES.Client[I] do
      begin
        RegisterAndWaitFolder(F, F, F, 'jf93v83', 3);

        repeat
          Status := CheckChallengesStatus;
          Sleep(1000);
        until Status <> asPending;

        if Status = asValid then
        begin
          Status := ES.Client[I].CompleteDomainRegistration(Cert, PrivateKey, 'jf93v83');
        end;
      end;

    ES.CheckCertificatesBackground;
    ES.Redirect('xxx.com', 'https://xxx.com');
    ES.Redirect('www.xxx.com', 'https://www.xxx.com');
  finally
    ES.Free;
  end;

end;

Below is the JSON file, to be used by the LoadFromKeyStoreFolder method.

{
  "contact": "mailto:admin@xxx.com",
  "subjects": [
    "xxx.com"
    "www.com"
  ]
}   

1.Since I don't have a domain name or a public IP, I cannot perform testing. I’m not sure if what I wrote is correct. Is there anything else I should pay attention to?

2.How does ACME integrate with TMvcApplication? Should I directly call RegisterAndWaitFolder to save the certificate files, or how should it be handled?

  HttpServer := TRestHttpServer.Create([RestServerDB], '443', 32, secTLS, HTTPSERVER_DEFAULT_OPTIONS,
    'C:\Users\FBI\Desktop\BOOK\1\ssl\mycert.pfx',
    'C:\Users\FBI\Desktop\BOOK\1\ssl\privkey.pem',
    '|&VwVx;2S',                                      
    'C:\Users\FBI\Desktop\BOOK\1\ssl\cert.pem'
    ); 
  FRestHttpServer.DomainHostRedirect('xxx.com', 'root');
  FRestHttpServer.RootRedirectToURI('xxx.com', 'blog/default', true, true);

Last edited by testgary (2025-06-06 09:53:52)

Offline

#2 2025-06-06 07:23:38

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 15,123
Website

Re: ACME + TMVCApplication

I can't understand your language (please use English) but I can a little understand pascal, and you seem to have two subjects.

Offline

#3 2025-06-06 09:55:43

testgary
Member
Registered: 2025-02-06
Posts: 25

Re: ACME + TMVCApplication

Sorry, I forgot to check before sending the content.

Offline

#4 2025-06-06 11:22:03

Chaa
Member
Registered: 2011-03-26
Posts: 258

Re: ACME + TMVCApplication

I created pull request to fix minor ACME client bug:
https://github.com/synopse/mORMot2/pull/367

MVC Blog example with HTTPS and Let's Encrypt certificate:
https://gist.github.com/achechulin/f423 … f5ed261b89

Check it out in real life:
https://mvc-blog.fun/

Offline

#5 2025-06-07 11:01:48

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 15,123
Website

Re: ACME + TMVCApplication

@Chaa
Your PR has been merged.
Thanks a lot!
cool

Your "real live" web site is amazing to see.
Thanks again!

Offline

#6 2025-06-07 11:35:48

testgary
Member
Registered: 2025-02-06
Posts: 25

Re: ACME + TMVCApplication

Chaa wrote:

I created pull request to fix minor ACME client bug:
https://github.com/synopse/mORMot2/pull/367

First of all, thank you very much for sharing

I have reviewed your code and noticed that you associate tmvcapplication and acme through reading and writing local certificate files, rather than linking them directly in the code. Could this cause any issues, such as file access conflicts?

Also, it seems you did not use the RegisterAndWaitFolder CompleteDomainRegistration method. Have you tested your code to ensure it runs completely without any problems?

Offline

#7 2025-06-09 03:57:25

Chaa
Member
Registered: 2011-03-26
Posts: 258

Re: ACME + TMVCApplication

There is low level TAcmeClient class that implements the ACME V2 client, and high level TAcmeLetsEncrypt/TAcmeLetsEncryptClient/TAcmeLetsEncryptServer classes that implements Let's Encrypt domains certificates management.

testgary wrote:

Also, it seems you did not use the RegisterAndWaitFolder CompleteDomainRegistration method.

TAcmeLetsEncrypt.CheckCertificates check certificates expiration and renew if needed.

testgary wrote:

I have reviewed your code and noticed that you associate tmvcapplication and acme through reading and writing local certificate files, rather than linking them directly in the code. Could this cause any issues, such as file access conflicts?

In TAcmeLetsEncrypt.LoadFromKeyStoreFolder we set callback mormot.net.sock.OnNetTlsAcceptServerName to point to TAcmeLetsEncrypt implementation, that returns certfifcate matched to requested server name, and after certificate renewed it's reread it from file.

Offline

#8 2025-07-27 08:06:22

testgary
Member
Registered: 2025-02-06
Posts: 25

Re: ACME + TMVCApplication

type
  Tzerossl = class
  private
    FAcmeClient: TAcmeClient;
    FICryptCert: ICryptCert;
    FChallengeRec: ^TAcmeChallenge;
  protected
    procedure DoChallenges(Sender: TObject; const Domain, Key, Token: RawUtf8);
  public
    constructor Create;
    destructor Destroy; override;

    procedure start;
    procedure CheckCertificate(Sender: TObject);
    procedure CheckCertificatesBackground;
  published
  end;


implementation

const
  ACME_ZEROSSL_URL = 'https://acme.zerossl.com/v2/DV90';
  ACME_CHALLENGE_PATH = '/.well-known/pki-validation/';

procedure Tzerossl.DoChallenges(Sender: TObject; const Domain, Key, Token: RawUtf8);
begin

  if Key <> '' then
  begin
    FChallengeRec^.Token := Token;
    FChallengeRec^.Key := Key;
  end;
end;

constructor Tzerossl.Create;
begin

  inherited Create;
  New(FChallengeRec);
  FChallengeRec^ := Default(TAcmeChallenge);


  FICryptCert := Cert('x509-es256');
  FAcmeClient := TAcmeClient.Create(nil, FICryptCert, ACME_ZEROSSL_URL, 'tempEmail@temp.com', 'www.temp.com');
  // FAcmeClient.OnChallenges := @DoChallenges;
end;

destructor Tzerossl.Destroy;
begin
  if Assigned(FChallengeRec) then
  begin
    Dispose(FChallengeRec);
    FChallengeRec := nil;
  end;

  FAcmeClient.Free;
  inherited Destroy;
end;

procedure Tzerossl.start;
var
  Terminated: boolean = False;
  SSLDir: TFileName;
const
  WaitForSec = 30; 
  PrivateKeyPassword = 'tempPwd';
begin
  SSLDir := IncludeTrailingPathDelimiter(GetCurrentDir) + 'ssl' + PathDelim;
  if not DirectoryExists(SSLDir) then
    ForceDirectories(SSLDir);

  if FAcmeClient.RegisterAndWait(@DoChallenges, SSLDir + 'mycert.pen', SSLDir + 'privkey.pem', PrivateKeyPassword, WaitForSec, @Terminated) = TAcmeStatus.asValid then
  begin
    // ok
  end;

end;

procedure Tzerossl.CheckCertificate(Sender: TObject);
var
  ExpiryDate: TDateTime;
  SSLDIR: TFileName;
  CertFile: TFileName;
const
  RenewBeforeDays = 30;
begin
  SSLDIR := IncludeTrailingPathDelimiter(GetCurrentDir) + 'ssl' + PathDelim;
  CertFile := SSLDIR + 'mycert.pen';

  if not FileExists(CertFile) then Exit;
  if not FICryptCert.LoadFromFile(CertFile) then Exit;

  ExpiryDate := FICryptCert.GetNotAfter;
  if ExpiryDate < (NowUtc + RenewBeforeDays) then
    start;
end;

procedure Tzerossl.CheckCertificatesBackground;
begin
  TLoggedWorkThread.Create(TSynLog, 'CheckCertificate', self, @CheckCertificate, nil, False);
end;

Right now I’m using a ZeroSSL certificate instead of a Let’s Encrypt one. The snippets above show some code, but there’s no documentation and I don’t know how to write it. I’m using TMVCApplication and I need help

Offline

#9 2025-07-27 17:27:46

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 15,123
Website

Re: ACME + TMVCApplication

@testgary

Please follow the forum rules and put the code in some external source, like a gist.
cool

Offline

#10 2025-07-28 10:37:25

Chaa
Member
Registered: 2011-03-26
Posts: 258

Re: ACME + TMVCApplication

ZeroSSL required externalAccountBinding with their "EAB Credentials".
https://zerossl.com/documentation/acme/

So, we need some changes in mormot.net.acme to add such binding.

I'll try to figure it out.

Last edited by Chaa (2025-07-28 10:48:37)

Offline

#11 2025-08-01 06:38:09

Chaa
Member
Registered: 2011-03-26
Posts: 258

Re: ACME + TMVCApplication

I created pull request to support zerossl.com ACME server:
https://github.com/synopse/mORMot2/pull/371

To provide External Account Binding (EAB) credentials you need go to Developer Section of your ZeroSSL account. Then select Generate EAB Credentials and put it into your domain.json file.

{
  "contact": "mailto:support@mvc-blog.fun",
  "subjects": ["mvc-blog.fun", "www.mvc-blog.fun"],
  "eab": {
    "algo": "Sha256",
    "kid": "XyzNHzLJ-vzzVz0z4b7zGw",
    "mac_key": "zzxxWwwL8109G07_u0sux_zg9Q999im9a9kCHB8PZz1y55vp5xL5rVP5cZa5K5Br2qWu5Wi5e5iC5s355Jw46"
  }
}

Then point your app to new ACME server:

  aAcmeServer := TAcmeLetsEncryptServer.Create(TSynLog, 'acme', 'https://acme.zerossl.com/v2/DV90',
    '', 'passw0rd', nil, 2);

Check it out in real life with ZeroSSL certificate:
https://mvc-blog.fun/

Offline

#12 2025-08-01 08:04:14

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 15,123
Website

Re: ACME + TMVCApplication

Your code is just perfect.
I have merged it.

Thanks a lot!

Apart from that, I found out that we do not provide the full certificate chain, and TLS tests services complain about it:
https://www.ssllabs.com/ssltest/analyze … c-blog.fun
Do you have any idea how to improve this?

Offline

#13 2025-08-01 10:43:40

Chaa
Member
Registered: 2011-03-26
Posts: 258

Re: ACME + TMVCApplication

ab wrote:

Do you have any idea how to improve this?

Let's Encrypt and ZeroSSL returns certificate with full chain.

Problem is in TOpenSslNetTls.SetupCtx - LoadCertificate only load first cert from file.
We need change it to LoadCertificates and then use first cert for SSL_CTX_use_certificate, and the remaining for SSL_CTX_add0_chain_cert.
For reference, see SSL_CTX_use_certificate_chain_file:
https://github.com/openssl/openssl/blob … rsa.c#L551

Quick prototype:

    xa := LoadCertificates(cert, 16); // PEM or DER
    if Length(xa) > 0 then
    try
      EOpenSslNetTls.Check(self, 'SetupCtx Certificate',
        SSL_CTX_use_certificate(fCtx, xa[0]));
      xa[0] := nil;
      for i := 1 to Length(xa) - 1 do
      begin
        EOpenSslNetTls.Check(self, 'SetupCtx Chain Certificate',
          SSL_CTX_add_extra_chain_cert(fCtx, xa[i]));
        xa[i] := nil;
      end;
    finally
      PX509DynArrayFree(xa);
    end

And SSL Rating now A:
https://www.ssllabs.com/ssltest/analyze … c-blog.fun

But at the moment, I dont understand difference between SSL_CTX_add0_chain_cert and SSL_CTX_add_extra_chain_cert.

Offline

#14 2025-08-01 17:51:01

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 15,123
Website

Re: ACME + TMVCApplication

I have made a huge refactoring of the OpenSSL server code.

The CA of the certificates were not properly published - not only in the context of ACME.
Also enhanced a lot the error handling.

Please try with the current code.

Offline

#15 2025-08-02 09:53:31

danielkuettner
Member
From: Germany
Registered: 2014-08-06
Posts: 395

Re: ACME + TMVCApplication

chaa wrote:

But at the moment, I dont understand difference between SSL_CTX_add0_chain_cert and SSL_CTX_add_extra_chain_cert.

https://mailman.nginx.org/pipermail/ngi … 08252.html

Offline

#16 2025-08-02 20:03:01

testgary
Member
Registered: 2025-02-06
Posts: 25

Re: ACME + TMVCApplication

Thank you very much for your efforts! I'm really happy to see how powerful mormot has become.

Offline

#17 2025-08-05 10:39:49

Chaa
Member
Registered: 2011-03-26
Posts: 258

Re: ACME + TMVCApplication

Thank you, now difference is clear.

I create pull request with fixes for latest changes:
https://github.com/synopse/mORMot2/pull/372

Offline

Board footer

Powered by FluxBB