From ff1b43417899af37a231beb76590fb8e341c75eb Mon Sep 17 00:00:00 2001 From: c0de Date: Sat, 26 Aug 2023 01:07:09 -0500 Subject: [PATCH] Create script to generate PGP Smart Card AIDs --- .editorconfig | 12 +++++ .vscode/settings.json | 6 +++ LICENSE.md | 28 ++++++++++++ README.md | 44 ++++++++++++++++++ aid_generator.py | 101 ++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 6 files changed, 192 insertions(+) create mode 100644 .editorconfig create mode 100644 .vscode/settings.json create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 aid_generator.py create mode 100644 requirements.txt diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c1e2c64 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3266f3f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "license.author": "c0de ", + "license.extension": ".md", + "license.filename": "LICENSE", + "license.year": "auto" +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..ef6f210 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2023, c0de + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..94c89d2 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# PGP AID Generator + +Script to generate a valid Application Identifier (AID) for an implementation +of a PGP Smart Cart (such as SmartPGP) + +The AID is 16 bytes in length and must be unique (multiple cards should have different AIDs). +The AID is written to the Smart Card during application install, and can not be changed without +reinstalling the `SmartPGP.cap` + +## The structure of the AID is described as follows + +The first 6 bytes are static and always `0xD2 0x76 0x00 0x01 0x24 0x01`. The next 2 bytes represent the version of the PGP Specification, which is `0x03 0x04` for SmartPGP (PGP 3.4) + +The following 2 bytes are the "Manufacturer ID"; They're supposed to be registered by OpenPGP devs, but using an unknown one doesn't cause issues, and it looks better than "Test Card". + +Additionally, newer versions of OpenPGP include a new "Manufacturer Name" attribute, that is not part of the AID + +[Official Manufacturer IDs](https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=scd/app-openpgp.c;hb=HEAD#l292) + +The manufacturer id this script uses is `0xC0 0xDE`, feel free to change this to something else. + +Following the manufacturer id, the next 4 bytes are for the serial number. This must be unique for all cards that share the same manufacturer id. + +There are 2 bytes after the serial number, that are static and always `0x00 0x00` (reserved for future use) + +To generate the serial number, you must provide this script with the following information: + + 1. The `ICSerialNumber` value from `gp -i` + 2. One or more domain names that you control + +The UUID of `ICSerialNumber` will be created using the namespace of the IC Manufacturer; In this +case, we are using Infineon. You should define the namespace for your device if different. + +For every domain name provided, its UUID will be calculated, in the DNS namespace. +All UUIDs will then be concatinated together, and the SHA1 hash will be taken from that. + +The SHA1 hash will then have a UUID generated from that, in the SHA1 namespace. +The first 8 characters of the resulting UUID are the serial number. + +A complete AID that could be produced by this script is: `d276000124010304c0decdf177610000` + +Usage: + aid_generator.py ... + diff --git a/aid_generator.py b/aid_generator.py new file mode 100644 index 0000000..03913b0 --- /dev/null +++ b/aid_generator.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 + +""" +Script to generate a valid Application Identifier (AID) for an implementation +of a PGP Smart Cart (such as SmartPGP) + +The AID is 16 bytes in length and must be unique (multiple cards should have different AIDs). +The AID is written to the Smart Card during application install, and can not be changed without +reinstalling the `SmartPGP.cap` + +The structure of the AID is described as follows: +The first 6 bytes are static and always `0xD2 0x76 0x00 0x01 0x24 0x01`. The next 2 bytes represent +the version of the PGP Specification, which is `0x03 0x04` for SmartPGP (PGP 3.4) + +The following 2 bytes are the "Manufacturer ID"; They're supposed to be registered by OpenPGP devs, +but using an unknown one doesn't cause issues, and it looks better than "Test Card". Additionally, +newer versions of OpenPGP include a new "Manufacturer Name" attribute, that is not part of the AID + +The list of registered manufacturer ids can be found here: +https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=scd/app-openpgp.c;hb=HEAD#l292 + +The manufacturer id this script uses is `0xC0 0xDE`, feel free to change this to something else. + +Following the manufacturer id, the next 4 bytes are for the serial number. This must be unique for +all cards that share the same manufacturer id. There are 2 bytes after the serial number, that are +static and always `0x00 0x00` (reserved for future use) + +To generate the serial number, you must provide this script with the following information: + + 1. The `ICSerialNumber` value from `gp -i` + 2. One or more domain names that you control + +The UUID of `ICSerialNumber` will be created using the namespace of the IC Manufacturer; In this +case, we are using Infineon. You should define the namespace for your device if different. + +For every domain name provided, its UUID will be calculated, in the DNS namespace. +All UUIDs will then be concatinated together, and the SHA1 hash will be taken from that. + +The SHA1 hash will then have a UUID generated from that, in the SHA1 namespace. +The first 8 characters of the resulting UUID are the serial number. + +A complete AID that could be produced by this script is: `d276000124010304c0decdf177610000` + +Usage: + aid_generator.py ... + aid_generator.py (-h | --help) + aid_generator.py --version + +Options: + -h --help Show this screen + --version Show current version +""" + +import uuid +import hashlib + +from typing import Any + +from docopt import docopt + +AID_PREFIX = "d276000124010304" # RID:10, APP:2, SPEC:4 +AID_POSTFIX = "0000" # RFU:4 +MANUFACTURER_ID = "c0de" + +NAMESPACE_INFINEON: uuid.UUID = uuid.uuid5(uuid.NAMESPACE_OID, "1.2.276.0.68") +"""UUID Namespace for Infineon devices, defined from OID Namespace + +https://oid-rep.orange-labs.fr/get/1.2.276.0.68""" + +NAMESPACE_SHAONE: uuid.UUID = uuid.uuid5(uuid.NAMESPACE_OID, "1.3.14.3.2.26") +"""UUID Namespace for SHA1 hashes, defined from OID Namespace + +https://oid-rep.orange-labs.fr/get/1.3.14.3.2.26""" + + +def main(options: dict[str, Any]) -> None: + """Main method""" + + uuid_hash = hashlib.sha1() + uuid_result: uuid.UUID + aid_result: str + + if ( + len(options.get("", "")) > 0 + and len(options.get("", [])) > 0 + ): + ic_uuid: uuid.UUID = uuid.uuid5( + NAMESPACE_INFINEON, str(options[""]) + ) + uuid_hash.update(ic_uuid.bytes) + + for domain in list(options[""]): + uuid_hash.update(uuid.uuid5(uuid.NAMESPACE_DNS, str(domain)).bytes) + + uuid_result = uuid.uuid5(NAMESPACE_SHAONE, uuid_hash.hexdigest()) + aid_result = f"{AID_PREFIX}{MANUFACTURER_ID}{uuid_result.hex[:8]}{AID_POSTFIX}" + print(f"Your AID is: {aid_result}") + +if __name__ == "__main__": + arguments: dict[str, Any] = docopt(__doc__, version="2023.08.1") + main(arguments) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1d9dff8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +docopt==0.6.2