Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
BasicAccessControl.cs
1using System;
2using System.Security.Cryptography;
3using System.Text.RegularExpressions;
4using System.Threading.Tasks;
5using Waher.Content;
7
9{
16 public static class BasicAccessControl
17 {
25 public static bool ParseMrz(string MRZ, out DocumentInformation? Info)
26 {
27 Match M = td2_mrz_nr9charsplus.Match(MRZ);
28 if (M.Success)
29 {
30 Info = AssembleInfo2(M);
31 return Info is not null;
32 }
33
34 M = td2_mrz_nr9chars.Match(MRZ);
35 if (M.Success)
36 {
37 Info = AssembleInfo1(M);
38 return Info is not null;
39 }
40
41 M = td1_mrz_nr9charsplus.Match(MRZ);
42 if (M.Success)
43 {
44 Info = AssembleInfo2(M);
45 return Info is not null;
46 }
47
48 M = td1_mrz_nr9chars.Match(MRZ);
49 if (M.Success)
50 {
51 Info = AssembleInfo1(M);
52 return Info is not null;
53 }
54
55 // TODO: Checks
56
57 Info = null;
58 return false;
59 }
60
61 private static DocumentInformation? AssembleInfo2(Match M)
62 {
63 DocumentInformation Result = AssembleInfo(M);
64 Result.DocumentNumber = M.Groups["Nr1"].Value + M.Groups["Nr2"].Value;
65
66 return CalcMrzInfo(Result, M) ? Result : null;
67 }
68
69 private static DocumentInformation? AssembleInfo1(Match M)
70 {
71 DocumentInformation Result = AssembleInfo(M);
72 Result.DocumentNumber = M.Groups["Nr"].Value;
73
74 return CalcMrzInfo(Result, M) ? Result : null;
75 }
76
77 private static bool CalcMrzInfo(DocumentInformation Info, Match M)
78 {
79 if (Info.DocumentNumber is null || Info.DateOfBirth is null || Info.ExpiryDate is null)
80 return false;
81
82 string NrCheck = M.Groups["NrCheck"].Value;
83 if (NrCheck != CalcCheckDigit(Info.DocumentNumber))
84 return false;
85
86 string BirthCheck = M.Groups["BirthCheck"].Value;
87 if (BirthCheck != CalcCheckDigit(Info.DateOfBirth))
88 return false;
89
90 string ExpiryCheck = M.Groups["ExpiryCheck"].Value;
91 if (ExpiryCheck != CalcCheckDigit(Info.ExpiryDate))
92 return false;
93
94 if (!string.IsNullOrEmpty(Info.OptionalData))
95 {
96 string s = Info.OptionalData!.Replace("<", string.Empty);
97
98 if (!string.IsNullOrEmpty(s))
99 {
100 string OptionalCheck = M.Groups["OptionalCheck"].Value;
101 if (OptionalCheck != CalcCheckDigit(Info.OptionalData))
102 return false;
103 }
104
105 Info.OptionalData = s;
106 }
107
108 // TODO: Check OverallCheck
109
110 Info.MRZ_Information = Info.DocumentNumber + NrCheck +
111 Info.DateOfBirth + BirthCheck + Info.ExpiryDate + ExpiryCheck;
112
113 Info.DocumentNumber = Info.DocumentNumber.Replace("<", string.Empty);
114
115 return true;
116 }
117
118 private static string CalcCheckDigit(string Value)
119 {
120 // §4.9, ISO/IEC 9303, Part 3: https://www.icao.int/publications/Documents/9303_p3_cons_en.pdf
121
122 int Sum = 0;
123 int i = 0;
124 int j;
125
126 foreach (char ch in Value)
127 {
128 if (ch >= '0' && ch <= '9')
129 j = ch - '0';
130 else if (ch >= 'A' && ch <= 'Z')
131 j = ch - 'A' + 10;
132 else if (ch >= 'a' && ch <= 'z')
133 j = ch - 'a' + 10;
134 else if (ch == '<')
135 j = 0;
136 else
137 return string.Empty;
138
139 j *= weights[i++];
140 Sum += j;
141 i %= 3;
142 }
143
144 return new string((char)('0' + Sum % 10), 1);
145 }
146
147 private static readonly int[] weights = [7, 3, 1];
148
149 private static DocumentInformation AssembleInfo(Match M)
150 {
151 return new DocumentInformation()
152 {
153 DocumentType = M.Groups["DocType"].Value,
154 IssuingState = M.Groups["Issuer"].Value,
155 Nationality = M.Groups["Nationality"].Value,
156 PrimaryIdentifier = M.Groups["PID"].Value.Split('<'),
157 SecondaryIdentifier = M.Groups["SID"].Value.Split('<'),
158 Gender = M.Groups["Gender"].Value,
159 DocumentNumber = M.Groups["Nr"].Value,
160 DateOfBirth = M.Groups["Birth"].Value,
161 ExpiryDate = M.Groups["Expires"].Value,
162 OptionalData = M.Groups["Optional"].Value
163 };
164 }
165
166 // TD2, ref: ICAO 9303-5, §B: https://www.icao.int/publications/Documents/9303_p5_cons_en.pdf
167 private static readonly Regex td2_mrz_nr9charsplus = new(@"^(?'DocType'.{1,2})<(?'Issuer'\w{3})(?'PID'[^<]+(<[^<]+)*)<<(?'SID'[^<]+(<[^<]+)*)<*\n(?'Nr1'[^<]{9})<(?'Nationality'\w{3})(?'Birth'[^<]*)(?'BirthCheck'\d)(?'Gender'[MF])(?'Expires'[^<]{6})(?'ExpiryCheck'\d)(?'Nr2'[^<]*)(?'NrCheck'\d)((?'Optional'.*)(?'OptionalCheck'\d))?<*(?'OverallCheck'\d)$", RegexOptions.Multiline);
168 private static readonly Regex td2_mrz_nr9chars = new(@"^(?'DocType'.{1,2})<(?'Issuer'\w{3})(?'PID'[^<]+(<[^<]+)*)<<(?'SID'[^<]+(<[^<]+)*)<*\n(?'Nr'.{9})(?'NrCheck'\d)(?'Nationality'\w{3})(?'Birth'[^<]{6})(?'BirthCheck'\d)(?'Gender'[MF])(?'Expires'[^<]{6})(?'ExpiryCheck'\d)((?'Optional'.*)(?'OptionalCheck'\d))?<*(?'OverallCheck'\d)$", RegexOptions.Multiline);
169
170 // TD1, ref: ICAO 9303-5, §B: https://www.icao.int/publications/Documents/9303_p5_cons_en.pdf
171 private static readonly Regex td1_mrz_nr9charsplus = new(@"^(?'DocType'.{1,2})<(?'Issuer'\w{3})(?'Nr1'[^<]{9})<(?'Nr2'.{3})(?'NrCheck'\d)((?'Optional'.*)(?'OptionalCheck'\d))?<*\n(?'Birth'[^<]{6})(?'BirthCheck'\d)(?'Gender'[MF])(?'Expires'[^<]{6})(?'ExpiryCheck'\d)(?'Nationality'\w{3})<*(?'OverallCheck'\d)\n(?'PID'[^<]+(<[^<]+)*)<<(?'SID'[^<]+(<[^<]+)*).*$", RegexOptions.Multiline);
172 private static readonly Regex td1_mrz_nr9chars = new(@"^(?'DocType'.{1,2})<(?'Issuer'\w{3})(?'Nr'.{9})(?'NrCheck'.)((?'Optional'.*)(?'OptionalCheck'\d))?<*\n(?'Birth'[^<]{6})(?'BirthCheck'\d)(?'Gender'[MF])(?'Expires'[^<]{6})(?'ExpiryCheck'\d)(?'Nationality'\w{3})<*(?'OverallCheck'\d)\n(?'PID'[^<]+(<[^<]+)*)<<(?'SID'[^<]+(<[^<]+)*).*$", RegexOptions.Multiline);
173
178 public static byte[] KSeed(this DocumentInformation Info)
179 {
180 byte[] Data = CommonTypes.ISO_8859_1.GetBytes(Info.MRZ_Information);
181 byte[] H = Hashes.ComputeSHA1Hash(Data);
182 Array.Resize<byte>(ref H, 16);
183 return H;
184 }
185
189 public static byte[] KEnc(this DocumentInformation Info)
190 {
191 return CalcKey(Info, 1);
192 }
193
197 public static byte[] KMac(this DocumentInformation Info)
198 {
199 return CalcKey(Info, 2);
200 }
201
202 private static byte[] CalcKey(this DocumentInformation Info, int Counter)
203 {
204 byte[] KSeed = Info.KSeed();
205 byte[] D = new byte[20];
206 Array.Copy(KSeed, 0, D, 0, 16);
207 int i;
208
209 for (i = 19; i >= 16; i--)
210 {
211 D[i] = (byte)(Counter);
212 Counter >>= 8;
213 }
214
215 byte[] H = Hashes.ComputeSHA1Hash(D);
216 Array.Resize<byte>(ref H, 16);
217 OddParity(H);
218
219 return H;
220 }
221
222 private static void OddParity(byte[] H)
223 {
224 int i, j, c = H.Length;
225 byte b;
226
227 for (i = 0; i < c; i++)
228 {
229 b = H[i];
230 j = 0;
231
232 while (b != 0)
233 {
234 j += (b & 1);
235 b >>= 1;
236 }
237
238 if ((j & 1) == 0)
239 H[i] ^= 1;
240 }
241 }
242
249 public static byte[] CONCAT(this byte[] Bytes, params byte[][] MoreBytes)
250 {
251 int c = Bytes.Length;
252 int i = c;
253
254 foreach (byte[] A in MoreBytes)
255 c += A.Length;
256
257 byte[] Result = new byte[c];
258
259 Array.Copy(Bytes, 0, Result, 0, i);
260
261 foreach (byte[] A in MoreBytes)
262 {
263 Array.Copy(A, 0, Result, i, c = A.Length);
264 i += c;
265 }
266
267 return Result;
268 }
269
279 public static byte[] CalcChallengeResponse(byte[] Challenge, byte[] Rnd1, byte[] Rnd2,
280 byte[] KEnc, byte[] KMac)
281 {
282 byte[] S = Rnd1.CONCAT(Challenge, Rnd2);
283 byte[] EIFD;
284 byte[] MIFD;
285
286 using (TripleDES Cipher = TripleDES.Create())
287 {
288 Cipher.Mode = CipherMode.CBC;
289 Cipher.Padding = PaddingMode.None;
290
291 using ICryptoTransform Encryptor = Cipher.CreateEncryptor(KEnc, new byte[8]);
292 EIFD = Encryptor.TransformFinalBlock(S, 0, 32);
293 }
294
295 // MAC Algorithm described in ISO/IEC 9797-1
296 // Ref: https://en.wikipedia.org/wiki/ISO/IEC_9797-1
297
298 using (DES Cipher = DES.Create())
299 {
300 Cipher.Mode = CipherMode.CBC;
301 Cipher.Padding = PaddingMode.None;
302
303 int i = 0;
304 int c = EIFD.Length;
305 int j;
306
307 byte[] Data = new byte[c + 8];
308 Array.Copy(EIFD, 0, Data, 0, c);
309 Data[c] = 0x80; // Padding method 2, append 80 00 00 00 00 00 00 00
310
311 byte[] Ka = new byte[8];
312 byte[] Kb = new byte[8];
313
314 Array.Copy(KMac, 0, Ka, 0, 8);
315 Array.Copy(KMac, 8, Kb, 0, 8);
316
317 byte[] Block = new byte[8];
318 byte[]? H = null;
319
320 c += 8;
321 using (ICryptoTransform Encryptor2 = Cipher.CreateEncryptor(Ka, new byte[8]))
322 {
323 while (i < c)
324 {
325 Array.Copy(Data, i, Block, 0, 8);
326 i += 8;
327
328 if (H is not null)
329 {
330 for (j = 0; j < 8; j++)
331 Block[j] ^= H[j];
332 }
333
334 H = Encryptor2.TransformFinalBlock(Block, 0, 8);
335 }
336
337 using (ICryptoTransform FinalDecryptor = Cipher.CreateDecryptor(Kb, new byte[8]))
338 {
339 H = FinalDecryptor.TransformFinalBlock(H, 0, 8);
340 }
341
342 H = Encryptor2.TransformFinalBlock(H, 0, 8);
343 }
344
345 MIFD = H;
346 }
347
348 return EIFD.CONCAT(MIFD);
349 }
350
357 public static byte[] CalcChallengeResponse(this DocumentInformation Info, byte[] Challenge)
358 {
359 byte[] Rnd1 = new byte[8];
360 byte[] Rnd2 = new byte[16];
361
362 using (RandomNumberGenerator Rnd = RandomNumberGenerator.Create())
363 {
364 Rnd.GetBytes(Rnd1);
365 Rnd.GetBytes(Rnd2);
366 }
367
368 return CalcChallengeResponse(Challenge, Rnd1, Rnd2, Info.KEnc(), Info.KMac());
369 }
370
376 public static async Task<byte[]?> GetChallenge(this IIsoDepInterface TagInterface)
377 {
378 byte[] Command =
379 [
380 0x00, // CLA
381 0x84, // INS
382 0x00, // P1
383 0x00, // P2
384 0x08 // Le
385 ];
386
387 byte[] Response = await TagInterface.ExecuteCommand(Command);
388 if (Response.Length != 10 || Response[8] != 0x90 || Response[9] != 0x00)
389 return null;
390
391 byte[] Challenge = new byte[8];
392 Array.Copy(Response, 0, Challenge, 0, 8);
393
394 return Response;
395 }
396
403 public static async Task<byte[]?> ExternalAuthenticate(this IIsoDepInterface TagInterface,
404 byte[] ChallengeResponse)
405 {
406 byte Lc = (byte)ChallengeResponse.Length;
407 byte[] Command = new byte[]
408 {
409 0x00, // CLA
410 0x82, // INS
411 0x00, // P1
412 0x00, // P2
413 Lc
414 }.CONCAT(ChallengeResponse,
415 [
416 0x28 // Le
417 ]);
418
419 byte[] Response = await TagInterface.ExecuteCommand(Command);
420 if (Response.Length != 10 || Response[8] != 0x90 || Response[9] != 0x00)
421 return null;
422
423 byte[] Challenge = new byte[8];
424 Array.Copy(Response, 0, Challenge, 0, 8);
425
426 return Response;
427 }
428 }
429}
Contains NFC Extensions for Basic Access Control.
static byte[] KEnc(this DocumentInformation Info)
3DES Encryption Key (§D.1)
static byte[] KSeed(this DocumentInformation Info)
Seed for computing cryptographic keys (§D.2)
static async Task< byte[]?> ExternalAuthenticate(this IIsoDepInterface TagInterface, byte[] ChallengeResponse)
Send Response to challenge (§7.1.5.4, §D.3)
static byte[] CalcChallengeResponse(byte[] Challenge, byte[] Rnd1, byte[] Rnd2, byte[] KEnc, byte[] KMac)
Calculates a response to a challenge.
static byte[] KMac(this DocumentInformation Info)
DES MAC Key (§D.1)
static byte[] CONCAT(this byte[] Bytes, params byte[][] MoreBytes)
Concatenates a series of byte arrays.
static async Task< byte[]?> GetChallenge(this IIsoDepInterface TagInterface)
Get Challenge (§7.1.5.4, §D.3)
static byte[] CalcChallengeResponse(this DocumentInformation Info, byte[] Challenge)
Calculates a response to a challenge.
static bool ParseMrz(string MRZ, out DocumentInformation? Info)
Derives Basic Access Control Keys from the second row of the Machine-Readable string in passport (MRZ...
Contains parsed information from a machine-readable document information string.
string? MRZ_Information
MRZ-information for use with Basic Access Control (BAC).
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static Encoding ISO_8859_1
ISO-8859-1 encoding.
Contains methods for simple hash calculations.
Definition: Hashes.cs:59
static byte[] ComputeSHA1Hash(byte[] Data)
Computes the SHA-1 hash of a block of binary data.
Definition: Hashes.cs:294
ISO DEP interface, for communication with an NFC Tag.
Task< byte[]> ExecuteCommand(byte[] Command)
Executes an ISO 14443-4 command on the tag.
DocumentType
Type of markdown document.