11 KiB
Notes for client implementation
This document should help client application developers to integrate support for the hardware wallet in their applications.
Low-level communication
The hardware wallet is a JavaCard application and as such is deployed on ISO7816 compatible SmartCards. Communication will happen exchanging APDUs using either the T=0 or preferably the T=1 protocol. Most operating systems use an implementation of this Microsoft API like PCSC lite. Your language of choice might provide bindings for this library or an higher-level API built on top of it.
A few things to keep in mind when communicating with SmartCards
- The card can never initiate communication. The card only responds to commands sent from the client.
- When connecting to the card using SCardConnect, always use the SCARD_SHARE_EXCLUSIVE mode to avoid OS services messing with the card while you are using it.
- A SmartCard can have multiple applications installed. If using only the basic channel (recommended for our use-case) only a single application can be selected at the time. This must be done explicitly on each reset by issuing the SELECT command with the AID of the wallet application.
- Since we are not using extended APDUs, the maximum size of the data field of the APDU is 255 bytes.
Wallet management and security
Before thinking about the application-specific communication (i.e: actually using the wallet applet to derive keys and sign transactions) the client must be able to actually talk with the card using its Secure Channel protocol.
The first step, after an APDU channel is available is to SELECT the wallet application on the card. The wallet will return its Instance UID and public key for Secure Channel establishment. Although both values are unique, only use the Instance UID to identify the wallet since only this value is guaranteed not to change over the lifetime of the card. If your application has already performed pairing with the wallet with this Instance UID, you can establish a Secure Channel session (described later). Otherwise you should proceed with pairing.
For pairing, the client must show that it knows the pairing code. For this reason the user must be prompted to insert said code. The result of pairing is a secret value shared by both parties which is used during the session key generation on establishment of a Secure Channel. The client must permanently store the association of the Instance UID, the secret generated during pairing and the index which the card assigned to this client. Said index must be provided when opening a Secure Channel so that the card knows with which client it is speaking. Since the pairing secret is sensitive data, it should be stored as securely as possible, eventually with password protection. Losing this secret allows an attacker to pose either as the client to the card or as the card to the client.
Note that the card can only pair with a limited number of clients (currently 5). Unpairing allows to replace old clients with new ones.
When a card and a client are paired, they can establish a Secure Channel session. The Secure Channel provides the authentication, confidentiality and integrity guarantees which the plain APDU channel does not provide. This phase is divided in 2 steps:
- Sending an OPEN SECURE CHANNEL APDU to generate session keys
- Sending a MUTUALLY AUTHENTICATE command to verify that both parties have the same keys.
After this happens, all further communication will be encrypted and with a MAC providing integrity and authentication for each APDU.
The card and client must abort the Secure Channel session at any time if MAC verification fails, since this means that the APDU has been corrupted, possibly as a result of an attack attempt. The card also resets the session when the application is (re-)selected or the card is reset (or on power loss).
Note that while pairing requires user input, opening a secure channel session does not.
User authentication
Aside from the Secure Channel-related APDUs, the application also provides commands to authenticate the user (as opposed as authenticating the client) and to manage the user's authentication credentials. These are the VERIFY PIN, CHANGE PIN and UNBLOCK PIN commands. The client is not supposed to store the PIN and verification should always require user input. After verifying the PIN the card considers to the user to be authenticated for the entire application session. The application session ends on card reset, power loss or application re-selection. The client should keep track of whether the user has been authenticated or not in order to avoid repeatedly asking for the PIN. The card will respond with an error to any APDU requiring user authentication if the user has not been authenticated in the current application session.
Wallet features and workflow
Now that the client can finally talk with the applet and provide user authentication facilities, it is time to look on how to actually use the wallet. The wallet applet allows management of a single HD wallet as described in the BIP32 specifications. It provides the following features
- Source of entropy to generate master seeds (GENERATE MNEMONIC). No PIN required (has no effect on card state).
- Loading of master key (LOAD KEY). PIN required. The same command can be used to replace the loaded master key.
- Set the key to be used for signing by deriving from the master key (DERIVE KEY). PIN required unless a PIN-less path is set.
- Sign hashes (SIGN). PIN required unless the current key path is set as PIN-less. Note that the Keccak-256 hash of the transaction is signed and must be generate off-card. The actual transaction data are not sent to the card.
- Setting/Unsetting a PIN-less path (SET PINLESS PATH). PIN required. It allows disabling PIN authentication for a specific key path.
- Exporting keys (EXPORT KEY). PIN required. Currently this is only allowed if the current key path is that of the Whisper key (m/1/1)
Additionally the GET STATUS command (no PIN required) allows retrieving information about the card status and the current key path.
The card is shipped with no keys on-board. In this state the SIGN and DERIVE KEY commands will not work. The client can detect when a card has no keys by issuing a GET STATUS command. In this case it could prompt the user to either enter a mnemonic passphrase or allow the card to generate a new one (GENERATE MNEMONIC is issued). In both cases, the mnemonic must be converted to a master key from the client and loaded to the card using the LOAD KEY command.
The card stores the master key permanently, until replaced. Additionally the card has the concept of current key and current key path. The current key is the one used during the SIGN command. The current key equals the master key when new keys are loaded and the current key path is empty. Using the DERIVE KEY command however, the client can move through the tree of the HD wallet. This command derives the key as described in BIP32 and sets both the current key and current key path according to its arguments and the results of key derivation. Using the GET STATUS command the client can always know the current key path and thus decide if further derivation is needed or not before issuing a SIGN command. The current key persists across application sessions (including in case of power loss). A flag in the DERIVE KEY command decides whether derivations restarts from the master key or is performed on the current key, extending the current path. The current maximum key path depth is 10 levels under the master key.
The SIGN command signs the Keccak-256 hash provided in the APDU's data field using the current key. The client must calculate the hash of the transaction off-card. The signature (r,s) and the public key used to verify it are both returned. The client must use this to calculate 'v' and format the signature as required by Ethereum (v,r,s) if it wishes to submit the transaction to the network.
Another feature is the ability to define a key path which requires no PIN authentication for signing. When a PIN-less path is set the DERIVE KEY command never requires a PIN (because there must be a PIN-less way to reach that path). Note that setting the PIN-less key path does not automatically set the current key path to the PIN-less one. You still need to use DERIVE KEY to get there. Only when the current key path matches the PIN-less one the SIGN command will not require PIN authentication. The master key can never be used without PIN.
Finally, some keys can be exported (after PIN authentication). At the moment only the Whisper key (m/1/1) can be exported. The EXPORT KEY command does not automatically do key derivation. This means that you must use DERIVE KEY first so that the current key path matches m/1/1 and only then will the EXPORT KEY command work.
Additional notes
- The SIGN command also allows signing data directly instead of a precomputed hash (if P1 is 0x00 instead of 0x01), transmitted over several blocks if needed. This feature is never used because this signature uses SHA-256 instead of Keccak-256 (which is not available) making it useless. This mode of operation has been implemented before the current one with precomputed hashes. It remains because if we get hold of a hardware platform implementing Keccak-256, or we decide to implement it in software, we might want to operate this way.
- The DERIVE KEY command on the current hardware platform can only be used in "assisted key derivation" mode, because it does not have support of the KeyAgreement.ALG_EC_SVDP_DH_PLAIN_XY algorithm. Assisted key derivation is indeed a workaround which should be removed if we find a suitable hardware platform (currently there is no JavaCard 3.0.5 implementation that can be easily found on the low-volumes market)
- Communication has less overhead when using the T=1 protocol. However the card/reader/os combination I have seems to only work with T=0 (although all claim to support T=1). Contactless readers always emulate T=1 when operated through the PC/SC interface.
- You can refer to the tests for examples of communication with the card. The manual-only signTransactionTest test in particular generates a real transaction, signs it and submits it to the network.
- If you test your client against our fork of jCardSim instead of a real card, keep in mind that it supports unassisted key derivation, but you shouldn't use it because, as explained above, it wouldn't work on the card.
- If using jCardSim, only use our fork, since some of the needed algorithms are unsupported in the upstream version.
- The pairing code is a randomly generated password (using whatever password generation algorithm is desired). This password must be converted to a 256-bit key using PBKDF2 with the salt "Status Hardware Wallet Lite" and 50000 iterations.