Skip to content
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
2 changes: 1 addition & 1 deletion cryptnox_cli/command/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
120 changes: 120 additions & 0 deletions cryptnox_cli/command/manufacturer_certificate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
"""
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
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 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}")
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:")
# 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, 16)
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: {sig_alg}")
print("Signature Value:")
sig_hex = ':'.join(f'{b:02x}' for b in cert.signature)
self._print_indented_hex(sig_hex, 4)

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)
11 changes: 11 additions & 0 deletions cryptnox_cli/command/options/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
1 change: 1 addition & 0 deletions cryptnox_cli/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ class Command(Enum):
UNLOCK_PIN = "unlock_pin"
USER_KEY = "user_key"
TRANSFER = "transfer"
CERTIFICATE = "cert"
14 changes: 14 additions & 0 deletions docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
8 changes: 8 additions & 0 deletions docs/cryptnox_cli.command.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------------------------

Expand Down
Binary file modified requirements.txt
Binary file not shown.