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.
- cryptnox_id_cli/__init__.py +22 -0
- cryptnox_id_cli/__main__.py +6 -0
- cryptnox_id_cli/applets/__init__.py +2 -0
- cryptnox_id_cli/applets/fido/__init__.py +7 -0
- cryptnox_id_cli/applets/fido/authdata.py +81 -0
- cryptnox_id_cli/applets/fido/constants.py +155 -0
- cryptnox_id_cli/applets/fido/ctap.py +430 -0
- cryptnox_id_cli/applets/fido/errors.py +90 -0
- cryptnox_id_cli/applets/fido/pinproto.py +166 -0
- cryptnox_id_cli/applets/genuine/__init__.py +12 -0
- cryptnox_id_cli/applets/genuine/constants.py +32 -0
- cryptnox_id_cli/applets/genuine/genuine.py +85 -0
- cryptnox_id_cli/applets/genuine/verify.py +150 -0
- cryptnox_id_cli/applets/mifare/__init__.py +10 -0
- cryptnox_id_cli/applets/mifare/desfire.py +353 -0
- cryptnox_id_cli/applets/mifare/ev2.py +421 -0
- cryptnox_id_cli/applets/piv/__init__.py +9 -0
- cryptnox_id_cli/applets/piv/admin.py +182 -0
- cryptnox_id_cli/applets/piv/apt.py +58 -0
- cryptnox_id_cli/applets/piv/constants.py +66 -0
- cryptnox_id_cli/applets/piv/keyimport.py +244 -0
- cryptnox_id_cli/applets/piv/objects.py +126 -0
- cryptnox_id_cli/applets/piv/perso.py +138 -0
- cryptnox_id_cli/applets/piv/piv.py +171 -0
- cryptnox_id_cli/applets/piv/preperso.py +174 -0
- cryptnox_id_cli/applets/piv/profiles.py +486 -0
- cryptnox_id_cli/applets/piv/slots.py +44 -0
- cryptnox_id_cli/cli/__init__.py +1 -0
- cryptnox_id_cli/cli/commands/__init__.py +1 -0
- cryptnox_id_cli/cli/commands/apdu.py +123 -0
- cryptnox_id_cli/cli/commands/doctor.py +144 -0
- cryptnox_id_cli/cli/commands/factory.py +312 -0
- cryptnox_id_cli/cli/commands/fido.py +729 -0
- cryptnox_id_cli/cli/commands/genuine.py +192 -0
- cryptnox_id_cli/cli/commands/info.py +94 -0
- cryptnox_id_cli/cli/commands/mifare.py +998 -0
- cryptnox_id_cli/cli/commands/piv.py +2558 -0
- cryptnox_id_cli/cli/commands/readers.py +64 -0
- cryptnox_id_cli/cli/commands/report.py +217 -0
- cryptnox_id_cli/cli/commands/shell.py +130 -0
- cryptnox_id_cli/cli/context.py +71 -0
- cryptnox_id_cli/cli/dryrun.py +181 -0
- cryptnox_id_cli/cli/main.py +117 -0
- cryptnox_id_cli/crypto/__init__.py +1 -0
- cryptnox_id_cli/crypto/attestation.py +169 -0
- cryptnox_id_cli/crypto/csr.py +135 -0
- cryptnox_id_cli/crypto/piv_objects.py +79 -0
- cryptnox_id_cli/crypto/x509util.py +37 -0
- cryptnox_id_cli/output/__init__.py +5 -0
- cryptnox_id_cli/output/render.py +106 -0
- cryptnox_id_cli/secrets/__init__.py +5 -0
- cryptnox_id_cli/secrets/redaction.py +141 -0
- cryptnox_id_cli/secrets/resolver.py +93 -0
- cryptnox_id_cli/state/__init__.py +19 -0
- cryptnox_id_cli/state/detector.py +273 -0
- cryptnox_id_cli/state/model.py +147 -0
- cryptnox_id_cli/transport/__init__.py +28 -0
- cryptnox_id_cli/transport/apdu.py +72 -0
- cryptnox_id_cli/transport/elevation.py +165 -0
- cryptnox_id_cli/transport/errors.py +185 -0
- cryptnox_id_cli/transport/pcsc.py +351 -0
- cryptnox_id_cli/transport/scp02.py +247 -0
- cryptnox_id_cli/transport/scp03.py +213 -0
- cryptnox_id_cli/trust/__init__.py +90 -0
- cryptnox_id_cli/trust/genuine/cryptnox-attestation-ca.pem +18 -0
- cryptnox_id_cli/trust/genuine/cryptnox-dlt-cards-ca.pem +17 -0
- cryptnox_id_cli/trust/genuine/cryptnox-genuineness-ca.pem +18 -0
- cryptnox_id_cli/trust/genuine/cryptnox-intermediate-ca-2.pem +19 -0
- cryptnox_id_cli/trust/genuine/cryptnox-intermediate-ca.pem +19 -0
- cryptnox_id_cli/trust/genuine/cryptnox-root-ca.pem +19 -0
- cryptnox_id_cli/util/__init__.py +1 -0
- cryptnox_id_cli/util/hexutil.py +36 -0
- cryptnox_id_cli/util/tlv.py +144 -0
- cryptnox_id_cli-1.0.2.dist-info/METADATA +227 -0
- cryptnox_id_cli-1.0.2.dist-info/RECORD +79 -0
- cryptnox_id_cli-1.0.2.dist-info/WHEEL +5 -0
- cryptnox_id_cli-1.0.2.dist-info/entry_points.txt +4 -0
- cryptnox_id_cli-1.0.2.dist-info/licenses/LICENSE +165 -0
- 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,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
|
+
}
|