cryptnox-id-cli 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. cryptnox_id_cli/__init__.py +22 -0
  2. cryptnox_id_cli/__main__.py +6 -0
  3. cryptnox_id_cli/applets/__init__.py +2 -0
  4. cryptnox_id_cli/applets/fido/__init__.py +7 -0
  5. cryptnox_id_cli/applets/fido/authdata.py +81 -0
  6. cryptnox_id_cli/applets/fido/constants.py +155 -0
  7. cryptnox_id_cli/applets/fido/ctap.py +430 -0
  8. cryptnox_id_cli/applets/fido/errors.py +90 -0
  9. cryptnox_id_cli/applets/fido/pinproto.py +166 -0
  10. cryptnox_id_cli/applets/genuine/__init__.py +12 -0
  11. cryptnox_id_cli/applets/genuine/constants.py +32 -0
  12. cryptnox_id_cli/applets/genuine/genuine.py +85 -0
  13. cryptnox_id_cli/applets/genuine/verify.py +150 -0
  14. cryptnox_id_cli/applets/mifare/__init__.py +10 -0
  15. cryptnox_id_cli/applets/mifare/desfire.py +353 -0
  16. cryptnox_id_cli/applets/mifare/ev2.py +421 -0
  17. cryptnox_id_cli/applets/piv/__init__.py +9 -0
  18. cryptnox_id_cli/applets/piv/admin.py +182 -0
  19. cryptnox_id_cli/applets/piv/apt.py +58 -0
  20. cryptnox_id_cli/applets/piv/constants.py +66 -0
  21. cryptnox_id_cli/applets/piv/keyimport.py +244 -0
  22. cryptnox_id_cli/applets/piv/objects.py +126 -0
  23. cryptnox_id_cli/applets/piv/perso.py +138 -0
  24. cryptnox_id_cli/applets/piv/piv.py +171 -0
  25. cryptnox_id_cli/applets/piv/preperso.py +174 -0
  26. cryptnox_id_cli/applets/piv/profiles.py +486 -0
  27. cryptnox_id_cli/applets/piv/slots.py +44 -0
  28. cryptnox_id_cli/cli/__init__.py +1 -0
  29. cryptnox_id_cli/cli/commands/__init__.py +1 -0
  30. cryptnox_id_cli/cli/commands/apdu.py +123 -0
  31. cryptnox_id_cli/cli/commands/doctor.py +144 -0
  32. cryptnox_id_cli/cli/commands/factory.py +312 -0
  33. cryptnox_id_cli/cli/commands/fido.py +729 -0
  34. cryptnox_id_cli/cli/commands/genuine.py +192 -0
  35. cryptnox_id_cli/cli/commands/info.py +94 -0
  36. cryptnox_id_cli/cli/commands/mifare.py +998 -0
  37. cryptnox_id_cli/cli/commands/piv.py +2558 -0
  38. cryptnox_id_cli/cli/commands/readers.py +64 -0
  39. cryptnox_id_cli/cli/commands/report.py +217 -0
  40. cryptnox_id_cli/cli/commands/shell.py +130 -0
  41. cryptnox_id_cli/cli/context.py +71 -0
  42. cryptnox_id_cli/cli/dryrun.py +181 -0
  43. cryptnox_id_cli/cli/main.py +117 -0
  44. cryptnox_id_cli/crypto/__init__.py +1 -0
  45. cryptnox_id_cli/crypto/attestation.py +169 -0
  46. cryptnox_id_cli/crypto/csr.py +135 -0
  47. cryptnox_id_cli/crypto/piv_objects.py +79 -0
  48. cryptnox_id_cli/crypto/x509util.py +37 -0
  49. cryptnox_id_cli/output/__init__.py +5 -0
  50. cryptnox_id_cli/output/render.py +106 -0
  51. cryptnox_id_cli/secrets/__init__.py +5 -0
  52. cryptnox_id_cli/secrets/redaction.py +141 -0
  53. cryptnox_id_cli/secrets/resolver.py +93 -0
  54. cryptnox_id_cli/state/__init__.py +19 -0
  55. cryptnox_id_cli/state/detector.py +273 -0
  56. cryptnox_id_cli/state/model.py +147 -0
  57. cryptnox_id_cli/transport/__init__.py +28 -0
  58. cryptnox_id_cli/transport/apdu.py +72 -0
  59. cryptnox_id_cli/transport/elevation.py +165 -0
  60. cryptnox_id_cli/transport/errors.py +185 -0
  61. cryptnox_id_cli/transport/pcsc.py +351 -0
  62. cryptnox_id_cli/transport/scp02.py +247 -0
  63. cryptnox_id_cli/transport/scp03.py +213 -0
  64. cryptnox_id_cli/trust/__init__.py +90 -0
  65. cryptnox_id_cli/trust/genuine/cryptnox-attestation-ca.pem +18 -0
  66. cryptnox_id_cli/trust/genuine/cryptnox-dlt-cards-ca.pem +17 -0
  67. cryptnox_id_cli/trust/genuine/cryptnox-genuineness-ca.pem +18 -0
  68. cryptnox_id_cli/trust/genuine/cryptnox-intermediate-ca-2.pem +19 -0
  69. cryptnox_id_cli/trust/genuine/cryptnox-intermediate-ca.pem +19 -0
  70. cryptnox_id_cli/trust/genuine/cryptnox-root-ca.pem +19 -0
  71. cryptnox_id_cli/util/__init__.py +1 -0
  72. cryptnox_id_cli/util/hexutil.py +36 -0
  73. cryptnox_id_cli/util/tlv.py +144 -0
  74. cryptnox_id_cli-1.0.2.dist-info/METADATA +227 -0
  75. cryptnox_id_cli-1.0.2.dist-info/RECORD +79 -0
  76. cryptnox_id_cli-1.0.2.dist-info/WHEEL +5 -0
  77. cryptnox_id_cli-1.0.2.dist-info/entry_points.txt +4 -0
  78. cryptnox_id_cli-1.0.2.dist-info/licenses/LICENSE +165 -0
  79. cryptnox_id_cli-1.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,22 @@
1
+ """cryptnox-id — CLI for the Cryptnox multi-applet smartcard.
2
+
3
+ Three independent functions live on one physical card and are managed by
4
+ separate, isolated modules (see ``applets/``):
5
+
6
+ * PIV — OpenFIPS201 2.0.0 FIPS JavaCard applet (contact).
7
+ * FIDO2 — CTAP 2.1 JavaCard applet (needs Administrator elevation on Windows).
8
+ * DESFire — MIFARE DESFire EV2/EV3 contactless function (needs a contactless reader).
9
+
10
+ Covered: PC/SC transport and diagnostics (``readers``/``info``/``doctor``), full
11
+ PIV personalization and factory pre-personalization over SCP02/SCP03, FIDO2
12
+ credential and policy management, DESFire application/file/key operations
13
+ including EV3 Secure Dynamic Messaging, and read-only genuineness/attestation
14
+ verification.
15
+ """
16
+
17
+ __version__ = "1.0.2"
18
+
19
+ #: Primary console command name. Single source of truth for every user-facing
20
+ #: render of the CLI's name (usage/version, prompt, banner, messages). The
21
+ #: ``cnx-id`` and ``cryptnox-id-card`` aliases are declared in pyproject.
22
+ CLI_NAME = "cryptnox-id"
@@ -0,0 +1,6 @@
1
+ """Allow ``python -m cryptnox_id_cli`` to invoke the CLI."""
2
+
3
+ from cryptnox_id_cli.cli.main import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,2 @@
1
+ """Per-applet modules. The three applets are independent and isolated:
2
+ ``piv`` (this phase), ``fido`` and ``mifare`` (later phases)."""
@@ -0,0 +1,7 @@
1
+ """FIDO2 / CTAP 2.1 applet support (Cryptnox NXP applet).
2
+
3
+ Phase 7A scope is read-only: SELECT, authenticatorGetInfo, ping, clientPIN status,
4
+ and CTAP error decoding. On Windows the OS blocks PC/SC access to the FIDO CTAP
5
+ AID for non-elevated processes, so these commands require an Administrator
6
+ terminal (the CLI detects the block and says so).
7
+ """
@@ -0,0 +1,81 @@
1
+ """Parse CTAP2 authenticatorData (the signed structure inside make/getAssertion).
2
+
3
+ Layout: ``rpIdHash(32) || flags(1) || signCount(4 BE) || [attestedCredentialData] ||
4
+ [extensions]``. Attested credential data (present when the AT flag 0x40 is set) is
5
+ ``aaguid(16) || credentialIdLength(2 BE) || credentialId || credentialPublicKey(COSE)``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import io
11
+ from dataclasses import dataclass
12
+
13
+ import cbor2
14
+
15
+ FLAG_UP = 0x01 # user present
16
+ FLAG_UV = 0x04 # user verified
17
+ FLAG_AT = 0x40 # attested credential data included
18
+ FLAG_ED = 0x80 # extension data included
19
+
20
+
21
+ @dataclass
22
+ class AuthenticatorData:
23
+ rp_id_hash: bytes
24
+ flags: int
25
+ sign_count: int
26
+ aaguid: bytes | None = None
27
+ credential_id: bytes | None = None
28
+ credential_public_key: dict | None = None
29
+
30
+ @property
31
+ def user_present(self) -> bool:
32
+ return bool(self.flags & FLAG_UP)
33
+
34
+ @property
35
+ def user_verified(self) -> bool:
36
+ return bool(self.flags & FLAG_UV)
37
+
38
+
39
+ def parse_authenticator_data(data: bytes) -> AuthenticatorData:
40
+ if len(data) < 37:
41
+ raise ValueError(f"authenticatorData too short ({len(data)} bytes)")
42
+ ad = AuthenticatorData(
43
+ rp_id_hash=data[:32],
44
+ flags=data[32],
45
+ sign_count=int.from_bytes(data[33:37], "big"),
46
+ )
47
+ rest = data[37:]
48
+ if ad.flags & FLAG_AT:
49
+ if len(rest) < 18:
50
+ raise ValueError("attested credential data truncated")
51
+ ad.aaguid = rest[:16]
52
+ cid_len = int.from_bytes(rest[16:18], "big")
53
+ if len(rest) < 18 + cid_len:
54
+ raise ValueError("credentialId truncated")
55
+ ad.credential_id = rest[18 : 18 + cid_len]
56
+ # The COSE public key is one CBOR item; extensions (if any) follow it.
57
+ decoder = cbor2.CBORDecoder(io.BytesIO(rest[18 + cid_len :]))
58
+ key = decoder.decode()
59
+ if not isinstance(key, dict):
60
+ raise ValueError("credentialPublicKey is not a COSE map")
61
+ ad.credential_public_key = key
62
+ return ad
63
+
64
+
65
+ def verify_es256_assertion(
66
+ cose_public_key: dict, auth_data: bytes, client_data_hash: bytes, signature: bytes
67
+ ) -> bool:
68
+ """Verify a getAssertion ES256 signature over ``authData || clientDataHash``."""
69
+ from cryptography.exceptions import InvalidSignature
70
+ from cryptography.hazmat.primitives import hashes
71
+ from cryptography.hazmat.primitives.asymmetric import ec
72
+
73
+ from cryptnox_id_cli.applets.fido.pinproto import cose_to_public_key
74
+
75
+ try:
76
+ cose_to_public_key(cose_public_key).verify(
77
+ signature, auth_data + client_data_hash, ec.ECDSA(hashes.SHA256())
78
+ )
79
+ return True
80
+ except InvalidSignature:
81
+ return False
@@ -0,0 +1,155 @@
1
+ """FIDO2 / CTAP2 constants (verified against the applet source + Android client)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ FIDO_AID = bytes.fromhex("A0000006472F0001")
6
+
7
+ # APDU instructions (CLA 0x80).
8
+ INS_CTAP_MSG = 0x10
9
+ INS_CTAP_GET_RESPONSE = 0x11
10
+
11
+ # CTAP2 command bytes.
12
+ CTAP_MAKE_CREDENTIAL = 0x01
13
+ CTAP_GET_ASSERTION = 0x02
14
+ CTAP_GET_INFO = 0x04
15
+ CTAP_CLIENT_PIN = 0x06
16
+ CTAP_RESET = 0x07
17
+ CTAP_GET_NEXT_ASSERTION = 0x08
18
+ CTAP_CREDENTIAL_MANAGEMENT = 0x0A
19
+ CTAP_AUTHENTICATOR_CONFIG = 0x0D
20
+
21
+ # authenticatorClientPIN sub-commands.
22
+ PIN_GET_RETRIES = 0x01
23
+ PIN_GET_KEY_AGREEMENT = 0x02
24
+ PIN_SET_PIN = 0x03
25
+ PIN_CHANGE_PIN = 0x04
26
+ PIN_GET_PIN_TOKEN = 0x05
27
+ PIN_GET_UV_RETRIES = 0x07
28
+ PIN_GET_TOKEN_USING_PIN = 0x09 # getPinUvAuthTokenUsingPinWithPermissions
29
+
30
+ # authenticatorClientPIN request map keys.
31
+ CP_PROTOCOL = 0x01
32
+ CP_SUBCOMMAND = 0x02
33
+ CP_KEY_AGREEMENT = 0x03
34
+ CP_PIN_UV_AUTH_PARAM = 0x04
35
+ CP_NEW_PIN_ENC = 0x05
36
+ CP_PIN_HASH_ENC = 0x06
37
+ CP_PERMISSIONS = 0x09
38
+ CP_RP_ID = 0x0A
39
+
40
+ # authenticatorClientPIN response map keys.
41
+ CPR_KEY_AGREEMENT = 0x01
42
+ CPR_PIN_UV_AUTH_TOKEN = 0x02
43
+ CPR_PIN_RETRIES = 0x03
44
+ CPR_POWER_CYCLE_STATE = 0x04
45
+ CPR_UV_RETRIES = 0x05
46
+
47
+ # pinUvAuthToken permissions.
48
+ PERM_MAKE_CREDENTIAL = 0x01
49
+ PERM_GET_ASSERTION = 0x02
50
+ PERM_CREDENTIAL_MGMT = 0x04
51
+ PERM_BIO_ENROLLMENT = 0x08
52
+ PERM_LARGE_BLOB = 0x10
53
+ PERM_AUTHENTICATOR_CONFIG = 0x20
54
+
55
+ # authenticatorCredentialManagement (0x0A) request map keys.
56
+ CM_SUBCOMMAND = 0x01
57
+ CM_SUBCOMMAND_PARAMS = 0x02
58
+ CM_PIN_UV_AUTH_PROTOCOL = 0x03
59
+ CM_PIN_UV_AUTH_PARAM = 0x04
60
+ # sub-commands
61
+ CM_GET_CREDS_METADATA = 0x01
62
+ CM_ENUMERATE_RPS_BEGIN = 0x02
63
+ CM_ENUMERATE_RPS_NEXT = 0x03
64
+ CM_ENUMERATE_CREDS_BEGIN = 0x04
65
+ CM_ENUMERATE_CREDS_NEXT = 0x05
66
+ CM_DELETE_CREDENTIAL = 0x06
67
+ CM_UPDATE_USER_INFO = 0x07
68
+ # sub-command params keys
69
+ CMP_RP_ID_HASH = 0x01
70
+ CMP_CREDENTIAL_ID = 0x02
71
+ CMP_USER = 0x03
72
+ # response keys
73
+ CMR_EXISTING_RESIDENT_COUNT = 0x01
74
+ CMR_MAX_REMAINING = 0x02
75
+ CMR_RP = 0x03
76
+ CMR_RP_ID_HASH = 0x04
77
+ CMR_TOTAL_RPS = 0x05
78
+ CMR_USER = 0x06
79
+ CMR_CREDENTIAL_ID = 0x07
80
+ CMR_PUBLIC_KEY = 0x08
81
+ CMR_TOTAL_CREDENTIALS = 0x09
82
+ CMR_CRED_PROTECT = 0x0A
83
+
84
+ # authenticatorConfig (0x0D) request map keys.
85
+ AC_SUBCOMMAND = 0x01
86
+ AC_SUBCOMMAND_PARAMS = 0x02
87
+ AC_PIN_UV_AUTH_PROTOCOL = 0x03
88
+ AC_PIN_UV_AUTH_PARAM = 0x04
89
+ # sub-commands
90
+ AC_ENABLE_ENTERPRISE_ATTESTATION = 0x01
91
+ AC_TOGGLE_ALWAYS_UV = 0x02
92
+ AC_SET_MIN_PIN_LENGTH = 0x03
93
+ # setMinPINLength sub-command params keys
94
+ ACP_NEW_MIN_PIN_LENGTH = 0x01
95
+ ACP_MIN_PIN_LENGTH_RP_IDS = 0x02
96
+ ACP_FORCE_CHANGE_PIN = 0x03
97
+
98
+ # COSE key labels + values (EC2 / P-256).
99
+ COSE_KTY = 1
100
+ COSE_ALG = 3
101
+ COSE_CRV = -1
102
+ COSE_X = -2
103
+ COSE_Y = -3
104
+ COSE_KTY_EC2 = 2
105
+ COSE_CRV_P256 = 1
106
+ COSE_ALG_ECDH_ES_HKDF256 = -25
107
+ COSE_ALG_ES256 = -7
108
+
109
+ # authenticatorMakeCredential request map keys.
110
+ MC_CLIENT_DATA_HASH = 0x01
111
+ MC_RP = 0x02
112
+ MC_USER = 0x03
113
+ MC_PUB_KEY_CRED_PARAMS = 0x04
114
+ MC_EXCLUDE_LIST = 0x05
115
+ MC_OPTIONS = 0x07
116
+ MC_PIN_UV_AUTH_PARAM = 0x08
117
+ MC_PIN_UV_AUTH_PROTOCOL = 0x09
118
+ # response keys
119
+ MCR_FMT = 0x01
120
+ MCR_AUTH_DATA = 0x02
121
+ MCR_ATT_STMT = 0x03
122
+
123
+ # authenticatorGetAssertion request map keys.
124
+ GA_RP_ID = 0x01
125
+ GA_CLIENT_DATA_HASH = 0x02
126
+ GA_ALLOW_LIST = 0x03
127
+ GA_OPTIONS = 0x05
128
+ GA_PIN_UV_AUTH_PARAM = 0x06
129
+ GA_PIN_UV_AUTH_PROTOCOL = 0x07
130
+ # response keys
131
+ GAR_CREDENTIAL = 0x01
132
+ GAR_AUTH_DATA = 0x02
133
+ GAR_SIGNATURE = 0x03
134
+ GAR_USER = 0x04
135
+ GAR_NUMBER_OF_CREDENTIALS = 0x05
136
+
137
+ # authenticatorGetInfo response map keys (CTAP 2.1).
138
+ INFO_VERSIONS = 0x01
139
+ INFO_EXTENSIONS = 0x02
140
+ INFO_AAGUID = 0x03
141
+ INFO_OPTIONS = 0x04
142
+ INFO_MAX_MSG_SIZE = 0x05
143
+ INFO_PIN_UV_AUTH_PROTOCOLS = 0x06
144
+ INFO_MAX_CREDS_IN_LIST = 0x07
145
+ INFO_MAX_CRED_ID_LENGTH = 0x08
146
+ INFO_TRANSPORTS = 0x09
147
+ INFO_ALGORITHMS = 0x0A
148
+ INFO_MIN_PIN_LENGTH = 0x0D
149
+ INFO_FIRMWARE_VERSION = 0x0E
150
+
151
+ # Known Cryptnox AAGUIDs (from the Android client).
152
+ CRYPTNOX_AAGUIDS = {
153
+ "9c835346796b4c278898d6032f515cc5",
154
+ "1d1b4e3376a147fb97a014b10d0933f1",
155
+ }