-
Notifications
You must be signed in to change notification settings - Fork 130
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
35cc678
commit 26e990b
Showing
2 changed files
with
309 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
from enum import IntEnum | ||
|
||
|
||
class BCD(IntEnum): | ||
bcd_zero = 0x01 # 00001 0x00 0 | ||
bcd_one = 0x10 # 10000 0x01 1 | ||
bcd_two = 0x08 # 01000 0x02 2 | ||
bcd_three = 0x19 # 11001 0x03 3 | ||
bcd_four = 0x04 # 00100 0x04 4 | ||
bcd_five = 0x15 # 10101 0x05 5 | ||
bcd_six = 0x0D # 01101 0x06 6 | ||
bcd_seven = 0x1C # 11100 0x07 7 | ||
bcd_eight = 0x02 # 00010 0x08 8 | ||
bcd_nine = 0x13 # 10011 0x09 9 | ||
bcd_colon = 0x0B # 01011 0x0a : | ||
bcd_ss = 0x1A # 11010 0x0b ; | ||
bcd_less = 0x07 # 00111 0x0c < | ||
bcd_fs = 0x16 # 10110 0x0d = | ||
bcd_grtr = 0x0E # 01110 0x0e > | ||
bcd_es = 0x1F # 11111 0x0f ? | ||
|
||
@classmethod | ||
def translate(cls, b: int) -> int: | ||
match b: | ||
case 0x00: | ||
return cls.bcd_zero | ||
case 0x01: | ||
return cls.bcd_one | ||
case 0x02: | ||
return cls.bcd_two | ||
case 0x03: | ||
return cls.bcd_three | ||
case 0x04: | ||
return cls.bcd_four | ||
case 0x05: | ||
return BCD.bcd_five | ||
case 0x06: | ||
return BCD.bcd_six | ||
case 0x07: | ||
return BCD.bcd_seven | ||
case 0x08: | ||
return BCD.bcd_eight | ||
case 0x09: | ||
return BCD.bcd_nine | ||
case _: | ||
raise Exception(f"unknown input '{b}'") | ||
|
||
@classmethod | ||
def reverse_translate(cls, b: int) -> int: | ||
match b: | ||
case BCD.bcd_zero: | ||
return 0x00 | ||
case BCD.bcd_one: | ||
return 0x01 | ||
case BCD.bcd_two: | ||
return 0x02 | ||
case BCD.bcd_three: | ||
return 0x03 | ||
case BCD.bcd_four: | ||
return 0x04 | ||
case BCD.bcd_five: | ||
return 0x05 | ||
case BCD.bcd_six: | ||
return 0x06 | ||
case BCD.bcd_seven: | ||
return 0x07 | ||
case BCD.bcd_eight: | ||
return 0x08 | ||
case BCD.bcd_nine: | ||
return 0x09 | ||
case _: | ||
raise Exception(f"unknown input '{b}'") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
from __future__ import annotations | ||
|
||
from yubikit.nist_piv import BCD | ||
|
||
from abc import ABC | ||
from bitarray import bitarray | ||
from io import StringIO | ||
|
||
|
||
class FASCNBuilder(ABC): | ||
# Agency Code | ||
ac: list[int] = [BCD.bcd_zero] * 4 | ||
|
||
# System Code | ||
sc: list[int] = [BCD.bcd_zero] * 4 | ||
|
||
# Credential Number | ||
cn: list[int] = [BCD.bcd_zero] * 6 | ||
|
||
# Credential Series | ||
cs: int = BCD.bcd_zero | ||
|
||
# Individual Credential Issue | ||
ici: int = BCD.bcd_zero | ||
|
||
# Person Identifier | ||
pi: list[int] = [BCD.bcd_zero] * 10 | ||
|
||
# Organizational Category | ||
oc: int = BCD.bcd_zero | ||
|
||
# Organizational Identifier | ||
oi: list[int] = [BCD.bcd_zero] * 4 | ||
|
||
# Person/Organization Association Category | ||
poa: int = BCD.bcd_zero | ||
|
||
def agency_code(self, ac: str): | ||
n = list(map(int, ac)) | ||
self.ac = list(map(BCD.translate, n)) | ||
return self | ||
|
||
def system_code(self, sc: str): | ||
n = list(map(int, sc)) | ||
self.sc = list(map(BCD.translate, n)) | ||
return self | ||
|
||
def credential_number(self, cn: str): | ||
n = list(map(int, cn)) | ||
self.cn = list(map(BCD.translate, n)) | ||
return self | ||
|
||
def credential_series(self, cs: str): | ||
self.cs = BCD.translate(int(cs)) | ||
return self | ||
|
||
def individual_credential_issue(self, ici: str): | ||
self.ici = BCD.translate(int(ici)) | ||
return self | ||
|
||
def person_identifier(self, pi: str): | ||
n = list(map(int, pi)) | ||
self.pi = list(map(BCD.translate, n)) | ||
return self | ||
|
||
def organizational_category(self, oc: str): | ||
self.oc = BCD.translate(int(oc)) | ||
return self | ||
|
||
def organization_association_category(self, poa: str): | ||
self.poa = BCD.translate(int(poa)) | ||
return self | ||
|
||
def build(self) -> FASCN: | ||
return FASCN(self) | ||
|
||
|
||
class FASCN(object): | ||
def __init__(self, builder: FASCNBuilder): | ||
self.ac = builder.ac | ||
self.sc = builder.sc | ||
self.cn = builder.cn | ||
self.cs = builder.cs | ||
self.ici = builder.ici | ||
self.pi = builder.pi | ||
self.oc = builder.oc | ||
self.oi = builder.oi | ||
self.poa = builder.poa | ||
|
||
def encode(self) -> bytes: | ||
b = bytearray() | ||
b.append(BCD.bcd_ss.value) | ||
b.extend(self.ac) | ||
b.append(BCD.bcd_fs) | ||
b.extend(self.sc) | ||
b.append(BCD.bcd_fs) | ||
b.extend(self.cn) | ||
b.append(BCD.bcd_fs) | ||
b.append(self.cs) | ||
b.append(BCD.bcd_fs) | ||
b.append(self.ici) | ||
b.append(BCD.bcd_fs) | ||
b.extend(self.pi) | ||
b.append(self.oc) | ||
b.extend(self.oi) | ||
b.append(self.poa) | ||
b.append(BCD.bcd_es) | ||
|
||
bs = bitarray(200) | ||
current_bit = 0 | ||
for item in b: | ||
tmp = item << 3 | ||
mask = 0x80 | ||
for _ in range(0, 5): | ||
if (tmp & mask) == mask: | ||
bs[current_bit] = True | ||
tmp = tmp << 1 | ||
current_bit += 1 | ||
|
||
bcd = bitarray(5) | ||
for marker in range(0, 195, 5): | ||
tmp = bs[marker : marker + 5] | ||
bcd = bcd ^ tmp | ||
|
||
# set error code | ||
for append in range(195, 200): | ||
if bcd[append - 195]: | ||
bs[append] = True | ||
|
||
out = bytearray() | ||
current_bit = 0 | ||
for _ in range(0, 25): | ||
current_byte = 0x00 | ||
for j in range(0, 8): | ||
if bs[current_bit]: | ||
current_byte = { | ||
0: lambda x: x | 0x80, | ||
1: lambda x: x | 0x40, | ||
2: lambda x: x | 0x20, | ||
3: lambda x: x | 0x10, | ||
4: lambda x: x | 0x08, | ||
5: lambda x: x | 0x04, | ||
6: lambda x: x | 0x02, | ||
7: lambda x: x | 0x01, | ||
}[j](current_byte) | ||
current_bit += 1 | ||
out.append(current_byte) | ||
return out | ||
|
||
@staticmethod | ||
def decode(b: bytes) -> FASCN: | ||
out = bytearray(b) | ||
bs = bitarray(200) | ||
current_bit = 0 | ||
for i in out: | ||
temp_byte = i | ||
mask = 0x80 | ||
for _ in range(0, 8): | ||
if (temp_byte & mask) == mask: | ||
bs[current_bit] = True | ||
temp_byte = temp_byte << 1 | ||
current_bit += 1 | ||
|
||
out = bytearray() | ||
for j in range(0, 200, 5): | ||
current_byte = 0x00 | ||
tmp = bs[j : j + 5] | ||
current_bit = 0 | ||
for k in range(0, 5): | ||
if tmp[current_bit]: | ||
current_byte = { | ||
0: lambda x: x | 0x10, | ||
1: lambda x: x | 0x08, | ||
2: lambda x: x | 0x04, | ||
3: lambda x: x | 0x02, | ||
4: lambda x: x | 0x01, | ||
}[k](current_byte) | ||
current_bit += 1 | ||
out.append(current_byte) | ||
|
||
fascn = FASCNBuilder().build() # default values | ||
fascn.ac = out[1:5] # 4 bytes | ||
fascn.sc = out[6:10] # 4 bytes | ||
fascn.cn = out[11:17] # 6 bytes | ||
fascn.cs = out[18] # 1 byte | ||
fascn.ici = out[20] # 1 byte | ||
fascn.pi = out[22:32] # 10 bytes | ||
fascn.oc = out[32] # 1 byte | ||
fascn.oi = out[33:37] # 5 bytes | ||
fascn.poa = out[37] # 1 byte | ||
return fascn | ||
|
||
def __eq__(self, other: object) -> bool: | ||
if isinstance(other, FASCN): | ||
return self.encode() == other.encode() | ||
return False | ||
|
||
def __str__(self) -> str: | ||
out = StringIO() | ||
out.write( | ||
"Agency Code: {}\n".format( | ||
[str(x) for x in map(BCD.reverse_translate, self.ac)] | ||
) | ||
) | ||
out.write( | ||
"System Code: {}\n".format( | ||
[str(x) for x in map(BCD.reverse_translate, self.sc)] | ||
) | ||
) | ||
out.write( | ||
"Credential Number: {}\n".format( | ||
[str(x) for x in map(BCD.reverse_translate, self.cn)] | ||
) | ||
) | ||
out.write("Credential Series: {}\n".format(BCD.reverse_translate(self.cs))) | ||
out.write( | ||
"Individual Credential Issue: {}\n".format(BCD.reverse_translate(self.ici)) | ||
) | ||
out.write( | ||
"Person Identifier: {}\n".format( | ||
[str(x) for x in map(BCD.reverse_translate, self.pi)] | ||
) | ||
) | ||
out.write( | ||
"Organizational Category: {}\n".format(BCD.reverse_translate(self.oc)) | ||
) | ||
out.write( | ||
"Organizational Identifier: {}\n".format( | ||
[str(x) for x in map(BCD.reverse_translate, self.oi)] | ||
) | ||
) | ||
out.write( | ||
"Person/Organization Association Category: {}\n".format( | ||
BCD.reverse_translate(self.poa) | ||
) | ||
) | ||
return out.getvalue() |