Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
TravelDocumentsClient.cs
1using System;
3using System.Diagnostics.CodeAnalysis;
4using System.Globalization;
5using System.IO;
6using System.Reflection;
8using System.Threading.Tasks;
9using System.Xml;
18using Waher.Content;
19using Waher.Events;
24using Waher.Security;
26
28{
38 public sealed class TravelDocumentsClient(IIsoDepInterface TagInterface,
39 DocumentInformation DocumentInformation, byte[]? LocalKeySeed, params ISniffer[] Sniffers)
40 : CommunicationLayer(false, Sniffers), IDisposable
41 {
42 private static readonly Dictionary<ushort, IDataObject> dataObjects = GetDataObjects();
43 private ApplicationLevelInformation? appInfo;
44 private DocumentSecurityObject? securityinfo;
45 private MrzDataObject? mrz;
46 private BiometricInformationTemplate[]? biometricEncodingFace;
47 private BiometricInformationTemplate[]? biometricEncodingFingers;
48 private BiometricInformationTemplate[]? biometricEncodingIrises;
49 private DisplayedSignatures? displayedSignatures;
50 private AdditionalPersonalDetails? personalInformation;
51 private readonly IIsoDepInterface tagInterface = TagInterface;
52 private readonly DocumentInformation documentInformation = DocumentInformation;
53 private readonly byte[]? localKeySeed = LocalKeySeed;
54 private TravelDocumentsState state = TravelDocumentsState.Detected;
55 private IPaceProtocol? protocol;
56 private CMac? cMac = null;
57 private byte[]? ks_Enc = null;
58 private byte[]? ks_Mac = null;
59 private byte[]? sendSequenceCounter = null;
60 private byte[]? zeroIv = null;
61 private bool encrypted = false;
62 private bool enhancedSecurity = false;
63 private bool permitPlatformDependentValidation = true;
64 private bool disposed = false;
65
69 public void Dispose()
70 {
71 if (!this.disposed)
72 {
73 this.disposed = true;
74
75 if (this.ks_Enc is not null)
76 {
77 Array.Clear(this.ks_Enc, 0, this.ks_Enc.Length);
78 this.ks_Enc = null;
79 }
80
81 if (this.ks_Mac is not null)
82 {
83 Array.Clear(this.ks_Mac, 0, this.ks_Mac.Length);
84 this.ks_Mac = null;
85 this.cMac = null;
86 }
87
88 this.encrypted = false;
89
90 this.tagInterface.CloseIfOpen();
91 }
92 }
93
97 public ApplicationLevelInformation? AppInfo
98 {
99 get => this.appInfo;
100 set => this.appInfo = value;
101 }
102
106 public bool PermitPlatformDependentValidation
107 {
108 get => this.permitPlatformDependentValidation;
109 set => this.permitPlatformDependentValidation = value;
110 }
111
115 public event EventHandlerAsync? AppInfoUpdated;
116
120 public DocumentSecurityObject? SecurityInfo => this.securityinfo;
121
125 public event EventHandlerAsync? SecurityInfoUpdated;
126
130 public MrzDataObject? Mrz => this.mrz;
131
135 public event EventHandlerAsync? MrzUpdated;
136
140 public BiometricInformationTemplate[]? BiometricEncodingFace => this.biometricEncodingFace;
141
145 public event EventHandlerAsync? BiometricEncodingFaceUpdated;
146
150 public BiometricInformationTemplate[]? BiometricEncodingFingers => this.biometricEncodingFingers;
151
155 public event EventHandlerAsync? BiometricEncodingFingersUpdated;
156
160 public BiometricInformationTemplate[]? BiometricEncodingIrises => this.biometricEncodingIrises;
161
165 public event EventHandlerAsync? BiometricEncodingIrisesUpdated;
166
170 public DisplayedSignatures? DisplayedSignatures => this.displayedSignatures;
171
175 public event EventHandlerAsync? DisplayedSignaturesUpdated;
176
180 public AdditionalPersonalDetails? PersonalInformation => this.personalInformation;
181
185 public event EventHandlerAsync? PersonalInformationUpdated;
186
190 public TravelDocumentsState State => this.state;
191
192 private Task SetState(TravelDocumentsState NewState)
193 {
194 return this.SetState(NewState, null);
195 }
196
197 private async Task SetState(TravelDocumentsState NewState, object? AssociatedData)
198 {
199 this.state = NewState;
200 await this.StateChanged.Raise(this, new TravelDocumentsStateEventArgs(this, NewState, AssociatedData));
201 }
202
206 public event EventHandlerAsync<TravelDocumentsStateEventArgs>? StateChanged;
207
212 public async Task<bool> SelectMaster()
213 {
214 // Ref §3.6.1.1, ISOC 9303-10: https://www2023.icao.int/publications/Documents/9303_p10_cons_en.pdf
215
216 await this.SetState(TravelDocumentsState.SelectingMaster);
217
218 if (this.HasSniffers)
219 this.Information("SelectMaster()");
220
221 byte[] Command =
222 [
224 ISO_7816.Instructions.Select,
225 0x00, // P1 (Select master)
226 0x0c, // P2 (No File Control Information returned)
227 0x02, // Length of data
228 0x3f,
229 0x00
230 ];
231
232 byte[] Response = await this.ExecuteCommand(Command);
233
234 return this.CheckResponse(Response);
235 }
236
242 public async Task<bool> SelectApplication(byte[] ApplicationId)
243 {
244 // Ref §3.6.1.2, ISOC 9303-10: https://www2023.icao.int/publications/Documents/9303_p10_cons_en.pdf
245
246 await this.SetState(TravelDocumentsState.SelectingApplication, ApplicationId);
247
248 if (this.HasSniffers)
249 this.Information("SelectApplication(" + Hashes.BinaryToString(ApplicationId) + ")");
250
251 byte[] Command =
252 CONCAT(
253 [
255 ISO_7816.Instructions.Select,
256 0x04, // P1 (Select by Application ID)
257 0x0c, // P2 (No File Control Information returned)
258 (byte)ApplicationId.Length // Length of data
259 ],
260 ApplicationId);
261
262 byte[] Response = await this.ExecuteCommand(Command);
263
264 return this.CheckResponse(Response);
265 }
266
272 public async Task<bool> SelectFile(ushort FileId)
273 {
274 // Ref §3.6.2, ISOC 9303-10: https://www2023.icao.int/publications/Documents/9303_p10_cons_en.pdf
275
276 await this.SetState(TravelDocumentsState.SelectingFile, FileId);
277
278 if (this.HasSniffers)
279 this.Information("SelectFile(" + FileId.ToString("X4", CultureInfo.InvariantCulture) + ")");
280
281 byte[] Command =
282 [
284 ISO_7816.Instructions.Select,
285 0x02, // P1 (Select by File ID)
286 0x0c, // P2 (No File Control Information returned)
287 0x02, // Length of data
288 (byte)(FileId >> 8),
289 (byte)FileId
290 ];
291
292 byte[] Response = await this.ExecuteCommand(Command);
293
294 return this.CheckResponse(Response);
295 }
296
297 private async Task<byte[]> ExecuteCommand(byte[] Command)
298 {
299 byte[] Response = await this.ExecuteCommandSingle(Command);
300 return await this.GetRemainingResponseData(Response);
301 }
302
303 private async Task<byte[]> ExecuteCommandSingle(byte[] Command)
304 {
305 if (!this.encrypted)
306 return await this.tagInterface.ExecuteCommand(Command, this);
307
308 // Ref §9.8.4, ISOC 9303-11: https://www2023.icao.int/publications/Documents/9303_p11_cons_en.pdf
309
310 if (this.HasSniffers)
311 this.Information("Encrypting APDU: " + Hashes.BinaryToString(Command));
312
313 if (Command.Length < 5)
314 throw new ArgumentException("Command too short.", nameof(Command));
315
316 int BlockSize = this.protocol!.BlockLength;
317 byte INS = Command[1];
318 byte P1 = Command[2];
319 byte P2 = Command[3];
320 byte Lc;
321 byte Le;
322
323 if (Command.Length == 5)
324 {
325 Lc = 0;
326 Le = Command[4];
327 }
328 else
329 {
330 Lc = Command[4];
331 if (5 + Lc > Command.Length)
332 throw new ArgumentException("Command data length exceeds command length.", nameof(Command));
333
334 Le = Lc + 5 < Command.Length ? Command[Lc + 5] : (byte)0;
335 }
336
337 byte[] Header =
338 [
340 INS,
341 P1,
342 P2
343 ];
344 byte[] HeaderPadding = new byte[BlockSize - 4];
345 HeaderPadding[0] = 0x80;
346
347 int PaddedDataLen = (Lc + BlockSize - 1) & ~(BlockSize - 1);
348
349 if (PaddedDataLen + 17 > byte.MaxValue)
350 throw new ArgumentException("Command data too long.", nameof(Command));
351
352 byte[] PaddedData = new byte[PaddedDataLen];
353
354 if (Lc > 0)
355 Buffer.BlockCopy(Command, 5, PaddedData, 0, Lc);
356
357 if (Lc < PaddedDataLen)
358 PaddedData[Lc] = 0x80;
359
360 this.IncrementCounter();
361
362 if (this.HasSniffers)
363 this.Information("Send Sequence Number: " + Hashes.BinaryToString(this.sendSequenceCounter));
364
365 byte[] IV = this.protocol.Encrypt(this.ks_Enc!, this.zeroIv!, this.sendSequenceCounter!);
366
367 if (this.HasSniffers)
368 {
369 this.Information("IV: " + Hashes.BinaryToString(IV));
370 this.Information("Padded data to encrypt: " + Hashes.BinaryToString(PaddedData));
371 }
372
373 byte[] EncryptedData = this.protocol.Encrypt(this.ks_Enc!, IV, PaddedData);
374
375 if (this.HasSniffers)
376 this.Information("Encrypted data: " + Hashes.BinaryToString(EncryptedData));
377
378 byte[] Footer =
379 [
380 0x97,
381 1,
382 Le
383 ];
384
385 byte[] FooterPadding = new byte[BlockSize - 3];
386 FooterPadding[0] = 0x80;
387
388 byte[] EncryptedDataHeader = PaddedDataLen == 0 ? [] :
389 [
390 (INS & 1) == 0 ? (byte)0x87 : (byte)0x85,
391 (byte)(PaddedDataLen + 1),
392 1
393 ];
394
395 int AssociatedDataPadLen = (EncryptedDataHeader.Length + Footer.Length) % BlockSize; // Len(SSC+Header+HeaderPading+EncryptedData)=0 mod BlockSize
396 byte[] AssociatedDataPadding;
397
398 if (AssociatedDataPadLen == 0)
399 AssociatedDataPadding = [];
400 else
401 {
402 AssociatedDataPadding = new byte[BlockSize - AssociatedDataPadLen];
403 AssociatedDataPadding[0] = 0x80;
404 }
405
406 byte[] AssociatedData = CONCAT(
407 this.sendSequenceCounter!,
408 Header,
409 HeaderPadding,
410 EncryptedDataHeader,
411 EncryptedData,
412 Footer,
413 AssociatedDataPadding);
414
415 if (this.HasSniffers)
416 this.Information("Associated data to sign: " + Hashes.BinaryToString(AssociatedData));
417
418 byte[] Signature = this.cMac!.Sign(AssociatedData, 8);
419
420 byte[] EncryptedCommand = CONCAT(
421 Header,
422 [(byte)(EncryptedDataHeader.Length + EncryptedData.Length + Footer.Length + 10)],
423 EncryptedDataHeader,
424 EncryptedData,
425 Footer,
426 [
427 0x8e,
428 0x08
429 ],
430 Signature,
431 [
432 0 // Standard length
433 ]);
434
435 byte[] Response = await this.tagInterface.ExecuteCommand(EncryptedCommand, this);
436 int c = Response.Length - 2;
437
438 if (c <= 0)
439 return Response ?? [];
440
441 byte[]? EncryptedResponseData = null;
442 byte[]? ResponseSignature = null;
443 int i = 0;
444 byte SW1 = Response[c];
445 byte SW2 = Response[c + 1];
446 int StartOfSignature = 0;
447
448 while (i < c)
449 {
450 switch (Response[i++])
451 {
452 case 0x87:
453 if (i >= c)
454 {
455 this.UnexpectedEndOfResponse();
456 return Response;
457 }
458
459 int L = Response[i++];
460
461 switch (L)
462 {
463 case 0x81:
464 if (i >= c)
465 {
466 this.UnexpectedEndOfResponse();
467 return Response;
468 }
469
470 L = Response[i++];
471 break;
472
473 case 0x82:
474 if (i + 1 >= c)
475 {
476 this.UnexpectedEndOfResponse();
477 return Response;
478 }
479
480 L = Response[i++];
481 L <<= 8;
482 L |= Response[i++];
483 break;
484
485 default:
486 L &= 0x7f;
487 break;
488 }
489
490 if (i >= c)
491 {
492 this.UnexpectedEndOfResponse();
493 return Response;
494 }
495
496 if (L == 0)
497 {
498 this.Error("Expected length of DO'87' block.");
499 return Response;
500 }
501
502 byte PaddingByte = Response[i++];
503
504 if (PaddingByte != 1 && PaddingByte != 2)
505 {
506 this.Error("Expected 01 or 02 as padding byte in DO'87' block.");
507 return Response;
508 }
509
510 L--;
511 if (i + L > c)
512 {
513 this.UnexpectedEndOfResponse();
514 return Response;
515 }
516
517 EncryptedResponseData = new byte[L];
518 Buffer.BlockCopy(Response, i, EncryptedResponseData, 0, L);
519 i += L;
520
521 if (PaddingByte == 2)
522 {
523 if (i < c && Response[i] == 0x80)
524 {
525 i++;
526
527 while (i < c && Response[i] == 0x00)
528 i++;
529 }
530 }
531 break;
532
533 case 0x99:
534 if (i >= c)
535 {
536 this.UnexpectedEndOfResponse();
537 return Response;
538 }
539
540 L = Response[i++];
541
542 if (L != 2)
543 {
544 this.Error("Expected DO'99' block to have a length of 02.");
545 return Response;
546 }
547
548 if (i + L > c)
549 {
550 this.UnexpectedEndOfResponse();
551 return Response;
552 }
553
554 SW1 = Response[i++];
555 SW2 = Response[i++];
556 break;
557
558 case 0x8e:
559 StartOfSignature = i - 1;
560
561 if (i >= c)
562 {
563 this.UnexpectedEndOfResponse();
564 return Response;
565 }
566
567 L = Response[i++];
568
569 if (L != 8)
570 {
571 this.Error("Expected DO'8E' block to have a length of 08.");
572 return Response;
573 }
574
575 if (i + L > c)
576 {
577 this.UnexpectedEndOfResponse();
578 return Response;
579 }
580
581 ResponseSignature = new byte[L];
582 Buffer.BlockCopy(Response, i, ResponseSignature, 0, L);
583 i += L;
584 break;
585
586 default:
587 this.Error("Unexpected DO block: " + Response[i - 1].ToString("X2", CultureInfo.InvariantCulture));
588 return Response;
589 }
590 }
591
592 if (ResponseSignature is null)
593 {
594 this.Error("Missing DO'8E' block with response signature.");
595 return Response;
596 }
597
598 int ResponsePadLength = StartOfSignature % BlockSize;
599 byte[] ResponsePadding;
600
601 if (ResponsePadLength == 0)
602 ResponsePadding = [];
603 else
604 {
605 ResponsePadding = new byte[BlockSize - ResponsePadLength];
606 ResponsePadding[0] = 0x80;
607 }
608
609 this.IncrementCounter();
610
611 AssociatedData = new byte[StartOfSignature];
612 Buffer.BlockCopy(Response, 0, AssociatedData, 0, StartOfSignature);
613
614 AssociatedData = CONCAT(
615 this.sendSequenceCounter!,
616 AssociatedData,
617 ResponsePadding);
618
619 if (this.HasSniffers)
620 this.Information("Associated data to verify: " + Hashes.BinaryToString(AssociatedData));
621
622 if (!this.cMac.Verify(AssociatedData, ResponseSignature))
623 {
624 this.Error("Invalid response signature.");
625 return Response;
626 }
627
628 if (EncryptedResponseData is null)
629 Response = [SW1, SW2];
630 else
631 {
632 IV = this.protocol.Encrypt(this.ks_Enc!, this.zeroIv!, this.sendSequenceCounter!);
633 Response = this.protocol.Decrypt(this.ks_Enc!, IV, EncryptedResponseData);
634
635 if (IsPadded(Response, out int NrBytesPadding))
636 Array.Resize(ref Response, Response.Length - NrBytesPadding);
637
638 Response = CONCAT(Response, [SW1, SW2]);
639 }
640
641 if (this.HasSniffers)
642 this.Information("Decrypted response: " + Hashes.BinaryToString(Response));
643
644 return Response;
645 }
646
647 private async Task<byte[]> GetRemainingResponseData(byte[] Response)
648 {
649 while (Response.Length >= 2 &&
650 Response[^2] == (byte)Iso7816StatusCategory.DataStillAvailable)
651 {
652 byte Le = Response[^1];
653 byte[] GetResponseCommand =
654 [
656 ISO_7816.Instructions.GetResponse,
657 0x00,
658 0x00,
659 Le
660 ];
661
662 byte[] NextResponse = await this.ExecuteCommandSingle(GetResponseCommand);
663 byte[] CombinedResponse = new byte[Response.Length + NextResponse.Length - 2];
664
665 Buffer.BlockCopy(Response, 0, CombinedResponse, 0, Response.Length - 2);
666 Buffer.BlockCopy(NextResponse, 0, CombinedResponse, Response.Length - 2, NextResponse.Length);
667 Response = CombinedResponse;
668 }
669
670 return Response;
671 }
672
673 private static bool IsPadded(byte[] Data, out int NrBytesPadding)
674 {
675 NrBytesPadding = 0;
676
677 if (Data is null)
678 return false;
679
680 int c = Data.Length;
681 if (c == 0)
682 return false;
683
684 while (c > 0 && Data[--c] == 0)
685 ;
686
687 if (Data[c] != 0x80)
688 return false;
689
690 NrBytesPadding = Data.Length - c;
691
692 return true;
693 }
694
695 private void UnexpectedEndOfResponse()
696 {
697 this.Error("Unexpected end of encrypted response.");
698 }
699
700 private void IncrementCounter()
701 {
702 if (this.sendSequenceCounter is null)
703 throw new InvalidOperationException("Send Sequence Counter is not initialized.");
704
705 int i = this.sendSequenceCounter.Length;
706
707 while (++this.sendSequenceCounter[--i] == 0 && i >= 0)
708 ;
709 }
710
716 private bool CheckResponse(byte[] CheckResponse)
717 {
718 if (CheckResponse is null || CheckResponse.Length < 2)
719 return false;
720
721 byte SW1 = CheckResponse[^2];
722 byte SW2 = CheckResponse[^1];
723
724 switch ((Iso7816StatusCategory)SW1)
725 {
726 case Iso7816StatusCategory.Ok:
727 return true;
728
729 case Iso7816StatusCategory.DataStillAvailable:
730 this.Information(SW2.ToString(CultureInfo.InvariantCulture) + " bytes still available");
731 return true;
732
733 case Iso7816StatusCategory.WarningUnchanged:
734 switch (SW2)
735 {
736 case 0:
737 this.Warning("Warning, state unchanged. No information given.");
738 break;
739
740 default:
741 this.Warning("Warning " + SW2.ToString("X2", CultureInfo.InvariantCulture) + " triggered by card. State unchanged.");
742 break;
743
744 case 0x81:
745 this.Warning("Part of returned data may be corrupted");
746 break;
747
748 case 0x82:
749 this.Warning("End of file or record reached before reading Ne bytes.");
750 break;
751
752 case 0x83:
753 this.Warning("Selected file deactivated.");
754 break;
755
756 case 0x84:
757 this.Warning("File control information not formatted correctly.");
758 break;
759
760 case 0x85:
761 this.Warning("Selected file in termination state.");
762 break;
763
764 case 0x86:
765 this.Warning("No input data available from a sensor on the card.");
766 break;
767 }
768 return true;
769
770 case Iso7816StatusCategory.WarningChanged:
771 switch (SW2)
772 {
773 case 0:
774 this.Warning("Warning, state changed. No information given.");
775 break;
776
777 default:
778 this.Warning("Warning " + SW2.ToString("X2", CultureInfo.InvariantCulture) + " triggered by card. State changed.");
779 break;
780
781 case 0x81:
782 this.Warning("File filled up by the last write.");
783 break;
784 }
785 return true;
786
787 case Iso7816StatusCategory.ErrorUnchanged:
788 switch (SW2)
789 {
790 case 0:
791 this.Error("Error, state unchanged. No information given.");
792 break;
793
794 default:
795 this.Error("Error " + SW2.ToString("X2", CultureInfo.InvariantCulture) + " triggered by card. State unchanged.");
796 break;
797
798 case 0x01:
799 this.Error("Immediate response required by the card.");
800 break;
801 }
802 return false;
803
804 case Iso7816StatusCategory.ErrorChanged:
805 switch (SW2)
806 {
807 case 0:
808 this.Error("Error, state changed. No information given.");
809 break;
810
811 default:
812 this.Error("Error " + SW2.ToString("X2", CultureInfo.InvariantCulture) + " triggered by card. State changed.");
813 break;
814
815 case 0x81:
816 this.Error("Memory failure.");
817 break;
818 }
819 return false;
820
821 case Iso7816StatusCategory.SecurityIssue:
822 this.Error("Security issue detected.");
823 return false;
824
825 case Iso7816StatusCategory.WrongLength:
826 this.Error("Wrong length.");
827 return false;
828
829 case Iso7816StatusCategory.FunctionNotSupported:
830 switch (SW2)
831 {
832 case 0:
833 this.Error("Function Not Supported. No information given.");
834 break;
835
836 default:
837 this.Error("Function Not Supported " + SW2.ToString("X2", CultureInfo.InvariantCulture) + " triggered by card.");
838 break;
839
840 case 0x81:
841 this.Error("Logical channel not supported.");
842 break;
843
844 case 0x82:
845 this.Error("Secure messaging not supported.");
846 break;
847
848 case 0x83:
849 this.Error("Last command of the chain expected.");
850 break;
851
852 case 0x84:
853 this.Error("Command chaining not supported.");
854 break;
855 }
856 return false;
857
858 case Iso7816StatusCategory.NotAllowed:
859 switch (SW2)
860 {
861 case 0:
862 this.Error("Not Allowed. No information given.");
863 break;
864
865 default:
866 this.Error("Not Allowed " + SW2.ToString("X2", CultureInfo.InvariantCulture) + " triggered by card.");
867 break;
868
869 case 0x81:
870 this.Error("Command incompatible with file structure.");
871 break;
872
873 case 0x82:
874 this.Error("Security status not satisfied.");
875 break;
876
877 case 0x83:
878 this.Error("Authentication method blocked.");
879 break;
880
881 case 0x84:
882 this.Error("Reference data not usable.");
883 break;
884
885 case 0x85:
886 this.Error("Conditions of use not satisfied.");
887 break;
888
889 case 0x86:
890 this.Error("Command not allowed (no current EF).");
891 break;
892
893 case 0x87:
894 this.Error("Expected secure messaging data objects missing.");
895 break;
896
897 case 0x88:
898 this.Error("Incorrect secure messaging data objects.");
899 break;
900 }
901 return false;
902
903 case Iso7816StatusCategory.WrongParameters:
904 switch (SW2)
905 {
906 case 0:
907 this.Error("Wrong Parameters. No information given.");
908 break;
909
910 default:
911 this.Error("Wrong Parameters " + SW2.ToString("X2", CultureInfo.InvariantCulture) + " triggered by card.");
912 break;
913
914 case 0x80:
915 this.Error("Incorrect parameters in the command data field.");
916 break;
917
918 case 0x81:
919 this.Error("Function not supported.");
920 break;
921
922 case 0x82:
923 this.Error("File or application not found.");
924 break;
925
926 case 0x83:
927 this.Error("Record not found.");
928 break;
929
930 case 0x84:
931 this.Error("Not enough memory space in the file.");
932 break;
933
934 case 0x85:
935 this.Error("Nc inconsistent with TLV structure.");
936 break;
937
938 case 0x86:
939 this.Error("Incorrect parameters P1-P2.");
940 break;
941
942 case 0x87:
943 this.Error("Nc inconsistent with parameters P1-P2.");
944 break;
945
946 case 0x88:
947 this.Error("Referenced data or reference data not found (exact meaning depending on the command).");
948 break;
949
950 case 0x89:
951 this.Error("File already exists.");
952 break;
953
954 case 0x8A:
955 this.Error("DF name already exists.");
956 break;
957 }
958 return false;
959
960 case Iso7816StatusCategory.WrongLeField:
961 this.Error("Le field incorrect. Should be " + SW2.ToString("X2", CultureInfo.InvariantCulture));
962 return false;
963
964 default:
965 this.Error("Unexpected response received. SW1=" + SW1.ToString("X2", CultureInfo.InvariantCulture) +
966 ", SW2=" + SW2.ToString("X2", CultureInfo.InvariantCulture));
967 return false;
968 }
969 }
970
975 public Task<KeyValuePair<byte[]?, bool>> ReadBinary(uint Offset)
976 {
977 return this.ReadBinary(Offset, 0);
978 }
979
985 public async Task<KeyValuePair<byte[]?, bool>> ReadBinary(uint Offset, byte NrBytes)
986 {
987 // Ref §3.6.3, ISOC 9303-10: https://www2023.icao.int/publications/Documents/9303_p10_cons_en.pdf
988
989 await this.SetState(TravelDocumentsState.ReadingBinary, Offset);
990
991 if (this.HasSniffers)
992 {
993 this.Information("ReadBinary(" + Offset.ToString(CultureInfo.InvariantCulture) + "," +
994 NrBytes.ToString(CultureInfo.InvariantCulture) + ")");
995 }
996
997 byte[] Command;
998
999 if (Offset <= short.MaxValue)
1000 {
1001 Command =
1002 [
1004 ISO_7816.Instructions.ReadBinary,
1005 (byte)(Offset >> 8), // P1
1006 (byte)Offset, // P2
1007 NrBytes // Le
1008 ];
1009 }
1010 else if (Offset < 0x1000000)
1011 {
1012 Command =
1013 [
1015 ISO_7816.Instructions.ReadBinary + 1,
1016 0, // P1
1017 0, // P2
1018 5, // Lc=5 bytes
1019 0x54, // DO'54'
1020 3, // Length of DO'54' value
1021 (byte)(Offset >> 16),
1022 (byte)(Offset >> 8),
1023 (byte)Offset,
1024 NrBytes // Le
1025 ];
1026 }
1027 else
1028 {
1029 Command =
1030 [
1032 ISO_7816.Instructions.ReadBinary + 1,
1033 0, // P1
1034 0, // P2
1035 6, // Lc=6 bytes
1036 0x54, // DO'54'
1037 4, // Length of DO'54' value
1038 (byte)(Offset >> 24),
1039 (byte)(Offset >> 16),
1040 (byte)(Offset >> 8),
1041 (byte)Offset,
1042 NrBytes // Le
1043 ];
1044 }
1045
1046 byte[] Response = await this.ExecuteCommand(Command);
1047 int c = Response.Length;
1048
1049 if (!this.CheckResponse(Response))
1050 {
1051 if (Response is not null &&
1052 c >= 2 &&
1053 Response[^2] == (byte)Iso7816StatusCategory.WrongLeField)
1054 {
1055 Command[^1] = Response[^1];
1056 Response = await this.ExecuteCommand(Command);
1057
1058 if (!this.CheckResponse(Response))
1059 return new KeyValuePair<byte[]?, bool>(null, false);
1060 }
1061 else
1062 return new KeyValuePair<byte[]?, bool>(null, false);
1063 }
1064
1065 c = Response.Length;
1066 bool More = Response[^2] == (byte)Iso7816StatusCategory.DataStillAvailable;
1067 byte[] Data = new byte[c - 2];
1068 Buffer.BlockCopy(Response, 0, Data, 0, c - 2);
1069
1070 return new KeyValuePair<byte[]?, bool>(Data, More);
1071 }
1072
1079 public async Task<byte[]?> DownloadFile(ushort FileId, string FileName)
1080 {
1081 await this.SetState(TravelDocumentsState.DownloadingFile, FileName);
1082
1083 this.Information("Downloading " + FileName + "...");
1084
1085 if (!await this.SelectFile(FileId))
1086 return null;
1087
1088 using MemoryStream File = new();
1089 uint Offset = 0;
1090 int? ExpectedLength = null;
1091 int BytesDownloaded = 0;
1092
1093 while (!ExpectedLength.HasValue || BytesDownloaded < ExpectedLength.Value)
1094 {
1095 KeyValuePair<byte[]?, bool> P = await this.ReadBinary(Offset);
1096 if (P.Key is null)
1097 return null;
1098
1099 File.Write(P.Key, 0, P.Key.Length);
1100 BytesDownloaded += P.Key.Length;
1101
1102 if (!ExpectedLength.HasValue)
1103 {
1104 ExpectedLength = GetExpectedLength(P.Key);
1105 if (ExpectedLength.HasValue)
1106 this.Information("Expected length of file: " + ExpectedLength.Value.ToString(CultureInfo.InvariantCulture));
1107 }
1108
1109 if (!P.Value && !ExpectedLength.HasValue)
1110 {
1111 await this.SetState(TravelDocumentsState.DownloadedFile, FileName);
1112 return File.ToArray();
1113 }
1114
1115 Offset += (uint)P.Key.Length;
1116 }
1117
1118 await this.SetState(TravelDocumentsState.DownloadedFile, FileName);
1119 return File.ToArray();
1120 }
1121
1122 private static int? GetExpectedLength(byte[] Bin)
1123 {
1124 if (Bin is null)
1125 return null;
1126
1127 uint i = 0;
1128 uint c = (uint)Bin.Length;
1129 byte b;
1130
1131 if (c == 0)
1132 return 0;
1133
1134 b = Bin[i++];
1135 if ((b & 0x1f) == 0x1f)
1136 {
1137 do
1138 {
1139 if (i >= c)
1140 return null;
1141
1142 b = Bin[i++];
1143 }
1144 while ((b & 0x80) != 0);
1145 }
1146
1147 if (i >= c)
1148 return null;
1149
1150 b = Bin[i++];
1151
1152 if (b < 0x80)
1153 return (int)(i + b);
1154
1155 b -= 0x80;
1156
1157 if (b > 4)
1158 return null; // Length too long to be valid.
1159
1160 if (i + b > c)
1161 return null; // Length exceeds available data.
1162
1163 uint Length = 0;
1164
1165 while (b > 0)
1166 {
1167 Length <<= 8;
1168 Length |= Bin[i++];
1169 b--;
1170 }
1171
1172 Length += i;
1173
1174 if (Length > int.MaxValue)
1175 return null; // Length too long to be valid.
1176
1177 return (int)Length;
1178 }
1179
1185 private async Task<bool> TryFindPaceProtocol(object? CardAccess)
1186 {
1187 await this.SetState(TravelDocumentsState.FindingCipher);
1188
1189 /*
1190 * Contents of EF.CardAccess:
1191 *
1192 * SecurityInfos ::= SET of SecurityInfo
1193 *
1194 * SecurityInfo ::= SEQUENCE
1195 * {
1196 * protocol OBJECT IDENTIFIER,
1197 * requiredData ANY DEFINED BY protocol,
1198 * optionalData ANY DEFINED BY protocol OPTIONAL
1199 * }
1200 */
1201
1202 if (CardAccess is not Vector SecurityInfos)
1203 return false;
1204
1205 ChunkedList<string> OidsFound = [];
1206 IPaceProtocol? Best = null;
1207
1208 foreach (object Item in SecurityInfos)
1209 {
1210 if (Item is IPaceProtocol Current)
1211 {
1212 OidsFound.Add(Current.Oid);
1213
1214 if (this.HasSniffers)
1215 this.Information("OID " + Current.Oid + " (" + Current.GetType().Name.Replace('_', '-') + ") supported.");
1216
1217 if (Best is null ||
1218 Current.SecurityStrength > Best.SecurityStrength ||
1219 (Current.SecurityStrength == Best.SecurityStrength &&
1220 Current.ChipAuthenticationMapping && !Best.ChipAuthenticationMapping))
1221 {
1222 Best = Current;
1223 }
1224 }
1225 else if (Item is Vector SecurityInfo &&
1226 SecurityInfo.Length > 0 &&
1227 SecurityInfo.FirstElement is string Oid)
1228 {
1229 OidsFound.Add(Oid);
1230
1231 if (this.HasSniffers)
1232 this.Information("OID " + Oid + " lacks implemented support.");
1233 }
1234 else
1235 continue;
1236 }
1237
1238 if (Best is null && OidsFound.HasFirstItem)
1239 {
1240 // Notify operators & developers that ciphers have been detected that
1241 // require implementation.
1242 //
1243 // Note: Do not include sensitive personal information in the log entry.
1244
1245 Log.Alert("No supported PACE protocol found. OIDs found: " +
1246 string.Join(", ", OidsFound),
1247 new KeyValuePair<string, object>("DocumentType", this.documentInformation.DocumentType ?? string.Empty),
1248 new KeyValuePair<string, object>("IssuingState", this.documentInformation.IssuingState ?? string.Empty),
1249 new KeyValuePair<string, object>("Nationality", this.documentInformation.Nationality ?? string.Empty));
1250 }
1251
1252 this.protocol = Best;
1253 this.zeroIv = new byte[this.protocol?.BlockLength ?? 0];
1254
1255 return Best is not null;
1256 }
1257
1262 private async Task<bool> InitializePACE()
1263 {
1264 await this.SetState(TravelDocumentsState.SelectingCipher, this.protocol!.GetType().Name);
1265
1266 if (this.HasSniffers)
1267 this.Information("MSE:Set AT(" + this.protocol.Oid + ",MRZ)");
1268
1269 string[] Parts = this.protocol!.Oid.Split('.');
1270 int i, c = Parts.Length - 1;
1271 byte[] PartBytes = new byte[c];
1272
1273 for (i = 0; i < c; i++)
1274 {
1275 if (!byte.TryParse(Parts[i + 1], out PartBytes[i])) // Skip first 0.
1276 return false;
1277 }
1278
1279 byte[] ParameterIdEncoding;
1280
1281 if (this.protocol.ParameterId.HasValue)
1282 {
1283 ParameterIdEncoding = this.protocol.ParameterId.Value.ToByteArray(true, true);
1284
1285 ParameterIdEncoding = CONCAT(
1286 [
1287 0x84,
1288 (byte)ParameterIdEncoding.Length
1289 ],
1290 ParameterIdEncoding);
1291 }
1292 else
1293 ParameterIdEncoding = [];
1294
1295 byte[] Command = CONCAT(
1296 [
1298 ISO_7816.Instructions.MessageSecurityEnvironment,
1299 0xC1, // P1 - Set
1300 0xA4, // P2 - PACE
1301 (byte)(5 + c + ParameterIdEncoding.Length), // Lc
1302 0x80, // Algorithm reference
1303 (byte)c // OID Length (excluding first zero)
1304 ],
1305 [
1306 PartBytes,
1307 [
1308 0x83, // Key reference
1309 0x01, // Key reference length
1310 0x01 // MRZ key reference (0x02 = CAN, 0x03 = PIN, 0x04 = PUK)
1311 ],
1312 ParameterIdEncoding
1313 ]);
1314
1315 byte[] Response = await this.ExecuteCommand(Command);
1316
1317 return this.CheckResponse(Response);
1318 }
1319
1326 public static byte[] CONCAT(byte[] Bytes, params byte[][] MoreBytes)
1327 {
1328 int c = Bytes.Length;
1329 int i = c;
1330
1331 foreach (byte[] A in MoreBytes)
1332 c += A.Length;
1333
1334 byte[] Result = new byte[c];
1335
1336 Buffer.BlockCopy(Bytes, 0, Result, 0, i);
1337
1338 foreach (byte[] A in MoreBytes)
1339 {
1340 Buffer.BlockCopy(A, 0, Result, i, c = A.Length);
1341 i += c;
1342 }
1343
1344 return Result;
1345 }
1346
1353 public static byte[] XOR(byte[] A, byte[] B)
1354 {
1355 int i, c = A.Length;
1356
1357 if (B.Length != c)
1358 throw new ArgumentException("Byte arrays must have the same length.");
1359
1360 byte[] Result = new byte[c];
1361 for (i = 0; i < c; i++)
1362 Result[i] = (byte)(A[i] ^ B[i]);
1363
1364 return Result;
1365 }
1366
1371 public static byte[] PACE_K(DocumentInformation Info)
1372 {
1373 byte[] Data = InternetContent.ISO_8859_1.GetBytes(Info.MRZ_Information!);
1374 return Hashes.ComputeSHA1Hash(Data);
1375 }
1376
1386 public static byte[] KDF(byte[] KSeed, int Counter, bool AdjustParity,
1387 HashFunctionArray HashFunction, int NrBytes)
1388 {
1389 int c = KSeed.Length;
1390 byte[] D = new byte[c + 4];
1391 Buffer.BlockCopy(KSeed, 0, D, 0, c);
1392 int i;
1393
1394 for (i = c + 3; i >= c; i--)
1395 {
1396 D[i] = (byte)Counter;
1397 Counter >>= 8;
1398 }
1399
1400 byte[] H = HashFunction(D);
1401
1402 if (H.Length > NrBytes)
1403 Array.Resize(ref H, NrBytes);
1404
1405 if (AdjustParity)
1406 OddParity(H);
1407
1408 return H;
1409 }
1410
1411 private static void OddParity(byte[] H)
1412 {
1413 int i, j, c = H.Length;
1414 byte b;
1415
1416 for (i = 0; i < c; i++)
1417 {
1418 b = H[i];
1419 j = 0;
1420
1421 while (b != 0)
1422 {
1423 j += b & 1;
1424 b >>= 1;
1425 }
1426
1427 if ((j & 1) == 0)
1428 H[i] ^= 1;
1429 }
1430 }
1435 public static byte[] BAC_KSeed(DocumentInformation Info)
1436 {
1437 byte[] Data = InternetContent.ISO_8859_1.GetBytes(Info.MRZ_Information!);
1438 byte[] H = Hashes.ComputeSHA1Hash(Data);
1439 Array.Resize(ref H, 16);
1440 return H;
1441 }
1442
1446 public static byte[] BAC_KEnc(DocumentInformation Info)
1447 {
1448 return BAC_KDF(Info, 1, true); // KDF(K,1)
1449 }
1450
1454 public static byte[] BAC_KMac(DocumentInformation Info)
1455 {
1456 return BAC_KDF(Info, 2, true); // KDF(K,2)
1457 }
1458
1459 private static byte[] BAC_KDF(DocumentInformation Info, int Counter, bool AdjustParity)
1460 {
1461 byte[] KSeed = BAC_KSeed(Info);
1462 return KDF(KSeed, Counter, AdjustParity, Hashes.ComputeSHA1Hash, 16);
1463 }
1464
1469 private async Task<byte[]?> GetPaceEncryptedNonce()
1470 {
1471 await this.SetState(TravelDocumentsState.GettingNonce);
1472
1473 this.Information("General Authenticate (Get Encrypted Nonce)");
1474
1475 byte[] Command =
1476 [
1478 ISO_7816.Instructions.GeneralAuthenticate,
1479 0x00, // P1
1480 0x00, // P2
1481 0x02, // Lc
1482 0x7c, 0x00, // Absent
1483 0x00 // Le (Maximal response length: 256 bytes)
1484 ];
1485
1486 byte[] Response = await this.ExecuteCommand(Command);
1487
1488 if (!this.CheckResponse(Response))
1489 return null;
1490
1491 if (Response.Length < 6 ||
1492 Response[0] != 0x7c ||
1493 Response.Length != Response[1] + 4 ||
1494 Response[2] != 0x80 || // Encrypted nonce
1495 Response.Length != Response[3] + 6 ||
1496 Response[^2] != 0x90 ||
1497 Response[^1] != 0x00)
1498 {
1499 this.Error("Unexpected response received.");
1500 return null;
1501 }
1502
1503 int c = Response[3];
1504 byte[] Nonce = new byte[c];
1505
1506 Buffer.BlockCopy(Response, 4, Nonce, 0, c);
1507
1508 return Nonce;
1509 }
1510
1516 private async Task<byte[]?> GetPaceRemotePublicKey(byte[] LocalPublicKey)
1517 {
1518 await this.SetState(TravelDocumentsState.GettingPublicKey);
1519
1520 return DecodePublicKey(await this.GeneralAuthenticate(
1521 EncodePublicKey(LocalPublicKey),
1522 "Get Remote Public Key",
1523 false, // More commands in chain expected
1524 0x81, // Mapping Data
1525 0x82)); // Mapping Data response
1526 }
1527
1533 private async Task<byte[]?> GetPaceRemotePublicEphemeralKey(byte[] LocalPublicEphemeralKey)
1534 {
1535 await this.SetState(TravelDocumentsState.GettingEphemeralPublicKey);
1536
1537 return DecodePublicKey(await this.GeneralAuthenticate(
1538 EncodePublicKey(LocalPublicEphemeralKey),
1539 "Get Remote Ephemeral Public Key",
1540 false, // More commands in chain expected
1541 0x83, // Terminal's Ephemeral Public Key
1542 0x84)); // Chip's Ephemeral Public Key
1543 }
1544
1550 private async Task<byte[]?> GetPaceRemoteVerificationToken(
1551 byte[] LocalVerificationToken)
1552 {
1553 await this.SetState(TravelDocumentsState.GettingVerificationToken);
1554
1555 return await this.GeneralAuthenticate(LocalVerificationToken,
1556 "Get Remote Verification Token",
1557 true, // Last command in chain
1558 0x85, // Terminal's Verification Token
1559 0x86); // Chip's Verification Token
1560 }
1561
1562 private static byte[] EncodePublicKey(byte[] LocalPublicKey)
1563 {
1564 int c = LocalPublicKey.Length;
1565 byte[] EncodedPublicKey = new byte[c + 1];
1566
1567 EncodedPublicKey[0] = 4; // X coordinate following by Y coordinate (default for EEC curves)
1568 Buffer.BlockCopy(LocalPublicKey, 0, EncodedPublicKey, 1, c);
1569
1570 return EncodedPublicKey;
1571 }
1572
1573 private static byte[]? DecodePublicKey(byte[]? Data)
1574 {
1575 int c;
1576
1577 if (Data is null || (c = Data.Length) == 0 || Data[0] != 4) // X coordinate following by Y coordinate (default for EEC curves)
1578 return null;
1579
1580 byte[] DecodedPublicKey = new byte[c - 1];
1581 Buffer.BlockCopy(Data, 1, DecodedPublicKey, 0, c - 1);
1582
1583 return DecodedPublicKey;
1584 }
1585
1586 private async Task<byte[]?> GeneralAuthenticate(byte[] Data, string Comment, bool LastInChain,
1587 byte Command, byte ExpectedResponse)
1588 {
1589 this.Information("General Authenticate (" + Comment + ")");
1590
1591 int c = Data.Length;
1592
1593 byte[] Request = CONCAT(
1594 [
1596 ISO_7816.Instructions.GeneralAuthenticate,
1597 0x00, // P1
1598 0x00, // P2
1599 (byte)(c + 4) // Lc
1600 ],
1601 [
1602 [
1603 0x7c, // Dynamic Authentication Data
1604 (byte)(c + 2),
1605 Command,
1606 (byte)c
1607 ],
1608 Data,
1609 [ 0x00 ] // Le (Maximal response length: 256 bytes)
1610 ]);
1611
1612 byte[] Response = await this.ExecuteCommand(Request);
1613
1614 if (!this.CheckResponse(Response))
1615 return null;
1616
1617 if (Response.Length < 6 ||
1618 Response[0] != 0x7c ||
1619 Response.Length != Response[1] + 4 ||
1620 Response[2] != ExpectedResponse ||
1621 Response.Length != Response[3] + 6 ||
1622 Response[^2] != 0x90 ||
1623 Response[^1] != 0x00)
1624 {
1625 this.Error("Unexpected response received.");
1626 return null;
1627 }
1628
1629 c = Response[3];
1630 byte[] ResponseData = new byte[c];
1631
1632 Buffer.BlockCopy(Response, 4, ResponseData, 0, c);
1633
1634 return ResponseData;
1635 }
1636
1641 private async Task<byte[]?> GetBacChallenge()
1642 {
1643 await this.SetState(TravelDocumentsState.GettingChallenge);
1644
1645 this.Information("GetChallenge");
1646
1647 byte[] Command =
1648 [
1650 ISO_7816.Instructions.GetChallenge,
1651 0x00, // P1
1652 0x00, // P2
1653 0x08 // Le
1654 ];
1655
1656 byte[] Response = await this.ExecuteCommand(Command);
1657
1658 if (!this.CheckResponse(Response))
1659 return null;
1660
1661 if (Response.Length != 10 || Response[8] != 0x90 || Response[9] != 0x00)
1662 {
1663 this.Error("Unexpected response received.");
1664 return null;
1665 }
1666
1667 byte[] Challenge = new byte[8];
1668 Buffer.BlockCopy(Response, 0, Challenge, 0, 8);
1669
1670 return Response;
1671 }
1672
1678 private async Task<byte[]?> ExternalBacAuthenticate(byte[] ChallengeResponse)
1679 {
1680 await this.SetState(TravelDocumentsState.RespondingToChallenge);
1681
1682 this.Information("ChallengeResponse");
1683
1684 byte Lc = (byte)ChallengeResponse.Length;
1685 byte[] Command = CONCAT(
1686 [
1688 ISO_7816.Instructions.ExternalAuthenticate,
1689 0x00, // P1
1690 0x00, // P2
1691 Lc
1692 ],
1693 ChallengeResponse,
1694 [
1695 0x28 // Le
1696 ]);
1697
1698 byte[] Response = await this.ExecuteCommand(Command);
1699
1700 if (!this.CheckResponse(Response))
1701 return null;
1702
1703 if (Response.Length != 10 || Response[8] != 0x90 || Response[9] != 0x00)
1704 {
1705 this.Error("Unexpected response received.");
1706 return null;
1707 }
1708
1709 byte[] Challenge = new byte[8];
1710 Buffer.BlockCopy(Response, 0, Challenge, 0, 8);
1711
1712 return Response;
1713 }
1714
1720 public async Task<AuthenticateResult> Authenticate()
1721 {
1722 // §4.2 1. https://www2023.icao.int/publications/Documents/9303_p11_cons_en.pdf
1723
1724 byte[]? Data = await this.TryDownloadCardAccessForAuthentication();
1725
1726 if (Data is not null &&
1727 ASN1.TryDecodeDer(this, Data, out object? CardAccess) &&
1728 await this.TryFindPaceProtocol(CardAccess))
1729 {
1730 if (this.encrypted)
1731 return AuthenticateResult.AlreadyEncrypted; // TODO: Renegotiate session keys, see §9.8.2, ICAO 9303-11.
1732
1733 // PACE
1734 // §4.2 3. https://www2023.icao.int/publications/Documents/9303_p11_cons_en.pdf
1735
1736 if (this.HasSniffers)
1737 this.Information("PACE protocol " + this.protocol!.GetType().Name.Replace('_', '-') + " selected.");
1738
1739 if (!await this.InitializePACE())
1740 {
1741 this.Error("Unable to initialize PACE protocol.");
1742 return AuthenticateResult.UnableToInitializePace;
1743 }
1744 else if (this.protocol is PaceEcdhProtocol EecProtocol)
1745 this.Information("PACE protocol initialized (" + EecProtocol.Curve?.CurveName + ").");
1746 else
1747 this.Information("PACE protocol initialized.");
1748
1749 if (!await this.protocol!.Authenticate(this))
1750 {
1751 this.Error("Authentication unsuccessful.");
1752 return AuthenticateResult.UnableToAuthenticatePace;
1753 }
1754 }
1755 else
1756 {
1757 // BAC
1758 // §4.2 4. https://www2023.icao.int/publications/Documents/9303_p11_cons_en.pdf
1759
1760 this.Information("Attempting legacy BAC protocol.");
1761
1762 // §4.3, §D.3, https://www.icao.int/publications/Documents/9303_p11_cons_en.pdf
1763
1764 byte[]? Challenge = await this.GetBacChallenge();
1765
1766 if (Challenge is null)
1767 {
1768 this.Error("Unable to get BAC challenge.");
1769 return AuthenticateResult.UnableToGetBacChallenge;
1770 }
1771
1772 byte[] ChallengeResponse = CalcChallengeResponse3DES(this.documentInformation, Challenge);
1773 byte[]? Response = await this.ExternalBacAuthenticate(ChallengeResponse);
1774
1775 // TODO: Implement/Test BAC
1776
1777 return AuthenticateResult.BacNotImplemented;
1778 }
1779
1780 return AuthenticateResult.Success;
1781 }
1782
1783 private async Task<byte[]?> TryDownloadCardAccessForAuthentication()
1784 {
1785 byte[]? Data = await this.DownloadFile(EF.CardAccess, "EF.CardAccess");
1786 if (Data is not null)
1787 return Data;
1788
1789 if (this.encrypted)
1790 return Data;
1791
1792 this.Information("Retrying EF.CardAccess after explicit master file selection.");
1793 if (!await this.SelectMaster())
1794 {
1795 this.Error("Unable to select the master file before reading EF.CardAccess.");
1796 return Data;
1797 }
1798
1799 Data = await this.DownloadFile(EF.CardAccess, "EF.CardAccess");
1800 return Data;
1801 }
1802
1812 public static byte[] CalcChallengeResponse3DES(byte[] Challenge, byte[] Rnd1, byte[] Rnd2,
1813 byte[] KEnc, byte[] KMac)
1814 {
1815 byte[] S = CONCAT(Rnd1, Challenge, Rnd2);
1816 byte[] EIFD;
1817 byte[] MIFD;
1818
1819 using (TripleDES Cipher = TripleDES.Create())
1820 {
1821 Cipher.Mode = CipherMode.CBC;
1822 Cipher.Padding = PaddingMode.None;
1823
1824 using ICryptoTransform Encryptor = Cipher.CreateEncryptor(KEnc, new byte[8]);
1825 EIFD = Encryptor.TransformFinalBlock(S, 0, 32);
1826 }
1827
1828 // MAC Algorithm described in ISO/IEC 9797-1
1829 // Ref: https://en.wikipedia.org/wiki/ISO/IEC_9797-1
1830
1831 using (DES Cipher = DES.Create())
1832 {
1833 Cipher.Mode = CipherMode.CBC;
1834 Cipher.Padding = PaddingMode.None;
1835
1836 int i = 0;
1837 int c = EIFD.Length;
1838 int j;
1839
1840 byte[] Data = new byte[c + 8];
1841 Buffer.BlockCopy(EIFD, 0, Data, 0, c);
1842 Data[c] = 0x80; // Padding method 2, append 80 00 00 00 00 00 00 00
1843
1844 byte[] Ka = new byte[8];
1845 byte[] Kb = new byte[8];
1846
1847 Buffer.BlockCopy(KMac, 0, Ka, 0, 8);
1848 Buffer.BlockCopy(KMac, 8, Kb, 0, 8);
1849
1850 byte[] Block = new byte[8];
1851 byte[]? H = null;
1852
1853 c += 8;
1854 using (ICryptoTransform Encryptor2 = Cipher.CreateEncryptor(Ka, new byte[8]))
1855 {
1856 while (i < c)
1857 {
1858 Buffer.BlockCopy(Data, i, Block, 0, 8);
1859 i += 8;
1860
1861 if (H is not null)
1862 {
1863 for (j = 0; j < 8; j++)
1864 Block[j] ^= H[j];
1865 }
1866
1867 H = Encryptor2.TransformFinalBlock(Block, 0, 8);
1868 }
1869
1870 using (ICryptoTransform FinalDecryptor = Cipher.CreateDecryptor(Kb, new byte[8]))
1871 {
1872 H = FinalDecryptor.TransformFinalBlock(H!, 0, 8);
1873 }
1874
1875 H = Encryptor2.TransformFinalBlock(H, 0, 8);
1876 }
1877
1878 MIFD = H;
1879 }
1880
1881 return CONCAT(EIFD, MIFD);
1882 }
1883
1890 public static byte[] CalcChallengeResponse3DES(DocumentInformation Info, byte[] Challenge)
1891 {
1892 byte[] Rnd1 = new byte[8];
1893 byte[] Rnd2 = new byte[16];
1894
1895 using (RandomNumberGenerator Rnd = RandomNumberGenerator.Create())
1896 {
1897 Rnd.GetBytes(Rnd1);
1898 Rnd.GetBytes(Rnd2);
1899 }
1900
1901 return CalcChallengeResponse3DES(Challenge, Rnd1, Rnd2, BAC_KEnc(Info), BAC_KMac(Info));
1902 }
1903
1909 internal async Task<bool> AuthenticateGenericMapping()
1910 {
1911 if (this.protocol is not PaceEcdhProtocol EcdhProtocol)
1912 return false;
1913
1914 EllipticCurve? Curve = EcdhProtocol!.Curve;
1915 if (Curve is null)
1916 return false;
1917
1918 try
1919 {
1920 // Encrypted Nonce
1921
1922 byte[]? z = await this.GetPaceEncryptedNonce();
1923 if (z is null)
1924 {
1925 this.Error("Unable to get PACE encrypted nonce.");
1926 return false;
1927 }
1928
1929 this.Information("Encrypted nonce: " + Hashes.BinaryToString(z));
1930
1931 byte[] Kπ = this.protocol.KDFπ(this.documentInformation);
1932 byte[] s = this.protocol.DecryptNonce(Kπ, z);
1933
1934 this.Information("Decrypted nonce: " + Hashes.BinaryToString(s));
1935
1936 // Main keys
1937
1938 byte[] LocalPublicKey;
1939 int KeyIndex = 0;
1940
1941 IsoDepReplay? Replay = this.tagInterface as IsoDepReplay;
1942
1943 // Creates a public key in big-endian format.
1944 LocalPublicKey = this.protocol.CreateNewKey(this.localKeySeed, ref KeyIndex);
1945
1946 if (Replay is not null)
1947 {
1948 string LocalPrivateKey = Replay.GetInfo("Local private key:", this);
1949 XmlDocument Doc = new();
1950 Doc.LoadXml(LocalPrivateKey);
1951
1952 byte[] LocalPublicKey2 = this.protocol.ImportKey(Doc);
1953
1954 Curve = EcdhProtocol.Curve;
1955 if (Curve is null)
1956 return false;
1957
1958 if (this.localKeySeed is null)
1959 LocalPublicKey = LocalPublicKey2;
1960 else if (Convert.ToBase64String(LocalPublicKey) != Convert.ToBase64String(LocalPublicKey2))
1961 {
1962 this.Error("Local public key mismatch.");
1963 return false;
1964 }
1965 }
1966
1967 this.Information("Local public key: " + Hashes.BinaryToString(LocalPublicKey));
1968 this.Information("Local private key: " + Curve.Export());
1969
1970 byte[]? RemotePublicKey = await this.GetPaceRemotePublicKey(LocalPublicKey); // Big-endian format.
1971
1972 if (RemotePublicKey is null)
1973 {
1974 this.Error("Unable to get PACE remote public key.");
1975 return false;
1976 }
1977
1978 this.Information("Remote public key: " + Hashes.BinaryToString(RemotePublicKey));
1979
1980 if (!Curve.IsPoint(RemotePublicKey, true))
1981 {
1982 this.Error("Remote public key not on curve.");
1983 return false;
1984 }
1985
1986 // Shared Secret
1987
1988 byte[] SharedSecret = this.protocol.GetSharedSecret(RemotePublicKey);
1989
1990 this.Information("Shared secret: " + Hashes.BinaryToString(SharedSecret));
1991
1992 // Map
1993
1994 PointOnCurve Ĝ = EcdhProtocol.GetGenericMap(s, RemotePublicKey);
1995 byte[] Generator = Curve.Encode(Ĝ, true);
1996
1997 this.Information("Generator Ĝ: " + Hashes.BinaryToString(Generator));
1998
1999 // Ephemeral keys
2000
2001 byte[] LocalEphemeralPrivateKey;
2002
2003 if (Replay is not null)
2004 {
2005 string EphemeralKey = Replay.GetInfo("Local ephemeral private key:", this);
2006 LocalEphemeralPrivateKey = Hashes.StringToBinary(EphemeralKey);
2007
2008 if (this.localKeySeed is not null)
2009 {
2010 byte[] LocalEphemeralPrivateKey2 = EcdhProtocol.GenerateSecret(this.localKeySeed, ref KeyIndex);
2011
2012 if (Convert.ToBase64String(LocalEphemeralPrivateKey) != Convert.ToBase64String(LocalEphemeralPrivateKey2))
2013 {
2014 this.Error("Local ephemeral private key mismatch.");
2015 return false;
2016 }
2017 }
2018 }
2019 else
2020 LocalEphemeralPrivateKey = EcdhProtocol.GenerateSecret(this.localKeySeed, ref KeyIndex);
2021
2022 this.Information("Local ephemeral private key: " + Hashes.BinaryToString(LocalEphemeralPrivateKey));
2023
2024 PointOnCurve P1 = Curve.ScalarMultiplication(LocalEphemeralPrivateKey, Ĝ, true);
2025 byte[] LocalEphemeralPublicKey = Curve.Encode(P1, true);
2026
2027 this.Information("Local ephemeral public key: " + Hashes.BinaryToString(LocalEphemeralPublicKey));
2028
2029 byte[]? RemoteEphemeralPublicKey = await this.GetPaceRemotePublicEphemeralKey(LocalEphemeralPublicKey);
2030
2031 if (RemoteEphemeralPublicKey is null)
2032 {
2033 this.Error("Unable to get PACE remote ephemeral public key.");
2034 return false;
2035 }
2036
2037 this.Information("Remote ephemeral public key: " + Hashes.BinaryToString(RemoteEphemeralPublicKey));
2038
2039 if (!Curve.IsPoint(RemoteEphemeralPublicKey, true))
2040 {
2041 this.Error("Remote ephemeral public key not on curve.");
2042 return false;
2043 }
2044
2045 // Ephemeral shared secret
2046
2047 int c = RemoteEphemeralPublicKey.Length;
2048 int c2 = c >> 1;
2049 byte[] RemoteEphemeralPublicKeyX = new byte[c2];
2050 byte[] RemoteEphemeralPublicKeyY = new byte[c2];
2051
2052 Buffer.BlockCopy(RemoteEphemeralPublicKey, 0, RemoteEphemeralPublicKeyX, 0, c2);
2053 Buffer.BlockCopy(RemoteEphemeralPublicKey, c2, RemoteEphemeralPublicKeyY, 0, c2);
2054
2055 Array.Reverse(RemoteEphemeralPublicKeyX);
2056 Array.Reverse(RemoteEphemeralPublicKeyY);
2057
2058 PointOnCurve RemoteEphemeralPublicPoint = new(
2059 EllipticCurve.ToInt(RemoteEphemeralPublicKeyX),
2060 EllipticCurve.ToInt(RemoteEphemeralPublicKeyY));
2061
2062 PointOnCurve EphemeralSharedPoint = Curve.ScalarMultiplication(
2063 LocalEphemeralPrivateKey, RemoteEphemeralPublicPoint, true);
2064
2065 byte[] EphemeralSharedPointX = EphemeralSharedPoint.X.ToByteArray(); // Little-endian
2066
2067 if (EphemeralSharedPointX.Length != Curve.OrderBytes)
2068 Array.Resize(ref EphemeralSharedPointX, Curve.OrderBytes);
2069
2070 Array.Reverse(EphemeralSharedPointX); // Big-endian
2071
2072 this.Information("Ephemeral shared secret: " + Hashes.BinaryToString(EphemeralSharedPointX));
2073
2074 // Session keys
2075
2076 this.ks_Enc = this.protocol.KDF_Enc(EphemeralSharedPointX);
2077 this.ks_Mac = this.protocol.KDF_Mac(EphemeralSharedPointX);
2078
2079 this.Information("KS_Enc: " + Hashes.BinaryToString(this.ks_Enc));
2080 this.Information("KS_Mac: " + Hashes.BinaryToString(this.ks_Mac));
2081
2082 // Associated Data
2083
2084 byte[] AD_IFD = PaceProtocol.CreateAssociatedData(this.protocol.Oid, RemoteEphemeralPublicKey);
2085 byte[] AD_IC = PaceProtocol.CreateAssociatedData(this.protocol.Oid, LocalEphemeralPublicKey);
2086
2087 this.Information("AD_IFD: " + Hashes.BinaryToString(AD_IFD));
2088 this.Information("AD_IC: " + Hashes.BinaryToString(AD_IC));
2089
2090 // Computing MAC
2091
2092 this.cMac = this.protocol.GetAuthenticator(this.ks_Mac);
2093
2094 byte[] T_IFD = this.cMac.Sign(AD_IFD, 8);
2095
2096 this.Information("T_IFD: " + Hashes.BinaryToString(T_IFD));
2097
2098 byte[]? RemoteToken = await this.GetPaceRemoteVerificationToken(T_IFD);
2099
2100 if (RemoteToken is null)
2101 {
2102 this.Error("Unable to get remote token.");
2103 return false;
2104 }
2105
2106 this.Information("Remote Token: " + Hashes.BinaryToString(RemoteToken));
2107
2108 if (!this.cMac.Verify(AD_IC, RemoteToken))
2109 {
2110 byte[] T_IC = this.cMac.Sign(AD_IC, 8);
2111
2112 this.Error("PACE token validation failed. Expected _IC: " + Hashes.BinaryToString(T_IC));
2113 return false;
2114 }
2115
2116 this.Information("Authentication successful.");
2117
2118 this.encrypted = true;
2119 this.enhancedSecurity = false;
2120 this.sendSequenceCounter = new byte[this.protocol.BlockLength];
2121
2122 return true;
2123 }
2124 catch (Exception ex)
2125 {
2126 this.Exception(ex);
2127 return false;
2128 }
2129 }
2130
2134 public bool ReadDG1 { get; set; } = true;
2135
2139 public bool ReadDG2 { get; set; } = true;
2140
2144 public bool ReadDG3 { get; set; } = false;
2145
2149 public bool ReadDG4 { get; set; } = false;
2150
2154 public bool ReadDG5 { get; set; } = false;
2155
2159 public bool ReadDG7 { get; set; } = false;
2160
2164 public bool ReadDG8 { get; set; } = false;
2165
2169 public bool ReadDG9 { get; set; } = false;
2170
2174 public bool ReadDG10 { get; set; } = false;
2175
2179 public bool ReadDG11 { get; set; } = true;
2180
2184 public bool ReadDG12 { get; set; } = false;
2185
2189 public bool ReadDG13 { get; set; } = false;
2190
2194 public bool ReadDG14 { get; set; } = false;
2195
2199 public bool ReadDG15 { get; set; } = false;
2200
2204 public bool ReadDG16 { get; set; } = false;
2205
2211 public async Task<ReadTravelDocumentResult> ReadTravelDocument(string IdDomain)
2212 {
2213 if (!await this.SelectApplication(Applications.DF1))
2214 {
2215 this.Error("Unable to select the LDS1 eMRTD application.");
2216 return ReadTravelDocumentResult.Lds1ApplicationNotFound;
2217 }
2218
2219 // Reading EF.COM
2220
2221 this.Information("LDS1 eMRTD application selected.");
2222
2223 byte[]? Data = await this.DownloadFile(EF.COM, "EF.COM");
2224 if (Data is null)
2225 {
2226 this.Error("Unable to download EF.COM.");
2227 return ReadTravelDocumentResult.UnableToReadEfCom;
2228 }
2229
2230 if (!TryParseDataObject(Data, this, out ApplicationLevelInformation? AppInfo))
2231 {
2232 this.Error("Unable to parse application level information.");
2233 return ReadTravelDocumentResult.UnableToParseEfCom;
2234 }
2235
2236 this.appInfo = AppInfo;
2237 await this.AppInfoUpdated.Raise(this, EventArgs.Empty);
2238
2239 // Reading EF.SOD, §4.6.2 ICAO 9303-10
2240
2241 Data = await this.DownloadFile(EF.SOD, "EF.SOD");
2242 if (Data is null)
2243 {
2244 this.Error("Unable to download EF.SOD.");
2245 return ReadTravelDocumentResult.UnableToReadEfSod;
2246 }
2247
2248 if (!TryParseDataObject(Data, this, out DocumentSecurityObject? SecurityInfo))
2249 {
2250 this.Error("Unable to decode Document Security Object.\r\n\r\n" +
2251 Convert.ToBase64String(Data, Base64FormattingOptions.InsertLineBreaks));
2252 return ReadTravelDocumentResult.UnableToParseEfSod;
2253 }
2254
2255 if ((SecurityInfo.SignedData?.Certificates?.Length ?? 0) == 0)
2256 {
2257 this.Error("No certificates available in EF.SOD.");
2258 return ReadTravelDocumentResult.NoCertificates;
2259 }
2260
2261 if (SecurityInfo.SignedData!.Certificates.Length > 1)
2262 {
2263 this.Error("Multiple certificates available in EF.SOD.");
2264 return ReadTravelDocumentResult.MultipleCertificates;
2265 }
2266
2267 // Validating chip certificate to ensure valid issuer.
2268
2269 this.Information("Validating certificate.");
2270 await this.SetState(TravelDocumentsState.ValidatingCertificate);
2271
2272 foreach (Certificate Cert in SecurityInfo.SignedData!.Certificates)
2273 {
2274 ChunkedList<Certificate> Certificates = [Cert];
2275 Dictionary<string, bool> CrlUrls = [];
2276
2277 foreach (string CrlUrl in GetRevocationListUrls(Cert))
2278 CrlUrls[CrlUrl] = true;
2279
2280 KeyValuePair<string?, byte[]?> P = GetAuthorityKeyIdentifier(Cert);
2281 Dictionary<string, bool> Processed = [];
2282 string? CountryCode = P.Key;
2283 byte[]? IssuerKeyReference = P.Value;
2284
2285 if (string.IsNullOrEmpty(CountryCode) || IssuerKeyReference is null)
2286 {
2287 this.Error("Required Authority Key Identifier not found in certificate.");
2288 return ReadTravelDocumentResult.InvalidCertificate;
2289 }
2290
2291 while (!string.IsNullOrEmpty(CountryCode) && IssuerKeyReference is not null)
2292 {
2293 string Key = Convert.ToBase64String(IssuerKeyReference);
2294 if (Processed.ContainsKey(Key))
2295 break;
2296
2297 Processed[Key] = true;
2298
2299 this.Information("Retrieving issuer certificate: " + Hashes.BinaryToString(IssuerKeyReference));
2300
2301 Certificate? IssuerCertificate = await CertificateStore.TryLoadCertificate(
2302 IdDomain, CountryCode, IssuerKeyReference, this);
2303
2304 if (IssuerCertificate is null)
2305 {
2306 this.Error("Issuer certificate not found.");
2307 return ReadTravelDocumentResult.InvalidCertificate;
2308 }
2309
2310 Certificates.Insert(0, IssuerCertificate);
2311
2312 // Make sure to use Certificate Revocation Lists (CRLs) from ICAO approved certificates.
2313
2314 foreach (string CrlUrl in GetRevocationListUrls(IssuerCertificate))
2315 CrlUrls[CrlUrl] = true;
2316
2317 P = GetAuthorityKeyIdentifier(IssuerCertificate);
2318 CountryCode = P.Key;
2319 IssuerKeyReference = P.Value;
2320 }
2321
2322 if (CrlUrls.Count == 0)
2323 {
2324 this.Error("No approved CRLs found.");
2325 return ReadTravelDocumentResult.InvalidCertificate;
2326 }
2327
2328 foreach (string CrlUrl in CrlUrls.Keys)
2329 {
2330 this.Information("Retrieving CRL: " + CrlUrl);
2331
2332 CertificateList? RevokedCertificates = await CertificateStore.TryLoadCrl(CrlUrl, this);
2333 if (RevokedCertificates is null)
2334 {
2335 this.Error("Unable to load CRL.");
2336 return ReadTravelDocumentResult.InvalidCertificate;
2337 }
2338
2339 this.Information("Verifying CRL signature.");
2340
2341 if (!await RevokedCertificates.VerifySignature(IdDomain, CountryCode!, this))
2342 {
2343 this.Error("CRL Signature invalid.");
2344 return ReadTravelDocumentResult.InvalidCertificate;
2345 }
2346
2347 this.Information("Checking if certificates are revoked.");
2348
2349 if (RevokedCertificates.HasBeenRevoked(Cert, out RevokedReason Reason))
2350 {
2351 this.Error("Certificate " + Cert.SerialNumber.ToString("X", CultureInfo.InvariantCulture) + " has been revoked: " + Reason.ToString());
2352 return ReadTravelDocumentResult.InvalidCertificate;
2353 }
2354
2355 foreach (Certificate Certificate2 in Certificates)
2356 {
2357 if (RevokedCertificates.HasBeenRevoked(Certificate2, out Reason))
2358 {
2359 this.Error("Certificate " + Certificate2.SerialNumber.ToString("X", CultureInfo.InvariantCulture) + " has been revoked: " + Reason.ToString());
2360 return ReadTravelDocumentResult.InvalidCertificate;
2361 }
2362 }
2363 }
2364
2365 this.Information("Verifying certificate chain.");
2366
2367 if (!CertificateChain.VerifySignatures(this, [.. Certificates]))
2368 {
2369 this.Error("Signatures in certificate chain not valid.");
2370 return ReadTravelDocumentResult.InvalidCertificate;
2371 }
2372 }
2373
2374 this.securityinfo = SecurityInfo;
2375 await this.SecurityInfoUpdated.Raise(this, EventArgs.Empty);
2376
2377 if (this.ReadDG1 && (this.appInfo.TagList?.HasDataGroup(1) ?? false))
2378 {
2379 // Reading EF.DG1 (MRZ), §4.7.1 ICAO 9303-10
2380
2381 this.Information("EF.DG1 (MRZ) supported.");
2382
2383 Data = await this.DownloadFile(EF.DG1, "EF.DG1");
2384 if (Data is null)
2385 {
2386 this.Error("Unable to download EF.DG1.");
2387 return ReadTravelDocumentResult.UnableToReadEfDg;
2388 }
2389
2390 if (!this.ValidateDataGroupData(1, Data))
2391 return ReadTravelDocumentResult.DgHashDigestInvalid;
2392
2393 if (!TryParseDataObject(Data, this, out MachineReadableZoneInformation? DataGroup1) ||
2394 DataGroup1.Mrz is null)
2395 {
2396 this.Error("Unable to decode DG1 (MRZ Information).\r\n\r\n" +
2397 Convert.ToBase64String(Data, Base64FormattingOptions.InsertLineBreaks));
2398 return ReadTravelDocumentResult.UnableToParseEfDg;
2399 }
2400
2401 this.mrz = DataGroup1.Mrz;
2402 if (this.mrz.DocumentInformation is null)
2403 this.Warning("Unable to parse MRZ information.");
2404
2405 await this.MrzUpdated.Raise(this, EventArgs.Empty);
2406 }
2407
2408 if (this.ReadDG2 && (this.appInfo.TagList?.HasDataGroup(2) ?? false))
2409 {
2410 // Reading EF.DG2 (Encoded Identification Features — Face), §4.7.2 ICAO 9303-10
2411
2412 this.Information("EF.DG2 (Encoded Identification Features — Face) supported.");
2413
2414 Data = await this.DownloadFile(EF.DG2, "EF.DG2");
2415 if (Data is null)
2416 {
2417 this.Error("Unable to download EF.DG2.");
2418 return ReadTravelDocumentResult.UnableToReadEfDg;
2419 }
2420
2421 if (!this.ValidateDataGroupData(2, Data))
2422 return ReadTravelDocumentResult.DgHashDigestInvalid;
2423
2424 if (!TryParseDataObject(Data, this, out BiometricEncodingFace? BiometricEncoding))
2425 {
2426 this.Error("Unable to decode Biometric Encoding in DG2 (Encoded Identification Features — Face).\r\n\r\n" +
2427 Convert.ToBase64String(Data, Base64FormattingOptions.InsertLineBreaks));
2428 return ReadTravelDocumentResult.UnableToParseEfDg;
2429 }
2430
2431 this.biometricEncodingFace = BiometricEncoding.Templates?.Templates;
2432 await this.BiometricEncodingFaceUpdated.Raise(this, EventArgs.Empty);
2433 }
2434
2435 if (this.enhancedSecurity && this.ReadDG3 && (this.appInfo.TagList?.HasDataGroup(3) ?? false))
2436 {
2437 try
2438 {
2439 // Reading EF.DG3 (Additional Identification Feature — Finger(s)), §4.7.3 ICAO 9303-10
2440
2441 this.Information("EF.DG3 (Additional Identification Feature — Finger(s)) supported.");
2442
2443 Data = await this.DownloadFile(EF.DG3, "EF.DG3");
2444 if (Data is null)
2445 {
2446 this.Error("Unable to download EF.DG3.");
2447 return ReadTravelDocumentResult.UnableToReadEfDg;
2448 }
2449
2450 if (!this.ValidateDataGroupData(3, Data))
2451 return ReadTravelDocumentResult.DgHashDigestInvalid;
2452
2453 if (!TryParseDataObject(Data, this, out BiometricEncodingFingers? BiometricEncoding))
2454 {
2455 this.Error("Unable to decode Biometric Encoding in DG3 (Additional Identification Feature — Finger(s)).\r\n\r\n" +
2456 Convert.ToBase64String(Data, Base64FormattingOptions.InsertLineBreaks));
2457 return ReadTravelDocumentResult.UnableToParseEfDg;
2458 }
2459
2460 this.biometricEncodingFingers = BiometricEncoding.Templates?.Templates;
2461 await this.BiometricEncodingFingersUpdated.Raise(this, EventArgs.Empty);
2462 }
2463 catch (Exception ex)
2464 {
2465 this.Error(ex.Message); // Access to DG3 might be restricted. Just log an error.
2466 }
2467 }
2468
2469 if (this.enhancedSecurity && this.ReadDG4 && (this.appInfo.TagList?.HasDataGroup(4) ?? false))
2470 {
2471 try
2472 {
2473 // Reading EF.DG4 (Additional Identification Feature — Finger(s)), §4.7.3 ICAO 9303-10
2474
2475 this.Information("EF.DG4 (Additional Identification Feature — Iris(es)) supported.");
2476
2477 Data = await this.DownloadFile(EF.DG4, "EF.DG4");
2478 if (Data is null)
2479 {
2480 this.Error("Unable to download EF.DG4.");
2481 return ReadTravelDocumentResult.UnableToReadEfDg;
2482 }
2483
2484 if (!this.ValidateDataGroupData(4, Data))
2485 return ReadTravelDocumentResult.DgHashDigestInvalid;
2486
2487 if (!TryParseDataObject(Data, this, out BiometricEncodingIrises? BiometricEncoding))
2488 {
2489 this.Error("Unable to decode Biometric Encoding in DG4 (Additional Identification Feature — Iris(es)).\r\n\r\n" +
2490 Convert.ToBase64String(Data, Base64FormattingOptions.InsertLineBreaks));
2491 return ReadTravelDocumentResult.UnableToParseEfDg;
2492 }
2493
2494 this.biometricEncodingIrises = BiometricEncoding.Templates?.Templates;
2495 await this.BiometricEncodingIrisesUpdated.Raise(this, EventArgs.Empty);
2496 }
2497 catch (Exception ex)
2498 {
2499 this.Error(ex.Message); // Access to DG3 might be restricted. Just log an error.
2500 }
2501 }
2502
2503 if (this.ReadDG5 && (this.appInfo.TagList?.HasDataGroup(5) ?? false))
2504 {
2505 // Reading EF.DG5 (Displayed Portrait), §4.7.5 ICAO 9303-10
2506
2507 this.Information("EF.DG5 (Displayed Portrait) supported.");
2508
2509 Data = await this.DownloadFile(EF.DG5, "EF.DG5");
2510 if (Data is null)
2511 {
2512 this.Error("Unable to download EF.DG5.");
2513 return ReadTravelDocumentResult.UnableToReadEfDg;
2514 }
2515
2516 if (!this.ValidateDataGroupData(5, Data))
2517 return ReadTravelDocumentResult.DgHashDigestInvalid;
2518
2519 if (!TryParseDataObject(Data, this, out DisplayedPortraits? DataGroup5))
2520 {
2521 this.Error("Unable to decode DG5 (Displayed Portrait).\r\n\r\n" +
2522 Convert.ToBase64String(Data, Base64FormattingOptions.InsertLineBreaks));
2523 return ReadTravelDocumentResult.UnableToParseEfDg;
2524 }
2525
2526 if ((DataGroup5?.Photos?.Length ?? 0) > 0)
2527 {
2528 foreach (DisplayedPortrait Photo in DataGroup5!.Photos!)
2529 this.Warning(Convert.ToBase64String(Photo.Value));
2530 }
2531 }
2532
2533 if (this.ReadDG7 && (this.appInfo.TagList?.HasDataGroup(7) ?? false))
2534 {
2535 // Reading EF.DG7 (Displayed Signature or Usual Mark), §4.7.2 ICAO 9303-10
2536
2537 this.Information("EF.DG7 (Displayed Signature or Usual Mark) supported.");
2538
2539 Data = await this.DownloadFile(EF.DG7, "EF.DG7");
2540 if (Data is null)
2541 {
2542 this.Error("Unable to download EF.DG7.");
2543 return ReadTravelDocumentResult.UnableToReadEfDg;
2544 }
2545
2546 if (!this.ValidateDataGroupData(7, Data))
2547 return ReadTravelDocumentResult.DgHashDigestInvalid;
2548
2549 if (!TryParseDataObject(Data, this, out DisplayedSignatures? DisplayedSignatures))
2550 {
2551 this.Error("Unable to decode Displayed Signatures in DG7 (Displayed Signature or Usual Mark).\r\n\r\n" +
2552 Convert.ToBase64String(Data, Base64FormattingOptions.InsertLineBreaks));
2553 return ReadTravelDocumentResult.UnableToParseEfDg;
2554 }
2555
2556 this.displayedSignatures = DisplayedSignatures;
2557 await this.DisplayedSignaturesUpdated.Raise(this, EventArgs.Empty);
2558 }
2559
2560 if (this.ReadDG8 && (this.appInfo.TagList?.HasDataGroup(8) ?? false))
2561 {
2562 this.Warning("EF.DG8 (Data Feature(s)) supported but not implemented.");
2563
2564 // TODO: Data Group 8 (Data Feature(s)) (In LDS1 eMRTD Application)
2565 }
2566
2567 if (this.ReadDG9 && (this.appInfo.TagList?.HasDataGroup(9) ?? false))
2568 {
2569 this.Warning("EF.DG9 (Structure Feature(s)) supported but not implemented.");
2570
2571 // TODO: Data Group 9 (Structure Feature(s)) (In LDS1 eMRTD Application)
2572 }
2573
2574 if (this.ReadDG10 && (this.appInfo.TagList?.HasDataGroup(10) ?? false))
2575 {
2576 this.Warning("EF.DG10 (Substance Feature(s)) supported but not implemented.");
2577
2578 // TODO: Data Group 10 (Substance Feature(s)) (In LDS1 eMRTD Application)
2579 }
2580
2581 if (this.ReadDG11 && (this.appInfo.TagList?.HasDataGroup(11) ?? false))
2582 {
2583 // Reading EF.DG11 (Additional Personal Detail(s)), §4.7.11 ICAO 9303-10
2584
2585 this.Information("EF.DG11 (Additional Personal Detail(s)) supported.");
2586
2587 Data = await this.DownloadFile(EF.DG11, "EF.DG11");
2588 if (Data is null)
2589 {
2590 this.Error("Unable to download EF.DG11.");
2591 return ReadTravelDocumentResult.UnableToReadEfDg;
2592 }
2593
2594 if (!this.ValidateDataGroupData(11, Data))
2595 return ReadTravelDocumentResult.DgHashDigestInvalid;
2596
2597 if (!TryParseDataObject(Data, this, out AdditionalPersonalDetails? AdditionalPersonalDetails))
2598 {
2599 this.Error("Unable to decode DG11 (Additional Personal Detail(s)).\r\n\r\n" +
2600 Convert.ToBase64String(Data, Base64FormattingOptions.InsertLineBreaks));
2601 return ReadTravelDocumentResult.UnableToParseEfDg;
2602 }
2603
2604 this.personalInformation = AdditionalPersonalDetails;
2605 await this.PersonalInformationUpdated.Raise(this, EventArgs.Empty);
2606 }
2607
2608 if (this.ReadDG12 && (this.appInfo.TagList?.HasDataGroup(12) ?? false))
2609 {
2610 this.Warning("EF.DG12 (Additional Document Detail(s)) supported but not implemented.");
2611
2612 // TODO: Data Group 12 (Additional Document Detail(s)) (In LDS1 eMRTD Application)
2613 }
2614
2615 if (this.ReadDG13 && (this.appInfo.TagList?.HasDataGroup(13) ?? false))
2616 {
2617 this.Warning("EF.DG13 (Optional Details(s)) supported but not implemented.");
2618
2619 // TODO: Data Group 13 (Optional Details(s)) (In LDS1 eMRTD Application)
2620 }
2621
2622 if (this.ReadDG14 && (this.appInfo.TagList?.HasDataGroup(14) ?? false))
2623 {
2624 this.Warning("EF.DG14 (Security Options) supported.");
2625
2626 // TODO: Data Group 14 (Security Options) (In LDS1 eMRTD Application)
2627 }
2628
2629 if (this.ReadDG15 && (this.appInfo.TagList?.HasDataGroup(15) ?? false))
2630 {
2631 this.Warning("EF.DG15 (Active Authentication Public Key Info) supported but not implemented.");
2632
2633 // TODO: Data Group 15 (Active Authentication Public Key Info) (In LDS1 eMRTD Application)
2634 }
2635
2636 if (this.ReadDG16 && (this.appInfo.TagList?.HasDataGroup(16) ?? false))
2637 {
2638 this.Warning("EF.DG16 (Person(s) to Notify) supported but not implemented.");
2639
2640 // TODO: Data Group 16 (Person(s) to Notify) (In LDS1 eMRTD Application)
2641 }
2642
2643 await this.SetState(TravelDocumentsState.Idle);
2644
2645 return ReadTravelDocumentResult.Success;
2646 }
2647
2648 private bool ValidateDataGroupData(int Nr, byte[] Data)
2649 {
2650 this.Information("Validating data with EF.SOD");
2651
2652 if (this.securityinfo is null)
2653 {
2654 this.Error("EF.SOD not read.");
2655 return false;
2656 }
2657 else if (this.securityinfo.ValidateDataGroup(Nr, Data))
2658 {
2659 this.Information("Data valid in accordance to Hash Digest in EF.SOD.");
2660 return true;
2661 }
2662 else
2663 {
2664 this.Error("Invalid data. Hash Digest of data does not match Hash Digest in EF.SOD.");
2665 return false;
2666 }
2667 }
2668
2676 public static bool TryParseDataObject<T>(byte[] Data, TravelDocumentsClient Client,
2677 [NotNullWhen(true)] out T? DataObject)
2678 where T : IDataObject
2679 {
2680 if (TryParseDataObjects(Data, Client, out IDataObject[]? DataObjects))
2681 {
2682 foreach (IDataObject Object in DataObjects)
2683 {
2684 if (Object is T TypedObject)
2685 {
2686 DataObject = TypedObject;
2687 return true;
2688 }
2689 }
2690 }
2691
2692 DataObject = default;
2693 return false;
2694 }
2695
2703 public static bool TryParseDataObjects(byte[] Data, TravelDocumentsClient Client,
2704 [NotNullWhen(true)] out IDataObject[]? DataObjects)
2705 {
2706 DataObjects = null;
2707
2708 ChunkedList<IDataObject> Found = [];
2709 int i = 0;
2710 int c = Data.Length;
2711 ushort Tag;
2712 ushort Len;
2713 byte[] Value;
2714
2715 while (i < c)
2716 {
2717 Tag = Data[i++];
2718
2719 if (Tag == 0x80)
2720 {
2721 int j;
2722
2723 for (j = i; j < c; j++)
2724 {
2725 if (Data[j] != 0)
2726 break;
2727 }
2728
2729 if (j == c)
2730 break; // Padding
2731 }
2732
2733 if ((Tag & 31) == 31)
2734 {
2735 if (i == c)
2736 return false;
2737
2738 Tag <<= 8;
2739 Tag |= Data[i++];
2740 }
2741
2742 if (i == c)
2743 return false;
2744
2745 Len = Data[i++];
2746
2747 switch (Len)
2748 {
2749 case 0x81:
2750 if (i == c)
2751 return false;
2752
2753 Len = Data[i++];
2754 break;
2755
2756 case 0x82:
2757 if (i + 1 >= c)
2758 return false;
2759
2760 Len = Data[i++];
2761 Len <<= 8;
2762 Len |= Data[i++];
2763 break;
2764
2765 default:
2766 Len &= 0x7f;
2767 break;
2768 }
2769
2770 if (i + Len > c)
2771 return false;
2772
2773 Value = new byte[Len];
2774 if (Len > 0)
2775 {
2776 Buffer.BlockCopy(Data, i, Value, 0, Len);
2777 i += Len;
2778 }
2779
2780 if (dataObjects.TryGetValue(Tag, out IDataObject? TypedObject))
2781 {
2782 if (TypedObject.TryParse(Value, Client, out IDataObject? ParsedObject))
2783 Found.Add(ParsedObject);
2784 else
2785 {
2786 Client.Warning("Unable to parse data object with tag: " + Tag.ToString("X4", CultureInfo.InvariantCulture));
2787 Found.Add(new BinaryDataObject(Tag, Value));
2788 }
2789 }
2790 else
2791 {
2792 Client.Warning("Unknown application level information tag: " + Tag.ToString("X4", CultureInfo.InvariantCulture));
2793 Found.Add(new BinaryDataObject(Tag, Value));
2794 }
2795 }
2796
2797 DataObjects = [.. Found];
2798
2799 return true;
2800 }
2801
2802 private static Dictionary<ushort, IDataObject> GetDataObjects()
2803 {
2804 Dictionary<ushort, IDataObject> Result = [];
2805
2806 foreach (Type T in Types.GetTypesImplementingInterface(typeof(IDataObject)))
2807 {
2808 ConstructorInfo? CI = Types.GetDefaultConstructor(T);
2809 if (CI is null)
2810 continue;
2811
2812 try
2813 {
2814 IDataObject DO = (IDataObject)CI.Invoke(Types.NoParameters);
2815 Result[DO.Tag] = DO;
2816 }
2817 catch (Exception ex)
2818 {
2819 Log.Exception(ex);
2820 }
2821 }
2822
2823 return Result;
2824 }
2825
2832 public static KeyValuePair<string?, byte[]?> GetSubjectKeyIdentifier(Certificate Certificate)
2833 {
2834 string CountryCode = Certificate.Subject.CountryName;
2835
2836 foreach (object Extension in Certificate.Extensions?.Elements ?? Array.Empty<object>())
2837 {
2838 if (Extension is SubjectKeyIdentifier SubjectKeyIdentifier &&
2840 {
2841 return new KeyValuePair<string?, byte[]?>(CountryCode,
2843 }
2844 }
2845
2846 return new KeyValuePair<string?, byte[]?>(null, null);
2847 }
2848
2856 public static KeyValuePair<string?, byte[]?> GetAuthorityKeyIdentifier(Certificate Certificate)
2857 {
2858 string CountryCode = Certificate.Issuer.CountryName;
2859
2860 foreach (object Extension in Certificate.Extensions?.Elements ?? Array.Empty<object>())
2861 {
2864 {
2865 return new KeyValuePair<string?, byte[]?>(CountryCode,
2867 }
2868 }
2869
2870 return new KeyValuePair<string?, byte[]?>(null, null);
2871 }
2872
2878 public static string[] GetRevocationListUrls(Certificate Certificate)
2879 {
2880 return GetRevocationListUrls(null, Certificate);
2881 }
2882
2889 public static string[] GetRevocationListUrls(ICommunicationLayer? Client, Certificate Certificate)
2890 {
2891 ChunkedList<string> Urls = [];
2892
2893 foreach (object Extension in Certificate.Extensions?.Elements ?? Array.Empty<object>())
2894 {
2895 if (Extension is not DistributionPoints DistributionPoints)
2896 continue;
2897
2899 Urls.Add(Point.Url);
2900 }
2901
2902 if (Urls.Count == 0)
2903 {
2904 Client?.Warning("No CRL distribution points found in certificate.");
2905 return [];
2906 }
2907
2908 return [.. Urls];
2909 }
2910 }
2911}
Class implementing the IIsoDepInterface interface for serial communication with an NFC chip,...
Definition: IsoDepReplay.cs:18
Static class for parsing and decoding security objects encoded using Abstract Syntax Notation 1 (ASN....
Definition: ASN1.cs:22
static bool TryDecodeDer(byte[] Data, out object? Value)
Decodes a DER-encoded object.
Definition: ASN1.cs:77
Travel Document Applications.
Definition: Applications.cs:7
static readonly byte[] DF1
LDS1 eMRTD Application. §4 ICAO Doc 9303-10: https://www2023.icao.int/publications/Documents/9303_p10...
Definition: Applications.cs:12
Static class for validation of ICAO certificate chains
static bool VerifySignatures(params Certificate[] Certificates)
Verifies the signatures of a chain of ICAO certificates.
Certificate, as defined in RFC 5280, §4.1.
Definition: Certificate.cs:13
System.Numerics.BigInteger SerialNumber
Serial Number
Definition: Certificate.cs:121
static Task< Certificate?> TryLoadCertificate(string IdDomain, string Country, byte[] KeyReference)
Tries to load an ICAO certificate, provided its country and key reference.
static Task< CertificateList?> TryLoadCrl(string Url)
Tries to load an ICAO-compliant CRL, provided its URL.
Additional Personal Details. Reference: §4.7.11, EF.DG11, ICAO Doc 9303-10, Table 71.
Application-Level Information. Reference: §4.6.1, EF.COM, ICAO Doc 9303-10, Table 35.
Biometric Encoding of DG2. Reference: §4.7.2, EF.DG2, ICAO Doc 9303-10, Table 43.
Biometric Encoding of DG3. Reference: §4.7.3, EF.DG3, ICAO Doc 9303-10, Table 46.
Biometric Encoding of DG4. Reference: §4.7.4, EF.DG4, ICAO Doc 9303-10, Table 53.
Biometric Information Template. Reference: §4.7.2.1, EF.COM, ICAO Doc 9303-10, Table 44.
Displayed Portraits in DG5. Reference: §4.7.5, EF.DG5, ICAO Doc 9303-10, Table 58.
Displayed Signatures or Usual Marks (DG7). Reference: §4.7.7, EF.DG7, ICAO Doc 9303-10,...
Document Security Object. Reference: §4.6.2, EF.SOD, ICAO Doc 9303-10, Table 36.
bool ValidateDataGroup(int Nr, byte[] DataRead)
Validates data read from a data group, using the information in the LDS Security Object.
Machine Readable Zone Information in DG1. Reference: §4.7.1, EF.DG1, ICAO Doc 9303-10,...
bool HasDataGroup(int DataGroupNumber)
Checks if a data group is supported.
Definition: TagList.cs:62
Contains parsed information from a machine-readable document information string.
string? MRZ_Information
MRZ-information for use with Basic Access Control (BAC) and PACE.
Elementary Files in travel documents.
Definition: EF.cs:7
const ushort DG4
Data Group 4 (Additional Identification Feature — Iris(es)) (In LDS1 eMRTD Application)
Definition: EF.cs:72
const ushort DG1
Data Group 1 (MRZ) (In LDS1 eMRTD Application)
Definition: EF.cs:57
const ushort SOD
Security Object Data (In LDS1 eMRTD Application)
Definition: EF.cs:52
const ushort DG3
Data Group 3 (Additional Identification Feature — Finger(s)) (In LDS1 eMRTD Application)
Definition: EF.cs:67
const ushort DG7
Data Group 7 (Displayed Signature or Usual Mark) (In LDS1 eMRTD Application)
Definition: EF.cs:82
const ushort COM
Common Data (In LDS1 eMRTD Application)
Definition: EF.cs:47
const ushort DG5
Data Group 5 (Displayed Portrait) (In LDS1 eMRTD Application)
Definition: EF.cs:77
const ushort CardAccess
EF.CardAccess. §3.11.3 ICAO Doc 9303-10: https://www2023.icao.int/publications/Documents/9303_p10_con...
Definition: EF.cs:36
const ushort DG11
Data Group 11 (Additional Personal Detail(s)) (In LDS1 eMRTD Application)
Definition: EF.cs:87
const ushort DG2
Data Group 2 (Encoded Identification Features — Face) (In LDS1 eMRTD Application)
Definition: EF.cs:62
Event arguments for travel document state-related events.
const byte SecureMessaging
Secure messaging.
Definition: ISO7816.cs:105
const byte Basic
Standard Command, Basic Channel, No Secure Messaging.
Definition: ISO7816.cs:100
const byte Chaining
Command Chaining.
Definition: ISO7816.cs:110
Static class with extensions related to the ISO/IEC 7816 standard for Identification cards — Integrat...
Definition: ISO7816.cs:94
Implements the CMAC algorithm, as defined in NIST SP 800-38B, revision 2016. Ref: https://nvlpubs....
Definition: CMac.cs:11
bool Verify(byte[] Message, byte[] Signature)
Verifies a CMAC Signature.
Definition: CMac.cs:237
byte[] Sign(byte[] Message, int Len)
Signs a message using the current CMAC.
Definition: CMac.cs:144
Certificate List, as defined in RFC 5280, §5.1
bool HasBeenRevoked(Certificate Certificate, out RevokedReason Reason)
Checks if a certificate has been revoked.
Task< bool > VerifySignature(string IdDomain, string CountryCode)
Verifies the signature of the CRL
Static class managing encoding and decoding of internet content.
static readonly Encoding ISO_8859_1
ISO-8859-1 character encoding.
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
static void Alert(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an alert event.
Definition: Log.cs:1237
Simple base class for classes implementing communication protocols.
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
bool HasFirstItem
If there is a first item in the collection
Definition: ChunkedList.cs:778
void Insert(int Index, T Item)
Inserts an item to the list at the specified index.
int Count
Number of elements in collection.
Definition: ChunkedList.cs:68
void Add(T Item)
Adds an item to the collection.
Definition: ChunkedList.cs:272
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static object[] NoParameters
Contains an empty array of parameter values.
Definition: Types.cs:572
static Type[] GetTypesImplementingInterface(string InterfaceFullName)
Gets all types implementing a given interface.
Definition: Types.cs:85
static ConstructorInfo GetDefaultConstructor(Type Type)
Gets the default constructor of a type, if one exists.
Definition: Types.cs:1742
Abstract base class for elliptic curves.
byte[] Encode(PointOnCurve Point)
Encodes a point on the curve.
virtual bool IsPoint(byte[] Point)
Checks if an encoded point is on the curve.
virtual void Export(XmlWriter Output)
Exports the curve parameters to XML.
static BigInteger ToInt(byte[] Binary, bool BigEndian)
Converts a little-endian binary representation of a big integer to a BigInteger.
int OrderBytes
Number of bytes required to represent the order of the curve.
PointOnCurve ScalarMultiplication(BigInteger N, PointOnCurve P, bool Normalize)
Performs the scalar multiplication of N *P .
Contains methods for simple hash calculations.
Definition: Hashes.cs:57
static byte[] ComputeSHA1Hash(byte[] Data)
Computes the SHA-1 hash of a block of binary data.
Definition: Hashes.cs:415
static byte[] StringToBinary(string s)
Parses a hex string.
Definition: Hashes.cs:100
static string BinaryToString(byte[] Data)
Converts an array of bytes to a string with their hexadecimal representations (in lower case).
Definition: Hashes.cs:63
ISO DEP interface, for communication with an NFC Tag.
Basic interface for PACE protocols.
byte[] DecryptNonce(byte[] Kπ, byte[] EncryptedNonce)
Decrypts an encrypted nonce value.
byte[] KDFπ(DocumentInformation DocInfo)
Calculates Kπ, given the shared secret and the document information.
byte[] Decrypt(byte[] KS_Enc, byte[] IV, byte[] Data)
Decrypts data.
int BlockLength
Number of bytes used for blocks.
CMac GetAuthenticator(byte[] Key)
Gets the authenticator
System.Numerics.? BigInteger ParameterId
Optional Parameter ID.
byte[] CreateNewKey(byte[]? Seed, ref int Index)
Creates a new private and public key, used in the PACE protocol.
Grade SecurityStrength
Security strength mapped as a grade.
byte[] Encrypt(byte[] KS_Enc, byte[] IV, byte[] Data)
Encrypts data.
byte[] ImportKey(XmlDocument Xml)
Imports the private key and public key from a previous export.
bool ChipAuthenticationMapping
If Chip-Authentication-Mapping is supported by the protocol.
byte[] GetSharedSecret(byte[] RemotePublicKey)
Gets the shared secret, given the local private key previously generated using CreateNewKey and a rem...
void Warning(string Warning)
Called to inform the viewer of a warning state.
Interface for observable classes implementing communication protocols.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:10
class BinaryDataObject(ushort Tag, byte[] Value)
Binary Data Object. Used when no specific data object type is defined for a given tag.
abstract class PaceEcdhProtocol()
Abstract base class for PACE protocols using Elliptic Curve Cryptography (EEC).
RevokedReason
Reason for revoking a certificate
Definition: RevokedReason.cs:7
ReadTravelDocumentResult
Enumerations of possible results when reading a travel document.
AuthenticateResult
Enumerations of possible results when authenticating the app with the travel document.
Iso7816StatusCategory
First byte of a status word.
Definition: ISO7816.cs:7
TravelDocumentsState
State of travel documents interface.
class Photo(byte[] Binary, int Rotation, Attachment? Attachment)
Class containing information about a photo.
Definition: Photo.cs:10
class Header(ISimulationNode Parent, Model Model)
Represents an identity property.
Definition: Header.cs:18
delegate string ToString(IElement Element)
Delegate for callback methods that convert an element value to a string.
delegate Task EventHandlerAsync(object Sender, EventArgs e)
Asynchronous version of EventArgs.
Reason
Reason a token is not valid.
Definition: JwtFactory.cs:15
delegate byte[] HashFunctionArray(byte[] Data)
Delegate to hash function.
HashFunction
Hash method enumeration.
Definition: Hashes.cs:26
Represents a point on a curve.
Definition: PointOnCurve.cs:10