Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Printing of Authorization bits from BSI TR 03110-4 #7

Merged
merged 5 commits into from
Oct 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cvc/certificates.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def cpi(self, val = 0):
return self.body().find(0x5f29).data()
self.__a = self.__a.add_tag(0x5f29, to_bytes(val))
return self

def pubkey(self, pubkey=None, scheme=None, full=None):
if (self.__data != None):
return self.body().find(0x7f49)
Expand Down Expand Up @@ -239,4 +239,4 @@ def find_domain(self, cert_dir='', outer=False):
depth -= 1
except FileNotFoundError:
print(f'[Warning: File {car.decode()} not found]')
return None
return None
70 changes: 70 additions & 0 deletions cvc/tools/cvc_print.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,58 @@
logger = logging.getLogger(__name__)
cert_dir = b''

# Authorization bits according to
# BSI-TR-03110-4 Chapter 2.2.3.2 Table 4
AuthorizationBits = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not necessary to have a dict, a reverse list is sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the explicit functionality to check set bits with human readable strings like self.assertTrue(bits[AuthorizationBits["Age Verification"]])

How would you structure the code with a reverse list ?

"Upper Role Bit": 39,
"Lower Role Bit": 38,
"Write Datagroup 17": 37,
"Write Datagroup 18": 36,
"Write Datagroup 19": 35,
"Write Datagroup 20": 34,
"Write Datagroup 21": 33,
"Write Datagroup 22": 32,
"RFU": 31,
"PSA": 30,
"Read Datagroup 22": 29,
"Read Datagroup 21": 28,
"Read Datagroup 20": 27,
"Read Datagroup 19": 26,
"Read Datagroup 18": 25,
"Read Datagroup 17": 24,
"Read Datagroup 16": 23,
"Read Datagroup 15": 22,
"Read Datagroup 14": 21,
"Read Datagroup 13": 20,
"Read Datagroup 12": 19,
"Read Datagroup 11": 18,
"Read Datagroup 10": 17,
"Read Datagroup 09": 16,
"Read Datagroup 08": 15,
"Read Datagroup 07": 14,
"Read Datagroup 06": 13,
"Read Datagroup 05": 12,
"Read Datagroup 04": 11,
"Read Datagroup 03": 10,
"Read Datagroup 02": 9,
"Read Datagroup 01": 8,
"Install Qualified Certificate": 7,
"Install Certificate": 6,
"PIN Management": 5,
"CAN allowed": 4,
"Privileged Terminal": 3,
"Restricted Identification": 2,
"Municipality ID Verification": 1,
"Age Verification": 0,
}

def parse_args():
global cert_dir
parser = argparse.ArgumentParser(description='Prints a Card Verifiable Certificate')
parser.add_argument('--version', help='Displays the current version', action='store_true')
parser.add_argument('file',help='Certificate to print', metavar='FILENAME')
parser.add_argument('-d','--directory', help='Directory where chain CV certificates are located', metavar='DIRECTORY')
parser.add_argument('--print-bits', help='Print a detailed info about Authorization bits set',action='store_true')
if ('--version' in sys.argv):
print('Card Verifiable Certificate tools for Python')
print('Author: Pol Henarejos')
Expand All @@ -53,6 +99,19 @@ def parse_args():
def bcd2date(v):
return date(v[0]*10+v[1]+2000, v[2]*10+v[3], v[4]*10+v[5])

def decode_authorization_bits(chat_bytes):
# get CHAT according to BSI-TR-03110-3 Appendix C.1.5
# It holds "A discretionary data object that encodes the relative authorization"
# Appendix D.2 Table 27 states Tag 0x53 for "Discretionary Data"
# convert the byte array to a bit string
bits = "".join(format(byte, "08b") for byte in chat_bytes)
# reverse the bit string since the table provided in
# BSI-TR-03110-4 Chapter 2.2.3.2 Table 4 is MSB
# e.g. "Age verification" has place 0 in the Table
# but "Age verification" is actually the highest/last bit in a series of 5 bytes
# and not the first (index zero) so we simply reverse the bitstring
return bits[::-1]

def main(args):
with open(args.file, 'rb') as f:
cdata = f.read()
Expand Down Expand Up @@ -83,6 +142,17 @@ def main(args):
print(' Role: TypeAT')
elif (o == oid.ID_ST):
print(' Role: TypeST')
if(args.print_bits):
chat_bytes = CVC().decode(cdata).role().find(0x53).data()
bits = decode_authorization_bits(chat_bytes)
for bit in AuthorizationBits:
print(
" Field '{:<32}' has value: {:^6} at offset: {:2}".format(
bit,
str(bits[AuthorizationBits[bit]] == "1"),
AuthorizationBits[bit],
)
)
print(f' Bytes: {hexlify(CVC().decode(cdata).role().find(0x53).data()).decode()}')
print(f' Since: {bcd2date(CVC().decode(cdata).valid()).strftime("%Y-%m-%d")}')
print(f' Expires: {bcd2date(CVC().decode(cdata).expires()).strftime("%Y-%m-%d")}')
Expand Down
65 changes: 65 additions & 0 deletions tests/bits_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import sys
import unittest
import binascii
import logging

from cvc.certificates import CVC
from cvc.tools.cvc_print import AuthorizationBits, decode_authorization_bits

# created with --rid --read-dg1 --write-dg22 --verify-age --install-cert
CERT_WITH_RID_READ_DG1_WRITE_DG22_VERIFY_AGE_INSTALL_CERT = b"7f2181e37f4e819c5f290100420d5a5a41544456434130303030317f494f060a04007f000702020202038641043eb3b230afe41d99b0e564b8673ca9f830de0c4f1a21ccfbebbb378b980d2384750751df9403878cb46f9297d5507a759e80ab463b0bb233e5dce068ddeb55665f200d5a5a41545445524d30303030317f4c12060904007f000703010202530501000001455f25060203000900095f24060203010100085f374094d5eb4162b8f38ab531b2259af0a8aaa7fdadaa126d21948e5d68a739bac4141b59ca43fb411165b7725c39ad4fa71ab548ede169616282de72860e7de9179b"
# created with --rid --read-dg22
CERT_WITH_RID_READ_DG22 = b"7f2181e37f4e819c5f290100420d5a5a41544456434130303030317f494f060a04007f00070202020203864104ed9e6911b2b4c39c7571f717ca0b61b7074fb05701d90f4474ee7e314d42828eb54ec3278a4cfc14cfe83014f01b534733e42ecee9a347c9c85691226a4692665f200d5a5a41545445524d30303030317f4c12060904007f000703010202530500200000045f25060203000900085f24060203010100075f37406e7f93614c8a63bbecc05ac8765055fe81b0d8a27389a8489aaed6ae9176503693d3016d1109d2cade63d4f0b661b142d7fc3368369ac3fe9c86154659a17518"

class TestAuthorizationBits(unittest.TestCase):
log = logging.getLogger("AuthBits")

def test_parse_authorization_bits(self):
self.log.info(
" Testing CERT_WITH_RID_READ_DG1_WRITE_DG22_VERIFY_AGE_INSTALL_CERT"
)
cvc = CVC().decode(
binascii.unhexlify(
CERT_WITH_RID_READ_DG1_WRITE_DG22_VERIFY_AGE_INSTALL_CERT
)
)
self.log.info(cvc)
bits = decode_authorization_bits(cvc.role().find(0x53).data())

self.log.info("Raw authorization bits:" + bits)
for bit in AuthorizationBits:
self.log.info(
"Field '{:<32}' has value: {:^6} at offset: {:2}".format(
bit,
str(bits[AuthorizationBits[bit]] == "1"),
AuthorizationBits[bit],
)
)
self.assertTrue(bits[AuthorizationBits["Age Verification"]])
self.assertTrue(bits[AuthorizationBits["Restricted Identification"]])
self.assertTrue(bits[AuthorizationBits["Install Certificate"]])
self.assertTrue(bits[AuthorizationBits["Read Datagroup 01"]])
self.assertTrue(bits[AuthorizationBits["Write Datagroup 22"]])

self.log.info(" Testing CERT_WITH_RID_READ_DG22")
cvc = CVC().decode(binascii.unhexlify(CERT_WITH_RID_READ_DG22))
self.log.info(cvc)
bits = decode_authorization_bits(cvc.role().find(0x53).data())

self.log.info("Raw authorization bits:" + bits)
for bit in AuthorizationBits:
self.log.info(
"Field '{:<32}' has value: {:^6} at offset: {:2}".format(
bit,
str(bits[AuthorizationBits[bit]] == "1"),
AuthorizationBits[bit],
)
)
self.assertTrue(bits[AuthorizationBits["Restricted Identification"]])
self.assertTrue(bits[AuthorizationBits["Read Datagroup 22"]])


if __name__ == "__main__":
logging.basicConfig(stream=sys.stderr)
logging.getLogger().setLevel(logging.DEBUG)
unittest.main()
19 changes: 19 additions & 0 deletions tests/cvcert_to_hexstring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import binascii
import argparse


def file_to_hex(filename):
with open(filename, "rb") as f:
content = f.read()
print(binascii.hexlify(content))


if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Helper util to convert CVCert(or any file) to hex string"
)
parser.add_argument("file", help="File to convert to hex")

args = parser.parse_args()

file_to_hex(args.file)