diff --git a/README.html b/README.html
new file mode 100755
index 0000000..5a9b502
--- /dev/null
+++ b/README.html
@@ -0,0 +1,71 @@
+
Introduction
+This software implements the PK-PACS specification in Python using a USB contactless smartcard reader. This code is provided as-is and is intended
+to be used as a reference implementation. Questions and comments about this software should be directed to pkpacs@taglio.com
.
+The PK-PACS specification can be found here:
+https://github.com/TaglioLLC/pk-pacs-spec
+Setup
+You will need to have the following in order to run this utility:
+
+- A machine running Windows 8 or greater, or Linux (e.g. Ubuntu 22.04 LTS).
+- Python version 3.6 or greater, or version 3.11 or less. We recommend version 3.11 if you're installing Python for the first time. You can download version 3.11 here.
+- A USB contactless card reader that supports ISO/IEC 14443 such as this.
+- The reader needs to be plugged into a USB port on the machine. Windows has built-in drivers for CCID compatible readers.
+- A smartcard that implements the PK-PACS standard. (If you need a test card, contact Taglio at
pkpacs@taglio.com
).
+
+Before running the utility, you need to install the required packages. If you are using Windows, this can be done by double-clicking on the install_requirements.py
script. If you are using Linux, please follow the directions in the next section.
+Linux installation
+(Please skip to the next section if you are using Windows.) Execute the following steps to install the required software:
+sudo apt install swig
+sudo apt install libpcsclite-dev
+sudo apt install pcsc-tools
+sudo apt install pcscd
+sudo apt install libccid
+sudo apt install libnss3-tools
+Then install the required Python packages by running:
+python3 install_requirements.py
+Running
+Double-clicking on pkpacs.py (Windows) or running python3 pkpacs.py
(Linux) will run the PK-PACS utility in its identifier mode (this is the default mode without any command-line arguments), which will print the ID-OID value of the PK-PACS card presented, according to the pkpacs_config.json
configuration file.
+Or you run can the pkpacs.py
utility with the following command-line arguments:
+
+- -config This argument specifies an alternate configuration file or path. For example, you could specify
-config c:\some_path
and it will look for pkpacs_config.json
+in the c:\some_path
directory. Or you could specify -config c:\some_path\my_config.json
and it will use the specified configuration file. By default pkpacs.py
will look
+in the same directory for pkpacs_config.json
+- -test This argument is a test mode that reads out information on the PK-PACS card presented.
+- -copy_keys This will instruct the utlity to extract the public keys from the certificates in the PK-TrustCert Path and write them as
.pem
file into the PK-TrustKey Path.
+- -verbose This is useful for debugging, etc.
+
+If you're using Windows, running the utlity from the command-line can be done from a command prompt (e.g. cmd
). Within cmd
you can run py
, which should be within the PATH to run Python. So for example, after changing directories into the directory containing pkpacs.py
, you can run:
+py pkpacs.py -test -verbose
+If you're using Linux, running this utility from the command-line is recommended. Typically, python3
is pointing to a compatible version of Python:
+python3 pkpacs.py -test -verbose
+Configuration
+The configuration of the utility is contained in the pkpacs_config.json
file, which is located in the same directory as pkpacs.py
by default. The file contains
+the following configuration fields:
+
+- PK-TrustKey Path This value is a string that specifies the path of the PK-TrustKeys, which should be in
.pem
format. The path can either be relative to
+the directory that pkpacs.py
is located in, or it can be an absolute path. For example "c:\\some_path"
. (Note, the use of double backslashes to indicate a backslash within a
+string literal.)
+- PK-TrustCert Path This value is a string that specifies the path of the PK-TrustCertificates, which can be in
.pem
, .crt
, or .cer
format. The path can either
+be relative to directory that pkpacs.py
is located in, or it can be an absolute path. For example "c:\\some_path"
. (Note, the use of double backslashes to indicate a
+backslash) within a string literal.
+- Keys This is a list that specifies the order of public keys to try when validating the signature on a PK-PACS card. Each key should be in
.pem
format.
+Each entry in the list looks like:
+{"<key label>", "<absolute file path to key or relative to Certificate Path>"}
. So for example, here is a possible list of keys containing relative paths to the PK-TrustKey Path or absolute paths:
+"Keys": [
+ {"key1": "pkpacs_root.pem"},
+ {"key2": "pkpacs_demo.pem"},
+ {"key3": "c:\\demo_keys\\demo_cert.pem"}
+],
+- Priority List A list of of validation combinations goes here. This specifies the ID-OID value that gets printed to the console if the PK-PACS card is verified
+(both a challenge verification is successful and the signature is verified using the one of the keys listed in the
Keys
field). If the first ID-OID in the Priority List isn't contained in the card, the second ID-OID in the Priority List is used, and so on. If none of the ID-OID are present or the card fails during verification, no value is printed to the console. The Priority List is in the following format:
+["<key label from Keys>", "<ID-OID">, "<output format: UUID, HEX, or ASCII>"]
Note, if <output format>
is not specified, it will look-up the preferred format
+based on the ID-OID and use that format when printing.
+For example, here is a Priority list with both implied and specified formats:
+"Priority List": [
+ ["key1", "44986.8.1"],
+ ["key1", "44986.8.2", "HEX"],
+ ["key2", "59685.8.2", "HEX"]
+],
+
+Keeping in touch
+Questions and comments about this software should be directed to pkpacs@taglio.com
.
\ No newline at end of file
diff --git a/__init__.py b/__init__.py
new file mode 100755
index 0000000..ad0f9c7
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,2 @@
+from .pkpacs import PKPACS, main
+from .about import __version__, __license__, __author__, __email__
diff --git a/about.py b/about.py
new file mode 100755
index 0000000..c29e9e6
--- /dev/null
+++ b/about.py
@@ -0,0 +1,5 @@
+__title__ = "pk-pacs-reader"
+__version__ = "0.1.4"
+__license__ = "MIT"
+__author__ = "Taglio LLC"
+__email__ = "pkpacs@taglio.com"
\ No newline at end of file
diff --git a/install_requirements.py b/install_requirements.py
new file mode 100755
index 0000000..4aea33e
--- /dev/null
+++ b/install_requirements.py
@@ -0,0 +1,20 @@
+import subprocess
+import sys
+
+def install_packages(requirements_path='requirements.txt'):
+ """Install packages listed in the given requirements file using pip."""
+
+ python_executable = sys.executable
+
+ # Run pip install for each requirement
+ subprocess.check_call([python_executable, '-m', 'pip', 'install', '-r', requirements_path])
+
+
+if __name__ == '__main__':
+ try:
+ install_packages()
+ print("\nSuccess!")
+ except Exception as e:
+ print(f"\nError: {e}")
+
+ input("Press enter to continue...")
diff --git a/keys/SECUPASCA1.pem b/keys/SECUPASCA1.pem
new file mode 100755
index 0000000..d5f3542
--- /dev/null
+++ b/keys/SECUPASCA1.pem
@@ -0,0 +1,11 @@
+-----BEGIN PUBLIC KEY-----
+MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEApun4TJcsk+hK1yvA++1B
+b343vFVVPF4SQ2drfG+4Y6HU/LbAQeNYmiGoGIk7NEbLWMOwn3cqihDn+Pqx06ki
+JWIHvrk+mhmM4bxEnBqGP5D2GXcn3Y1wSYBVsKJHnPhupLqvO3fjjMU/ct1b8UpR
+lQSg66lztHU3522zNdLJh5x+81Dfg+3ytsgHM48QebyiTqqC1PCP2BEDtiNTWwwJ
+LQuvuhoTr1UHcgAl+FbTa36UlQNA9Vmo3qHUR677deRFw/XxKxuwjgUQ1jzkJUWi
+Tt9S8vQ/cnR2csdvNweRcig/AHmmT3yW3ZAboDNaUE7Qf1EGgEv9PEbdM264kSnp
+g4YLOiLGafeG60hzLJp32MVdU6PAHcI3GnKiA6KgNp399vhJ94kLVHoEKDbOnl9W
+qzZv/hhaK5ge2QXGswrK539w/OsmSHasnZ+pzFUoGN0zxKJWZD1t1uqaA6Zvx5oZ
+XFvbaaDAAiZhy5R+MKqIuuJLoEDb8iNRyrulCRBJSCK9AgMBAAE=
+-----END PUBLIC KEY-----
diff --git a/keys/taglio-demo-device-ca.pem b/keys/taglio-demo-device-ca.pem
new file mode 100755
index 0000000..4dbbdae
--- /dev/null
+++ b/keys/taglio-demo-device-ca.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArpVTeM6FYKQCTMyM+fMJ
+SzYZ6ykfnwqLKCH7HYU+Ph0ACczpMe8S9WDPeniEL2vjwY/KPJ7dCgojrJqcQhCB
+jVcat3mRDVb7lVCzojo6pGVGvvTJ0DL67rGgzNfG7p4JCyL93aVMneLXznhmr6NX
+p3fYNB68mpkYlIisuSSgnKf1WrZuMSdDiN5bSjnQjE1xQjG1MWOp2JqjZOdgp9lQ
+u0lQffQAREv6uLRSSNG/VJNn3PoweZxlxQwZL8DQWDQLiqOb3rpC4NhM53h3NXCg
+p6GONmxyKGHWXHVdZJpn3nfwJExO40bXKHq2sznwFZTlU3O4fM0THzJRMW7HbbQm
+IwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/pkpacs.py b/pkpacs.py
new file mode 100755
index 0000000..6b4b8b8
--- /dev/null
+++ b/pkpacs.py
@@ -0,0 +1,617 @@
+import time
+import os
+import io
+import gzip
+import hashlib
+import glob
+import json
+import argparse
+import uuid
+import secrets
+from smartcard.System import readers
+from smartcard.Exceptions import CardConnectionException
+from pynput import keyboard
+from cryptography import x509
+from cryptography.hazmat.backends import default_backend
+from cryptography.exceptions import InvalidSignature
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import padding
+
+
+DEFAULT_CONFIG = {
+ # Path to directory containing public keys for use as PK-TrustKeys.
+ # "." means relative to current directory. The files in this directory
+ # need to be PEM format and have .pem file extension.
+ # For example:
+ #
+ # "c:\\keys" (backslashes need to be doubled)
+ # or "c:/keys" (forward slashes instead of backslashes)
+ # or "/etc/keys" (Linux or macOS)
+ "PK-TrustKey Path": ".",
+ # Path to directory containing certificates for use as PK-TrustCerts.
+ # "." means relative to current directory. The files in this directory
+ # can be .pem, .cer, or .crt format.
+ # For example:
+ #
+ # "c:\\certs" (backslashes need to be doubled)
+ # or "c:/certs" (forward slashes instead of backslashes)
+ # or "/etc/certs" (Linux or macOS)
+ "PK-TrustCert Path": ".",
+ # A list of keys goes here in the following format:
+ # {"", ""}
+ # For example:
+ #
+ # {"key1": "pkpacs_root.pem"},
+ # {"key2": "pkpacs_demo.pem"},
+ # {"key3": "c:\\demo_keys/demo_cert.pem"},
+ # ...
+ "Keys": [],
+ # A list of validation combinations goes here in the following format:
+ # ["", ", "