-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathExecute.ACME.Interface.pas
More file actions
300 lines (254 loc) · 9.03 KB
/
Execute.ACME.Interface.pas
File metadata and controls
300 lines (254 loc) · 9.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
unit Execute.ACME;
{
ACME Delphi client for Let's Encrypt (c)2018-2023 Execute SARL <contact@execute.fr>
This component is NOT FREE !
BUT you can use it any OpenSource project under the GPL
AND you have to register a licence to use it in any commercial product
you are NOT allowed to use this component to register a commercial website
without a registered licence, even with a GPL product around this component
}
{
Purpose of use: add a Let's Encrypt certificat to a standalone Delphi WebBroker application
Put a TExecuteACME component on a form
Set or generate the AccountKey and DomainKey RSA Private Keys
Define DomaineName
Define ContactEmail (optional)
Define OnHttpChallenge and/or OnDnsChallenge
Define OnCertificate
call TExecuteACME.RegisterDomain();
-> OnHttpChallenge (this is NOT an HTTPS request)
on this request :
http://<DomainName>/.well-known/acme-client/<Token>
you have to reply :
<Token>.<Thumbprint>
this can be done by creating the file on an external webserver or by an idHTTPServer component
-> OnDnsChallenge
you have to add an entry in the domain DNS:
_acme-challenge<.subdomain> IN TXT "<digest>"
for a wildcard entry "*.mydomain.com" there's no subdomain
_acme-challenge IN TXT "<digest>"
-> OnCertificate
you have a registered certificat in the provided TStrings parameter
}
{
New in version 1.1 (2019-02-18)
- Add a FinalizeDomain method to check the request status
if you need to restart the application, save the OrderURL properties of the component
- Add SubjectAltNames(SAN) property to register multiples domains
DomainName = 'www.mydomain.com'
SubjectAlternativeNames = ['ftp.mydomain.com']
}
{
version 1.2 (2019-12-10)
- POST-as-GET support
}
{
version 1.3 (2020-04-08)
- extract local types from Record because of bad support by the IDE
- Linux64 support
- add *Now method for direct call of methods (no thread)
}
{
version 1.4 (2021-08-23)
- switch from Indy TidHTTP to System.Net.HTTPClient for TLS 1.3 support
- version tested only with Delphi 10.4.2
}
{
version 1.5 (2023-10-15)
- add DNS Challenge
- add Processing status (between pending and valid or invalid)
- Execute.JSON is now the UTF8 version
}
interface
{$ZEROBASEDSTRINGS OFF}
{.$DEFINE INDY} // INDY still don't support TLS 1.3 !
{$IFDEF DEBUG}
{.$DEFINE LOG}
{$ENDIF}
uses
{$IFDEF LOG}
{$IFDEF MSWINDOWS}
Winapi.Windows,
{$ENDIF}
{$ENDIF}
System.SysUtils,
System.Classes,
System.Hash,
idHTTP,
IdSSLOpenSSL,
IdSSLOpenSSLHeaders,
{$IFDEF LINUX64}
IdGlobal,
{$ENDIF}
{$IFNDEF INDY}
System.Net.HttpClient,
{$ENDIF}
Execute.RTTI,
Execute.JSON.UTF8;
type
TEnvironment = (
// Staging, - deprecated - non supported
// Production, - deprecated - non supported
StagingV2, // for testing
ProductionV2, // for production
Custom
);
TKeyType = (
ktAccount,
ktDomain
);
TACMERevokeReason = (
unspecified = 0,
keyCompromise = 1,
cACompromise = 2,
affiliationChanged = 3,
superseded = 4,
cessationOfOperation = 5,
certificateHold = 6,
// _not_used_ = 7,
removeFromCRL = 8,
privilegeWithdrawn = 9,
aACompromise = 10
);
TPasswordEvent = function(Sender: TObject; KeyType: TKeyType; var Password: string): Boolean of object;
THttpChallengeEvent = procedure(Sender: TObject; const Domain, Token, Thumbprint: string; var Processed: Boolean) of object;
TDnsChallengeEvent = procedure(Sender: TObject; const Domain, Digest: string; var Processed: Boolean) of object;
TCertificateEvent = procedure(Sender: TObject; Certificate: TStrings) of object;
TErrorEvent = procedure(Sender: TObject; const Error: string) of object;
TDomainRegistrationThread = class
end;
TSubmitThread = class(TThread)
public
constructor Create(Request: TDomainRegistrationThread);
procedure Execute; override;
end;
TACMEOrderStatus = (
osNone,
osPending,
osProcessing,
osReady,
osValid,
osInvalid,
osExpired,
osRevoked,
osUnknown
);
TExecuteACME = class(TComponent)
public const
/// <summary>
/// Path of a HTTP Challenge
/// </summary>
WELL_KNOWN_URL = '/.well-known/acme-challenge/';
DNS_PREFIX = '_acme-challenge';
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
/// <summary>
/// same as RegisterDomain or RegisterDomainNow
/// </summary>
function DoRegisterDomain(Submit: Boolean): TDomainRegistrationThread;
/// <summary>
/// same as FinalizeDomain or FinalizeDomainNow
/// </summary>
function DoFinalizeDomain(Submit: Boolean): TDomainRegistrationThread;
/// <summary>
/// same as UnregisterDomain or UnregisterDomainNow
/// </summary>
function DoUnregisterDomain(const CRT: string; Reason: TACMERevokeReason; Submit: Boolean): TDomainRegistrationThread;
/// <summary>
/// Request a new registration - in a thread
/// </summary>
function RegisterDomain: TDomainRegistrationThread; inline;
/// <summary>
/// Request a new registration - direct call
/// </summary>
procedure RegisterDomainNow; inline;
/// <summary>
/// Check last registration status - in a thread
/// </summary>
function FinalizeDomain: TDomainRegistrationThread; inline;
/// <summary>
/// Check last registration status - direct call
/// </summary>
procedure FinalizeDomainNow; inline;
/// <summary>
/// Unregister a Certificat - in a thread
/// </summary>
function UnregisterDomain(const CRT: string; Reason: TACMERevokeReason): TDomainRegistrationThread; inline;
/// <summary>
/// Unregister a Certificat - direct call
/// </summary>
procedure UnregisterDomainNow(const CRT: string; Reason: TACMERevokeReason); inline;
/// <summary>
/// Generate a RSA Key (for Domain or Account for instance)
/// </summary>
class procedure GeneraRSAKey(Strings: TStrings; const Password: string = ''; KeyLength: Integer = 4096);
/// <summary>
/// The URL of the Order request
/// </summary>
property OrderURL: string read FOrderURL write FOrderURL;
/// <summary>
/// The Status of the Order request
/// </summary>
property OrderStatus: TACMEOrderStatus read FOrderStatus;
published
/// <summary>
/// StagingV2 for testing, ProductionV2 for production
/// </summary>
property Environment: TEnvironment read FEnvironment write SetEnvironment default StagingV2;
/// <summary>
/// automaticaly set by Environment
/// </summary>
property Directory: string read FDirectory write SetDirectory stored IsCustomEnvironment;
/// <summary>
/// the DomainName to register
/// </summary>
property DomainName: string read FDomainName write FDomainName;
/// <summary>
/// alternate domains
/// </summary>
property SubjectAltNames: TStrings index SUBJECTALTNAMES_INDEX read GetStrings write SetStrings;
/// <summary>
/// optional contact email
/// </summary>
property ContactEmail: string read FContactEMail write FContactEmail;
/// <summary>
/// a RSA Key for the Let's Encrypt account, can be generated with GeneraRSAKey()
/// </summary>
property AccountKey: TStrings index ACCOUNTKEY_INDEX read GetStrings write SetStrings;
/// <summary>
/// a RSA Key for the domain, can be generated with GeneraRSAKey()
/// </summary>
property DomainKey: TStrings index DOMAINKEY_INDEX read GetStrings write SetStrings;
/// <summary>
/// fired by the component when a Password is required
/// </summary>
property OnPassword: TPasswordEvent read FOnPassword write FOnPassword;
/// <summary>
/// fired by the component when a pending (Processed = False) HttpChallenge is found
/// </summary>
property OnHttpChallenge: THttpChallengeEvent read FOnHttpChallenge write FOnHttpChallenge;
/// <summary>
/// fired by the component when a pending (Processed = False) DnsChallenge is found
/// </summary>
property OnDnsChallenge: TDnsChallengeEvent read FOnDnsChallenge write FOnDnsChallenge;
/// <summary>
/// fired when the requested Certificat is available
/// </summary>
property OnCertificate: TCertificateEvent read FOnCertificate write FOnCertificate;
/// <summary>
/// fired when a error occurs
/// </summary>
property OnError: TErrorEvent read FOnError write FOnError;
/// <summary>
/// fired when the request is done without any other event, you can check OrderStatus
/// </summary>
property OnDone: TNotifyEvent read FOnDone write FOnDone;
end;
implementation
{$WARNING ! Source code available on https://store.execute.fr ! }
{$IFDEF MSWINDOWS}
initialization
{$IFDEF LOG}AllocConsole;{$ENDIF}
{$ENDIF}
end.