-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathIdSSLOpenSSL.pas
More file actions
4154 lines (3860 loc) · 128 KB
/
IdSSLOpenSSL.pas
File metadata and controls
4154 lines (3860 loc) · 128 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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{
This file is part of the Indy (Internet Direct) project, and is offered
under the dual-licensing agreement described on the Indy website.
(http://www.indyproject.org/)
Copyright:
(c) 1993-2024, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
unit IdSSLOpenSSL;
{
Author: Gregor Ibic (gregor.ibic@intelicom.si)
Copyright: (c) Gregor Ibic, Intelicom d.o.o and Indy Working Group.
}
{
Indy OpenSSL now uses the standard OpenSSL libraries
for pre-compiled win32 dlls, see:
http://www.openssl.org/related/binaries.html
recommended v0.9.8a or later
}
{
Important information concerning OnVerifyPeer:
Rev 1.39 of February 2005 deliberately broke the OnVerifyPeer interface,
which (obviously?) only affects programs that implemented that callback
as part of the SSL negotiation. Note that you really should always
implement OnVerifyPeer, otherwise the certificate of the peer you are
connecting to is NOT checked to ensure it is valid.
Prior to this, if the SSL library detected a problem with a certificate
or the Depth was insufficient (i.e. the "Ok" parameter in VerifyCallback
is 0 / FALSE), then irrespective of whether your OnVerifyPeer returned True
or False, the SSL connection would be deliberately failed.
This created a problem in that even if there was only a very minor
problem with one of the certificates in the chain (OnVerifyPeer is called
once for each certificate in the certificate chain), which the user may
have been happy to accept, the SSL negotiation would be failed. However,
changing the code to allow the SSL connection when a user returned True
for OnVerifyPeer would have meant that existing code which depended on
automatic rejection of invalid certificates would then be accepting
invalid certificates, which would have been an unacceptable security
change.
Consequently, OnVerifyPeer was changed to deliberately break existing code
by adding an AOk parameter. To preserve the previous functionality, your
OnVerifyPeer event should do "Result := AOk;". If you wish to consider
accepting certificates that the SSL library has considered invalid, then
in your OnVerifyPeer, make sure you satisfy yourself that the certificate
really is valid and then set Result to True. In reality, in addition to
checking AOk, you should always implement code that ensures you are only
accepting certificates which are valid (at least from your point of view).
Ciaran Costelloe, ccostelloe@flogas.ie
}
{
RLebeau 1/12/2011: Breaking OnVerifyPeer event again, this time to add an
additional AError parameter (patch courtesy of "jvlad", dmda@yandex.ru).
This helps user code distinquish between Self-signed and invalid certificates.
}
interface
{$I IdCompilerDefines.inc}
{$TYPEDADDRESS OFF}
uses
//facilitate inlining only.
{$IFDEF WINDOWS}
Windows,
{$ENDIF}
Classes,
IdBuffer,
IdCTypes,
IdGlobal,
IdException,
IdStackConsts,
IdSocketHandle,
IdSSLOpenSSLHeaders,
IdComponent,
IdIOHandler,
IdGlobalProtocols,
IdTCPServer,
IdThread,
IdTCPConnection,
IdIntercept,
IdIOHandlerSocket,
IdSSL,
IdSocks,
IdScheduler,
IdYarn;
type
TIdSSLVersion = (sslvSSLv2, sslvSSLv23, sslvSSLv3, sslvTLSv1,sslvTLSv1_1,sslvTLSv1_2);
TIdSSLVersions = set of TIdSSLVersion;
TIdSSLMode = (sslmUnassigned, sslmClient, sslmServer, sslmBoth);
TIdSSLVerifyMode = (sslvrfPeer, sslvrfFailIfNoPeerCert, sslvrfClientOnce);
TIdSSLVerifyModeSet = set of TIdSSLVerifyMode;
TIdSSLCtxMode = (sslCtxClient, sslCtxServer);
TIdSSLAction = (sslRead, sslWrite);
const
DEF_SSLVERSION = sslvTLSv1;
DEF_SSLVERSIONS = [sslvTLSv1];
P12_FILETYPE = 3;
MAX_SSL_PASSWORD_LENGTH = 128;
type
TIdSSLULong = packed record
case Byte of
0: (B1, B2, B3, B4: UInt8);
1: (W1, W2: UInt16);
2: (L1: Int32);
3: (C1: UInt32);
end;
TIdSSLEVP_MD = record
Length: TIdC_UINT;
MD: Array [0 .. EVP_MAX_MD_SIZE - 1] of TIdAnsiChar;
end;
TIdSSLByteArray = record
Length: TIdC_UINT;
Data: PByte;
end;
TIdX509 = class;
TIdSSLIOHandlerSocketOpenSSL = class;
TIdSSLCipher = class;
TCallbackEvent = procedure(const AMsg: String) of object;
TCallbackExEvent = procedure(ASender : TObject; const AsslSocket: PSSL;
const AWhere, Aret: TIdC_INT; const AType, AMsg : String ) of object;
TPasswordEvent = procedure(var Password: String) of object;
TPasswordEventEx = procedure( ASender : TObject; var VPassword: String; const AIsWrite : Boolean) of object;
TVerifyPeerEvent = function(Certificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean of object;
TIOHandlerNotify = procedure(ASender: TIdSSLIOHandlerSocketOpenSSL) of object;
TIdSSLOptions = class(TPersistent)
protected
fsRootCertFile,
fsCertFile,
fsKeyFile,
fsDHParamsFile: String;
fMethod: TIdSSLVersion;
fSSLVersions : TIdSSLVersions;
fMode: TIdSSLMode;
fVerifyDepth: Integer;
fVerifyMode: TIdSSLVerifyModeSet;
//fVerifyFile,
fVerifyDirs: String;
fCipherList: String;
procedure AssignTo(Destination: TPersistent); override;
procedure SetSSLVersions(const AValue : TIdSSLVersions);
procedure SetMethod(const AValue : TIdSSLVersion);
public
constructor Create;
// procedure Assign(ASource: TPersistent); override;
published
property RootCertFile: String read fsRootCertFile write fsRootCertFile;
property CertFile: String read fsCertFile write fsCertFile;
property KeyFile: String read fsKeyFile write fsKeyFile;
property DHParamsFile: String read fsDHParamsFile write fsDHParamsFile;
property Method: TIdSSLVersion read fMethod write SetMethod default DEF_SSLVERSION;
property SSLVersions : TIdSSLVersions read fSSLVersions write SetSSLVersions default DEF_SSLVERSIONS;
property Mode: TIdSSLMode read fMode write fMode;
property VerifyMode: TIdSSLVerifyModeSet read fVerifyMode write fVerifyMode;
property VerifyDepth: Integer read fVerifyDepth write fVerifyDepth;
// property VerifyFile: String read fVerifyFile write fVerifyFile;
property VerifyDirs: String read fVerifyDirs write fVerifyDirs;
property CipherList: String read fCipherList write fCipherList;
end;
TIdSSLContext = class(TObject)
protected
fMethod: TIdSSLVersion;
fSSLVersions : TIdSSLVersions;
fMode: TIdSSLMode;
fsRootCertFile, fsCertFile, fsKeyFile, fsDHParamsFile: String;
fVerifyDepth: Integer;
fVerifyMode: TIdSSLVerifyModeSet;
// fVerifyFile: String;
fVerifyDirs: String;
fCipherList: String;
fContext: PSSL_CTX;
fStatusInfoOn: Boolean;
// fPasswordRoutineOn: Boolean;
fVerifyOn: Boolean;
fSessionId: Integer;
fCtxMode: TIdSSLCtxMode;
procedure DestroyContext;
function SetSSLMethod: PSSL_METHOD;
procedure SetVerifyMode(Mode: TIdSSLVerifyModeSet; CheckRoutine: Boolean);
function GetVerifyMode: TIdSSLVerifyModeSet;
procedure InitContext(CtxMode: TIdSSLCtxMode);
public
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} Parent: TObject;
constructor Create;
destructor Destroy; override;
function Clone : TIdSSLContext;
function LoadRootCert: Boolean;
function LoadCert: Boolean;
function LoadKey: Boolean;
function LoadDHParams: Boolean;
property StatusInfoOn: Boolean read fStatusInfoOn write fStatusInfoOn;
// property PasswordRoutineOn: Boolean read fPasswordRoutineOn write fPasswordRoutineOn;
property VerifyOn: Boolean read fVerifyOn write fVerifyOn;
//THese can't be published in a TObject without a compiler warning.
// published
property SSLVersions : TIdSSLVersions read fSSLVersions write fSSLVersions;
property Method: TIdSSLVersion read fMethod write fMethod;
property Mode: TIdSSLMode read fMode write fMode;
property RootCertFile: String read fsRootCertFile write fsRootCertFile;
property CertFile: String read fsCertFile write fsCertFile;
property CipherList: String read fCipherList write fCipherList;
property KeyFile: String read fsKeyFile write fsKeyFile;
property DHParamsFile: String read fsDHParamsFile write fsDHParamsFile;
// property VerifyMode: TIdSSLVerifyModeSet read GetVerifyMode write SetVerifyMode;
// property VerifyFile: String read fVerifyFile write fVerifyFile;
property VerifyDirs: String read fVerifyDirs write fVerifyDirs;
property VerifyMode: TIdSSLVerifyModeSet read fVerifyMode write fVerifyMode;
property VerifyDepth: Integer read fVerifyDepth write fVerifyDepth;
end;
TIdSSLSocket = class(TObject)
protected
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} fParent: TObject;
fPeerCert: TIdX509;
fSSL: PSSL;
fSSLCipher: TIdSSLCipher;
fSSLContext: TIdSSLContext;
fHostName: String;
function GetPeerCert: TIdX509;
function GetSSLError(retCode: Integer): Integer;
function GetSSLCipher: TIdSSLCipher;
public
constructor Create(Parent: TObject);
destructor Destroy; override;
procedure Accept(const pHandle: TIdStackSocketHandle);
procedure Connect(const pHandle: TIdStackSocketHandle);
function Send(const ABuffer : TIdBytes; AOffset, ALength: Integer): Integer;
function Recv(var ABuffer : TIdBytes): Integer;
function GetSessionID: TIdSSLByteArray;
function GetSessionIDAsString:String;
procedure SetCipherList(CipherList: String);
//
property PeerCert: TIdX509 read GetPeerCert;
property Cipher: TIdSSLCipher read GetSSLCipher;
property HostName: String read fHostName;
end;
// TIdSSLIOHandlerSocketOpenSSL and TIdServerIOHandlerSSLOpenSSL have some common
// functions, but they do not have a common ancestor, so this interface helps
// bridge the gap...
IIdSSLOpenSSLCallbackHelper = interface(IInterface)
['{583F1209-10BA-4E06-8810-155FAEC415FE}']
function GetPassword(const AIsWrite : Boolean): string;
procedure StatusInfo(const ASSL: PSSL; AWhere, ARet: TIdC_INT; const AStatusStr: string);
function VerifyPeer(ACertificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean;
function GetIOHandlerSelf: TIdSSLIOHandlerSocketOpenSSL;
end;
TIdSSLIOHandlerSocketOpenSSL = class(TIdSSLIOHandlerSocketBase, IIdSSLOpenSSLCallbackHelper)
protected
fSSLContext: TIdSSLContext;
fxSSLOptions: TIdSSLOptions;
fSSLSocket: TIdSSLSocket;
//fPeerCert: TIdX509;
fOnStatusInfo: TCallbackEvent;
FOnStatusInfoEx : TCallbackExEvent;
fOnGetPassword: TPasswordEvent;
fOnGetPasswordEx : TPasswordEventEx;
fOnVerifyPeer: TVerifyPeerEvent;
fSSLLayerClosed: Boolean;
fOnBeforeConnect: TIOHandlerNotify;
// function GetPeerCert: TIdX509;
//procedure CreateSSLContext(axMode: TIdSSLMode);
//
procedure SetPassThrough(const Value: Boolean); override;
procedure DoBeforeConnect(ASender: TIdSSLIOHandlerSocketOpenSSL); virtual;
procedure DoStatusInfo(const AMsg: String); virtual;
procedure DoStatusInfoEx(const AsslSocket: PSSL;
const AWhere, Aret: TIdC_INT; const AWhereStr, ARetStr : String );
procedure DoGetPassword(var Password: String); virtual;
procedure DoGetPasswordEx(var VPassword: String; const AIsWrite : Boolean); virtual;
function DoVerifyPeer(Certificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean; virtual;
function RecvEnc(var VBuffer: TIdBytes): Integer; override;
function SendEnc(const ABuffer: TIdBytes; const AOffset, ALength: Integer): Integer; override;
procedure Init;
procedure OpenEncodedConnection; virtual;
//some overrides from base classes
procedure InitComponent; override;
procedure ConnectClient; override;
function CheckForError(ALastResult: Integer): Integer; override;
procedure RaiseError(AError: Integer); override;
{ IIdSSLOpenSSLCallbackHelper }
function GetPassword(const AIsWrite : Boolean): string;
procedure StatusInfo(const ASslSocket: PSSL; AWhere, ARet: TIdC_INT; const AStatusStr: string);
function VerifyPeer(ACertificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean;
function GetIOHandlerSelf: TIdSSLIOHandlerSocketOpenSSL;
public
destructor Destroy; override;
// TODO: add an AOwner parameter
function Clone : TIdSSLIOHandlerSocketBase; override;
procedure StartSSL; override;
procedure AfterAccept; override;
procedure Close; override;
procedure Open; override;
function Readable(AMSec: Integer = IdTimeoutDefault): Boolean; override;
property SSLSocket: TIdSSLSocket read fSSLSocket write fSSLSocket;
property OnBeforeConnect: TIOHandlerNotify read fOnBeforeConnect write fOnBeforeConnect;
property SSLContext: TIdSSLContext read fSSLContext write fSSLContext;
published
property SSLOptions: TIdSSLOptions read fxSSLOptions write fxSSLOptions;
property OnStatusInfo: TCallbackEvent read fOnStatusInfo write fOnStatusInfo;
property OnStatusInfoEx: TCallbackExEvent read fOnStatusInfoEx write fOnStatusInfoEx;
property OnGetPassword: TPasswordEvent read fOnGetPassword write fOnGetPassword;
property OnGetPasswordEx : TPasswordEventEx read fOnGetPasswordEx write fOnGetPasswordEx;
property OnVerifyPeer: TVerifyPeerEvent read fOnVerifyPeer write fOnVerifyPeer;
end;
TIdServerIOHandlerSSLOpenSSL = class(TIdServerIOHandlerSSLBase, IIdSSLOpenSSLCallbackHelper)
protected
fxSSLOptions: TIdSSLOptions;
fSSLContext: TIdSSLContext;
fOnStatusInfo: TCallbackEvent;
FOnStatusInfoEx : TCallbackExEvent;
fOnGetPassword: TPasswordEvent;
fOnGetPasswordEx : TPasswordEventEx;
fOnVerifyPeer: TVerifyPeerEvent;
//
//procedure CreateSSLContext(axMode: TIdSSLMode);
//procedure CreateSSLContext;
//
procedure DoStatusInfo(const AMsg: String); virtual;
procedure DoStatusInfoEx(const AsslSocket: PSSL;
const AWhere, Aret: TIdC_INT; const AWhereStr, ARetStr : String );
procedure DoGetPassword(var Password: String); virtual;
//TPasswordEventEx
procedure DoGetPasswordEx(var VPassword: String; const AIsWrite : Boolean); virtual;
function DoVerifyPeer(Certificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean; virtual;
procedure InitComponent; override;
{ IIdSSLOpenSSLCallbackHelper }
function GetPassword(const AIsWrite : Boolean): string;
procedure StatusInfo(const ASslSocket: PSSL; AWhere, ARet: TIdC_INT; const AStatusStr: string);
function VerifyPeer(ACertificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean;
function GetIOHandlerSelf: TIdSSLIOHandlerSocketOpenSSL;
public
procedure Init; override;
procedure Shutdown; override;
// AListenerThread is a thread and not a yarn. Its the listener thread.
function Accept(ASocket: TIdSocketHandle; AListenerThread: TIdThread;
AYarn: TIdYarn): TIdIOHandler; override;
// function Accept(ASocket: TIdSocketHandle; AThread: TIdThread) : TIdIOHandler; override;
destructor Destroy; override;
function MakeClientIOHandler : TIdSSLIOHandlerSocketBase; override;
//
function MakeFTPSvrPort : TIdSSLIOHandlerSocketBase; override;
function MakeFTPSvrPasv : TIdSSLIOHandlerSocketBase; override;
//
property SSLContext: TIdSSLContext read fSSLContext;
published
property SSLOptions: TIdSSLOptions read fxSSLOptions write fxSSLOptions;
property OnStatusInfo: TCallbackEvent read fOnStatusInfo write fOnStatusInfo;
property OnStatusInfoEx: TCallbackExEvent read fOnStatusInfoEx write fOnStatusInfoEx;
property OnGetPassword: TPasswordEvent read fOnGetPassword write fOnGetPassword;
property OnGetPasswordEx : TPasswordEventEx read fOnGetPasswordEx write fOnGetPasswordEx;
property OnVerifyPeer: TVerifyPeerEvent read fOnVerifyPeer write fOnVerifyPeer;
end;
TIdX509Name = class(TObject)
protected
fX509Name: PX509_NAME;
function CertInOneLine: String;
function GetHash: TIdSSLULong;
function GetHashAsString: String;
public
constructor Create(aX509Name: PX509_NAME);
//
property Hash: TIdSSLULong read GetHash;
property HashAsString: string read GetHashAsString;
property OneLine: string read CertInOneLine;
//
property CertificateName: PX509_NAME read fX509Name;
end;
TIdX509Info = class(TObject)
protected
//Do not free this here because it belongs
//to the X509 or something else.
FX509 : PX509;
public
constructor Create( aX509: PX509);
//
property Certificate: PX509 read FX509;
end;
TIdX509Fingerprints = class(TIdX509Info)
protected
function GetMD5: TIdSSLEVP_MD;
function GetMD5AsString:String;
function GetSHA1: TIdSSLEVP_MD;
function GetSHA1AsString:String;
function GetSHA224 : TIdSSLEVP_MD;
function GetSHA224AsString : String;
function GetSHA256 : TIdSSLEVP_MD;
function GetSHA256AsString : String;
function GetSHA384 : TIdSSLEVP_MD;
function GetSHA384AsString : String;
function GetSHA512 : TIdSSLEVP_MD;
function GetSHA512AsString : String;
public
property MD5 : TIdSSLEVP_MD read GetMD5;
property MD5AsString : String read GetMD5AsString;
{IMPORTANT!!!
FIPS approves only these algorithms for hashing.
SHA-1
SHA-224
SHA-256
SHA-384
SHA-512
http://csrc.nist.gov/CryptoToolkit/tkhash.html
}
property SHA1 : TIdSSLEVP_MD read GetSHA1;
property SHA1AsString : String read GetSHA1AsString;
property SHA224 : TIdSSLEVP_MD read GetSHA224;
property SHA224AsString : String read GetSHA224AsString;
property SHA256 : TIdSSLEVP_MD read GetSHA256;
property SHA256AsString : String read GetSHA256AsString;
property SHA384 : TIdSSLEVP_MD read GetSHA384;
property SHA384AsString : String read GetSHA384AsString;
property SHA512 : TIdSSLEVP_MD read GetSHA512;
property SHA512AsString : String read GetSHA512AsString;
end;
TIdX509SigInfo = class(TIdX509Info)
protected
function GetSignature : String;
function GetSigType : TIdC_INT;
function GetSigTypeAsString : String;
public
property Signature : String read GetSignature;
property SigType : TIdC_INT read GetSigType ;
property SigTypeAsString : String read GetSigTypeAsString;
end;
TIdX509 = class(TObject)
protected
FFingerprints : TIdX509Fingerprints;
FSigInfo : TIdX509SigInfo;
FCanFreeX509 : Boolean;
FX509 : PX509;
FSubject : TIdX509Name;
FIssuer : TIdX509Name;
FDisplayInfo : TStrings;
function RSubject:TIdX509Name;
function RIssuer:TIdX509Name;
function RnotBefore:TDateTime;
function RnotAfter:TDateTime;
function RFingerprint:TIdSSLEVP_MD;
function RFingerprintAsString:String;
function GetSerialNumber: String;
function GetVersion : TIdC_LONG;
function GetDisplayInfo : TStrings;
public
Constructor Create(aX509: PX509; aCanFreeX509: Boolean = True); virtual;
Destructor Destroy; override;
property Version : TIdC_LONG read GetVersion;
//
property SigInfo : TIdX509SigInfo read FSigInfo;
property Fingerprints : TIdX509Fingerprints read FFingerprints;
//
property Fingerprint: TIdSSLEVP_MD read RFingerprint;
property FingerprintAsString: String read RFingerprintAsString;
property Subject: TIdX509Name read RSubject;
property Issuer: TIdX509Name read RIssuer;
property notBefore: TDateTime read RnotBefore;
property notAfter: TDateTime read RnotAfter;
property SerialNumber : string read GetSerialNumber;
property DisplayInfo : TStrings read GetDisplayInfo;
//
property Certificate: PX509 read FX509;
end;
TIdSSLCipher = class(TObject)
protected
FSSLSocket: TIdSSLSocket;
function GetDescription: String;
function GetName: String;
function GetBits: Integer;
function GetVersion: String;
public
constructor Create(AOwner: TIdSSLSocket);
destructor Destroy; override;
//These can't be published without a compiler warning.
// published
property Description: String read GetDescription;
property Name: String read GetName;
property Bits: Integer read GetBits;
property Version: String read GetVersion;
end;
EIdOSSLCouldNotLoadSSLLibrary = class(EIdOpenSSLError);
EIdOSSLModeNotSet = class(EIdOpenSSLError);
EIdOSSLGetMethodError = class(EIdOpenSSLError);
EIdOSSLCreatingSessionError = class(EIdOpenSSLError);
EIdOSSLCreatingContextError = class(EIdOpenSSLAPICryptoError);
EIdOSSLLoadingRootCertError = class(EIdOpenSSLAPICryptoError);
EIdOSSLLoadingCertError = class(EIdOpenSSLAPICryptoError);
EIdOSSLLoadingKeyError = class(EIdOpenSSLAPICryptoError);
EIdOSSLLoadingDHParamsError = class(EIdOpenSSLAPICryptoError);
EIdOSSLSettingCipherError = class(EIdOpenSSLError);
EIdOSSLFDSetError = class(EIdOpenSSLAPISSLError);
EIdOSSLDataBindingError = class(EIdOpenSSLAPISSLError);
EIdOSSLAcceptError = class(EIdOpenSSLAPISSLError);
EIdOSSLConnectError = class(EIdOpenSSLAPISSLError);
{$IFNDEF OPENSSL_NO_TLSEXT}
EIdOSSLSettingTLSHostNameError = class(EIdOpenSSLAPISSLError);
{$ENDIF}
function LoadOpenSSLLibrary: Boolean;
procedure UnLoadOpenSSLLibrary;
function OpenSSLVersion: string;
implementation
uses
{$IFDEF HAS_UNIT_Generics_Collections}
System.Generics.Collections,
{$ENDIF}
{$IFDEF USE_VCL_POSIX}
Posix.SysTime,
Posix.Time,
Posix.Unistd,
{$ENDIF}
IdFIPS,
IdResourceStringsCore,
IdResourceStringsProtocols,
IdResourceStringsOpenSSL,
IdStack,
IdStackBSDBase,
IdAntiFreezeBase,
IdExceptionCore,
IdResourceStrings,
IdThreadSafe,
IdCustomTransparentProxy,
IdURI,
SysUtils,
SyncObjs;
type
// TODO: TIdThreadSafeObjectList instead?
{$IFDEF HAS_GENERICS_TThreadList}
TIdCriticalSectionThreadList = TThreadList<TIdCriticalSection>;
TIdCriticalSectionList = TList<TIdCriticalSection>;
{$ELSE}
// TODO: flesh out to match TThreadList<TIdCriticalSection> and TList<TIdCriticalSection> on non-Generics compilers
TIdCriticalSectionThreadList = TThreadList;
TIdCriticalSectionList = TList;
{$ENDIF}
// RLebeau 1/24/2019: defining this as a private implementation for now to
// avoid a change in the public interface above. This should be rolled into
// the public interface at some point...
TIdSSLOptions_Internal = class(TIdSSLOptions)
public
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} Parent: TObject;
end;
var
SSLIsLoaded: TIdThreadSafeBoolean = nil;
LockInfoCB: TIdCriticalSection = nil;
LockPassCB: TIdCriticalSection = nil;
LockVerifyCB: TIdCriticalSection = nil;
CallbackLockList: TIdCriticalSectionThreadList = nil;
procedure GetStateVars(const sslSocket: PSSL; AWhere, Aret: TIdC_INT; var VTypeStr, VMsg : String);
{$IFDEF USE_INLINE}inline;{$ENDIF}
begin
case AWhere of
SSL_CB_ALERT :
begin
VTypeStr := IndyFormat( RSOSSLAlert,[SSL_alert_type_string_long(Aret)]);
VMsg := String(SSL_alert_type_string_long(Aret));
end;
SSL_CB_READ_ALERT :
begin
VTypeStr := IndyFormat(RSOSSLReadAlert,[SSL_alert_type_string_long(Aret)]);
VMsg := String( SSL_alert_desc_string_long(Aret));
end;
SSL_CB_WRITE_ALERT :
begin
VTypeStr := IndyFormat(RSOSSLWriteAlert,[SSL_alert_type_string_long(Aret)]);
VMsg := String( SSL_alert_desc_string_long(Aret));
end;
SSL_CB_ACCEPT_LOOP :
begin
VTypeStr := RSOSSLAcceptLoop;
VMsg := String( SSL_state_string_long(sslSocket));
end;
SSL_CB_ACCEPT_EXIT :
begin
if ARet < 0 then begin
VTypeStr := RSOSSLAcceptError;
end else begin
if ARet = 0 then begin
VTypeStr := RSOSSLAcceptFailed;
end else begin
VTypeStr := RSOSSLAcceptExit;
end;
end;
VMsg := String( SSL_state_string_long(sslSocket) );
end;
SSL_CB_CONNECT_LOOP :
begin
VTypeStr := RSOSSLConnectLoop;
VMsg := String( SSL_state_string_long(sslSocket) );
end;
SSL_CB_CONNECT_EXIT :
begin
if ARet < 0 then begin
VTypeStr := RSOSSLConnectError;
end else begin
if ARet = 0 then begin
VTypeStr := RSOSSLConnectFailed
end else begin
VTypeStr := RSOSSLConnectExit;
end;
end;
VMsg := String( SSL_state_string_long(sslSocket) );
end;
SSL_CB_HANDSHAKE_START :
begin
VTypeStr := RSOSSLHandshakeStart;
VMsg := String( SSL_state_string_long(sslSocket) );
end;
SSL_CB_HANDSHAKE_DONE :
begin
VTypeStr := RSOSSLHandshakeDone;
VMsg := String( SSL_state_string_long(sslSocket) );
end;
end;
{var LW : TIdC_INT;
begin
VMsg := '';
LW := Awhere and (not SSL_ST_MASK);
if (LW and SSL_ST_CONNECT) > 0 then begin
VWhereStr := 'SSL_connect:';
end else begin
if (LW and SSL_ST_ACCEPT) > 0 then begin
VWhereStr := ' SSL_accept:';
end else begin
VWhereStr := ' undefined:';
end;
end;
// IdSslStateStringLong
if (Awhere and SSL_CB_LOOP) > 0 then begin
VMsg := IdSslStateStringLong(sslSocket);
end else begin
if (Awhere and SSL_CB_ALERT) > 0 then begin
if (Awhere and SSL_CB_READ > 0) then begin
VWhereStr := VWhereStr + ' read:'+ IdSslAlertTypeStringLong(Aret);
end else begin
VWhereStr := VWhereStr + 'write:'+ IdSslAlertTypeStringLong(Aret);
end;;
VMsg := IdSslAlertDescStringLong(Aret);
end else begin
if (Awhere and SSL_CB_EXIT) > 0 then begin
if ARet = 0 then begin
VWhereStr := VWhereStr +'failed';
VMsg := IdSslStateStringLong(sslSocket);
end else begin
if ARet < 0 then begin
VWhereStr := VWhereStr +'error';
VMsg := IdSslStateStringLong(sslSocket);
end;
end;
end;
end;
end; }
end;
function PasswordCallback(buf: PIdAnsiChar; size: TIdC_INT; rwflag: TIdC_INT; userdata: Pointer): TIdC_INT; cdecl;
{$IFDEF USE_MARSHALLED_PTRS}
type
TBytesPtr = ^TBytes;
{$ENDIF}
var
Password: String;
{$IFDEF STRING_IS_UNICODE}
LPassword: TIdBytes;
{$ENDIF}
IdSSLContext: TIdSSLContext;
LErr : Integer;
LHelper: IIdSSLOpenSSLCallbackHelper;
begin
//Preserve last eror just in case OpenSSL is using it and we do something that
//clobers it. CYA.
LErr := GStack.WSGetLastError;
try
LockPassCB.Enter;
try
Password := ''; {Do not Localize}
IdSSLContext := TIdSSLContext(userdata);
if Supports(IdSSLContext.Parent, IIdSSLOpenSSLCallbackHelper, IInterface(LHelper)) then begin
Password := LHelper.GetPassword(rwflag > 0);
LHelper := nil;
end;
FillChar(buf^, size, 0);
{$IFDEF STRING_IS_UNICODE}
LPassword := IndyTextEncoding_OSDefault.GetBytes(Password);
if Length(LPassword) > 0 then begin
{$IFDEF USE_MARSHALLED_PTRS}
TMarshal.Copy(TBytesPtr(@LPassword)^, 0, TPtrWrapper.Create(buf), IndyMin(Length(LPassword), size));
{$ELSE}
Move(LPassword[0], buf^, IndyMin(Length(LPassword), size));
{$ENDIF}
end;
Result := Length(LPassword);
{$ELSE}
StrPLCopy(buf, Password, size);
Result := Length(Password);
{$ENDIF}
buf[size-1] := #0; // RLebeau: truncate the password if needed
finally
LockPassCB.Leave;
end;
finally
GStack.WSSetLastError(LErr);
end;
end;
procedure InfoCallback(const sslSocket: PSSL; where, ret: TIdC_INT); cdecl;
var
IdSSLSocket: TIdSSLSocket;
StatusStr : String;
LErr : Integer;
LHelper: IIdSSLOpenSSLCallbackHelper;
begin
{
You have to save the value of WSGetLastError as some Operating System API
function calls will reset that value and we can't know what a programmer will
do in this event. We need the value of WSGetLastError so we can report
an underlying socket error when the OpenSSL function returns.
JPM.
}
LErr := GStack.WSGetLastError;
try
LockInfoCB.Enter;
try
IdSSLSocket := TIdSSLSocket(SSL_get_app_data(sslSocket));
if Supports(IdSSLSocket.fParent, IIdSSLOpenSSLCallbackHelper, IInterface(LHelper)) then begin
StatusStr := IndyFormat(RSOSSLStatusString, [String(SSL_state_string_long(sslSocket))]);
LHelper.StatusInfo(sslSocket, where, ret, StatusStr);
LHelper := nil;
end;
finally
LockInfoCB.Leave;
end;
finally
GStack.WSSetLastError(LErr);
end;
end;
function TranslateInternalVerifyToSSL(Mode: TIdSSLVerifyModeSet): Integer;
{$IFDEF USE_INLINE} inline; {$ENDIF}
begin
Result := SSL_VERIFY_NONE;
if sslvrfPeer in Mode then begin
Result := Result or SSL_VERIFY_PEER;
end;
if sslvrfFailIfNoPeerCert in Mode then begin
Result := Result or SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
end;
if sslvrfClientOnce in Mode then begin
Result := Result or SSL_VERIFY_CLIENT_ONCE;
end;
end;
function VerifyCallback(Ok: TIdC_INT; ctx: PX509_STORE_CTX): TIdC_INT; cdecl;
var
hcert: PX509;
Certificate: TIdX509;
hSSL: PSSL;
IdSSLSocket: TIdSSLSocket;
// str: String;
VerifiedOK: Boolean;
Depth: Integer;
Error: Integer;
LOk: Boolean;
LHelper: IIdSSLOpenSSLCallbackHelper;
begin
LockVerifyCB.Enter;
try
VerifiedOK := True;
try
hSSL := X509_STORE_CTX_get_app_data(ctx);
if hSSL = nil then begin
Result := Ok;
Exit;
end;
hcert := X509_STORE_CTX_get_current_cert(ctx);
Certificate := TIdX509.Create(hcert, False); // the certificate is owned by the store
try
IdSSLSocket := TIdSSLSocket(SSL_get_app_data(hSSL));
Error := X509_STORE_CTX_get_error(ctx);
Depth := X509_STORE_CTX_get_error_depth(ctx);
if not ((Ok > 0) and (IdSSLSocket.fSSLContext.VerifyDepth >= Depth)) then begin
Ok := 0;
{if Error = X509_V_OK then begin
Error := X509_V_ERR_CERT_CHAIN_TOO_LONG;
end;}
end;
LOk := False;
if Ok = 1 then begin
LOk := True;
end;
if Supports(IdSSLSocket.fParent, IIdSSLOpenSSLCallbackHelper, IInterface(LHelper)) then begin
VerifiedOK := LHelper.VerifyPeer(Certificate, LOk, Depth, Error);
LHelper := nil;
end;
finally
FreeAndNil(Certificate);
end;
except
VerifiedOK := False;
end;
//if VerifiedOK and (Ok > 0) then begin
if VerifiedOK {and (Ok > 0)} then begin
Result := 1;
end
else begin
Result := 0;
end;
// Result := Ok; // testing
finally
LockVerifyCB.Leave;
end;
end;
//////////////////////////////////////////////////////
// Utilities
//////////////////////////////////////////////////////
function IndySSL_load_client_CA_file(const AFileName: String) : PSTACK_OF_X509_NAME; forward;
function IndySSL_CTX_use_PrivateKey_file(ctx: PSSL_CTX; const AFileName: String;
AType: Integer): TIdC_INT; forward;
function IndySSL_CTX_use_certificate_file(ctx: PSSL_CTX; const AFileName: String;
AType: Integer): TIdC_INT; forward;
function IndySSL_CTX_use_certificate_chain_file(ctx :PSSL_CTX;
const AFileName: String) : TIdC_INT; forward;
function IndyX509_STORE_load_locations(ctx: PX509_STORE;
const AFileName, APathName: String): TIdC_INT; forward;
function IndySSL_CTX_load_verify_locations(ctx: PSSL_CTX;
const ACAFile, ACAPath: String): TIdC_INT; forward;
function IndySSL_CTX_use_DHparams_file(ctx: PSSL_CTX;
const AFileName: String; AType: Integer): TIdC_INT; forward;
// TODO
{
function d2i_DHparams_bio(bp: PBIO; x: PPointer): PDH; inline;
begin
Result := PDH(ASN1_d2i_bio(@DH_new, @d2i_DHparams, bp, x));
end;
}
// SSL_CTX_use_PrivateKey_file() and SSL_CTX_use_certificate_file() do not
// natively support PKCS12 certificates/keys, only PEM/ASN1, so load them
// manually...
function IndySSL_CTX_use_PrivateKey_file_PKCS12(ctx: PSSL_CTX; const AFileName: String): TIdC_INT;
var
LM: TMemoryStream;
B: PBIO;
LKey: PEVP_PKEY;
LCert: PX509;
P12: PPKCS12;
CertChain: PSTACK_OF_X509;
LPassword: array of TIdAnsiChar;
LPasswordPtr: PIdAnsiChar;
begin
Result := 0;
LM := nil;
try
LM := TMemoryStream.Create;
LM.LoadFromFile(AFileName);
except
// Surpress exception here since it's going to be called by the OpenSSL .DLL
// Follow the OpenSSL .DLL Error conventions.
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_SYS_LIB);
LM.Free;
Exit;
end;
try
B := BIO_new_mem_buf(LM.Memory, LM.Size);
if not Assigned(B) then begin
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_BUF_LIB);
Exit;
end;
try
SetLength(LPassword, MAX_SSL_PASSWORD_LENGTH+1);
LPassword[MAX_SSL_PASSWORD_LENGTH] := TIdAnsiChar(0);
LPasswordPtr := PIdAnsiChar(LPassword);
if Assigned(ctx^.default_passwd_callback) then begin
ctx^.default_passwd_callback(LPasswordPtr, MAX_SSL_PASSWORD_LENGTH, 0, ctx^.default_passwd_callback_userdata);
// TODO: check return value for failure
end else begin
// TODO: call PEM_def_callback(), like PEM_read_bio_X509() does
// when default_passwd_callback is nil
end;
P12 := d2i_PKCS12_bio(B, nil);
if not Assigned(P12) then begin
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_PKCS12_LIB);
Exit;
end;
try
CertChain := nil;
if PKCS12_parse(P12, LPasswordPtr, LKey, LCert, @CertChain) <> 1 then begin
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_PKCS12_LIB);
Exit;
end;
try
Result := SSL_CTX_use_PrivateKey(ctx, LKey);
finally
sk_pop_free(CertChain, @X509_free);
X509_free(LCert);
EVP_PKEY_free(LKey);
end;
finally
PKCS12_free(P12);
end;
finally
BIO_free(B);
end;
finally
FreeAndNil(LM);
end;
end;
function IndySSL_CTX_use_certificate_file_PKCS12(ctx: PSSL_CTX; const AFileName: String): TIdC_INT;
var
LM: TMemoryStream;
B: PBIO;
LCert: PX509;
P12: PPKCS12;
PKey: PEVP_PKEY;
CertChain: PSTACK_OF_X509;
LPassword: array of TIdAnsiChar;
LPasswordPtr: PIdAnsiChar;
begin
Result := 0;
LM := nil;
try
LM := TMemoryStream.Create;
LM.LoadFromFile(AFileName);
except
// Surpress exception here since it's going to be called by the OpenSSL .DLL
// Follow the OpenSSL .DLL Error conventions.
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_SYS_LIB);
LM.Free;
Exit;
end;
try
B := BIO_new_mem_buf(LM.Memory, LM.Size);
if not Assigned(B) then begin
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_BUF_LIB);
Exit;
end;
try
SetLength(LPassword, MAX_SSL_PASSWORD_LENGTH+1);
LPassword[MAX_SSL_PASSWORD_LENGTH] := TIdAnsiChar(0);
LPasswordPtr := PIdAnsiChar(LPassword);
if Assigned(ctx^.default_passwd_callback) then begin
ctx^.default_passwd_callback(LPasswordPtr, MAX_SSL_PASSWORD_LENGTH, 0, ctx^.default_passwd_callback_userdata);
// TODO: check return value for failure
end else begin
// TODO: call PEM_def_callback(), like PEM_read_bio_X509() does
// when default_passwd_callback is nil
end;
P12 := d2i_PKCS12_bio(B, nil);
if not Assigned(P12) then
begin
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_PKCS12_LIB);
Exit;
end;