-
-
Notifications
You must be signed in to change notification settings - Fork 120
/
mormot.crypt.secure.pas
7136 lines (6418 loc) · 247 KB
/
mormot.crypt.secure.pas
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
/// Framework Core Authentication and Security Features
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.crypt.secure;
{
*****************************************************************************
Authentication and Security types shared by all framework units
- TSyn***Password and TSynConnectionDefinition Classes
- Reusable Authentication Classes
- High-Level TSynSigner/TSynHasher Multi-Algorithm Wrappers
- Client and Server HTTP Access Authentication
- 64-bit TSynUniqueIdentifier and its efficient Generator
- IProtocol Safe Communication with Unilateral or Mutual Authentication
- TBinaryCookieGenerator Simple Cookie Generator
- Rnd/Hash/Sign/Cipher/Asym/Cert/Store High-Level Algorithms Factories
- Minimal PEM/DER Encoding/Decoding
- Windows Executable Digital Signature Stuffing
Uses optimized mormot.crypt.core.pas for its actual cryptographic process.
*****************************************************************************
}
interface
{$I ..\mormot.defines.inc}
uses
sysutils,
classes,
mormot.core.base,
mormot.core.os,
mormot.core.rtti,
mormot.core.unicode,
mormot.core.text,
mormot.core.datetime,
mormot.core.buffers,
mormot.core.data,
mormot.core.variants,
mormot.core.json,
mormot.crypt.core;
{ ***************** TSyn***Password and TSynConnectionDefinition Classes }
type
/// abstract TSynPersistent class allowing safe storage of a password
// - the associated Password, e.g. for storage or transmission encryption
// will be persisted encrypted with a private key (which can be customized)
// - if default simple symmetric encryption is not enough, it will also
// read passwords strongly obfuscated for a given user using
// mormot.crypt.core.pas' CryptDataForCurrentUser()
// - a published property should be defined as such in inherited class:
// ! property PasswordPropertyName: RawUtf8 read fPassword write fPassword;
// - use the PassWordPlain property to access to its uncyphered value
TSynPersistentWithPassword = class(TSynPersistent)
protected
fPassWord: SpiUtf8;
fKey: cardinal;
function GetKey: cardinal;
{$ifdef HASINLINE}inline;{$endif}
function GetPassWordPlain: SpiUtf8;
function GetPassWordPlainInternal(AppSecret: RawUtf8): SpiUtf8;
procedure SetPassWordPlain(const Value: SpiUtf8);
public
/// finalize the instance
destructor Destroy; override;
/// this class method could be used to compute the encrypted password,
// ready to be stored as JSON, according to a given private key
class function ComputePassword(const PlainPassword: SpiUtf8;
CustomKey: cardinal = 0): SpiUtf8; overload;
/// this class method could be used to compute the encrypted password from
// a binary digest, ready to be stored as JSON, according to a given private key
// - just a wrapper around ComputePassword(BinToBase64Uri())
class function ComputePassword(PlainPassword: pointer; PlainPasswordLen: integer;
CustomKey: cardinal = 0): SpiUtf8; overload;
/// this class method could be used to decrypt a password, stored as JSON,
// according to a given private key
// - may trigger a ECrypt if the password was stored using hardened
// CryptDataForCurrentUser, and the current user doesn't match the
// expected user stored in the field
class function ComputePlainPassword(const CypheredPassword: SpiUtf8;
CustomKey: cardinal = 0; const AppSecret: RawUtf8 = ''): SpiUtf8;
/// the private key used to cypher the password storage on serialization
// - application can override the default 0 value at runtime
property Key: cardinal
read GetKey write fKey;
/// access to the associated unencrypted Password value
// - may trigger a ECrypt if the password was stored using hardened
// CryptDataForCurrentUser, and the current user doesn't match the
// expected user stored in the field
property PasswordPlain: SpiUtf8
read GetPassWordPlain write SetPassWordPlain;
end;
type
/// could be used to store a credential pair, as user name and password
// - password will be stored with TSynPersistentWithPassword encryption
TSynUserPassword = class(TSynPersistentWithPassword)
protected
fUserName: RawUtf8;
published
/// the associated user name
property UserName: RawUtf8
read fUserName write fUserName;
/// the associated encrypted password
// - use the PasswordPlain public property to access to the uncrypted password
property Password: SpiUtf8
read fPassword write fPassword;
end;
/// handle safe storage of any connection properties
// - would be used by mormot.db to serialize TSqlDBConnectionProperties, or
// by mormot.rest.core.pas to serialize TRest instances
// - the password will be stored as Base64, after a simple encryption as
// defined by TSynPersistentWithPassword
// - typical content could be:
// $ {
// $ "Kind": "TSqlDBSQLite3ConnectionProperties",
// $ "ServerName": "server",
// $ "DatabaseName": "",
// $ "User": "",
// $ "Password": "PtvlPA=="
// $ }
// - the "Kind" value will be used to let the corresponding TRest or
// TSqlDBConnectionProperties NewInstance*() class methods create the
// actual instance, from its class name
TSynConnectionDefinition = class(TSynPersistentWithPassword)
protected
fKind: string;
fServerName: RawUtf8;
fDatabaseName: RawUtf8;
fUser: RawUtf8;
public
/// unserialize the database definition from JSON
// - as previously serialized with the SaveToJson method
// - you can specify a custom Key used for password encryption, if the
// default value is not safe enough for you
constructor CreateFromJson(const Json: RawUtf8; Key: cardinal = 0); virtual;
/// serialize the database definition as JSON
function SaveToJson: RawUtf8; virtual;
published
/// the class name implementing the connection or TRest instance
// - will be used to instantiate the expected class type
property Kind: string
read fKind write fKind;
/// the associated server name (or file, for SQLite3) to be connected to
property ServerName: RawUtf8
read fServerName write fServerName;
/// the associated database name (if any), or additional options
property DatabaseName: RawUtf8
read fDatabaseName write fDatabaseName;
/// the associated User Identifier (if any)
property User: RawUtf8
read fUser write fUser;
/// the associated Password, e.g. for storage or transmission encryption
// - will be persisted encrypted with a private key
// - use the PassWordPlain property to access to its uncyphered value
property Password: SpiUtf8
read fPassword write fPassword;
end;
/// simple symmetric obfuscation scheme using a 32-bit key
// - used e.g. by TSynPersistentWithPassword and mormot.db.proxy to obfuscate
// password or content - so it is not a real encryption
// - fast, but not cryptographically secure, since naively xor data bytes with
// crc32ctab[]: consider using mormot.crypt.core proven algorithms instead
procedure SymmetricEncrypt(key: cardinal; var data: RawByteString);
{ ***************** Reusable Authentication Classes }
type
/// class-reference type (metaclass) of an authentication class
TSynAuthenticationClass = class of TSynAuthenticationAbstract;
/// abstract authentication class, implementing safe token/challenge security
// and a list of active sessions
// - do not use this class, but plain TSynAuthentication
TSynAuthenticationAbstract = class
protected
fSessions: TIntegerDynArray;
fSessionsCount: integer;
fSessionGenerator: integer;
fTokenSeed: Int64;
fSafe: TSynLocker;
function ComputeCredential(previous: boolean;
const UserName, PassWord: RawUtf8): cardinal; virtual;
function GetPassword(const UserName: RawUtf8;
out Password: RawUtf8): boolean; virtual; abstract;
function GetUsersCount: integer; virtual; abstract;
// check the given Hash challenge, against stored credentials
function CheckCredentials(const UserName: RawUtf8; Hash: cardinal): boolean; virtual;
public
/// initialize the authentication scheme
constructor Create;
/// finalize the authentation
destructor Destroy; override;
/// register one credential for a given user
// - this abstract method will raise an exception: inherited classes should
// implement them as expected
procedure AuthenticateUser(const aName, aPassword: RawUtf8); virtual;
/// unregister one credential for a given user
// - this abstract method will raise an exception: inherited classes should
// implement them as expected
procedure DisauthenticateUser(const aName: RawUtf8); virtual;
/// create a new session
// - should return 0 on authentication error, or an integer session ID
// - this method will check the User name and password, and create a new session
function CreateSession(const User: RawUtf8; Hash: cardinal): integer; virtual;
/// check if the session exists in the internal list
function SessionExists(aID: integer): boolean;
/// delete a session
procedure RemoveSession(aID: integer);
/// returns the current identification token
// - to be sent to the client for its authentication challenge
function CurrentToken: Int64;
/// the number of current opened sessions
property SessionsCount: integer
read fSessionsCount;
/// the number of registered users
property UsersCount: integer
read GetUsersCount;
/// to be used to compute a Hash on the client sude, for a given Token
// - the token should have been retrieved from the server, and the client
// should compute and return this hash value, to perform the authentication
// challenge and create the session
// - internal algorithm is not cryptographic secure, but fast and safe
class function ComputeHash(Token: Int64;
const UserName, PassWord: RawUtf8): cardinal; virtual;
end;
/// simple authentication class, implementing safe token/challenge security
// - maintain a list of user / name credential pairs, and a list of sessions
// - is not meant to handle authorization, just plain user access validation
// - used e.g. by TSqlDBConnection.RemoteProcessMessage (on server side) and
// TSqlDBProxyConnectionPropertiesAbstract (on client side) in mormot.db.proxy
TSynAuthentication = class(TSynAuthenticationAbstract)
protected
fCredentials: TSynNameValue; // store user/password pairs
function GetPassword(const UserName: RawUtf8;
out Password: RawUtf8): boolean; override;
function GetUsersCount: integer; override;
public
/// initialize the authentication scheme
// - you can optionally register one user credential
constructor Create(const aUserName: RawUtf8 = '';
const aPassword: RawUtf8 = ''); reintroduce;
/// register one credential for a given user
procedure AuthenticateUser(const aName, aPassword: RawUtf8); override;
/// unregister one credential for a given user
procedure DisauthenticateUser(const aName: RawUtf8); override;
end;
type
/// optimized thread-safe storage of a list of IP v4 adresses
// - can be used e.g. as white-list or black-list of clients
// - will maintain internally a sorted list of 32-bit integers for fast lookup
// - with optional binary persistence
// - as used by TRestServer.BanIP/JwtForUnauthenticatedRequestWhiteIP
// - see also more efficient and lower level THttpAcceptBan in mormot.net.http
TIPBan = class(TSynPersistentStore)
protected
fIP4: TIntegerDynArray;
fCount: integer;
procedure LoadFromReader; override;
procedure SaveToWriter(aWriter: TBufferWriter); override;
public
/// register one IP to the list
function Add(const aIP: RawUtf8): boolean;
/// unregister one IP to the list
function Delete(const aIP: RawUtf8): boolean;
/// returns true if the IP is in the list
function Exists(const aIP: RawUtf8): boolean;
/// creates a TDynArray wrapper around the stored list of values
// - could be used e.g. for binary persistence
// - warning: caller should make Safe.Unlock(aLock) when finished
function DynArrayLocked(aLock: TRWLockContext = cWrite): TDynArray;
/// low-level access to the internal IPv4 list
// - 32-bit unsigned values are sorted, for fast O(log(n)) binary search
property IP4: TIntegerDynArray
read fIP4;
published
/// how many IPs are currently banned
property Count: integer
read fCount;
end;
{ **************** 64-bit TSynUniqueIdentifier and its Efficient Generator }
type
/// 64-bit integer unique identifier, as computed by TSynUniqueIdentifierGenerator
// - they are increasing over time (so are much easier to store/shard/balance
// than UUID/GUID), and contain generation time and a 16-bit process ID
// - mapped by TSynUniqueIdentifierBits memory structure
// - may be used on client side for something similar to a MongoDB ObjectID,
// but compatible with TOrm.ID: TID properties
TSynUniqueIdentifier = type TID;
/// 16-bit unique process identifier, used to compute TSynUniqueIdentifier
// - each TSynUniqueIdentifierGenerator instance is expected to have
// its own unique process identifier, stored as a 16-bit integer 0..65535 value
TSynUniqueIdentifierProcess = type word;
{$A-}
/// map 64-bit integer unique identifier internal memory structure
// - as stored in TSynUniqueIdentifier = TID = Int64 values, and computed by
// TSynUniqueIdentifierGenerator
// - bits 0..14 map a 15-bit increasing counter (collision-free)
// - bits 15..30 map a 16-bit process identifier
// - bits 31..63 map a 33-bit UTC time, encoded as seconds since Unix epoch
TSynUniqueIdentifierBits = object
public
/// the actual 64-bit storage value
// - in practice, only first 63 bits are used
Value: TSynUniqueIdentifier;
/// extract the 15-bit counter (0..32767), starting with a random value
function Counter: word;
{$ifdef HASINLINE}inline;{$endif}
/// extract the 16-bit unique process identifier
// - as specified to TSynUniqueIdentifierGenerator constructor
function ProcessID: TSynUniqueIdentifierProcess;
{$ifdef HASINLINE}inline;{$endif}
/// extract the UTC generation timestamp as seconds since the Unix epoch
// - time is expressed in Coordinated Universal Time (UTC), not local time
// - it uses in fact a 33-bit resolution, so is "Year 2038" bug-free
function CreateTimeUnix: TUnixTime;
{$ifdef HASINLINE}inline;{$endif}
/// extract the UTC generation timestamp as TDateTime
// - time is expressed in Coordinated Universal Time (UTC), not local time
function CreateDateTime: TDateTime;
{$ifdef HASINLINE}inline;{$endif}
/// extract the UTC generation timestamp as our TTimeLog
// - time is expressed in Coordinated Universal Time (UTC), not local time
function CreateTimeLog: TTimeLog;
/// fill this unique identifier structure from its TSynUniqueIdentifier value
// - is just a wrapper around PInt64(@self)^
procedure From(const aID: TSynUniqueIdentifier);
{$ifdef HASINLINE}inline;{$endif}
/// fill this unique identifier back from a 16 chars hexadecimal string
// - returns TRUE if the supplied hexadecimal is on the expected format
// - returns FALSE if the supplied text is invalid
function FromHexa(const hexa: RawUtf8): boolean;
/// fill this unique identifier with a fake value corresponding to a given
// timestamp
// - may be used e.g. to limit database queries on a particular time range
// - bits 0..30 would be 0, i.e. would set Counter = 0 and ProcessID = 0
procedure FromDateTime(const aDateTime: TDateTime);
/// fill this unique identifier with a fake value corresponding to a given
// timestamp
// - may be used e.g. to limit database queries on a particular time range
// - bits 0..30 would be 0, i.e. would set Counter = 0 and ProcessID = 0
procedure FromUnixTime(const aUnixTime: TUnixTime);
/// compare two Identifiers
function Equal(const Another: TSynUniqueIdentifierBits): boolean;
{$ifdef HASINLINE}inline;{$endif}
/// convert the identifier into a 16 chars hexadecimal string
function ToHexa: RawUtf8;
{$ifdef HASINLINE}inline;{$endif}
/// convert this identifier as an explicit TDocVariant JSON object
// - returns e.g.
// ! {"Created":"2016-04-19T15:27:58","Identifier":1,"Counter":1,
// ! "Value":3137644716930138113,"Hex":"2B8B273F00008001"}
function AsVariant: variant;
{$ifdef HASINLINE}inline;{$endif}
/// convert this identifier to an explicit TDocVariant JSON object
// - returns e.g.
// ! {"Created":"2016-04-19T15:27:58","Identifier":1,"Counter":1,
// ! "Value":3137644716930138113,"Hex":"2B8B273F00008001"}
procedure ToVariant(out Result: variant);
end;
{$A+}
/// points to a 64-bit integer identifier, as computed by TSynUniqueIdentifierGenerator
// - may be used to access the identifier internals, from its stored
// Int64 or TSynUniqueIdentifier value
PSynUniqueIdentifierBits = ^TSynUniqueIdentifierBits;
/// a 24 chars cyphered hexadecimal string, mapping a TSynUniqueIdentifier
// - has handled by TSynUniqueIdentifierGenerator.ToObfuscated/FromObfuscated
TSynUniqueIdentifierObfuscated = type RawUtf8;
/// thread-safe 64-bit integer unique identifier computation
// - may be used on client side for something similar to a MongoDB ObjectID,
// but compatible with TOrm.ID: TID properties, since it will contain
// a 63-bit unsigned integer, following our ORM expectations
// - each identifier would contain a 16-bit process identifier, which is
// supplied by the application, and should be unique for this process at a
// given time
// - identifiers may be obfuscated as hexadecimal text, using both encryption
// and digital signature
// - all its methods are thread-safe, even during obfuscation processing
TSynUniqueIdentifierGenerator = class(TSynPersistent)
protected
fSafe: TLightLock;
fUnixCreateTime: cardinal;
fLatestCounterOverflowUnixCreateTime: cardinal;
fIdentifier: TSynUniqueIdentifierProcess;
fIdentifierShifted: cardinal;
fLastCounter: cardinal;
fComputedCount: Int64;
fCollisions: cardinal;
fCryptoCRC: cardinal;
fCrypto: array[0..7] of cardinal; // only fCrypto[6..7] are used in practice
fCryptoAesE, fCryptoAesD: TAes; // Initialized if aSharedObfuscationKeyNewKdf
public
/// initialize the generator for the given 16-bit process identifier
// - you can supply an obfuscation key, which should be shared for the
// whole system, so that you may use FromObfuscated/ToObfuscated methods
// - if aSharedObfuscationKeyNewKdf is > 0, indicates the rounds count for
// a safer AES/SHA3 algorithm used for the obfuscation cryptography - keep
// it as default 0 for mORMot 1.18 backward compatibility
constructor Create(aIdentifier: TSynUniqueIdentifierProcess;
const aSharedObfuscationKey: RawUtf8 = '';
aSharedObfuscationKeyNewKdf: integer = 0); reintroduce;
/// finalize the generator structure
destructor Destroy; override;
/// return a new unique ID
// - this method is very optimized, and would use very little CPU
procedure ComputeNew(out result: TSynUniqueIdentifierBits); overload;
/// return a new unique ID, type-casted to an Int64
function ComputeNew: Int64; overload;
{$ifdef HASINLINE}inline;{$endif}
/// return an ID matching this generator pattern, at a given timestamp
// - may be used e.g. to limit database queries on a particular time range
// - the ID is not guaranted to be unique, but match the supplied TDateTime
procedure ComputeFromDateTime(const aDateTime: TDateTime;
out result: TSynUniqueIdentifierBits);
/// return an ID matching this generator pattern, at a given timestamp
// - may be used e.g. to limit database queries on a particular time range
// - the ID is not guaranted to be unique, but match the supplied TUnixTime
procedure ComputeFromUnixTime(const aUnixTime: TUnixTime;
out result: TSynUniqueIdentifierBits);
/// map a TSynUniqueIdentifier as 24/32 chars cyphered hexadecimal text
// - cyphering includes simple key-based encryption and a CRC-32 digital signature
// - returned text size is 24 for the legacy format, and 32 chars if
// aSharedObfuscationKeyNewKdf was set to true
function ToObfuscated(
const aIdentifier: TSynUniqueIdentifier): TSynUniqueIdentifierObfuscated;
/// retrieve a TSynUniqueIdentifier from 24/32 chars cyphered hexadecimal text
// - any file extension (e.g. '.jpeg') would be first deleted from the
// supplied obfuscated text
// - returns true if the supplied obfuscated text has the expected layout
// and a valid digital signature
// - returns false if the supplied obfuscated text is invalid
// - note that this method will work for any TSynUniqueIdentifierProcess
// of the same aSharedObfuscationKey - not only the Identifier of this node
function FromObfuscated(const aObfuscated: TSynUniqueIdentifierObfuscated;
out aIdentifier: TSynUniqueIdentifier): boolean;
/// paranoid loop until LastUnixCreateTime
// - may be called at server shutdown, if you expect a lot of collisions,
// and want to ensure the "fake" timestamp match the time at server restart
procedure WaitForSafeCreateTime(TimeOutSeconds: integer = 30);
/// some 32-bit value, derivated from aSharedObfuscationKey as supplied
// to the class constructor
// - FromObfuscated and ToObfuscated methods will validate their hexadecimal
// content with this value to secure the associated CRC
// - may be used e.g. as system-depending salt
property CryptoCRC: cardinal
read fCryptoCRC;
/// direct access to the associated mutex
property Safe: TLightLock
read fSafe;
published
/// the process identifier, associated with this generator
property Identifier: TSynUniqueIdentifierProcess
read fIdentifier;
/// how many times ComputeNew method has been called
property ComputedCount: Int64
read fComputedCount;
/// how many times ComputeNew method did have a collision and a fake
// increased timestamp has been involved
property Collisions: cardinal
read fCollisions;
/// low-level access to the last generated timestamp
// - you may need to persist this value if a lot of Collisions happened, and
// the timestamp was faked - you may also call WaitForSafeCreateTime
property LastUnixCreateTime: cardinal
read fUnixCreateTime write fUnixCreateTime;
end;
/// hold a dynamic array of TSynUniqueIdentifierGenerator instances
TSynUniqueIdentifierGenerators = array of TSynUniqueIdentifierGenerator;
{ **************** High-Level TSynSigner/TSynHasher Multi-Algorithm Wrappers }
{ implemented in this unit and not in mormot.crypt.core, since TSynSignerParams
expects JSON support, which requires mormot.core.json }
const
SIGNER_DEFAULT_SALT = 'I6sWioAidNnhXO9BK';
type
/// the HMAC/SHA-3 algorithms known by TSynSigner
// - HMAC/SHA-1 is considered unsafe, HMAC/SHA-2 are well proven, and
// HMAC/SHA-3 is newer but strong, so a good candidate for safety
// - saSha3S128 is used by default, i.e. SHA-3 in SHAKE_128 mode
TSignAlgo = (
saSha1,
saSha256,
saSha384,
saSha512,
saSha3224,
saSha3256,
saSha3384,
saSha3512,
saSha3S128,
saSha3S256);
/// JSON-serialization ready object as used by TSynSigner.Pbkdf2() overloaded methods
// - default value for unspecified parameters will be SHAKE_128 with
// rounds=1000 and a fixed salt
// - a typical (extended) JSON to supply to TSynSigner.Pbkdf2() may be
// ${algo:"saSha512",secret:"StrongPassword",salt:"FixedSalt",rounds:10000}
TSynSignerParams = packed record
algo: TSignAlgo;
secret, salt: RawUtf8;
rounds: integer;
end;
/// a generic wrapper object to handle digital HMAC-SHA-2/SHA-3 signatures
// - used e.g. to implement TJwtSynSignerAbstract
TSynSigner = object
private
ctxt: packed array[1..SHA3_CONTEXT_SIZE] of byte; // enough space for all
public
/// the size, in bytes, of the digital signature of this algorithm
// - potential values are 20, 28, 32, 48 and 64
SignatureSize: integer;
/// the algorithm used for digitial signature
Algo: TSignAlgo;
/// initialize the digital HMAC/SHA-3 signing context with some secret text
procedure Init(aAlgo: TSignAlgo; const aSecret: RawUtf8); overload;
/// initialize the digital HMAC/SHA-3 signing context with some secret binary
procedure Init(aAlgo: TSignAlgo; aSecret: pointer; aSecretLen: integer); overload;
/// initialize the digital HMAC/SHA-3 signing context with PBKDF2 safe
// iterative key derivation of a secret salted text
procedure Init(aAlgo: TSignAlgo; const aSecret, aSalt: RawUtf8;
aSecretPbkdf2Round: integer; aPbkdf2Secret: PHash512Rec = nil); overload;
/// process some message content supplied as memory buffer
procedure Update(aBuffer: pointer; aLen: integer); overload;
/// process some message content supplied as string
procedure Update(const aBuffer: RawByteString); overload;
{$ifdef HASINLINE}inline;{$endif}
/// returns the computed digital signature as lowercase hexadecimal text
function Final: RawUtf8; overload;
/// returns the raw computed digital signature
// - SignatureSize bytes will be written: use Signature.Lo/h0/b3/b accessors
procedure Final(out aSignature: THash512Rec;
aNoInit: boolean = false); overload;
/// one-step digital signature of a buffer as lowercase hexadecimal string
function Full(aAlgo: TSignAlgo; const aSecret: RawUtf8;
aBuffer: Pointer; aLen: integer): RawUtf8; overload;
/// one-step digital signature of a buffer with PBKDF2 derivation
function Full(aAlgo: TSignAlgo; const aSecret, aSalt: RawUtf8;
aSecretPbkdf2Round: integer; aBuffer: Pointer; aLen: integer): RawUtf8; overload;
/// convenient wrapper to perform PBKDF2 safe iterative key derivation
procedure Pbkdf2(aAlgo: TSignAlgo; const aSecret, aSalt: RawUtf8;
aSecretPbkdf2Round: integer; out aDerivatedKey: THash512Rec); overload;
/// convenient wrapper to perform PBKDF2 safe iterative key derivation
procedure Pbkdf2(const aParams: TSynSignerParams;
out aDerivatedKey: THash512Rec); overload;
/// convenient wrapper to perform PBKDF2 safe iterative key derivation
// - accept as input a TSynSignerParams serialized as JSON object e.g.
// ${algo:"saSha512",secret:"StrongPassword",salt:"FixedSalt",rounds:10000}
procedure Pbkdf2(aParamsJson: PUtf8Char; aParamsJsonLen: integer;
out aDerivatedKey: THash512Rec;
const aDefaultSalt: RawUtf8 = SIGNER_DEFAULT_SALT;
aDefaultAlgo: TSignAlgo = saSha3S128); overload;
/// convenient wrapper to perform PBKDF2 safe iterative key derivation
// - accept as input a TSynSignerParams serialized as JSON object e.g.
// ${algo:"saSha512",secret:"StrongPassword",salt:"FixedSalt",rounds:10000}
procedure Pbkdf2(const aParamsJson: RawUtf8;
out aDerivatedKey: THash512Rec;
const aDefaultSalt: RawUtf8 = SIGNER_DEFAULT_SALT;
aDefaultAlgo: TSignAlgo = saSha3S128); overload;
/// prepare a TAes object with the key derivated via a Pbkdf2() call
// - aDerivatedKey is defined as "var", since it will be zeroed after use
procedure AssignTo(var aDerivatedKey: THash512Rec;
out aAes: TAes; aEncrypt: boolean);
/// fill the internal context with zeros, for security
procedure Done;
end;
/// reference to a TSynSigner wrapper object
PSynSigner = ^TSynSigner;
/// hash algorithms available for HashFile/HashFull functions
// and TSynHasher object
THashAlgo = (
hfMD5,
hfSHA1,
hfSHA256,
hfSHA384,
hfSHA512,
hfSHA512_256,
hfSHA3_256,
hfSHA3_512);
/// set of algorithms available for HashFile/HashFull functions and TSynHasher object
THashAlgos = set of THashAlgo;
/// convenient multi-algorithm hashing wrapper
// - as used e.g. by HashFile/HashFull functions
// - we defined a record instead of a class, to allow stack allocation and
// thread-safe reuse of one initialized instance
TSynHasher = object
private
fAlgo: THashAlgo;
ctxt: array[1..SHA3_CONTEXT_SIZE] of byte; // enough space for all algorithms
public
/// initialize the internal hashing structure for a specific algorithm
// - returns false on unknown/unsupported algorithm
function Init(aAlgo: THashAlgo): boolean;
/// hash the supplied memory buffer
procedure Update(aBuffer: Pointer; aLen: integer); overload;
/// hash the supplied string content
procedure Update(const aBuffer: RawByteString); overload;
{$ifdef HASINLINE}inline;{$endif}
/// hash the supplied strings content
procedure Update(const aBuffer: array of RawByteString); overload;
/// returns the resulting hash as lowercase hexadecimal string
procedure Final(var aResult: RawUtf8); overload;
/// set the resulting hash into a binary buffer, and the size as result
function Final(out aDigest: THash512Rec): integer; overload;
/// one-step hash computation of a buffer as lowercase hexadecimal string
function Full(aAlgo: THashAlgo; aBuffer: Pointer; aLen: integer): RawUtf8; overload;
/// one-step hash computation of a buffer as lowercase hexadecimal string
function Full(aAlgo: THashAlgo; const aBuffer: RawByteString): RawUtf8; overload;
/// one-step hash computation of several buffers as lowercase hexadecimal string
procedure Full(aAlgo: THashAlgo; const aBuffer: array of RawByteString;
var aResult: RawUtf8); overload;
/// one-step hash computation of a buffer as a binary buffer
function Full(aAlgo: THashAlgo; aBuffer: Pointer; aLen: integer;
out aDigest: THash512Rec): integer; overload;
/// returns the number of bytes of the hash of the current Algo
function HashSize: integer;
/// the hash algorithm used by this instance
property Algo: THashAlgo
read fAlgo;
end;
/// TStreamRedirect with TSynHasher cryptographic hashing
// - do not use this abstract class but inherited with overloaded GetAlgo
TStreamRedirectSynHasher = class(TStreamRedirect)
protected
fHash: TSynHasher;
class function GetAlgo: THashAlgo; virtual; abstract;
procedure DoHash(data: pointer; len: integer); override;
public
constructor Create(aDestination: TStream; aRead: boolean = false); override;
function GetHash: RawUtf8; override;
class function GetHashFileExt: RawUtf8; override;
end;
/// TStreamRedirect with MD5 cryptographic hashing
TStreamRedirectMd5 = class(TStreamRedirectSynHasher)
protected
class function GetAlgo: THashAlgo; override;
end;
/// TStreamRedirect with SHA-1 cryptographic hashing
TStreamRedirectSha1 = class(TStreamRedirectSynHasher)
protected
class function GetAlgo: THashAlgo; override;
end;
/// TStreamRedirect with SHA-256 cryptographic hashing
TStreamRedirectSha256 = class(TStreamRedirectSynHasher)
protected
class function GetAlgo: THashAlgo; override;
end;
/// TStreamRedirect with SHA-384 cryptographic hashing
TStreamRedirectSha384 = class(TStreamRedirectSynHasher)
protected
class function GetAlgo: THashAlgo; override;
end;
/// TStreamRedirect with SHA-512 cryptographic hashing
TStreamRedirectSha512 = class(TStreamRedirectSynHasher)
protected
class function GetAlgo: THashAlgo; override;
end;
/// TStreamRedirect with SHA-512/256 cryptographic hashing
TStreamRedirectSha512_256 = class(TStreamRedirectSynHasher)
protected
class function GetAlgo: THashAlgo; override;
end;
/// TStreamRedirect with SHA-3-256 cryptographic hashing
TStreamRedirectSha3_256 = class(TStreamRedirectSynHasher)
protected
class function GetAlgo: THashAlgo; override;
end;
/// TStreamRedirect with SHA-3-512 cryptographic hashing
TStreamRedirectSha3_512 = class(TStreamRedirectSynHasher)
protected
class function GetAlgo: THashAlgo; override;
end;
/// the known 32-bit crc algorithms as returned by CryptCrc32()
// - ccaCrc32 and ccaAdler32 require mormot.lib.z.pas to be included
// - AesNiHash() is not part of it, because it is not cross-platform, and
// randomly seeded at process startup
TCrc32Algo = (
caCrc32c,
caCrc32,
caAdler32,
caxxHash32,
caFnv32);
/// returns the 32-bit crc function for a given algorithm
// - may return nil, e.g. for caCrc32/caAdler32 when mormot.lib.z is not loaded
function CryptCrc32(algo: TCrc32Algo): THasher;
function ToText(algo: TSignAlgo): PShortString; overload;
function ToText(algo: THashAlgo): PShortString; overload;
function ToText(algo: TCrc32Algo): PShortString; overload;
/// compute the hexadecimal hash of any (big) file
// - using a temporary buffer of 1MB for the sequential reading
function HashFile(const aFileName: TFileName; aAlgo: THashAlgo): RawUtf8; overload;
/// compute one or several hexadecimal hash(es) of any (big) file
// - using a temporary buffer of 1MB for the sequential reading
function HashFileRaw(const aFileName: TFileName; aAlgos: THashAlgos): TRawUtf8DynArray;
/// compute the hexadecimal hashe(s) of one file, as external .md5/.sha256/.. files
// - generate the text hash files in the very same folder
procedure HashFile(const aFileName: TFileName; aAlgos: THashAlgos); overload;
/// one-step hash computation of a buffer as lowercase hexadecimal string
function HashFull(aAlgo: THashAlgo; aBuffer: Pointer; aLen: integer): RawUtf8; overload;
/// one-step hash computation of a buffer as lowercase hexadecimal string
function HashFull(aAlgo: THashAlgo; const aBuffer: RawByteString): RawUtf8; overload;
/// compute the MD5 checksum of a given file
// - this function maps the THashFile signature as defined in mormot.core.buffers
function HashFileMd5(const FileName: TFileName): RawUtf8;
/// compute the SHA-1 checksum of a given file
// - this function maps the THashFile signature as defined in mormot.core.buffers
function HashFileSha1(const FileName: TFileName): RawUtf8;
/// compute the SHA-256 checksum of a given file
// - this function maps the THashFile signature as defined in mormot.core.buffers
function HashFileSha256(const FileName: TFileName): RawUtf8;
/// compute the SHA-384 checksum of a given file
// - this function maps the THashFile signature as defined in mormot.core.buffers
function HashFileSha384(const FileName: TFileName): RawUtf8;
/// compute the SHA-512 checksum of a given file
// - this function maps the THashFile signature as defined in mormot.core.buffers
function HashFileSha512(const FileName: TFileName): RawUtf8;
/// compute the SHA-512/256 checksum of a given file
// - this function maps the THashFile signature as defined in mormot.core.buffers
function HashFileSha512_256(const FileName: TFileName): RawUtf8;
/// compute the SHA-3-256 checksum of a given file
// - this function maps the THashFile signature as defined in mormot.core.buffers
function HashFileSha3_256(const FileName: TFileName): RawUtf8;
/// compute the SHA-3-512 checksum of a given file
// - this function maps the THashFile signature as defined in mormot.core.buffers
function HashFileSha3_512(const FileName: TFileName): RawUtf8;
{ **************** Client and Server HTTP Access Authentication }
type
/// the exception class raised during Digest access authentication
EDigest = class(ESynException);
/// the Digest access authentication supported algorithms
// - match the three official algorithms as registered by RFC 7616, with the
// addition of the unstandard (but safe) SHA3-256 algorithm
TDigestAlgo = (
daUndefined,
daMD5,
daMD5_Sess,
daSHA256,
daSHA256_Sess,
daSHA512_256,
daSHA512_256_Sess,
daSHA3_256,
daSHA3_256_Sess);
/// compute the Digest access authentication client code for a given algorithm
// - as defined in https://en.wikipedia.org/wiki/Digest_access_authentication
// - FromServer is the 'xxx' encoded value from 'WWW-Authenticate: Digest xxx'
// - may return '' if Algo does not match algorithm=... value (MD5 or SHA-256)
// - DigestUriName is customized as "digest-uri" e.g. for LDAP digest auth
function DigestClient(Algo: TDigestAlgo;
const FromServer, DigestMethod, DigestUri, UserName: RawUtf8;
const Password: SpiUtf8; const DigestUriName: RawUtf8 = 'uri'): RawUtf8;
/// extract the Digest access authentication realm on client side
// - FromServer is the 'xxx' encoded value from 'WWW-Authenticate: Digest xxx'
// - could be proposed to the user interation UI to specify the auth context
function DigestRealm(const FromServer: RawUtf8): RawUtf8;
/// compute the Basic access authentication client code
// - as defined in https://en.wikipedia.org/wiki/Basic_access_authentication
function BasicClient(const UserName: RawUtf8; const Password: SpiUtf8): RawUtf8;
/// extract the Basic access authentication realm on client side
// - FromServer is the 'xxx' encoded value from 'WWW-Authenticate: Basic xxx'
// - could be proposed to the user interation UI to specify the auth context
function BasicRealm(const FromServer: RawUtf8): RawUtf8;
/// compute the HA0 for a given set of Digest access credentials
function DigestHA0(Algo: TDigestAlgo; const UserName, Realm: RawUtf8;
const Password: SpiUtf8; out HA0: THash512Rec): integer;
const
/// the Digest access authentication processing cryptographic algorithms
DIGEST_ALGO: array[daMD5.. high(TDigestAlgo)] of THashAlgo = (
hfMD5, // daMD5
hfMD5, // daMD5_Sess
hfSHA256, // daSHA256
hfSHA256, // daSHA256_Sess
hfSHA512_256, // daSHA512_256
hfSHA512_256, // daSHA512_256_Sess
hfSHA3_256, // daSHA3_256
hfSHA3_256); // daSHA3_256_Sess
/// the Digest access authentication algorithm name as used during handshake
DIGEST_NAME: array[daMD5.. high(TDigestAlgo)] of RawUtf8 = (
'MD5', // daMD5
'MD5-sess', // daMD5_Sess
'SHA-256', // daSHA256
'SHA-256-sess', // daSHA256_Sess
'SHA-512-256', // daSHA512_256
'SHA-512-256-sess', // daSHA512_256_Sess
'SHA3-256', // daSHA3_256
'SHA3-256-sess'); // daSHA3_256_Sess
/// the Digest access authentication algorithms which hashes the session info
// - i.e. includes nonce, cnonce (and authzid) to the hashed response
// - it is slightly slower, but much safer, and recommended in our use case
// of authentication, not authorization
DIGEST_SESS = [daMD5_Sess, daSHA256_Sess, daSHA512_256_Sess, daSHA3_256_Sess];
/// initiate a Digest access authentication from server side for a given algorithm
// - Opaque could be e.g. an obfuscated HTTP connection ID to avoid MiM attacks
// - Prefix/Suffix are typically 'WWW-Authenticate: Digest ' and #13#10 to
// construct a HTTP header
function DigestServerInit(Algo: TDigestAlgo;
const QuotedRealm, Prefix, Suffix: RawUtf8; Opaque: Int64; Tix64: Int64 = 0): RawUtf8;
type
/// the result of IBasicAuthServer.CreckCredential() internal method
TAuthServerResult = (
asrUnknownUser,
asrIncorrectPassword,
asrRejected,
asrMatch);
/// callback event able to return the HA0 binary from a username
// - called by DigestServerAuth() e.g. to lookup from a local .htdigest file
// - should return the hash size in bytes, or 0 if User is unknown
// - is typically implemented via DigestHA0() wrapper function
TOnDigestServerAuthGetUserHash = function(
const User, Realm: RawUtf8; out HA0: THash512Rec): TAuthServerResult of object;
/// validate a Digest access authentication on server side
// - returns true and the user/uri from a valid input token, or false on error
function DigestServerAuth(Algo: TDigestAlgo; const Realm, Method: RawUtf8;
FromClient: PUtf8Char; Opaque: Int64;
const OnSearchUser: TOnDigestServerAuthGetUserHash;
out User, Url: RawUtf8; NonceExpSec: PtrUInt; Tix64: Qword = 0): TAuthServerResult;
/// parse a Basic access authentication on server side
// - returns true and the user/password from a valid input, or false on error
function BasicServerAuth(FromClient: PUtf8Char;
out User, Password: RawUtf8): boolean;
type
/// callback event used by TBasicAuthServer.OnBeforeAuth/OnAfterAuth
// - allow to reject an user before or after its credentials are checked
// - should return true to continue, or false to abort the authentication
// and let TBasicAuthServer.CheckCredential return asrRejected
TOnAuthServer = function(Sender: TObject; const User: RawUtf8): boolean of object;
/// parent abstract HTTP access authentication on server side
// - as used e.g. by THttpServerSocketGeneric for its optional authentication
// - you should use inherited IBasicAuthServer or IDigestAuthServer interfaces
IHttpAuthServer = interface
['{036B2802-56BE-422F-9146-773702C86387}']
/// the realm associated with this access authentication
function Realm: RawUtf8;
/// retrieve the implementation class instance
function Instance: TObject;
end;
/// HTTP BASIC access authentication on server side
// - as used e.g. by THttpServerSocketGeneric for its BASIC authentication
IBasicAuthServer = interface(IHttpAuthServer)
['{5C301470-39BB-4CBB-8366-01A7F23032F2}']
/// compute a Basic server authentication request header
// - return e.g. 'WWW-Authenticate: Basic realm="Realm"'#13#10
function BasicInit: RawUtf8;
/// validate a Basic client authentication response
// - FromClient typically follow 'Authorization: Basic ' header text
function BasicAuth(FromClient: PUtf8Char; out ClientUser: RawUtf8): boolean;
/// check the stored credentials as for the TOnHttpServerBasicAuth callback
// - used for the BASIC authentication scheme in THttpServerSocketGeneric
function OnBasicAuth(aSender: TObject;
const aUser: RawUtf8; const aPassword: SpiUtf8): boolean;
/// check the credentials stored for a given user
// - returns true if supplied aUser/aPassword are correct, false otherwise
function CheckCredential(const aUser: RawUtf8;
const aPassword: SpiUtf8): TAuthServerResult;
end;
/// HTTP DIGEST access authentication on server side
// - as used e.g. by THttpServerSocketGeneric for its DIGEST authentication
IDigestAuthServer = interface(IHttpAuthServer)
['{75B1B7B8-4981-4C09-BD8C-E938A2802ED1}']
/// compute a Digest server authentication request
// - used for the DIGEST authentication scheme in THttpServerSocketGeneric
// - returns the standard HTTP header with the default Prefix/Suffix
// - Opaque is a 64-bit number, typically the THttpServerConnectionID
// - properly implemented in TDigestAuthServer: THttpAuthServer raise EDigest
function DigestInit(Opaque, Tix64: Int64;
const Prefix: RawUtf8 = 'WWW-Authenticate: Digest ';
const Suffix: RawUtf8 = #13#10): RawUtf8;
/// validate a Digest client authentication response
// - used for the DIGEST authentication scheme in THttpServerSocketGeneric
// - FromClient typically follow 'Authorization: Digest ' header text
// - Opaque should match the value supplied on previous ServerInit() call
// - properly implemented in TDigestAuthServer: THttpAuthServer raise EDigest
function DigestAuth(FromClient: PUtf8Char; const Method: RawUtf8;
Opaque, Tix64: Int64; out ClientUser, ClientUrl: RawUtf8): TAuthServerResult;
/// quickly check if the supplied client response is likely to be compatible
// - FromClient is typically a HTTP header
// - will just search for the 'algorithm=xxx,' text pattern
function DigestAlgoMatch(const FromClient: RawUtf8): boolean;
end;
/// abstract BASIC access authentication on server side
// - don't use this class but e.g. TDigestAuthServerMem or TDigestAuthServerFile
// - will implement the IBasicAuthServer process in an abstract way
TBasicAuthServer = class(TInterfacedObjectWithCustomCreate, IBasicAuthServer)
protected
fRealm, fQuotedRealm, fBasicInit: RawUtf8;
fOnBeforeAuth, fOnAfterAuth: TOnAuthServer;
fOnAfterAuthDelayed: boolean;
function BeforeAuth(Sender: TObject; const User: RawUtf8): boolean;
{$ifdef HASINLINE}inline;{$endif}
function AfterAuth(Sender: TObject; const User: RawUtf8): boolean;
{$ifdef HASINLINE}inline;{$endif}
public
/// initialize the HTTP access authentication engine
constructor Create(const aRealm: RawUtf8); reintroduce;
/// check the credentials stored for a given user
// - this is the main abstract virtual method to override for BASIC auth
// - will also trigger OnBeforeAuth/OnAfterAuth callbacks
// - returns true if supplied aUser/aPassword are correct, false otherwise
function CheckCredential(const aUser: RawUtf8;
const aPassword: SpiUtf8): TAuthServerResult; virtual; abstract;
{ -- IHttpAuthServer and IBasicAuthServer methods }
/// retrieve the realm associated with this access authentication
// - a good practice is to use the server host name or UUID as realm
function Realm: RawUtf8;
/// retrieve the implementation class instance
function Instance: TObject;
/// compute a Basic server authentication request header
// - return e.g. 'WWW-Authenticate: Basic realm="Realm"'#13#10
function BasicInit: RawUtf8;
/// validate a Basic client authentication response
function BasicAuth(FromClient: PUtf8Char; out ClientUser: RawUtf8): boolean;
/// check the stored credentials as for the TOnHttpServerBasicAuth callback
function OnBasicAuth(aSender: TObject;
const aUser: RawUtf8; const aPassword: SpiUtf8): boolean;
/// allow to reject an user before its credentials are checked
// - can implement e.g. the "search and bind" pattern on a slow LDAP server
property OnBeforeAuth: TOnAuthServer
read fOnBeforeAuth write fOnBeforeAuth;
/// allow to reject an user after its credentials are checked
property OnAfterAuth: TOnAuthServer
read fOnAfterAuth write fOnAfterAuth;
end;