|
| Improved support for Let's Encrypt (tm) certificates | |
| Henrick Wibell Hellström 2025-12-21 14:20:49 Registered user |
Today's release of StreamSec Tools 4.0.1.346 includes improved support for integrating your HTTPS server applications into an Automated Certificate Management Environment, such as Let's Encrypt from ISRG. With the implementation of TLS-ALPN-01 functionality directly at the TLS layer level, it is now possible to develop HTTPS servers that handle all certificate management completely automatically, regardless of which HTTP implementation you use on top of the StreamSec Tools 4.0 TLS layer.
The methods that have been added to the TsmSimpleTLSInternalServer component over the past two releases, are: // Use this method for listing all server certificates when the server is // running, i.e. AFTER TLSSetupServer has been called, and when you don't // want to close the server just to manage certificates. // Information about the certificates will be added to aList on the format // '%s=%s', where the first string is the OID of the subject public key, // and the second string is the DNS name (either Subject CN or // SubjectAltName DNSName). There might be duplicates. The return value is // the number entries that where added to aList. function ListTLSServerCertificates(aList: TStrings): Integer; // Use this method for validation of server certificates when the server is // running, i.e. AFTER TLSSetupServer has been called, and when you don't // want to close the server just to manage certificates. // The aExpirationDate is the cut-off date time for the notAfter of the // server certificate. Information about the soon to expire certificates // will be added to aList on the format '%s=%s', where the first string // is the OID of the subject public key, and the second string is the // DNS name (either Subject CN or SubjectAltName DNSName). There might be // be duplicates. The return value is the number entries that where added // to aList. function FindExpiringTLSServerCertificates(aExpirationDate: TDateTime; aList: TStrings): Integer; // Use this method for replacing a server certificate assigned by // TLSSetupServer, without having to close the server and reload all // keys and certificates from file. The new certificate must have been added // to MyCerts prior to the call tp ReplaceTLSServerCertificate, e.g. by a // call to ImportFromPFX procedure ReplaceTLSServerCertificate(const aCert: iCertificate; const aPriv: iMPPrivateKey); procedure AddTLSAcmeALPN(const aPriv: iMPPrivateKey; const aHostName: string; const aAuthorization: tBytes); overload; procedure AddTLSAcmeALPN(const aPriv: iMPPrivateKey; const aHostName: string; const aToken: string; const aThumbprint: string); overload; procedure AddTLSApplicationLayerProtocolName(const ALPN: OctetString); procedure RemoveTLSApplicationLayerProtocolName(const ALPN: OctetString); procedure RemoveTLSAcmeALPN; |
| Henrick Wibell Hellström 2025-12-21 14:38:18 Registered user |
For this server, I have used the component TExecuteACME (version 1.7) from Paul Toth and Execute. You can find more information here.
In particular, the overloaded method: procedure AddTLSAcmeALPN(const aPriv: iMPPrivateKey; const aHostName: string; const aToken: string; const aThumbprint: string); overload; of the TsmSimpleTLSInternalServer component, is meant to be called from the OnALPNChallenge event of the TExecuteACME component. Furthermore, if you wish to use TExecuteACME in a Delphi Application running on Linux, you can use a StreamSec Tools 4.0 plugin for a platform independent implementation of the required cryptographic functionality. A Delphi package with this plugin can be found in the Delphi 13 demo folder of the StreamSec Tools 4.0.1.346 archive. |
| Henrick Wibell Hellström 2025-12-22 09:42:29 Registered user |
A few pointers:
1. In both the staging and production environment, you can only request a certificate for domains that are registered in DNS. Each domain must have an A record for the address where your server is running. 2. Your server must listen on port 443. 3. In order to avoid disruption of service while the TLS-ALPN challenges are being processed, you should register the appropriate application protocol for the normal services of your server, e.g. using: Server_SimpleTLSInternalServer.AddTLSApplicationLayerProtocolName('http/1.1'); 4. The first time you request a certificate from Let's Encrypt (tm) you will need an externally provided list of the domains you want covered by the server certificate, but after that you should set a timer. The certificates should be renewed approximately two-thirds into their validity period, which is currently 3 months, but might be considerably shorter in the future. In a HTTPS server that uses the functions in unit rtcSSecTest, your code might look something like below, immediately prior to the code for setting up the ExecuteACME component and calling RegisterDomain. ExecuteACME1.SubjectAltNames.Clear; if TLSServer = nil then TLSServer := (GetServerCryptPlugin as TRtcSTMTServerPlugin).TLSServer; SL := TStringList.Create; try SL.CaseSensitive := False; TLSServer.ListTLSServerCertificates(SL); for I := 0 to FDomains.Count-1 do begin lStr := Format('%s=%s',[rsaEncryption,LowerCase(FDomains[I])]); if SL.IndexOf(lStr) < 0 then ExecuteACME1.SubjectAltNames.Add(FDomains[I]); end; SL.Clear; TLSServer.FindExpiringTLSServerCertificates(Now+FExpirationCutOff,SL); if SL.Count > 0 then begin for I := 0 to SL.Count-1 do begin if SL.Names[I] = rsaEncryption then begin lStr := LowerCase(SL.ValueFromIndex[I]); // Exclude wildcard certificates - can't be requested through ALPN if (Pos('*',lStr) = 0) and (ExecuteACME1.SubjectAltNames.IndexOf(lStr) < 0) then begin ExecuteACME1.SubjectAltNames.Add(lStr); end; end; end; end; finally SL.Free; end; TLSServer.RemoveTLSAcmeALPN; ExecuteACME1.RegisterDomain; 5. The code in the OnALPNChallenge event handler should include the code below. If you already have private keys in your TLSServer component, you can let the first argument be nil. Here I have used a TstRSAKey component, which might be set e.g. to load the domain.key file. TLSServer.AddTLSAcmeALPN(tMPPrivateKeyIF.Create(stRSAKey1,nil),Domain,Token,Thumbprint); Processed := True; 6. Once all challenges have been processed, the ACME server will reply with a Pending response. Set a timer for, maybe, one minute. When the timer returns, call FinalizeDomain. In the ExecuteACME.OnCertificate event you will have to add the certificates and the domain key to the TLSServer component, and also save them to file, e.g. a PFX file. Don't forget to load the ISRG root certificates prior to this: var lMS: tMemoryStream; lPwd: iSecretKey; lC, lEE: iCertificate; lPFX: iPFX; lKeyStore: iKeyAndCertStoreEx; lName: string; I: Integer; lIni: TIniFile; lStatus: TCertStatusCode; begin Timer2.Enabled := False; TLSServer.RemoveTLSAcmeALPN; lPwd := tDualSecretKey.CreateForPFX(FDefaultPassword); lPFX := tPFX.Create(gcamFirstAssignment); lKeyStore := lPFX as iKeyAndCertStoreEx; lMS := TMemoryStream.Create; try Certificate.SaveToStream(lMS,TEncoding.Default); lMS.Position := 0; for I := 0 to 1 do begin lC := tCertificate.Create(gcamFirstAssignment); lC.GetStruct.LoadFromStream(lMS,fmtPEM); lC.GetStruct.ExpandFull; TLSServer.AddCertificate(lC,True,lStatus,False,True); if not lC.IsCA then begin lName := lC.tbsCertificate.subject.AsRdnSequence.CommonName; lEE := lC; end; lKeyStore.StoreCertificate(lPwd,lC,lC.tbsCertificate.subject.AsRdnSequence.CommonName,lC.PublicKeyIdentifier); end; finally lMS.Free; end; lKeyStore.StorePrivateKey(lPwd,stRSAKey1,rsaEncryption,lName,'',lEE.PublicKeyIdentifier,True); lPFX.ImposeAuthSafeContent(lPwd,True); lPFX.Version := tPFXVersion.V3; lPFX.GetStruct.SaveToFile(ExtractFilePath(ParamStr(0)) + lName + '.pfx',fmtDER); TLSServer.ReplaceTLSServerCertificate(lEE,tMPPrivateKeyIF.Create(stRSAKey1,nil)); |