-
-
Notifications
You must be signed in to change notification settings - Fork 182
Expand file tree
/
Copy pathmormot.crypt.ecc256r1.pas
More file actions
2116 lines (1895 loc) · 72.9 KB
/
Copy pathmormot.crypt.ecc256r1.pas
File metadata and controls
2116 lines (1895 loc) · 72.9 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
/// Framework Core Public-Key Cryptography with secp256r1/NISTP-256 ECC Curves
// - 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.ecc256r1;
{
*****************************************************************************
High-Performance secp256r1/NISTP-256/prime256v1 Elliptic-Curve Cryptography
- Low-Level ECC secp256r1 ECDSA and ECDH Functions
- Middle-Level Certificate-based Public Key Cryptography
If mormot.crypt.openssl.RegisterOpenSsl is called, uses faster OpenSSL.
*****************************************************************************
}
interface
{$I ..\mormot.defines.inc}
uses
classes,
sysutils,
mormot.core.base,
mormot.core.os,
mormot.core.rtti,
mormot.core.unicode,
mormot.core.text,
mormot.core.buffers, // for Base64 and baudot encoding
mormot.core.datetime,
mormot.crypt.core;
{ ***************** Low-Level ECC secp256r1 ECDSA and ECDH Functions }
const
/// the size of the 256-bit memory structure used for secp256r1 coordinates
// - map 32 bytes of memory
ECC_BYTES = SizeOf(THash256);
/// Mon, 01 Aug 2016 encoded as COM/TDateTime value
// - used to compute TEccDate 16-bit values to/from a TDateTime
// - 16-bit day resolution allow values from year 2016 to 2196
ECC_DELTA = 42583;
type
/// store a public key for ECC secp256r1 cryptography
// - use Ecc256r1MakeKey() to generate such a key
// - stored in single coordinate compressed form with its standard byte header,
// i.e. each public key consumes 33 bytes of memory
TEccPublicKey = array[0..ECC_BYTES] of byte;
/// store a public key for ECC secp256r1 cryptography as x,y coordinates
// - use Ecc256r1Uncompress() to compute such a key from a TEccPublicKey
// - stored in uncompressed form, consuming 64 bytes of memory
TEccPublicKeyUncompressed = array[0..(ECC_BYTES * 2) - 1] of byte;
/// store a private key for ECC secp256r1 cryptography as a single coordinate
// - use Ecc256r1MakeKey() to generate such a key
// - stored in compressed form, i.e. each private key consumes 32 bytes of memory
TEccPrivateKey = array[0..ECC_BYTES - 1] of byte;
/// store a public key and a private key for ECC secp256r1 cryptography
// - used e.g. for ECDHE shared secret computation, or to store a full key
TEccKeyPair = packed record
/// a public key for ECC secp256r1 cryptography
pub: TEccPublicKey;
/// a private key for ECC secp256r1 cryptography
priv: TEccPrivateKey;
end;
/// store a 256-bit hash, as expected by ECC secp256r1 cryptography
// - see e.g. Ecc256r1Sign() and Ecc256r1Verify() functions
TEccHash = THash256;
/// store a signature, as generated by ECC secp256r1 cryptography
// - see e.g. Ecc256r1Sign() and Ecc256r1Verify() functions
// - contains ECDSA's R and S integers
// - each ECC signature consumes 64 bytes of memory
TEccSignature = array[0..(ECC_BYTES * 2) - 1] of byte;
/// store an encryption key, as generated by ECC secp256r1 cryptography
// - use Ecc256r1SharedSecret() to compute such a key from public/private keys
// - 256-bit / 32 bytes derivation from secp256r1 ECDH is expected to have at
// least 247-bit of entropy so could better be derivated via a KDF before used
// as encryption secret - see @http://crypto.stackexchange.com/a/9428/40200
TEccSecretKey = THash256;
PEccPublicKey = ^TEccPublicKey;
PEccPublicKeyUncompressed = ^TEccPublicKeyUncompressed;
PEccPrivateKey = ^TEccPrivateKey;
PEccHash = ^TEccHash;
PEccSignature = ^TEccSignature;
PEccSecretKey = ^TEccSecretKey;
PEccKeyPair = ^TEccKeyPair;
var
/// create a public/private key pair
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - direct low-level access to the our pascal version, or OpenSSL wrappers
// - returns true if the key pair was generated successfully in pub/priv
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
Ecc256r1MakeKey: function(out pub: TEccPublicKey; out priv: TEccPrivateKey): boolean;
/// compute a shared secret given your secret key and someone else's public key
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - direct low-level access to the our pascal version, or OpenSSL wrappers
// - note: it is recommended that you hash the result of Ecc256r1SharedSecret
// before using it for symmetric encryption or HMAC (via an intermediate KDF)
// - returns true if the shared secret was generated successfully in secret
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
Ecc256r1SharedSecret: function(const pub: TEccPublicKey; const priv: TEccPrivateKey;
out secret: TEccSecretKey): boolean;
/// generate an ECDSA signature for a given hash value
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - direct low-level access to the our pascal version, or OpenSSL wrappers
// - returns true if the signature was successfully generated in sign
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
Ecc256r1Sign: function(const priv: TEccPrivateKey; const hash: TEccHash;
out sign: TEccSignature): boolean;
/// verify an ECDSA signature
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - direct low-level access to the our pascal version, or OpenSSL wrappers
// - returns true if the supplied signature is valid
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
Ecc256r1Verify: function(const pub: TEccPublicKey; const hash: TEccHash;
const sign: TEccSignature): boolean;
/// decompress a secp256r1 curve public key
// - could be used before calling Ecc256r1VerifyUncomp()
// - direct low-level access to the our pascal version - no OpenSSL yet
Ecc256r1Uncompress: procedure(const Compressed: TEccPublicKey;
out Uncompressed: TEccPublicKeyUncompressed);
/// verify an ECDSA signature using an uncompressed public supplied
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - direct low-level access to the our pascal version - no OpenSSL yet
// - returns true if the supplied signature is valid
// - returns false if an error occurred
// - this function is thread-safe and does not perform any memory allocation
// - it is slightly faster than plain Ecc256r1Verify() using TEccPublicKey,
// with the pascal version since public key doesn't need to be uncompressed,
// but it is slower when using the OpenSSL backend
Ecc256r1VerifyUncomp: function(const PublicKey: TEccPublicKeyUncompressed;
const Hash: TEccHash; const Signature: TEccSignature): boolean;
/// compress a public key for ECC secp256r1 cryptography
// - convert its uncompressed/flat form (64 bytes of memory) into its compressed
// form with its standard byte header (33 bytes of memory)
// - a pascal version is good enough for this immediate bit copy operation
procedure Ecc256r1Compress(const Uncompressed: TEccPublicKeyUncompressed;
out Compressed: TEccPublicKey);
/// derivate an ECC secp256r1 cryptography public key from a private key
procedure Ecc256r1PublicFromPrivate(const PrivateKey: TEccPrivateKey;
out PublicKey: TEccPublicKey);
/// check if an ECC secp256r1 cryptography public key does match a private key
function Ecc256r1MatchKeys(const PrivateKey: TEccPrivateKey;
const PublicKey: TEccPublicKey): boolean;
/// decode an uncompressed ASN-1 public key for ECC secp256r1 cryptography
// - input is likely to have a $04 initial byte for ASN-1 uncompressed key,
// as stored in a X.509 Certificate SubjectPublicKey field
function Ecc256r1ExtractAsn1(const Asn1: RawByteString;
out Uncompressed: TEccPublicKeyUncompressed): boolean;
/// compress an uncompressed ASN-1 public key for ECC secp256r1 cryptography
// - input is likely to have a $04 initial byte for ASN-1 uncompressed key,
// as stored in a X.509 Certificate SubjectPublicKey field
function Ecc256r1CompressAsn1(const Uncompressed: RawByteString;
out Compressed: TEccPublicKey): boolean;
/// save an ECC secp256r1 cryptography public key as expected by ASN-1
// - include a $04 initial byte as uncompressed key, and change endianness
// - as stored in a X.509 Certificate SubjectPublicKey field
function Ecc256r1UncompressAsn1(const Compressed: TEccPublicKey): RawByteString;
/// just a wrapper around Ecc256r1Verify/Ecc256r1VerifyUncomp depending on unc
// - this unit is faster with uncompressed keys, whereas OpenSSL prefers to
// work with compressed keys
function Ecc256r1DoVerify(const pub: TEccPublicKey; unc: PEccPublicKeyUncompressed;
const hash: TEccHash; const sign: TEccSignature): boolean;
/// pascal function to create a secp256r1 public/private key pair
// - is not optimized for performance, but for secrecy: the private key is
// generated with a very safe HMAC-SHA-256 diffusion of 256-bit entropy from the
// Operating System and 256-bit from our TAesPrng with proper
// - our idea was to minimize the chances that two consecutive key generations
// have any similarity, even if performance is not the ultimate goal
// - ephemeral keys (e.g. in ECDHE) could use faster OpenSSL instead
function ecc_make_key_pas(out PublicKey: TEccPublicKey;
out PrivateKey: TEccPrivateKey): boolean;
/// pascal function to compute a secp256r1 shared secret given your secret key
// and someone else's public key (in compressed format)
function ecdh_shared_secret_pas(const PublicKey: TEccPublicKey;
const PrivateKey: TEccPrivateKey; out Secret: TEccSecretKey): boolean;
/// pascal function to compute a secp256r1 shared secret given your secret key
// and someone else's public key (in uncompressed/flat format)
// - this overloaded function is slightly faster than the one using TEccPublicKey,
// since public key doesn't need to be uncompressed
function ecdh_shared_secret_uncompressed_pas(const PublicPoint: TEccPublicKeyUncompressed;
const PrivateKey: TEccPrivateKey; out Secret: TEccSecretKey): boolean;
/// pascal function to generate an ECDSA secp256r1 signature for a given hash value
function ecdsa_sign_pas(const PrivateKey: TEccPrivateKey; const Hash: TEccHash;
out Signature: TEccSignature): boolean;
/// pascal function to verify an ECDSA secp256r1 signature from someone else's
// public key (in compressed format)
function ecdsa_verify_pas(const PublicKey: TEccPublicKey; const Hash: TEccHash;
const Signature: TEccSignature): boolean;
/// pascal function to verify an ECDSA secp256r1 signature from someone else's
// public key (in uncompressed/flat format)
// - this overloaded function is slightly faster than the one using TEccPublicKey,
// since public key doesn't need to be uncompressed
function ecdsa_verify_uncompressed_pas(const PublicKey: TEccPublicKeyUncompressed;
const Hash: TEccHash; const Signature: TEccSignature): boolean;
/// uncompress a public key for ECC secp256r1 cryptography
// - convert from its compressed form with its standard byte header
// (33 bytes of memory) into uncompressed/flat form (64 bytes of memory)
procedure ecc_uncompress_key_pas(const Compressed: TEccPublicKey;
out Uncompressed: TEccPublicKeyUncompressed);
type
/// verification of a ECDSA signature using ECC secp256r1 cryptography
// - this class encapsultate the public key storage in the native form of
// the cryptographic library, which may be this unit pascal or OpenSSL
// - it is therefore slightly faster than Ecc256r1Verify()
TEcc256r1VerifyAbstract = class
protected
fPublicKey: TEccPublicKey;
public
/// initialize the verifier with a given ECC compressed public key
constructor Create(const pub: TEccPublicKey); virtual;
/// validate a signature against a hash using ECC
function Verify(const hash: TEccHash; const sign: TEccSignature): boolean;
virtual; abstract;
/// the public key as specified to the class constructor
property PublicKey: TEccPublicKey
read fPublicKey;
end;
/// meta-clas of ECDSA signature verification class
TEcc256r1VerifyClass = class of TEcc256r1VerifyAbstract;
/// pascal verification of a ECDSA signature using ECC secp256r1 cryptography
// - as implemented by ecdsa_verify_uncompressed_pas() in this unit
TEcc256r1VerifyPas = class(TEcc256r1VerifyAbstract)
protected
fPub: TEccPublicKeyUncompressed;
public
/// initialize the verifier with a given ECC compressed public key
constructor Create(const pub: TEccPublicKey); override;
/// finalize this instance
destructor Destroy; override;
/// validate a signature against a hash using ECC
function Verify(const hash: TEccHash; const sign: TEccSignature): boolean;
override;
end;
var
/// fastest available class to be used to verify a ECDSA signature
// - using secp256r1 curve, i.e. NIST P-256, or OpenSSL prime256v1
// - direct low-level access to our pascal/asm version, or OpenSSL wrappers
// - as used e.g. by TJwtEs256 high-level JWT processing class
TEcc256r1Verify: TEcc256r1VerifyClass = TEcc256r1VerifyPas;
{ ***************** Middle-Level Certificate-based Public Key Cryptography }
type
/// 128-bit (16 bytes) buffer used to identify a TEccCertificate
// - could be safely generated e.g. by the Random128() function
TEccCertificateID = type THash128;
/// 128-bit (16 bytes) buffer used to identify a TEccCertificate issuer
// - could be generated by AsciiToBaudot(), with truncation to 16 bytes
// (up to 25 Ascii-7 characters)
TEccCertificateIssuer = type THash128;
/// used to store a date in a TEccCertificate
// - i.e. 16-bit number of days since 1 August 2016 - up to 2195
// - use NowEccDate, EccDate(), EccToDateTime() or EccText() functions
TEccDate = word;
PEccCertificateID = ^TEccCertificateID;
PEccCertificateIssuer = ^TEccCertificateIssuer;
PEccDate = ^TEccDate;
/// indicate the validity state of a ECDSA signature against a certificate
// - as returned by low-level EccVerify() function, and
// TEccSignatureCertified.Verify, TEccCertificateChain.IsValid or
// TEccCertificateChain.IsSigned methods
// - see also ECC_VALIDSIGN constant
// - match TCertificateValidity enumerate in mormot.crypt.secure
TEccValidity = (
ecvUnknown,
ecvValidSigned,
ecvValidSelfSigned,
ecvNotSupported,
ecvBadParameter,
ecvCorrupted,
ecvInvalidDate,
ecvUnknownAuthority,
ecvDeprecatedAuthority,
ecvInvalidSignature,
ecvRevoked,
ecvWrongUsage,
ecvExhaustedPathLen);
/// the certification information of a TEccCertificate
// - as stored in TEccCertificateContent.Head.Signed
// - defined in a separate record, to be digitaly signed in the Signature field
// - map TEccCertificate.Version 1 of the binary format
// - "self-signed" certificates may be used as "root" certificates in the
// TEccCertificateChain list
TEccCertificateSigned = packed record
/// when this certificate was generated
IssueDate: TEccDate;
/// certificate valid not before
ValidityStart: TEccDate;
/// certificate valid not after
ValidityEnd: TEccDate;
/// a 128-bit genuine identifier for this certificate
// - is used later on to validate other certificates in chain
Serial: TEccCertificateID;
/// identify the certificate issuer
// - is either geniune random bytes, or some Baudot-encoded text
// - blank in V2, contains the (may be truncated) "subject" in V1 format
Issuer: TEccCertificateIssuer;
/// 128-bit genuine identifier of the authority certificate used for signing
// - should be used to retrieve the associated PublicKey used to compute
// the Signature field
// - may equal Serial, if was self-signed
AuthoritySerial: TEccCertificateID;
/// identify the authoritify issuer used for signing
// - is either geniune random bytes, or some Baudot-encoded text
// - blank in V2, contains the (may be truncated) "subject" in V1 format
AuthorityIssuer: TEccCertificateIssuer;
/// the ECDSA secp256r1 public key of this certificate
// - may be used later on for signing or key derivation
PublicKey: TEccPublicKey;
end;
/// points to certification information of a TEccCertificate
PEccCertificateSigned = ^TEccCertificateSigned;
/// store the version 1 TEccCertificate binary buffer for ECC cryptography
// - i.e. a certificate public key, with its ECDSA signature
// - would be stored in 173 bytes
TEccCertificateContentV1 = packed record
/// the TEccCertificate format version
// - 1 for mORMot 1 legacy layout, 2 for mORMot 2 with Usage + Issuer
Version: word;
/// the certification information, digitaly signed in the Signature field
Signed: TEccCertificateSigned;
/// SHA-256 + ECDSA secp256r1 signature of the Certificate record
Signature: TEccSignature;
/// FNV-1a checksum of all other fields
// - we use fnv32 and not crc32c here to avoid colision with crc64c hashing
// - avoiding to compute slow ECDSA verification in case of corruption,
// due e.g. to unexpected transmission/bug/fuzzing/dosattack
// - include V2 Info, as computed by TEccCertificateContent.ComputeCrc32
CRC: cardinal;
end;
{$A-}
/// up to 512 bytes of additional data for TEccCertificate binary version >= 2
TEccCertificateContentV2 = record
/// 16-bit storage for TCryptCertUsage
Usage: word;
/// 16-bit len of additional Data information
DataLen: word;
/// some additional data, e.g. the Subject, in up to 508 bytes
// - such data will be stored with variable length
Data: array[0..507] of byte;
end;
/// store a TEccCertificate binary buffer for ECC secp256r1 cryptography
// - i.e. a certificate public key, with its ECDSA signature
// - would be stored in 173 bytes (version 1) and 177+ bytes (version 2)
{$ifdef USERECORDWITHMETHODS}
TEccCertificateContent = record
{$else}
TEccCertificateContent = object
{$endif USERECORDWITHMETHODS}
public
/// basic content - version 1 compatible
Head: TEccCertificateContentV1;
/// new version >= 2 with additional information (up to 512 bytes)
Info: TEccCertificateContentV2;
/// set Certificate usage, as 16-bit TCryptCertUsages value
// - will also force the version to be 2 if maxversion allow it
procedure SetUsage(usage: integer; maxversion: byte);
/// get Certificate 16-bit TCryptCertUsage usage
// - returns CU_ALL = all Usage for version 1
function GetUsage: integer;
/// set Certificate subject
// - the input subject text could be CSV separated
// - will first try to store it in the V1 Issuer field
// - or switch to V2 and store after Baudot encoding into Info.Data - if
// maxversion allow the upgrade
procedure SetSubject(const sub: RawUtf8; maxversion: byte);
/// get Certificate subject, after Baudot decoding from additional Info.Data
function GetSubject: RawUtf8;
/// fast check of the binary buffer storage of a certificate
// - ensure CRC has the expected value, using FNV-1a checksum
// - does not validate the certificate against the certificates chain, nor
// perform any ECC signature: use TEccCertificateChain.IsValid instead
function Check: boolean;
/// fast check of the dates stored in a certificate binary buffer
// - could be validated against EccCheck()
// - you can specify your own UTC timestamp for expiration instead of NowUtc
function CheckDate(nowdate: PEccDate = nil; TimeUtc: TDateTime = 0): boolean;
/// fast check if the binary buffer storage of a certificate was self-signed
// - a self-signed certificate has its AuthoritySerial field matching Seial
function IsSelfSigned: boolean;
/// compare all fields of both Certificates
function FieldsEqual(const another: TEccCertificateContent): boolean;
/// copy of the used bytes of TEccCertificateContent buffer
procedure CopyTo(out dest: TEccCertificateContent);
/// compute the FNV-32 digest of the whole content
// - as stored in Head.CRC
function ComputeCrc32: cardinal;
/// compute the SHA-256 digest of the whole signed content
procedure ComputeHash(out hash: TSha256Digest; const salt: RawByteString = '');
/// serialize this certificate content as binary stream
function SaveToStream(s: TStream): boolean;
/// unserialize this certificate content from a binary stream
function LoadFromStream(s: TStream; maxversion: byte): boolean;
end;
PEccCertificateContentV1 = ^TEccCertificateContentV1;
PEccCertificateContentV2 = ^TEccCertificateContentV2;
/// points to a TEccCertificate binary buffer for ECC secp256r1 cryptography
PEccCertificateContent = ^TEccCertificateContent;
/// store a TEccSignatureCertified binary buffer for ECDSA secp256r1 signature
// - i.e. the digital signature of some content
// - stored in 100 bytes, including full signature and authority information
{$ifdef USERECORDWITHMETHODS}
TEccSignatureCertifiedContent = record
{$else}
TEccSignatureCertifiedContent = object
{$endif USERECORDWITHMETHODS}
public
/// the TEccSignatureCertificated format version
Version: word;
/// when this signature was generated
Date: TEccDate;
/// genuine identifier of the authority certificate used for signing
// - should be used to retrieve the associated PublicKey used to compute
// the Signature field
AuthoritySerial: TEccCertificateID;
/// identify the authoritify issuer used for signing
// - is either geniune random bytes, or some Baudot-encoded text
// - blank in V2, contains the (may be truncated) "subject" in V1 format
AuthorityIssuer: TEccCertificateIssuer;
/// SHA-256 + ECDSA secp256r1 digital signature of the content
Signature: TEccSignature;
/// fast check of the binary buffer storage of a signature
// - just check that the date and authority are set
function Check: boolean;
/// convert a supplied Base64 text into a TEccSignatureCertifiedContent binary buffer
function FromBase64(const base64: RawUtf8): boolean;
/// convert a supplied TEccSignatureCertifiedContent binary buffer into proper text
// - returns Base64 encoded text, or '' if the signature was filled with zeros
function ToText: RawUtf8; overload;
/// low-level verification of a TEccSignatureCertifiedContent binary buffer
// - will verify all internal signature fields according to a supplied authority,
// then will perform the ECDSA verification of the supplied 256-bit hash with
// the authority public key
// - optional authuncomp could be the uncompressed auth.Head.Signed.PublicKey
function Verify(const hash: THash256; const auth: TEccCertificateContent;
authuncomp: PEccPublicKeyUncompressed;
TimeUtc: TDateTime = 0): TEccValidity; overload;
/// low-level verification of a TEccSignatureCertifiedContent binary buffer
// - will verify all internal signature fields according to a supplied authority
// key, then perform the ECDSA verification of the supplied 256-bit hash with it
// - returns ecvValidSigned on success, or an error value otherwise
function Verify(const hash: THash256; const authkey: TEccPublicKey;
valid: TEccValidity = ecvValidSigned;
TimeUtc: TDateTime = 0): TEccValidity; overload;
end;
/// points to a TEccSignatureCertified buffer for ECDSA secp256r1 signature
PEccSignatureCertifiedContent = ^TEccSignatureCertifiedContent;
/// store a TEccCertificateChain Certificate Revocation List item
// - would be stored as 24 bytes
{$ifdef USERECORDWITHMETHODS}
TEccCertificateRevocation = record
{$else}
TEccCertificateRevocation = object
{$endif USERECORDWITHMETHODS}
public
/// contains the 65535 fixed number (ECC_REVOC_MAGIC)
// - make a clear distinction with TEccCertificateContentV1.Version
// - will be Base64-encoded as '/w...' so could be recognized from
// a Base64-encoded TEccCertificate
Magic: word;
/// the Revocation format version
// - currently equals 1
Version: word;
/// when this revocation becomes active
Date: TEccDate;
/// why this Certificate was revoked - usually TCryptCertRevocationReason
Reason: word;
/// the 128-bit (16 bytes) revocated Certificate Identifier
Serial: TEccCertificateID;
/// fast check of the binary buffer storage of a CRL item
function Check: boolean;
/// convert a supplied Base64 text into TEccCertificateRevocation binary buffer
function FromBase64(const base64: RawUtf8): boolean;
/// convert a TEccCertificateRevocation binary buffer into Base64 text
function ToBase64: RawUtf8;
/// setup a CRL item
function From(const id: TEccCertificateID; dt: TDateTime; why: word): boolean;
/// read TEccCertificateRevocation binary buffer from the given TStream
function LoadFromStream(st: TStream): boolean;
/// write the TEccCertificateRevocation binary buffer into the given TStream
procedure SaveToStream(st: TStream);
end;
PEccCertificateRevocation = ^TEccCertificateRevocation;
/// can store a whole Certificate Revocation List (CRL)
TEccCertificateRevocationDynArray = array of TEccCertificateRevocation;
{$A+}
/// the error codes returned by TEccCertificateSecret.Decrypt()
// - see also ECC_VALIDDECRYPT constant
TEccDecrypt = (
ecdDecrypted,
ecdDecryptedWithSignature,
ecdNoContent,
ecdCorrupted,
ecdInvalidSerial,
ecdNoPrivateKey,
ecdInvalidMAC,
ecdDecryptError,
ecdWriteFileError,
ecdUnsupported);
const
/// TEccValidity results indicating a valid digital signature
ECC_VALIDSIGN =
[ecvValidSigned, ecvValidSelfSigned];
/// TEccDecrypt results indicating a valid decryption process
ECC_VALIDDECRYPT =
[ecdDecrypted, ecdDecryptedWithSignature];
/// map all TCryptCertUsages flags for ECC Version 1 default value
// - should match word(CU_ALL) from mormot.crypt.secure.pas
ECCV1_USAGE_ALL = 65535;
function ToText(val: TEccValidity): PShortString; overload;
function ToText(res: TEccDecrypt): PShortString; overload;
/// fill all bytes of this ECC private key buffer with zero
// - may be used to cleanup stack-allocated content
// ! ... finally FillZero(PrivateKey); end;
procedure FillZero(out Priv: TEccPrivateKey); overload;
/// returns the current UTC date, as a TEccDate integer value
// - i.e. 16-bit number of days since 1 August 2016 - following UTC timing
function NowEccDate: TEccDate;
{$ifdef HASINLINE}inline;{$endif}
/// convert a supplied TDateTime value into a TEccDate integer value
// - i.e. 16-bit number of days since 1 August 2016
// - returns 0 if the supplied value is invalid, i.e. out of range
function EccDate(const DateTime: TDateTime): TEccDate;
/// convert a supplied a TEccDate integer value into a TDateTime value
// - i.e. 16-bit number of days since 1 August 2016
function EccToDateTime(EccDate: TEccDate): TDateTime;
{$ifdef HASINLINE}inline;{$endif}
/// convert a supplied a TEccDate integer value into a ISO-8601 text value
// - i.e. 16-bit number of days since 1 August 2016
function EccText(EccDate: TEccDate; Expanded: boolean = true): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// compare two TEccCertificateIssuer binary buffer values
function IsEqual(const issuer1, issuer2: TEccCertificateIssuer): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// compare two TEccCertificateID binary buffer values
function IsEqual(const id1, id2: TEccCertificateID): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// ensure a TEccCertificateIssuer binary buffer is not void, i.e. filled with 0
function IsZero(const issuer: TEccCertificateIssuer): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// ensure a TEccCertificateID binary buffer is not void, i.e. filled with 0
function IsZero(const id: TEccCertificateID): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// ensure a TEccPublicKey binary buffer is not void, i.e. filled with 0
function IsZero(const k: TEccPublicKey): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// ensure a TEccPrivateKey binary buffer is not void, i.e. filled with 0
function IsZero(const pk: TEccPrivateKey): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// ensure a TEccSignature binary buffer is not void, i.e. filled with 0
function IsZero(const sig: TEccSignature): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert a supplied TEccCertificateIssuer binary buffer into proper text
// - returns Ascii-7 text if was stored using Baudot encoding
// - or returns hexadecimal values, if it was 16 bytes of random binary
function EccText(const Issuer: TEccCertificateIssuer): RawUtf8; overload;
/// convert some Ascii-7 text into a TEccCertificateIssuer binary buffer
// - using Emile Baudot encoding
// - returns TRUE on Text truncation to fit into the 16 bytes
function EccIssuer(const Text: RawUtf8; out Issuer: TEccCertificateIssuer;
fullbaudot: PRawByteString = nil): boolean;
/// convert a supplied TEccCertificateID binary buffer into proper text
// - returns hexadecimal values, or '' if the ID is filled with zeros
function EccText(const ID: TEccCertificateID): RawUtf8; overload;
/// convert a supplied hexadecimal buffer into a TEccCertificateID binary buffer
// - returns TRUE if the supplied Text was a valid hexadecimal buffer
// - will also recognize GUID/UUID layout as {x-x-x-x} or x-x-x-x
function EccID(const Text: RawUtf8; out ID: TEccCertificateID): boolean;
/// convert a supplied TEccSignature binary buffer into proper text
// - returns Base64 encoded text, or '' if the signature was filled with zeros
function EccText(const sign: TEccSignature): RawUtf8; overload;
implementation
{ ***************** Low-Level ECC secp256r1 ECDSA and ECDH Functions }
{
Optimized pascal adaptation of "simple and secure ECDH and ECDSA library"
https://github.com/kmackay/micro-ecc
Copyright (c) 2014, Kenneth MacKay - BSD 2-Clause "Simplified" License
Notes:
- our branchless pascal/asm version is faster than the original micro-ecc code
- so we don't need nor publish .o / .obj static files any more
- mormot.crypt.openssl.RegisterOpenSsl activates the much faster OpenSSL asm
Some Numbers on Win32 Delphi:
- mORMot 50 Ecc256r1MakeKey in 47.49ms i.e. 1K/s, aver. 949us
- mORMot 50 Ecc256r1Sign in 48.38ms i.e. 1K/s, aver. 967us
- mORMot 50 Ecc256r1Verify in 60.64ms i.e. 824/s, aver. 1.21ms
- mORMot 98 Ecc256r1SharedSecret in 101.17ms i.e. 0.9K/s, aver. 1.03ms
As historical reference, Win32 statically linked .obj with gcc -O2:
- mORMot .obj 50 Ecc256r1MakeKey in 95.35ms i.e. 524/s, aver. 1.90ms
- mORMot .obj 50 Ecc256r1Sign in 96.84ms i.e. 516/s, aver. 1.93ms
- mORMot .obj 50 Ecc256r1Verify in 117.36ms i.e. 426/s, aver. 2.34ms
- mORMot .obj 98 Ecc256r1SharedSecret in 201.53ms i.e. 486/s, aver. 2.05ms
Some Numbers on Linux x86_64:
- mORMot 300 Ecc256r1MakeKey in 76.59ms i.e. 3.8K/s, aver. 255us
- mORMot 300 Ecc256r1Sign in 79.21ms i.e. 3.7K/s, aver. 264us
- mORMot 300 Ecc256r1Verify in 95.70ms i.e. 3K/s, aver. 319us
- mORMot 598 Ecc256r1SharedSecret in 158.93ms i.e. 3.6K/s, aver. 265us
- OpenSSL 300 Ecc256r1MakeKey in 5.09ms i.e. 57.5K/s, aver. 16us
- OpenSSL 300 Ecc256r1Sign in 7.97ms i.e. 36.7K/s, aver. 26us
- OpenSSL 300 Ecc256r1Verify in 28.66ms i.e. 10.2K/s, aver. 95us
- OpenSSL 598 Ecc256r1SharedSecret in 44.75ms i.e. 13K/s, aver. 74us
Some Numbers on Linux AARCH64 (Oracle Cloud VM) :
- mORMot 300 Ecc256r1MakeKey in 243.31ms i.e. 1.2K/s, aver. 811us
- mORMot 300 Ecc256r1Sign in 250.06ms i.e. 1.1K/s, aver. 833us
- mORMot 300 Ecc256r1Verify in 304.56ms i.e. 0.9K/s, aver. 1.01ms
- mORMot 598 Ecc256r1SharedSecret in 523.25ms i.e. 1.1K/s, aver. 875us
- OpenSSL 300 Ecc256r1MakeKey in 8.72ms i.e. 33.5K/s, aver. 29us
- OpenSSL 300 Ecc256r1Sign in 13.92ms i.e. 21K/s, aver. 46us
- OpenSSL 300 Ecc256r1Verify in 56.14ms i.e. 5.2K/s, aver. 187us
- OpenSSL 598 Ecc256r1SharedSecret in 90.75ms i.e. 6.4K/s, aver. 151us
}
const
ECC_QUAD = ECC_BYTES div 8; // = compute with 4 x 64-bit blocks
type
TEccPoint = record
x, y: THash256Rec;
end;
PEccPoint = ^TEccPoint;
const
Curve_P_32: THash256Rec = (
q: (QWord($FFFFFFFFFFFFFFFF),
QWord($00000000FFFFFFFF),
QWord($0000000000000000),
QWord($FFFFFFFF00000001)));
Curve_B_32: THash256Rec = (
q: (QWord($3BCE3C3E27D2604B),
QWord($651D06B0CC53B0F6),
QWord($B3EBBD55769886BC),
QWord($5AC635D8AA3A93E7)));
Curve_G_32: TEccPoint = (
x: (q:
(QWord($F4A13945D898C296),
QWord($77037D812DEB33A0),
QWord($F8BCE6E563A440F2),
QWord($6B17D1F2E12C4247)));
y: (q:
(QWord($CBB6406837BF51F5),
QWord($2BCE33576B315ECE),
QWord($8EE7EB4A7C0F9E16),
QWord($4FE342E2FE1A7F9B))));
Curve_N_32: THash256Rec = (
q: (QWord($F3B9CAC2FC632551),
QWord($BCE6FAADA7179E84),
QWord($FFFFFFFFFFFFFFFF),
QWord($FFFFFFFF00000000)));
P_1: THash256Rec = (q: (QWord(1), QWord(0), QWord(0), QWord(0)));
P_3: THash256Rec = (q: (QWord(3), QWord(0), QWord(0), QWord(0)));
P_11: THash256Rec = (q: (QWord($0101010101010101),
QWord($0101010101010101),
QWord($0101010101010101),
QWord($0101010101010101)));
procedure _set1(out V: THash256Rec);
{$ifdef HASINLINE}inline;{$endif}
begin
V.Q[0] := 1;
V.Q[1] := 0;
V.Q[2] := 0;
V.Q[3] := 0;
end;
function _isZero(const V: THash256Rec): boolean;
{$ifdef HASINLINE}inline;{$endif}
begin
result := (V.Q[0] = 0) and
(V.Q[1] = 0) and
(V.Q[2] = 0) and
(V.Q[3] = 0);
end;
function _equals(const Left, Right: THash256Rec): boolean;
{$ifdef HASINLINE}inline;{$endif}
begin
result := (Left.Q[0] = Right.Q[0]) and
(Left.Q[1] = Right.Q[1]) and
(Left.Q[2] = Right.Q[2]) and
(Left.Q[3] = Right.Q[3]);
end;
procedure _mv(out Dest: THash256Rec; const Source: THash256Rec);
{$ifdef HASINLINE}inline;{$endif}
begin
{$ifdef CPU32}
Dest := Source;
{$else}
Dest.Q[0] := Source.Q[0];
Dest.Q[1] := Source.Q[1];
Dest.Q[2] := Source.Q[2];
Dest.Q[3] := Source.Q[3];
{$endif CPU32}
end;
// computes result = (Left + Right) mod Modulo
// assumes that p_left < p_mod and p_right < p_mod, p_result != p_mod
procedure _modAddP(var Output: THash256Rec; const Left, Right: THash256Rec);
{$ifdef HASINLINE}inline;{$endif}
begin
if (_add256(Output, Left, Right) <> 0) or
(_cmp256(Output, Curve_P_32) >= 0) then
// result > Modulo (result = Modulo + Remainder),
// so subtract Modulo to get remainder
_dec256(Output, Curve_P_32);
end;
procedure _modAddN(var Output: THash256Rec; const Left, Right: THash256Rec);
{$ifdef HASINLINE}inline;{$endif}
begin
if (_add256(Output, Left, Right) <> 0) or
(_cmp256(Output, Curve_N_32) >= 0) then
// result > Modulo (result = Modulo + Remainder),
// so subtract Modulo to get remainder
_dec256(Output, Curve_N_32);
end;
// computes result = (Left - Right) mod Curve_P_32.
// assumes that Left < Curve_P_32 and Right < Curve_P_32 , result != Curve_P_32
procedure _modSubP(out Output: THash256Rec; const Left, Right: THash256Rec);
{$ifdef HASINLINE}inline;{$endif}
begin
if _sub256(Output, Left, Right) <> 0 then
// In this case, Output == -diff == (max int) - diff.
// Since -x mod d == d - x, we can get the correct result
// from Output + Modulo (with overflow)
_inc256(Output, Curve_P_32);
end;
// computes result = Product mod Curve_P_32
// from NIST https://tinyurl.com/3p5mt8kr
procedure _mmodP(out Output: THash256Rec; var Product: THash512Rec);
var
carry: PtrInt;
tmp: THash256Rec;
begin
// t
_mv(Output, Product.L);
if _isZero(Product.H) and
(_cmp256(Curve_P_32, Product.L) > 0) then
exit; // no modulo to apply
// s1
tmp.Q[0] := 0;
{$ifdef CPU32}
tmp.C[2] := 0;
tmp.C[3] := Product.C[11];
{$else}
tmp.Q[1] := Product.Q[5] and $FFFFFFFF00000000;
{$endif CPU32}
tmp.Q[2] := Product.Q[6];
tmp.Q[3] := Product.Q[7];
carry := _lshift1(tmp);
inc(carry, _inc256(Output, tmp));
// s2
tmp.Q[1] := Product.Q[6] shl 32;
tmp.Q[2] := (Product.Q[6] shr 32) or (Product.Q[7] shl 32);
tmp.Q[3] := Product.Q[7] shr 32;
inc(carry, _lshift1(tmp));
inc(carry, _inc256(Output, tmp));
// s3
tmp.Q[0] := Product.Q[4];
tmp.Q[1] := Product.Q[5] and $FFFFFFFF;
tmp.Q[2] := 0;
tmp.Q[3] := Product.Q[7];
inc(carry, _inc256(Output, tmp));
// s4
tmp.Q[0] := (Product.Q[4] shr 32) or (Product.Q[5] shl 32);
{$ifdef CPU32}
tmp.C[2] := Product.C[11];
tmp.C[3] := Product.C[13];
{$else}
tmp.Q[1] := (Product.Q[5] shr 32) or (Product.Q[6] and $FFFFFFFF00000000);
{$endif CPU32}
tmp.Q[2] := Product.Q[7];
tmp.Q[3] := (Product.Q[6] shr 32) or (Product.Q[4] shl 32);
inc(carry, _inc256(Output, tmp));
// d1
tmp.Q[0] := (Product.Q[5] shr 32) or (Product.Q[6] shl 32);
tmp.Q[1] := (Product.Q[6] shr 32);
tmp.Q[2] := 0;
tmp.Q[3] := (Product.Q[4] and $FFFFFFFF) or (Product.Q[5] shl 32);
dec(carry, _dec256(Output, tmp));
// d2
tmp.Q[0] := Product.Q[6];
tmp.Q[1] := Product.Q[7];
tmp.Q[2] := 0;
{$ifdef CPU32}
tmp.C[6] := Product.C[9];
tmp.C[7] := Product.C[11];
{$else}
tmp.Q[3] := (Product.Q[4] shr 32) or (Product.Q[5] and $FFFFFFFF00000000);
{$endif CPU32}
dec(carry, _dec256(Output, tmp));
// d3
tmp.Q[0] := (Product.Q[6] shr 32) or (Product.Q[7] shl 32);
tmp.Q[1] := (Product.Q[7] shr 32) or (Product.Q[4] shl 32);
tmp.Q[2] := (Product.Q[4] shr 32) or (Product.Q[5] shl 32);
tmp.Q[3] := (Product.Q[6] shl 32);
dec(carry, _dec256(Output, tmp));
// d4
tmp.Q[0] := Product.Q[7];
tmp.Q[2] := Product.Q[5];
{$ifdef CPU32}
tmp.C[2] := 0;
tmp.C[3] := Product.C[9];
tmp.C[6] := 0;
tmp.C[7] := Product.C[13];
{$else}
tmp.Q[1] := Product.Q[4] and $FFFFFFFF00000000;
tmp.Q[3] := Product.Q[6] and $FFFFFFFF00000000;
{$endif CPU32}
dec(carry, _dec256(Output, tmp));
if carry < 0 then
repeat
inc(carry, _inc256(Output, Curve_P_32));
until carry >= 0
else
while (carry <> 0) or
(_cmp256(Curve_P_32, Output) <= 0) do
dec(carry, _dec256(Output, Curve_P_32));
end;
// computes result = (Left * Right) mod Curve
procedure _modMultP(out Output: THash256Rec; const Left, Right: THash256Rec);
{$ifdef HASINLINE}inline;{$endif}
var
product: THash512Rec;
begin
_mult256(product, Left, Right);
_mmodP(Output, product);
end;
// computes result = Left^2 mod Curve
procedure _modSquareP(out Output: THash256Rec; const Left: THash256Rec);
{$ifdef HASINLINE}inline;{$endif}
var
product: THash512Rec;
begin
_square256(product, Left);
_mmodP(Output, product);
end;
// computes result = (1 / p_input) mod Modulo. All VLIs are the same size
// See "From Euclid's GCD to Montgomery Multiplication to the Great Divide"
// https://labs.oracle.com/techrep/2001/smli_tr-2001-95.pdf
procedure _modInv(out Output: THash256Rec; const Input, Modulo: THash256Rec);
var
a, b, v: THash256Rec;
carry: PtrUInt;
cmp: integer;
begin
if _isZero(Input) then
begin
FillZero(Output.b);
exit;
end;
_mv(a, Input);
_mv(b, Modulo);
_set1(Output);
FillZero(v.b);
repeat
cmp := _cmp256(a, b);
if cmp = 0 then
break;
carry := 0;
if (a.C[0] and 1) = 0 then
begin
_rshift1(a);
if (Output.C[0] and 1) = 1 then
carry := _inc256(Output, Modulo);
_rshift1(Output);
if carry <> 0 then
Output.B[ECC_BYTES - 1] := Output.B[ECC_BYTES - 1] or $80;
end
else if (b.C[0] and 1) = 0 then
begin
_rshift1(b);
if (v.C[0] and 1) = 1 then
carry := _add256(v, v, Modulo);
_rshift1(v);
if carry <> 0 then
v.B[ECC_BYTES - 1] := v.B[ECC_BYTES - 1] or $80;
end
else if cmp > 0 then
begin
_dec256(a, b);
_rshift1(a);
if _cmp256(Output, v) < 0 then
_inc256(Output, Modulo);
_dec256(Output, v);
if (Output.C[0] and 1) = 1 then
carry := _inc256(Output, Modulo);
_rshift1(Output);
if carry <> 0 then
Output.B[ECC_BYTES - 1] := Output.B[ECC_BYTES - 1] or $80;
end
else