From 45fdfe209b00449d43d883d63b7b7c5ecc2614e3 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Thu, 25 Dec 2025 15:33:08 +0700 Subject: [PATCH 1/6] Implemented getting manufacturer certificate command --- cryptnox_cli/command/factory.py | 2 +- cryptnox_cli/command/options/options.py | 11 +++++++++++ cryptnox_cli/enums.py | 1 + docs/cli.rst | 14 ++++++++++++++ docs/cryptnox_cli.command.rst | 8 ++++++++ requirements.txt | Bin 5902 -> 5902 bytes 6 files changed, 35 insertions(+), 1 deletion(-) diff --git a/cryptnox_cli/command/factory.py b/cryptnox_cli/command/factory.py index fc87272..ea21bd4 100644 --- a/cryptnox_cli/command/factory.py +++ b/cryptnox_cli/command/factory.py @@ -27,7 +27,7 @@ def command(data: Namespace, cards: CardManager = None) -> Command: 'btc', 'card_configuration', 'change_pin', 'change_puk', 'config', 'eth', 'history', 'info', 'initialize', 'seed', 'cards', 'server', 'reset', 'unlock_pin', 'user_key', 'transfer', 'get_xpub', - 'get_clearpubkey', 'decrypt' + 'get_clearpubkey', 'decrypt', 'manufacturer_certificate' ] for module_name in command_modules: diff --git a/cryptnox_cli/command/options/options.py b/cryptnox_cli/command/options/options.py index 28fce5e..b84fd7b 100644 --- a/cryptnox_cli/command/options/options.py +++ b/cryptnox_cli/command/options/options.py @@ -60,6 +60,7 @@ def add(parser, interactive: bool = False): _xpub_options(subparsers, interactive) _get_clearpubkey_options(subparsers, interactive) _decrypt_options(subparsers, interactive) + _cert_options(subparsers, interactive) if interactive: use_sub_parser = subparsers.add_parser("use", help="Change card to be used by default") @@ -300,3 +301,13 @@ def _decrypt_options(subparsers, interactive_mode): """ from ..decrypt import Decrypt Decrypt.add_options(subparsers) + + +def _cert_options(subparsers, _: bool): + """ + Add manufacturer certificate command options + + :param subparsers: Argument parser subparsers + """ + subparsers.add_parser(enums.Command.CERTIFICATE.value, + help="Retrieve and display the manufacturer certificate") diff --git a/cryptnox_cli/enums.py b/cryptnox_cli/enums.py index d578ae1..183f35d 100644 --- a/cryptnox_cli/enums.py +++ b/cryptnox_cli/enums.py @@ -34,3 +34,4 @@ class Command(Enum): UNLOCK_PIN = "unlock_pin" USER_KEY = "user_key" TRANSFER = "transfer" + CERTIFICATE = "cert" diff --git a/docs/cli.rst b/docs/cli.rst index 7c94558..2b4b9d0 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -91,6 +91,20 @@ Shows: - Public keys - Derivation information +cert +^^^^ + +Retrieve and display the manufacturer certificate from the card in a human-readable format. + +.. code-block:: bash + + cryptnox cert + +**Features:** + - Retrieves the full manufacturer certificate from the card + - Displays certificate details (Issuer, Subject, Validity, Public Key, Signature) + - Human-readable format similar to ``openssl x509 -text`` + Seed Management Commands ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/cryptnox_cli.command.rst b/docs/cryptnox_cli.command.rst index d8ce5f7..2778c33 100644 --- a/docs/cryptnox_cli.command.rst +++ b/docs/cryptnox_cli.command.rst @@ -136,6 +136,14 @@ cryptnox_cli.command.initialize module :undoc-members: :show-inheritance: +cryptnox_cli.command.manufacturer\_certificate module +---------------------------------------------------- + +.. automodule:: cryptnox_cli.command.manufacturer_certificate + :members: + :undoc-members: + :show-inheritance: + cryptnox_cli.command.reset module -------------------------------- diff --git a/requirements.txt b/requirements.txt index a89bd387f0fc14ed174390548d675979a825cda4..32730ec389754de7de5cba1cdd576577caa2b847 100644 GIT binary patch delta 16 XcmeCv>(kr7#yWWstHEY7)*FHVFck%L delta 16 XcmeCv>(kr7#yUBH*kUBwE%ya6 From d3f9541147bcbc73d804c2dc5bfcd4157cd05e46 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Fri, 26 Dec 2025 18:21:05 +0700 Subject: [PATCH 2/6] Implemented and modified manufacturer_certificate.py --- .../command/manufacturer_certificate.py | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 cryptnox_cli/command/manufacturer_certificate.py diff --git a/cryptnox_cli/command/manufacturer_certificate.py b/cryptnox_cli/command/manufacturer_certificate.py new file mode 100644 index 0000000..7646f7b --- /dev/null +++ b/cryptnox_cli/command/manufacturer_certificate.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +""" +Module containing command for retrieving and displaying manufacturer certificate +""" +import cryptnox_sdk_py +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec + +from .command import Command + +try: + import enums +except ImportError: + from .. import enums + + +class ManufacturerCertificate(Command): + """ + Command to retrieve and display the manufacturer certificate from the card + """ + _name = enums.Command.CERTIFICATE.value + + def _execute(self, card: cryptnox_sdk_py.Card) -> int: + print("Retrieving manufacturer certificate from the card...") + try: + # Check if the card object has the required method + if not hasattr(card, 'get_manufacturer_certificate'): + print("Error: The current card type does not support retrieving a manufacturer certificate.") + return -1 + + cert_raw = card.get_manufacturer_certificate(hexed=False) + if not cert_raw: + print("Error: Could not retrieve manufacturer certificate from the card.") + return -1 + + # Parse the certificate + cert = x509.load_der_x509_certificate(cert_raw) + + # Format and display the certificate + self._print_certificate_text(cert) + + return 0 + except Exception as e: + print(f"Error retrieving or processing manufacturer certificate: {e}") + return -1 + + def _print_certificate_text(self, cert: x509.Certificate): + """ + Prints the certificate in a human-readable format similar to openssl x509 -text + """ + print("Certificate:") + print(" Data:") + print(f" Version: {cert.version.value + 1} (0x{cert.version.value})") + print(" Serial Number:") + serial_hex = self._format_hex(cert.serial_number) + print(f" {serial_hex}") + print(f" Signature Algorithm: {self._get_sig_alg_name(cert)}") + print(f" Issuer: {self._format_name(cert.issuer)}") + print(" Validity") + # Use UTC for consistency as required by the GMT format in example + print(f" Not Before: {cert.not_valid_before_utc.strftime('%b %d %H:%M:%S %Y GMT')}") + print(f" Not After : {cert.not_valid_after_utc.strftime('%b %d %H:%M:%S %Y GMT')}") + print(f" Subject: {self._format_name(cert.subject)}") + + public_key = cert.public_key() + if isinstance(public_key, ec.EllipticCurvePublicKey): + print(" Subject Public Key Info:") + print(" Public Key Algorithm: id-ecPublicKey") + print(f" Public-Key: ({public_key.key_size} bit)") + print(" pub:") + pub_bytes = public_key.public_bytes( + encoding=serialization.Encoding.X962, + format=serialization.PublicFormat.UncompressedPoint + ) + pub_hex = ':'.join(f'{b:02x}' for b in pub_bytes) + self._print_indented_hex(pub_hex, 20) + print(f" ASN1 OID: {public_key.curve.name}") + if public_key.curve.name in ["prime256v1", "secp256r1"]: + print(" NIST CURVE: P-256") + + print(f" Signature Algorithm: {self._get_sig_alg_name(cert)}") + print(" Signature Value:") + sig_hex = ':'.join(f'{b:02x}' for b in cert.signature) + self._print_indented_hex(sig_hex, 8) + + def _format_hex(self, value: int) -> str: + h = hex(value)[2:] + if len(h) % 2 != 0: + h = '0' + h + return ':'.join(h[i:i+2] for i in range(0, len(h), 2)) + + def _print_indented_hex(self, hex_str: str, indent: int): + parts = hex_str.split(':') + line_size = 15 + for i in range(0, len(parts), line_size): + chunk = parts[i:i+line_size] + line = ":".join(chunk) + if i + line_size < len(parts): + line += ":" + print(" " * indent + line) + + def _format_name(self, name: x509.Name) -> str: + short_names = { + "commonName": "CN", + "organizationName": "O", + "countryName": "C", + "localityName": "L", + "stateOrProvinceName": "ST", + "organizationalUnitName": "OU" + } + parts = [] + # Reverse to match common OpenSSL display order (Subject/Issuer) + for attribute in reversed(list(name)): + name_str = attribute.oid._name + short_name = short_names.get(name_str, name_str) + parts.append(f"{short_name}={attribute.value}") + return ", ".join(parts) + + def _get_sig_alg_name(self, cert: x509.Certificate) -> str: + # Map common OID names to OpenSSL style names if needed + name = cert.signature_algorithm_oid._name + if name == "ecdsa-with-sha256": + return "ecdsa-with-SHA256" + return name + From 5f6292aaac340b0613ab915b1ab8a5e3316b8f36 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Mon, 5 Jan 2026 18:05:19 +0700 Subject: [PATCH 3/6] Resolved flake 8 error --- cryptnox_cli/command/manufacturer_certificate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cryptnox_cli/command/manufacturer_certificate.py b/cryptnox_cli/command/manufacturer_certificate.py index 7646f7b..6510da1 100644 --- a/cryptnox_cli/command/manufacturer_certificate.py +++ b/cryptnox_cli/command/manufacturer_certificate.py @@ -123,4 +123,3 @@ def _get_sig_alg_name(self, cert: x509.Certificate) -> str: if name == "ecdsa-with-sha256": return "ecdsa-with-SHA256" return name - From a925c09d5aa53ef935d05b0c713ad380d5514552 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Mon, 5 Jan 2026 18:36:14 +0700 Subject: [PATCH 4/6] Modified manufacturer certificate --- cryptnox_cli/command/manufacturer_certificate.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/cryptnox_cli/command/manufacturer_certificate.py b/cryptnox_cli/command/manufacturer_certificate.py index 6510da1..85e2e42 100644 --- a/cryptnox_cli/command/manufacturer_certificate.py +++ b/cryptnox_cli/command/manufacturer_certificate.py @@ -2,6 +2,7 @@ """ Module containing command for retrieving and displaying manufacturer certificate """ +import re import cryptnox_sdk_py from cryptography import x509 from cryptography.hazmat.primitives import serialization @@ -55,7 +56,8 @@ def _print_certificate_text(self, cert: x509.Certificate): print(" Serial Number:") serial_hex = self._format_hex(cert.serial_number) print(f" {serial_hex}") - print(f" Signature Algorithm: {self._get_sig_alg_name(cert)}") + sig_alg = re.sub(r'(with-)([a-z0-9]+)', lambda m: m.group(1) + m.group(2).upper(), cert.signature_algorithm_oid._name) + print(f" Signature Algorithm: {sig_alg}") print(f" Issuer: {self._format_name(cert.issuer)}") print(" Validity") # Use UTC for consistency as required by the GMT format in example @@ -79,7 +81,7 @@ def _print_certificate_text(self, cert: x509.Certificate): if public_key.curve.name in ["prime256v1", "secp256r1"]: print(" NIST CURVE: P-256") - print(f" Signature Algorithm: {self._get_sig_alg_name(cert)}") + print(f" Signature Algorithm: {sig_alg}") print(" Signature Value:") sig_hex = ':'.join(f'{b:02x}' for b in cert.signature) self._print_indented_hex(sig_hex, 8) @@ -116,10 +118,3 @@ def _format_name(self, name: x509.Name) -> str: short_name = short_names.get(name_str, name_str) parts.append(f"{short_name}={attribute.value}") return ", ".join(parts) - - def _get_sig_alg_name(self, cert: x509.Certificate) -> str: - # Map common OID names to OpenSSL style names if needed - name = cert.signature_algorithm_oid._name - if name == "ecdsa-with-sha256": - return "ecdsa-with-SHA256" - return name From a80c4adf49882d40762dea1c4cd8e7b010b3cff7 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Mon, 5 Jan 2026 18:42:16 +0700 Subject: [PATCH 5/6] Resolved flake 8 error --- cryptnox_cli/command/manufacturer_certificate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cryptnox_cli/command/manufacturer_certificate.py b/cryptnox_cli/command/manufacturer_certificate.py index 85e2e42..a64a1eb 100644 --- a/cryptnox_cli/command/manufacturer_certificate.py +++ b/cryptnox_cli/command/manufacturer_certificate.py @@ -56,7 +56,8 @@ def _print_certificate_text(self, cert: x509.Certificate): print(" Serial Number:") serial_hex = self._format_hex(cert.serial_number) print(f" {serial_hex}") - sig_alg = re.sub(r'(with-)([a-z0-9]+)', lambda m: m.group(1) + m.group(2).upper(), cert.signature_algorithm_oid._name) + sig_alg_name = cert.signature_algorithm_oid._name + sig_alg = re.sub(r'(with-)([a-z0-9]+)', lambda m: m.group(1) + m.group(2).upper(), sig_alg_name) print(f" Signature Algorithm: {sig_alg}") print(f" Issuer: {self._format_name(cert.issuer)}") print(" Validity") From 29a31b2fd9c081016c21fb7304edbcaae9dbe4c7 Mon Sep 17 00:00:00 2001 From: YanNaingWinn Date: Mon, 5 Jan 2026 18:53:33 +0700 Subject: [PATCH 6/6] Modified manufacturer certificate print lines --- .../command/manufacturer_certificate.py | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/cryptnox_cli/command/manufacturer_certificate.py b/cryptnox_cli/command/manufacturer_certificate.py index a64a1eb..efe8d15 100644 --- a/cryptnox_cli/command/manufacturer_certificate.py +++ b/cryptnox_cli/command/manufacturer_certificate.py @@ -50,42 +50,41 @@ def _print_certificate_text(self, cert: x509.Certificate): """ Prints the certificate in a human-readable format similar to openssl x509 -text """ - print("Certificate:") - print(" Data:") - print(f" Version: {cert.version.value + 1} (0x{cert.version.value})") - print(" Serial Number:") + print("Certificate Data:") + print(f" Version: {cert.version.value + 1} (0x{cert.version.value})") + print(" Serial Number:") serial_hex = self._format_hex(cert.serial_number) - print(f" {serial_hex}") + print(f" {serial_hex}") sig_alg_name = cert.signature_algorithm_oid._name sig_alg = re.sub(r'(with-)([a-z0-9]+)', lambda m: m.group(1) + m.group(2).upper(), sig_alg_name) - print(f" Signature Algorithm: {sig_alg}") - print(f" Issuer: {self._format_name(cert.issuer)}") - print(" Validity") + print(f" Signature Algorithm: {sig_alg}") + print(f" Issuer: {self._format_name(cert.issuer)}") + print(" Validity:") # Use UTC for consistency as required by the GMT format in example - print(f" Not Before: {cert.not_valid_before_utc.strftime('%b %d %H:%M:%S %Y GMT')}") - print(f" Not After : {cert.not_valid_after_utc.strftime('%b %d %H:%M:%S %Y GMT')}") - print(f" Subject: {self._format_name(cert.subject)}") + print(f" Not Before: {cert.not_valid_before_utc.strftime('%b %d %H:%M:%S %Y GMT')}") + print(f" Not After : {cert.not_valid_after_utc.strftime('%b %d %H:%M:%S %Y GMT')}") + print(f" Subject: {self._format_name(cert.subject)}") public_key = cert.public_key() if isinstance(public_key, ec.EllipticCurvePublicKey): - print(" Subject Public Key Info:") - print(" Public Key Algorithm: id-ecPublicKey") - print(f" Public-Key: ({public_key.key_size} bit)") - print(" pub:") + print(" Subject Public Key Info:") + print(" Public Key Algorithm: id-ecPublicKey") + print(f" Public-Key: ({public_key.key_size} bit)") + print(" pub:") pub_bytes = public_key.public_bytes( encoding=serialization.Encoding.X962, format=serialization.PublicFormat.UncompressedPoint ) pub_hex = ':'.join(f'{b:02x}' for b in pub_bytes) - self._print_indented_hex(pub_hex, 20) - print(f" ASN1 OID: {public_key.curve.name}") + self._print_indented_hex(pub_hex, 16) + print(f" ASN1 OID: {public_key.curve.name}") if public_key.curve.name in ["prime256v1", "secp256r1"]: - print(" NIST CURVE: P-256") + print(" NIST CURVE: P-256") - print(f" Signature Algorithm: {sig_alg}") - print(" Signature Value:") + print(f"Signature Algorithm: {sig_alg}") + print("Signature Value:") sig_hex = ':'.join(f'{b:02x}' for b in cert.signature) - self._print_indented_hex(sig_hex, 8) + self._print_indented_hex(sig_hex, 4) def _format_hex(self, value: int) -> str: h = hex(value)[2:]