Cryptnox Python Communication Library¶
Warning: This is a beta release of the software. It is released for development purposes. Use at your own risk.
A Python3 library to use the Cryptnox smartcard applet. It provides high level functions to send instructions with the Cryptnox and to manage its lifecycle. The core module is CryptnoxPy which provides a Connection class to establish a channel of communication that can be used to initialize a card instance through the factory method.
To buy NFC enabled cards that are supported by this library go to: https://www.cryptnox.com/
License¶
The library is available under dual licensing. You can use the library under the conditions of GNU LESSER GENERAL PUBLIC LICENSE 3.0+ or contact us to ask about commercial licensing.
Documentation¶
API documentation can be found in HTML format in the docs folder It is generated using Sphynx from the code and can be generated in other formats too.
Installation and requirements¶
Requires :
Python 3.6-3.9
PCSCd on Linux
Ubuntu / Debian
(sudo) apt-get install python3-pip python3-setuptools pcscd
Fedora / CentOS / RHEL
yum install python3-pip python3-setuptools pcsc-lite-ccid
On some Linux, starts PCSCd service
(sudo) systemctl start pcscd
(sudo) systemctl enable pcscd
Installation of this library¶
Make sure that you installed the required packages for your system. See the “Requires” section above.
Install with pip:
pip install cryptnoxpy
Install from source:
Download and run in its directory:
pip install .
or:
pip install git+ssh://git@github.com/Cryptnox-Software/cryptnoxpy.git
This might require sudo on some systems.
Remove:
pip uninstall cryptnoxpy
Installation issues¶
If the Linux system doesn't have Python 3.6, 3.7, 3.8 nor 3.9, install Python 3.7 with the following recipe (Debian like):
sudo apt-get install -y make build-essential libssl-dev zlib1g-dev swig libpcsclite-dev
sudo apt-get install -y libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm git
sudo apt-get install -y libncurses5-dev libncursesw5-dev xz-utils tk-dev pcscd opensc
wget https://www.python.org/ftp/python/3.7.8/Python-3.7.8.tgz
tar xf Python-3.7.8.tgz
cd Python-3.7.8
./configure --enable-optimizations
make -j8 build_all
sudo make -j8 altinstall
sudo pip3.7 install git+ssh://git@gitlab.com/cryptnox-phase2/cryptnoxpy.git
# or (if issue about agent forwarding with sudo) :
cd ~
git clone git@gitlab.com:cryptnox-phase2/cryptnoxpy.git
cd cryptnoxpy
sudo pip3.7 install .
In case of pyscard can't be installed automatically with pip:
Try to pip3 install with sudo or root:
sudo pip install .
If still a failure, install the following packages: Needed if pyscard can't be installed from package manager
sudo apt install python3-dev swig libpcsclite-dev
then retrysudo pip install .
.
If you use contactless readers on Linux, the RFID modules need to be disabled :
sudo rmmod pn533_usb
sudo rmmod pn533
sudo rm -r /lib/modules/*/kernel/drivers/nfc/pn533
Update issues¶
In case you just want to update the package, with old pip version on some Linux, it is better to remove and reinstall the package:
sudo pip uninstall cryptnoxpy
sudo pip install .
Library use¶
To get the card a connection has to be established with the reader’s index. The connection can then be passed to the factory that will initialize an object for the card in the reader from the correct class for the card type and version.
import cryptnoxpy
try:
connection = cryptnoxpy.Connection(0)
except cryptnoxpy.ReaderException:
print("Reader not found on index")
else:
try:
card = cryptnoxpy.factory.get_card(connection)
except cryptnoxpy.CryptnoxException as error:
# There is an issue with loading the card
# CryptnoxException is the base exception class for module
print(error)
else:
# Card is loaded and can be used
print(f"Card serial number: {card.serial_number}")
The factory will:
connect to the card
select the applet
read the applet parameters
select class to handle the card
The card contains basic information:
card.serial_number : Integer : Card/applet instance Unique ID
card.applet_version : 3 integers list : Applet version (ex. 1.2.2)
Initialization and pairing¶
Right after the installation, the applet is not initialized, and the user needs to send some parameters to use the card. The initialization can be executed once. Any change of the base parameters requires a full applet reinstallation (except PIN/PUK change).
After the initialization, the card and the PC must share a common secret to be used as authenticated secure channel. This secret is required any time further, to communicate with the card (using a secure channel). The registration of this common secret is done during the init phase.
The init parameters required are :
Name (up to 20 chars string)
Email (up to 60 chars string)
PIN (9 digits string)
PUK (15 digits string)
optional : the first Paring Secret (32 bytes bytearray)
pairing_key = card.init(name, email, pin, puk, pairing_secret)
The returned data is the first PairingKey (32 bytes byte-array) and its index (0) :
0x00 + ParingKeySlot0
During the initialization phase, until the user public key for authentication registration is allowed, the set_pairing_key command is also allowed. Then set_pairing_key needs the applet to have the signature unlocked.
After getting the pairing_key, the user needs to store it in a safe place. In the case the client would communicate with several cards, the user needs to associate the pairing_key with the instance serial number of the card, so that the user client can keep track of multiple cards, and use the right one with the right card. The pairing_key must be saved in a file to reconnect the next time to this card. It should be saved with the serial number of card in order to associate this card with this key.
A common hardcoded PairingKey can be used.
After this init phase, the secure channel must be used with all communications with the card. A secure channel is an encrypted and 2-ways authenticated link layer with the card using standards APDU messages. Many applet commands require a secure channel.
PIN¶
The PIN chosen during the initialization needs to be provided after each card reset, and a secure channel is opened.
To test a PIN string, simply use:
card.verify_pin(pin)
Seed administration¶
The applet manages a 256 bits master secret called the “seed”. This is the BIP32 Master Seed, and can be externally computed from a mnemonic to a binary seed using BIP39. The key pairs used for ECDSA are then computationally derived from this seed using BIP32 derivation scheme.
Seed generation¶
The seed can be generated in the card using the random number generator in the java chip system (AIS 20 class DRG.3). Doing this way, the seed secret never escapes the card protection.
The method to generate a new seed key is:
card.generate_seed(pin)
The card can also randomly generate BIP39 mnemonics words list. But in this case, the query answer is only output and not used internally by the card. It is administrator responsibility to get a mnemonic using the GENERATE MNEMONIC command and then eventually compute the corresponding seed, which can be uploaded in the card using RECOVER KEY command. We don’t recommend doing so, this is very insecure, as the seed is exposed in clear and full in the user’s system.
Recovery¶
The Cryptnox applet can load binary seed.
The seed is loaded in the card using this method:
card.load_seed(seed, pin)
Seed is 32 bytes.
Once this seed is loaded in the card using the load_seed method, this card now behaves like were (or the one) it was backup. Be aware that key derivation paths are not backup, and must be identical to retrieve the same key pairs. See derivation and key system just below for more details.
For more details about the recovery, see load_seed operation in the API documentation.
Derivation and keys system¶
The card applet is fully compliant with BIP32, except the maximum depth of derivation from the master key is 8 levels. It can be turned on for the card to return extended public keys for use in applications requiring it.
The card stores the present key pair (and its parent), used for signature. This can be changed using the derive method, and also during a signature command, giving a relative path (from the present key pair), or in an absolute path (from the master key pair). See derive method in the API documentation.
Any derivation aborts any opened signing sessions and resets the authentications for signature. The generated key is used for all subsequent sign sessions.
The ability to start derivation from the parent keys allows to more efficiently switch between children of the same key. Note however that only the immediate parent of the current key is cached so one cannot use this to go back in the keys hierarchy.
For ease of use, the user can derive from the root master node key pair (absolute path) at each card startup, or even before each signature. This takes a couple of seconds. So this is better to store intermediate public keys hash and check the status to observe the current key pair in use. This off-card complex key management is not needed if the signatures volume is below one thousand per day.
See derive and sign methods in the API documentation.
EC Signature¶
The derivation of the key pair node can be also possible using the signature command (relative or absolute).
The card applet can sign any 256 bits hash provided, using ECDSA with 256k1 EC parameters. Most of the blockchain system used SHA2-256 to hash the message, but this card applet is agnostic from this point, since the signature is performed on a hash provided by the user. Note that this hash needs to be confirmed by the users beforehand, when they provide their EC384 signature of this hash.
The code to sign with the EC current key node is:
signature = card.sign(data_hash, cryptnoxpy.Derivation.CURRENT_KEY)
data_hash is a byte-array containing the EC hash to sign using ECDSA ecp256k1:
The signature a byte array, encoded as an ASN1 DER sequence of two INTEGER values, r and s.
See the sign method in the API documentation for more information.
Card¶
- class cryptnoxpy.card.base.Base(connection: cryptnoxpy.connection.Connection, data: Optional[List[int]] = None, debug: bool = False)¶
Object that contains information about the card that is in the reader.
- Parameters
connection (Connection) – Connection to use for card initialization
debug (bool) – Show debug information to the user.
- Variables
applet_version (List[int]) – Version of the applet on the card.
serial_number (int) – Serial number of card.
session_public_key (str) – Public key of the session.
initialized (bool) – The card has been initialized with secrets.
- Raises
CardTypeException – The card in the reader is not a Cryptnox card
- property alive: bool¶
- Returns
The connection to the card is established and the card hasn’t been changed
- Return type
bool
- abstract change_pairing_key(index: int, pairing_key: bytes, puk: str = '') None ¶
Set the pairing key of the card
- Parameters
index (int) – Index of the pairing key
pairing_key (bytes) – Pairing key to set for the card
puk (str) – PUK code of the card
- Raises
DataValidationException – input data is not valid
SecureChannelException – operation not allowed
PukException – PUK code is not valid
- abstract change_pin(new_pin: str) None ¶
Change the current pin code of the card to a new pin code.
The method will set the given pin code as the pin code of the card. For it to work the card first must be opened with the current pin code.
- Requires
PIN code or challenge-response validated
- Parameters
new_pin (str) – The desired PIN code to be set for the card (4-9 digits).
- abstract change_puk(current_puk: str, new_puk: str) None ¶
Change the current pin code of the card to a new pin code.
The method will set the given pin code as the pin code of the card. For it to work the card first must be opened with the current pin code.
- Parameters
current_puk (str) – The current PUK code of the card
new_puk (str) – The desired PUK code to be set for the card
- check_init() None ¶
Check if the initialization has been done on the card.
It can be useful to check if the card is initialized before doing anything else, like asking for pin code from the user.
- Raises
InitializationException – The card is not initialized
- abstract derive(key_type: cryptnoxpy.enums.KeyType = KeyType.K1, path: str = '')¶
Derive key on path and make it the current key in the card
- Requires
PIN code or challenge-response validated
Seed must exist
- Parameters
key_type (KeyType) – Key type to do derive on
path (str) – Path on which to do derivation
- abstract dual_seed_load(data: bytes, pin: str = '') None ¶
Load public key and signature from the other card into the card to generate same seed.
- Requires
PIN code or challenge-response validated
- Parameters
pin (str) – PIN code of card if it was opened with a PIN check
data (bytes) – Public key and signature of public key from the other card
- abstract dual_seed_public_key(pin: str = '') bytes ¶
Get the public key from the card for dual initialization of the cards
- Requires
PIN code or challenge-response validated
- Parameters
pin (str) – PIN code of card if it was opened with a PIN check
- Returns
Public key and signature that can be sent into the other card
- Return type
bytes
- Raises
DataException – The received data is invalid
- abstract property extended_public_key: bool¶
- Returns
Extended public key turned on
- Return type
bool
- abstract generate_random_number(size: int) bytes ¶
Generate random number on the car and return it.
- Parameters
size (int) – Output data size in bytes (between 16 and 64, mod 4)
- Returns
Random number generated by the chip
- Return type
bytes
- Raises
DataValidationException – size in not a number between 16 and 64 or is not divisible by 4
- abstract generate_seed(pin: str = '') bytes ¶
Generate a seed directly on the card.
- Requires
PIN code or challenge-response validated
- Parameters
pin (str, optional) – PIN code of the card. Can be empty if card is opened with challenge-response validation
- Returns
Primary node “m” UID (hash of public key)
- Return type
bytes
- Raises
KeyGenerationException – There was an issue with generating the key
KeyAlreadyGenerated – The card already has a seed generated
- abstract get_public_key(derivation: cryptnoxpy.enums.Derivation, key_type: cryptnoxpy.enums.KeyType = KeyType.K1, path: str = '', compressed: bool = True) str ¶
Get the public key from the card.
- Requires
PIN code or challenge-response validated, except for PIN-less path
Seed must exist
- Parameters
derivation (Derivation) – Derivation to use.
key_type (KeyType) – Key type to use
path (str) –
compressed (bool) – The returned value is in compressed format.
- Returns
The public key for the given path in hexadecimal string format
- Return type
str
- Raises
DerivationSelectionException – Card is not initialized with seed
ReadPublicKeyException – Invalid data received from card
- abstract history(index: int = 0) NamedTuple ¶
Get history of hashes the card has signed regardless of any parameters given to sign
- Requires
PIN code or challenge-response validated
- Parameters
index (int) – Index of entry in history
- Returns
Return entry containing signing_counter, representing index of sign call, and hashed_data, the data that was signed
- Return type
NamedTuple
- property info: Dict[str, Any]¶
Get relevant information about the card.
- Returns
Dictionary containing information for the card
- Return type
Dict[str, Any]
- abstract init(name: str, email: str, pin: str, puk: str, pairing_secret: bytes) bytes ¶
Initialize the Cryptnox card.
Initialize the Cryptnox card with the owners name and email address. Set the PIN and PUK codes for authenticating with the card to be able to use it.
- Parameters
name (str) – Name of the card owner
email (str) – Email of the card owner
pin (str) – PIN code that will be used to open the card
puk (str) – PUK code that will be used to open the card
pairing_secret (bytes) – Pairing secret to use with the card
- Returns
Pairing secret
- Return type
bytes
- Raises
InitializationException – There was an issue with initialization
- abstract property initialized: bool¶
- Returns
Whether the card is initialized
- Return type
bool
- abstract load_seed(seed: bytes, pin: str = '') None ¶
Load the given seed into the Cryptnox card.
- Requires
PIN code or challenge-response validated
- Parameters
seed (bytes) – Seed to initialize the card with
pin (str, optional) – PIN code of the card. Can be empty if card is opened with challenge-response validation
- Raises
KeyGenerationException – Data is not correct
- property open: bool¶
- Returns
Whether the user has authenticated using the PIN code or challenge-response validation
- Return type
bool
- abstract property pin_authentication: bool¶
- Returns
Whether the PIN code can be used for authentication
- Return type
bool
- abstract property pin_rule: str¶
Human readable PIN code rule
- Returns
Human readable PIN code rule
- Return type
str
- abstract property pinless_enabled: bool¶
- Returns
Return whether the card has a pinless path
- Return type
bool
- abstract property puk_rule: str¶
Human readable PUK code rule
- Returns
Human readable PUK code rule
- Return type
str
- abstract reset(puk: str) None ¶
Reset the card and return it to factory settings.
- Parameters
puk – PUK code associated with the card
- abstract property seed_source: cryptnoxpy.enums.SeedSource¶
- Returns
How the seed was generated
- Return type
- abstract property select_apdu: List[int]¶
- Returns
Value to add to select command to select the applet on the card
- Return type
List[int]
- abstract set_extended_public_key(status: bool, puk: str) None ¶
Turn on/off extended public key output.
- Requires
Seed must be loaded
- Parameters
status (bool) – Status of PIN authentication
puk (str) – PUK code associated with the card
- Raises
DataValidationException – input data is not valid
PukException – PUK code is not valid
KeyException – Seed not found
- abstract set_pin_authentication(status: bool, puk: str) None ¶
Turn on/off authentication with the PIN code. Other methods can still be used.
- Parameters
status (bool) – Status of PIN authentication
puk (str) – PUK code associated with the card
- Raises
DataValidationException – input data is not valid
PukException – PUK code is not valid
- abstract set_pinless_path(path: bytes, puk: str) None ¶
Enable working with the card without a PIN on path.
- Parameters
path (bytes) – Path to be available without a PIN code
puk (str) – PUK code of the card
- Raises
DataValidationException – input data is not valid
PukException – PUK code is not valid
- abstract sign(data: bytes, derivation: cryptnoxpy.enums.Derivation, key_type: cryptnoxpy.enums.KeyType = KeyType.K1, path: str = '', pin: str = '', filter_eos: bool = False) bytes ¶
Sign the message using given derivation.
- Requires
PIN code provided, authenticate with user key by signing same message or PIN-less path used
Seed must be loaded
- Parameters
data (bytes) – Data to sign
derivation (Derivation) – Derivation to use.
key_type (KeyType, optional) – Key type to use. Defaults to K1
path (str, optional) – Path of the key. If empty use main key
pin (str, optional) – PIN code of the card
filter_eos (str, optional) – Filter signature so it is valid for EOS network, might take longer. Defaults to False
- Returns
The signature generated by the card in DER common format.
- Return type
bytes
- Raises
DataException – Invalid data received during signature
- abstract property signing_counter: int¶
- Returns
Counter of how many times the card has been used to sign
- Return type
int
- abstract property type: int¶
- Returns
Card type
- Return type
int
- unblock_pin(puk: str, new_pin: str) None ¶
Verifies the user using the PUK code and sets a new PIN code on the card.
Method should be used when the user has forgotten this/hers PIN code. By entering the PUK code the user verifies his/hers identity and can set the new PIN code on the card. Can be used only if the card is locked.
- Requires
User PIN must be locked
PIN code authentication must be enabled
- Parameters
puk (str) – PUK code for verification of the user, before changing the PIN code.
new_pin (str) – The desired PIN code to be set for the card (4-9 digits).
- Raises
PukException – PUK code not valid
CardNotBlocked – Card is not blocked, operation can’t be done
- abstract property user_data: bytes¶
- Returns
Read user data that was written into the card.
- Return type
bytes
- abstract user_key_add(slot: cryptnoxpy.enums.SlotIndex, data_info: str, public_key: bytes, puk_code: str, cred_id: bytes = b'') None ¶
Add user public key into the card for user authentication
- Parameters
slot (int) – Slot to write the public key to 1 - EC256R1 2 - RSA key, 2048 bits, public exponent must be 65537 3 - FIDO key
data_info (bytes) – 64 bytes of user data
public_key (bytes) – Public key of the secure element to be used for authentication
puk_code (str) – PUK code of the card
cred_id (bytes, optional) – Cred id. Used for FIDO2 authentication
- Raises
DataValidationException – Invalid input data
- abstract user_key_challenge_response_nonce() bytes ¶
Get 32 bytes random value from the card that is used to open the card with a user key
Take nonce value from the card. Sign it with a third party application, like TPM. Send the signature back into the card using
user_key_challenge_response_open()
- Returns
32 bytes random value used as nonce
- Return type
bytes
- abstract user_key_challenge_response_open(slot: cryptnoxpy.enums.SlotIndex, signature: bytes) bool ¶
Send the nonce signature to the card to open it for operations, like it was opened by a PIN code
- Parameters
slot (SlotIndex) – Slot to use to open the card
signature (bytes) – Signature generated by a third party like TPM.
- Returns
Whether the challenge response authentication succeeded
- Return type
bool
- Raises
DataValidationException – invalid input data
- abstract user_key_delete(slot: cryptnoxpy.enums.SlotIndex, puk_code: str) None ¶
Delete the user key from slot and free up for insertion
- Parameters
slot (SlotIndex) – Slot to remove the key from
puk_code (str) – PUK code of the card
- Raises
DataValidationException – Invalid input data
- abstract user_key_enabled(slot_index: cryptnoxpy.enums.SlotIndex) bool ¶
Check if user key is present in given slot
- Parameters
slot_index (SlotIndex) – Slot index to check for
- Returns
Whether the user key for slot is present
- Return type
bool
- abstract user_key_info(slot: cryptnoxpy.enums.SlotIndex) Tuple[str, str] ¶
Get the description and public key of the user key
- Requires
PIN code or challenge-response validated
- Parameters
slot (SlotIndex) – Index of slot for which to fetch the description
- Returns
Description and public key in slot
- Return type
tuple[str, str]
- abstract user_key_signature_open(slot: cryptnoxpy.enums.SlotIndex, message: bytes, signature: bytes) bool ¶
Used for opening the card to sign the given message
- Parameters
slot (SlotIndex) – Slot to use to open the card
message (bytes) – Message that will be sent to sign operation
signature (bytes) – Signature generated by a third party, like TPM, on the same message
- Returns
Whether the challenge response authentication succeeded
- Return type
bool
- Raises
DataValidationException – invalid input data
- abstract property valid_key: bool¶
Check if the card has a valid key
- Returns
Whether the card has a valid key.
- Return type
bool
- abstract static valid_pin(pin: str, pin_name: str = 'pin') str ¶
Check if provided pin is valid
- Parameters
pin (str) – The pin to check if valid
pin_name (str) – Value used in DataValidationException for pin name
- Return str
Provided pin in str format if valid
- Raises
DataValidationException – Provided pin is not valid
- abstract static valid_puk(puk: str, puk_name: str = 'puk') str ¶
Check if provided puk is valid
- Parameters
puk (str) – The puk to check if valid
puk_name (str, optional) – Value used in DataValidationException for puk name. Defaults to: puk
- Return str
Provided puk in str format if valid
- Raises
DataValidationException – Provided puk is not valid
- abstract verify_pin(pin: str) None ¶
Check PIN code and open the card for operations that are protected.
The method is sending the PIN code to the card to open it for other operations. If there is an issue an exception will be raised.
- Parameters
pin (str) – PIN code to check against the card.
- Raises
PinException – Invalid PIN code
DataValidationException – Invalid length or PIN code authentication disabled
SoftLock – The card has been locked and needs power cycling before it can be used again
Connection¶
- class cryptnoxpy.connection.Connection(index: int = 0, debug: bool = False)¶
Connection to the reader.
Sends and receives messages from the card using the reader.
- Parameters
index (int) – Index of the reader to initialize the connection with
debug (bool) – Show debug information during requests
- Variables
self.card (Card) – Information about the card.
- send_apdu(apdu: List[int]) Tuple[List[int], int, int] ¶
Send data to the card in plain format
- Parameters
apdu (int) – list of the APDU header
- Return bytes
Result of the query that was sent to the card
- Return type
bytes
- Raises
ConnectionException – Issue in the connection
- send_encrypted(apdu: List[int], data: bytes, receive_long: bool = False) bytes ¶
Send data to the card in encrypted format
- Parameters
apdu (int) – list of the APDU header
data – bytes of the data payload (in clear, will be encrypted)
receive_long (bool) –
- Return bytes
Result of the query that was sent to the card
- Return type
bytes
- Raises
CryptnoxException – General exceptions