Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ae975fbfc | ||
|
|
4f21bb87c6 | ||
|
|
37f474cdfd | ||
|
|
30a684e164 | ||
|
|
270224fb77 | ||
|
|
29ec47908b | ||
|
|
5328aef1fb | ||
|
|
bc63b4d9d1 | ||
|
|
76ca4ca576 | ||
|
|
264fafe1de | ||
|
|
8ff442a611 | ||
|
|
1f5e69f31c | ||
|
|
830afae870 | ||
|
|
f1703349c0 | ||
|
|
dd75005415 | ||
|
|
d299aa08d5 | ||
|
|
c960f368cb |
@@ -6,7 +6,7 @@
|
||||
],
|
||||
"rules": {
|
||||
"no-constant-binary-expression": "error",
|
||||
"no-restricted-globals": ["error", "process"],
|
||||
"no-restricted-globals": ["off", "process"],
|
||||
"tailwindcss/classnames-order": "off", // handled by official prettier-plugin-tailwindcss
|
||||
"tailwindcss/migration-from-tailwind-2": "off"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
node_modules/
|
||||
|
||||
.next/
|
||||
|
||||
public/
|
||||
|
||||
*.mdx
|
||||
*.md
|
||||
pnpm-lock.yaml
|
||||
@@ -2,6 +2,7 @@
|
||||
"recommendations": [
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
"esbenp.prettier-vscode",
|
||||
"graphql.vscode-graphql"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Next.js: debug client-side",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:3030",
|
||||
"skipFiles": [
|
||||
".next/**",
|
||||
"${workspaceFolder}/node_modules/**",
|
||||
"<node_internals>/**",
|
||||
"**/webpack-internal://**"
|
||||
]
|
||||
// "runtimeArgs": ["--auto-open-devtools-for-tabs"]
|
||||
// "cascadeTerminateToConfigurations": ["Next.js: debug full stack"]
|
||||
},
|
||||
// todo: consider compound config https://code.visualstudio.com/docs/editor/debugging#_compound-launch-configurations
|
||||
// todo: consider client+prelaunch
|
||||
{
|
||||
"name": "Next.js: debug full stack",
|
||||
// note: config https://github.com/microsoft/vscode-js-debug/blob/e620efe1841742402a332b532df2aa2ec95361f6/OPTIONS.md
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "yarn dev -- --port 3030",
|
||||
"serverReadyAction": {
|
||||
"pattern": "\\s- Local:\\s+(https?://\\S+)",
|
||||
"action": "startDebugging",
|
||||
"name": "Next.js: debug client-side",
|
||||
"killOnServerStop": true
|
||||
},
|
||||
// "cascadeTerminateToConfigurations": ["Next.js: debug client-side"],
|
||||
"skipFiles": [
|
||||
".next/**",
|
||||
"${workspaceFolder}/node_modules/**",
|
||||
"<node_internals>/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: apdu_changelog
|
||||
title: CHANGELOG
|
||||
---
|
||||
|
||||
# Changelog
|
||||
|
||||
## Version 3.1
|
||||
* Added IDENT command to Keycard an Cash applet
|
||||
* Added Ident applet
|
||||
* Added ability to export the extended public key
|
||||
* Added duress PIN
|
||||
* The INIT command now optionally accepts the max number of PIN, PUK retries and the duress PIN
|
||||
|
||||
## Version 3.0
|
||||
* **BREAKING** Removed the DUPLICATE KEY command
|
||||
* **BREAKING** Removed SET NDEF command (replaced by STORE DATA)
|
||||
* Added Cash applet for payments/burner wallet scenarios
|
||||
* STORE DATA/GET DATA can now be used also for NDEF and Cash data
|
||||
|
||||
## Version 2.2
|
||||
* Added additional options to the SIGN command to facilitate usage in POS transactions.
|
||||
* Make SET NDEF more tolerant on the input format by automatically appending missing length.
|
||||
|
||||
## Version 2.1
|
||||
* Added concept of capabilities, making some APDU conditional and extending the SELECT response.
|
||||
|
||||
## Version 2.0
|
||||
* **BREAKING** Changed application AID
|
||||
* **BREAKING** Completely redefined the EXPORT KEY command
|
||||
* **BREAKING** Removed assisted key derivation
|
||||
* **BREAKING** Removed plain data signing, now only 32-byte long hashes can be signed
|
||||
* Added internal key generation (GENERATE KEY)
|
||||
* Added the ability to customize the NDEF response (SET NDEF)
|
||||
* Added DUPLICATE KEY command
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
id: apdu_changepin
|
||||
title: CHANGEPIN
|
||||
---
|
||||
|
||||
# CHANGE PIN
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0x21
|
||||
* P1 = PIN identifier
|
||||
* P2 = 0x00
|
||||
* Data = the new PIN
|
||||
* Response SW = 0x9000 on success, 0x6A80 if the PIN format is invalid, 0x6A86 if P1 is invalid
|
||||
* Preconditions: Secure Channel must be opened, user PIN must be verified
|
||||
* Capability: Credentials management
|
||||
|
||||
Used to change a PIN or secret. In case of invalid format, the code 0x6A80 is returned. If the conditions match, the PIN or secret is updated. The no-error SW 0x9000 is returned.
|
||||
|
||||
P1:
|
||||
* 0x00: User PIN. Must be 6-digits. The updated PIN is authenticated for the rest of the session.
|
||||
* 0x01: Applet PUK. Must be 12-digits.
|
||||
* 0x02: Pairing secret. Must be 32-bytes long. Existing pairings are not broken, but new pairings will need to use the new secret. Only implemented if the Secure Channel capability is implemented.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
id: apdu_derivekey
|
||||
title: DERIVE KEY
|
||||
---
|
||||
|
||||
# DERIVE KEY
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0xD1
|
||||
* P1 = derivation options
|
||||
* P2 = 0x00
|
||||
* Data = a sequence of 32-bit integers (most significant byte first). Empty if the master key must be used.
|
||||
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid.
|
||||
* Preconditions: Secure Channel must be opened, user PIN must be verified, an extended keyset must be loaded
|
||||
|
||||
This command sets the derivation path used for subsequent SIGN and EXPORT KEY commands without an explicit path. The maximum depth of derivation from the master key is 10. Any attempt to get deeper results in 0x6A80 being returned.
|
||||
|
||||
Using this command is now discouraged. It is recommended to explicitly give the derivation path in each SIGN and EXPORT KEY command.
|
||||
|
||||
P1:
|
||||
* bit 0-5 = reserved
|
||||
* bit 7-6:
|
||||
- 00 derive from master keys
|
||||
- 01 derive from parent keys
|
||||
- 10 derive from current keys
|
||||
- 11 reserved
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: apdu_exportkey
|
||||
title: EXPORT KEY
|
||||
---
|
||||
|
||||
# EXPORT KEY
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0xC2
|
||||
* P1 = derivation options
|
||||
* P2 = export options
|
||||
* Response SW = 0x9000 on success, 0x6A86 if P1 or P2 are wrong
|
||||
* Data = a sequence of 32-bit integers (empty if P1=0x00)
|
||||
* Response Data = key pair template
|
||||
* Response SW = 0x9000 on success, 0x6985 if the private key cannot be exported, 0x6A80 if the path is malformed
|
||||
* Preconditions: Secure Channel must be opened, user PIN must be verified
|
||||
|
||||
P1:
|
||||
0x00 = Current key
|
||||
0x01 = Derive
|
||||
0x02 = Derive and make current
|
||||
|
||||
P2:
|
||||
0x00 = private and public key
|
||||
0x01 = public key only
|
||||
0x02 = extended public key
|
||||
|
||||
Response Data format:
|
||||
- Tag 0xA1 = keypair template
|
||||
- Tag 0x80 = ECC public key component (omitted if P2=0x00)
|
||||
- Tag 0x81 = ECC private key component (if P2=0x00)
|
||||
- Tag 0x82 = Chain code (if P2=0x02)
|
||||
|
||||
This command exports the requested public and private key. The public key can be always exported (P2=0x01), but the private key (P2=0x00) can be exported if and only if the requested key path is in the [EIP-1581](https://eips.ethereum.org/EIPS/eip-1581) subtree. The extended public key (P2=0x02) can be exported for any path except those in the EIP-1581 subtree.
|
||||
|
||||
The P1 parameter indicates how to the derive the desired key. P1 = 0x00 indicates that the current key must be exported, and no derivation will be performed. P1 = 0x01 derives the path given in the data field without changing the current path of the card. P1 = 0x02 derives the path but also changes the current path of the card. The source for derivation can be set by OR'ing P1 with the constants defined in the DERIVE KEY command. This allows deriving from master, parent or current.
|
||||
|
||||
If the private key is being exported, the card could omit exporting the public key for performance reason. The public key can then be calculate off-card if needed.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
id: apdu_generatekey
|
||||
title: GENERATE KEY
|
||||
---
|
||||
|
||||
# GENERATE KEY
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0xD4
|
||||
* P1 = 0x00
|
||||
* P2 = 0x00
|
||||
* Response SW = 0x9000 on success.
|
||||
* Response Data = the key UID, defined as the SHA-256 of the public key
|
||||
* Preconditions: Secure Channel must be opened, user PIN must be verified
|
||||
* Capability: Key management
|
||||
|
||||
Generates and stores keys completely on card. The state of the card after execution is the same as if a LOAD KEY command had been performed.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
id: apdu_generatemnemonic
|
||||
title: GENERATE MNEMONIC
|
||||
---
|
||||
|
||||
# GENERATE MNEMONIC
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0xD2
|
||||
* P1 = checksum size (between 4 and 8)
|
||||
* P2 = 0x00
|
||||
* Response SW = 0x9000 on success. 0x6A86 if P1 is invalid.
|
||||
* Response Data = a sequence of 16-bit integers (most significant byte first).
|
||||
* Preconditions: Secure Channel must be opened
|
||||
* Capability: Key management
|
||||
|
||||
Used to generate a mnemonic according to the algorithm specified in [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki). The returned data is a list of 16-byte integers which should be used as indexes in a wordlist to generate the human-readable mnemonic. Each integer can have a value from 0 to 2047.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
id: apdu_getdata
|
||||
title: GET DATA
|
||||
---
|
||||
|
||||
# GET DATA
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0xCA
|
||||
* P1 = 0x00 for public data, 0x01 for NDEF data, 0x02 for Cash data
|
||||
* P2 = 0x00
|
||||
* Response SW = 0x9000 on success, 0x6A86 on undefined P1
|
||||
* Response Data = the data as previously stored by STORE DATA command
|
||||
* Capabilities: NDEF for P1 = 0x01
|
||||
|
||||
Notably, this command does not require an open secure channel
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
id: apdu_getstatus
|
||||
title: GET STATUS
|
||||
---
|
||||
|
||||
# GET STATUS
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0xF2
|
||||
* P1 = 0x00 for application status, 0x01 for key path status
|
||||
* P2 = 0x00
|
||||
* Response SW = 0x9000 on success, 0x6A86 on undefined P1
|
||||
* Response Data = Application Status Template or Key Path
|
||||
* Preconditions: Secure Channel must be opened
|
||||
|
||||
Response Data format:
|
||||
if P1 = 0x00:
|
||||
- Tag 0xA3 = Application Status Template
|
||||
- Tag 0x02 = PIN retry count (1 byte)
|
||||
- Tag 0x02 = PUK retry count (1 byte)
|
||||
- Tag 0x01 = 0xff if key is initialized, 0 otherwise
|
||||
|
||||
This must be implemented even if Credential and/or Key management is not implemented. If the PIN/PUK retry count is not available or is not applicable, its value should be set to 0xff.
|
||||
|
||||
if P1 = 0x01:
|
||||
- a sequence of 32-bit numbers indicating the current key path. Empty if master key is selected.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
id: apdu_ident
|
||||
title: IDENT
|
||||
---
|
||||
|
||||
# IDENT
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0x14
|
||||
* P1 = 0x00
|
||||
* P2 = 0x00
|
||||
* Data = a 256-bit random challenge
|
||||
* Response Data = the challenge's signature, followed by the card's certificate
|
||||
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid.
|
||||
|
||||
Response Data format:
|
||||
- Tag 0xA0 = signature template
|
||||
- Tag 0x8A = Certificate
|
||||
- Tag 0x30 = ECDSA Signature
|
||||
- Tag 0x02 = R value
|
||||
- Tag 0x02 = S value
|
||||
|
||||
Signs the given challenge using the card's identification private key and returns the card certificate followed by the generated signature. The certificate is composed by the concatenation of the card's identification public key in compressed form (33 bytes) and its signature r,s (32 bytes each) and recovery id (1 byte) generated by the certificate authority.
|
||||
|
||||
This command does not require PIN authentication or a Secure Channel to be established. It is strongly recommended that the client performs this identification step before initializing the card and/or before pairing with it. After a card has been paired it is not necessary to repeat the identification step.
|
||||
|
||||
To perform verification, first verify the challenge's signature using public key in the certificate. If the signature is correct, recover the CA's public key using the r,s, and recovery ID part of the certificate. If the public key matches that of a certificate authority you trust proceed to verify the signature, which has been calculated on the SHA-256 hash of the card's identification public key in the form returned in the certificate.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
id: apdu_index
|
||||
title: APDU API
|
||||
---
|
||||
|
||||
# Summary
|
||||
|
||||
This document describes all APDUs part of the Keycard APDU protocol.
|
||||
|
||||
* [Overview](apdu/overview)
|
||||
* [CHANGELOG](apdu/changelog)
|
||||
* [SELECT](apdu/select)
|
||||
* [INIT](apdu/init)
|
||||
* [IDENT](apdu/ident)
|
||||
* [OPEN SECURE CHANNEL](apdu/opensecurechannel)
|
||||
* [MUTUALLY AUTHENTICATE](apdu/mutuallyauthenticate)
|
||||
* [PAIR](apdu/pair)
|
||||
* [UNPAIR](apdu/unpair)
|
||||
* [GET STATUS](apdu/getstatus)
|
||||
* [VERIFY PIN](apdu/verifypin)
|
||||
* [CHANGE PIN](apdu/changepin)
|
||||
* [UNBLOCK PIN](apdu/unblockpin)
|
||||
* [LOAD KEY](apdu/loadkey)
|
||||
* [DERIVE KEY](apdu/derivekey)
|
||||
* [GENERATE MNEMONIC](apdu/generatemnemonic)
|
||||
* [REMOVE KEY](apdu/removekey)
|
||||
* [GENERATE KEY](apdu/generatekey)
|
||||
* [SIGN](apdu/sign)
|
||||
* [SET PINLESS PATH](apdu/setpinlesspath)
|
||||
* [EXPORT KEY](apdu/exportkey)
|
||||
* [STORE DATA](apdu/storedata)
|
||||
* [KEY DATA](apdu/keydata)
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
id: apdu_init
|
||||
title: INIT
|
||||
---
|
||||
|
||||
# INIT
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0xFE
|
||||
* P1 = 0x00
|
||||
* P2 = 0x00
|
||||
* Data = EC public key (LV encoded) | IV | encrypted payload
|
||||
* Response SW = 0x9000 on success, 0x6D00 if the applet is already initialized, 0x6A80 if the data is invalid
|
||||
* Capability: Credentials management
|
||||
|
||||
This command is only available when the applet is in pre-initialized state and successful execution brings the applet in the initialized state. This command is needed to allow securely storing secrets on the applet at a different moment and place than installation is taking place. Currently these are the PIN, PUK and pairing password.
|
||||
|
||||
The client must take the public key received after the SELECT command, generate a random keypair and perform EC-DH to generate an AES key. It must then generate a random IV and encrypt the payload using AES-CBC with ISO/IEC 9797-1 Method 2 padding.
|
||||
|
||||
They payload is the concatenation of the PIN (6 digits/bytes), PUK (12 digits/bytes) and, if the Secure Channel capability is implemented, pairing secret (32 bytes).
|
||||
Additionally, since version 3.1 of the applet, the max number of PIN attempts (1 byte), the max number of PUK attempts (1 bytes) and a duress PIN (6 digits/bytes) can be specified.
|
||||
If these are not specified, the default values will be respectively, 3, 5 and the first half of the PUK.
|
||||
|
||||
This scheme guarantees protection against passive MITM attacks. Since the applet has no "owner" before the execution of this command, protection against active MITM cannot be provided at this stage. However since the communication happens locally (either through NFC or contacted interface) the realization of such an attack at this point is unrealistic.
|
||||
|
||||
After successful execution, this command cannot be executed anymore. The regular SecureChannel (with pairing) is active and PIN and PUK are initialized.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
id: apdu_loadkey
|
||||
title: LOAD KEY
|
||||
---
|
||||
|
||||
# LOAD KEY
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0xD0
|
||||
* P1 = key type
|
||||
* P2 = 0x00
|
||||
* Data = the key data
|
||||
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A86 if P1 is invalid
|
||||
* Response Data = the key UID, defined as the SHA-256 of the public key
|
||||
* Preconditions: Secure Channel must be opened, user PIN must be verified
|
||||
* Capability: Key management
|
||||
|
||||
P1:
|
||||
* 0x01 = ECC SECP256k1 keypair
|
||||
* 0x02 = ECC SECP256k1 extended keypair
|
||||
* 0x03 = Binary seed as defined in [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)
|
||||
|
||||
Data:
|
||||
|
||||
If P1 is 0x01 or 0x02
|
||||
- Tag 0xA1 = keypair template
|
||||
- Tag 0x80 = ECC public key component (can be omitted)
|
||||
- Tag 0x81 = ECC private key component
|
||||
- Tag 0x82 = chain code (if P1=0x02)
|
||||
|
||||
If P1 is 0x03 a 64 byte sequence generated according to the BIP39 specifications is expected. The master key will be generated according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) specifications. This is only supported if the hardware supports public key derivation.
|
||||
|
||||
This command is used to load or replace the keypair used for signing on the card. The PIN-less path will be reset. Unless a DERIVE KEY is sent, a subsequent SIGN command will use this keypair for signature.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
id: apdu_mutuallyauthenticate
|
||||
title: MUTUALLY AUTHENTICATE
|
||||
---
|
||||
|
||||
# MUTUALLY AUTHENTICATE
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0x11
|
||||
* P1 = 0x00
|
||||
* P2 = 0x00
|
||||
* Data = 256-bit random number
|
||||
* Response Data = 256-bit random number
|
||||
* Response SW = 0x9000 on success, 0x6985 if the previous successfully executed APDU was not OPEN SECURE CHANNEL, 0x6982 if authentication failed or the data is not 256-bit long
|
||||
* Capability: Secure Channel
|
||||
|
||||
This APDU allows both parties to verify that the keys generated in the OPEN SECURE CHANNEL step are matching and thus guarantee authentication of the counterpart. The data sent by both parties is a 256-bit random number The APDU data is sent encrypted with the keys generated in the OPEN SECURE CHANNEL step. Each party must verify the MAC of the received APDU. If the MAC can be verified, it means that both parties are using the same keys. Only after this step has been executed the secure channel can be considered to be open and other commands can be sent. If the authentication fails the card must respond with 0x6982. In this case the OPEN SECURE CHANNEL command must be repeated to generate new keys.
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
id: apdu_opensecurechannel
|
||||
title: OPEN SECURE CHANNEL
|
||||
---
|
||||
|
||||
# OPEN SECURE CHANNEL
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0x10
|
||||
* P1 = the pairing index
|
||||
* P2 = 0x00
|
||||
* Data = An EC-256 public key on the SECP256k1 curve encoded as an uncompressed point.
|
||||
* Response Data = A 256-bit salt and a 128-bit seed IV
|
||||
* Response SW = 0x9000 on success, 0x6A86 if P1 is invalid, 0x6A80 if the data is not a public key
|
||||
* Capability: Secure Channel
|
||||
|
||||
This APDU is the first step to establish a Secure Channel session. A session is aborted when the application is deselected, either directly or because of a card reset/tear.
|
||||
|
||||
The card generates a random 256-bit salt which is sent to the client. Both the client and the card do the following for key derivation
|
||||
|
||||
1. Use their private key and the counterpart public key to generate a secret using the EC-DH algorithm.
|
||||
2. The generated secret, the pairing key and the salt are concatenated and the SHA-512 of the concatenated value is calculated.
|
||||
3. The output of the SHA-512 algorithm is split in two parts of 256-bit. The first part is used as the encryption key and the second part is used as the MAC key for further communication.
|
||||
|
||||
The seed IV is used by the client as the IV for the next encrypted APDU.
|
||||
|
||||
## Encrypted APDUs
|
||||
|
||||
After a successful OPEN SECURE CHANNEL command all communication between card and client is encrypted. Note that only the data fields of C-APDU are encrypted, which means that CLA, INS, P1, P2 for C-APDU are plaintext. This means no sensitive data should be sent in these parameters. Additionally a MAC is calculated for the entire APDU, including the unencrypted fields.
|
||||
|
||||
Because R-APDU can only contain data if their SW is a success or warning status word (0x9000, 0x62XX, 0x63XX), when the secure channel is open all responses will have SW 0x9000. The actual SW is always appended at the end of the response data before encryption, which means the client must interpret the last two bytes of the plaintext response as the SW. An exception to this is SW 0x6982, which indicates that the SecureChannel has been aborted and as such is returned without any MAC.
|
||||
|
||||
To encrypt the data both the card and the client do the following:
|
||||
|
||||
1. The data is padded using the ISO/IEC 9797-1 Method 2 algorithm.
|
||||
2. The data is encrypted using AES in CBC mode using the session key.
|
||||
3. An AES CBC-MAC is calculated over the entire APDU data
|
||||
4. The data field of the APDU is set to the MAC followed by the encrypted data.
|
||||
|
||||
To decrypt the data both the card and the client do the following:
|
||||
|
||||
1. The first 16 bytes of the APDU data are the MAC to be verified
|
||||
2. The remaining data is decrypted using AES in CBC mode using the session key.
|
||||
3. The padding is removed.
|
||||
|
||||
The IV used for the encryption is the last seen MAC from the counterpart. This optimizes the number of transmitted bytes and guarantees protection from replay attacks. For the MAC generation, a zero IV is always used.
|
||||
|
||||
MAC generation for C-APDUs is calculated on the concatenation of CLA INS P1 P2 LC 00 00 00 00 00 00 00 00 00 00 00 and the encrypted data field. The 11-byte long padding does not become part of the data field and does not affect LC
|
||||
|
||||
MAC generation fo R-APDUs is calculated on the concatenation of Lr 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 and the encrypted data field. The 15-byte long padding does not become part of the response field. Lr is the length of the encrypted response data field and is not transmitted.
|
||||
|
||||
Because AES in CBC mode requires the data field length in bytes to be a multiple of 16, the maximum effective APDU size becomes 240 bytes. Of these 16 bytes are used for the MAC and minimum of 1 byte for padding, making the maximum payload size in a single APDU 223 bytes, meaning about a 13,5% overhead.
|
||||
|
||||
## Error conditions
|
||||
|
||||
1. If a sensitive command is received without an active Secure Channel, the card shall respond with SW 0x6985 (SW_CONDITIONS_NOT_SATISFIED)
|
||||
2. If a MAC cannot be verified the card shall respond 0x6982 and the Secure Channel must be closed
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
id: apdu_overview
|
||||
title: APDU API Overview
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
This document describes all APDUs part of the Keycard APDU protocol. Any implementation must fully implement this specification except for items explicitly marked as optional or conditional. The Keycard applet is a full implementation of this specification. The Java SDK provides both low-level methods to send each of the APDUs described here as well as convenience methods and classes to simplify integration.
|
||||
|
||||
## Version
|
||||
|
||||
This documentation reflects version 3.1 of the applet.
|
||||
|
||||
## Conventions
|
||||
|
||||
* Bits are counted from 0. If this documentation talks mentions bit 7 it refers to the MSB of a byte.
|
||||
* When a command has a precondition clause and these are not met the Status Word 0x6985 is returned.
|
||||
* When a command is unsupported the Status Word 0x6A81 is returned.
|
||||
* All tagged data structures are encoded in the [BER-TLV format](http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-d.aspx).
|
||||
* When parsing the card's response, the client should ignore unrecognized fields at the end of the response. This allows extending responses in future minor revisions without breaking compatibility with older clients.
|
||||
|
||||
## Capabilities
|
||||
|
||||
The Keycard protocol can be implemented by different devices which might have very different capabilities, different user interaction and connection types. For this reason some features not strictly related to signing transactions are optional. Each device reports its capabilities as response to the SELECT command. The optional capabilities are described below
|
||||
|
||||
### Secure Channel
|
||||
|
||||
The Secure Channel protocol defined in this document provides both passive and active MITM protection and PFS. Some devices however might have inherently secure connections to the client (for example Bluetooth based devices can use pairing and encrypted communication). To avoid overhead, devices using inherently secure connections can avoid implementing the secure channel defined here.
|
||||
|
||||
Devices not implementing the Secure Channel capability do not implement the **PAIR**, **UNPAIR**, **OPEN SECURE CHANNEL** and **MUTUALLY AUTHENTICATE** commands. The **CHANGE PIN** and **INIT** commands do not allow setting the pairing password.
|
||||
|
||||
### Key management
|
||||
|
||||
Key management is the ability to generate, remove, backup and restore keys. Some devices might have internal key management and not allow the client to take part in this process.
|
||||
|
||||
Devices not implementing the Key management capability do not implement the **LOAD KEY**, **GENERATE KEY**, **GENERATE MNEMONIC**, **REMOVE KEY** commands.
|
||||
|
||||
### Credentials management
|
||||
|
||||
Credentials management is the ability to authenticate the user and manipulate PIN, PUK and pairing password. Some devices, especially those with screen and buttons, might want to handle this completely internally without client intervention.
|
||||
|
||||
Devices not implementing the Credentials management capability do not implement the **INIT**, **VERIFY PIN**, **UNBLOCK PIN** and **CHANGE PIN** commands.
|
||||
|
||||
### NDEF
|
||||
|
||||
The NDEF capability is the ability to set a custom NDEF record on the NFC interface. The NDEF record is used, for example, by mobile phones to decide the action to perform when a card is tapped to the device and no application using NFC is open. Customizing the NDEF record allow setting a default client or link to launch. Obviously, not all devices have a NFC interface and even those who do might not provide a way to customize the NDEF response.
|
||||
|
||||
Devices not implementing the NDEF capability do not support P1 = 0x01 (SET NDEF DATA) in the **STORE DATA** command.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: apdu_pair
|
||||
title: PAIR
|
||||
---
|
||||
|
||||
# PAIR
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0x12
|
||||
* P1 = pairing phase
|
||||
* P2 = 0x00
|
||||
* Data = see below
|
||||
* Response Data = see below
|
||||
* Response SW = 0x9000 on success, 0x6A80 if the data is in the wrong format, 0x6982 if client cryptogram verification fails, 0x6A84 if all available pairing slot are taken, 0x6A86 if P1 is invalid or is 0x01 but the first phase was not completed, 0x6985 if a secure channel is open
|
||||
* Capability: Secure Channel
|
||||
|
||||
P1:
|
||||
* 0x00: First step
|
||||
* 0x01: Final step
|
||||
|
||||
Data:
|
||||
* On first step: a 256-bit random client challenge
|
||||
* On second step: the client cryptogram as SHA-256(shared secret, card challenge)
|
||||
|
||||
Response Data:
|
||||
* On first step: the card cryptogram as SHA-256(shared secret, client challenge) followed by a 256-bit card challenge
|
||||
* On second step: the pairing index followed by a 256-bit salt
|
||||
|
||||
This APDU is sent to pair a client. Pairing is performed with two commands which must be sent immediately one after the other.
|
||||
|
||||
In the first phase the client sends a random challenge to the card. The card replies with the SHA-256 hash of the shared secret and the challenge followed by its random challenge. The client is thus able to authenticate the card by verifying the card cryptogram (since the client can generate the same and verify that it matches).
|
||||
|
||||
In the second phase the client sends the client cryptogram which is the SHA-256 hash of the shared secret and the card challenge. The card verifies the cryptogram and thus authenticates the client. On success the card generates a random 256-bit salt which is appended to the shared secret. The SHA-256 hash of the concatenated value is stored in the first available pairing slot and will be further used to derive session keys. The card responds with the pairing index (which the client must send in all OPEN SECURE CHANNEL commands) and the salt used to generate the key, so that the client can generate and store the same key.
|
||||
|
||||
The shared secret is a 256-bit value which must be be known to both parts being paired.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
id: apdu_removekey
|
||||
title: REMOVE KEY
|
||||
---
|
||||
|
||||
# REMOVE KEY
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0xD3
|
||||
* P1 = 0x00
|
||||
* P2 = 0x00
|
||||
* Response SW = 0x9000 on success.
|
||||
* Preconditions: Secure Channel must be opened, user PIN must be verified
|
||||
* Capability: Key management
|
||||
|
||||
Removes the key from the card, bringing it back to an uninitialized state. No signing operation is possible after this command until a new LOAD KEY command is performed.
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: apdu_select
|
||||
title: SELECT
|
||||
---
|
||||
|
||||
# SELECT
|
||||
|
||||
* CLA = 0x00
|
||||
* INS = 0xA4
|
||||
* P1 = 0x04
|
||||
* P2 = 0x00
|
||||
* Data = the instance AID
|
||||
* Response = Application Info Template or ECC public key.
|
||||
|
||||
Response Data format:
|
||||
- Tag 0xA4 = Application Info Template
|
||||
- Tag 0x8F = Instance UID (16 bytes)
|
||||
- Tag 0x80 = ECC public Key (0 or 65 bytes)
|
||||
- Tag 0x02 = Application Version (2 bytes)
|
||||
- Tag 0x02 = Number of remaining pairing slots (1 byte)
|
||||
- Tag 0x8E = Key UID (0 or 32 bytes)
|
||||
- Tag 0x8D = Capabilities (1 byte)
|
||||
|
||||
The SELECT command is documented in the ISO 7816-4 specifications and is used to select the application on the card, making it the active one. The data field is the AID of the application.
|
||||
|
||||
The response is the Application Info template which contains:
|
||||
|
||||
* the instance UID, which can be used by the client to keep track of multiple cards.
|
||||
* an ECC public key for the Secure Channel. If the device does not implement the Secure Channel capability then this field has 0 length.
|
||||
* the application version number, formatted on two bytes. The first byte is the major version and the second is the minor version (e.g: version 2.1 is formatted as 0x0201). This number must match the version of the protocol implemented.
|
||||
* the number of remaining pairing slots for the Secure Channel. If the device does not implement the Secure Channel capability then this field has a fixed value of 0xff.
|
||||
* the Key UID, which can be either empty (when no key is loaded on card) or the SHA-256 hash of the master public key. Allows identifying the master key being used without revealing it.
|
||||
* the capabilities descriptor. Its value is the bitwise OR of all capabilities supported by the device. The capabilities are defined as:
|
||||
* Secure Channel: 0x01
|
||||
* Key management: 0x02
|
||||
* Credentials management: 0x04
|
||||
* NDEF: 0x08
|
||||
|
||||
When the applet is in pre-initializated state (which implies support of Credentials management capability), it only returns the ECC public key, BER-TLV encoded with tag 0x80 (with length 0 if Secure Channel is not implemented).
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
id: apdu_setpinlesspath
|
||||
title: SET PINLESS PATH
|
||||
---
|
||||
|
||||
# SET PINLESS PATH
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0xC1
|
||||
* P1 = 0x00
|
||||
* P2 = 0x00
|
||||
* Data = a sequence of 32-bit integers
|
||||
* Response SW = 0x9000 on success, 0x6A80 if data is invalid
|
||||
* Preconditions: Secure Channel must be opened, user PIN must be verified
|
||||
|
||||
Sets the given sequence of 32-bit integers as a PIN-less path. When the current derived key matches this path, SIGN will work even if no PIN authentication or pairing has been performed. An empty sequence means that no PIN-less path is defined.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
id: apdu_sign
|
||||
title: SIGN
|
||||
---
|
||||
|
||||
# SIGN
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0xC0
|
||||
* P1 = derivation options
|
||||
* P2 = 0x00
|
||||
* Data = the hash to sign (32 bytes) | sequence of 32-bit integers (if P1=01 or P1=02)
|
||||
* Response = public key and the signature
|
||||
* Response SW = 0x9000 on success, 0x6A80 if the data is less than 32-byte long (36 bytes if P1=01 or P1=02), 0x6A88 if P1=0x03 but no PIN-less path is defined
|
||||
* Preconditions: Secure Channel must be opened, user PIN must be verified (or a PIN-less key must be active), a valid keypair must be loaded
|
||||
|
||||
P1:
|
||||
0x00 = Current key
|
||||
0x01 = Derive
|
||||
0x02 = Derive and make current
|
||||
0x03 = PIN-less path
|
||||
|
||||
Response Data format:
|
||||
- Tag 0xA0 = signature template
|
||||
- Tag 0x80 = ECC public key component
|
||||
- Tag 0x30 = ECDSA Signature
|
||||
- Tag 0x02 = R value
|
||||
- Tag 0x02 = S value
|
||||
|
||||
Returns the ECDSA signature of the hash. The hash can be calculated using any algorithm, but must be 32-bytes long. The signature is returned in a signature template, containing the public key associated to the signature and the signature itself. For usage on the blockchain, you will need to calculate the recovery ID in addition to extracting R and S. To calculate the recovery ID you need to apply the same algorithm used for public key recovery from a transaction starting with a recovery ID of 0. If the public key matches the one returned in the template, then you have found the recovery ID, otherwise you try again by incrementing the recovery ID.
|
||||
P1 = 0x01 derives the path given in the data field without changing the current path of the card. P1 = 0x02 derives the path but also changes the current path of the card. The source for derivation can be set by OR'ing P1 with the constants defined in the DERIVE KEY command. This allows deriving from master, parent or current.
|
||||
P1 = 0x03 is specifically thought for POS transactions. It can be executed without Secure Channel (since no sensitive info is transmitted) and does not require PIN authentication. The current derivation path on the card remains unchanged, but the signing process is performed using the PIN-less derivation path previously defined using the SET PINLESS PATH command.
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
id: apdu_storedata
|
||||
title: STORE DATA
|
||||
---
|
||||
|
||||
# STORE DATA
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0xE2
|
||||
* P1 = 0x00 for public data, 0x01 for NDEF data, 0x02 for Cash data
|
||||
* P2 = 0x00
|
||||
* Data = the data to store. Implementations should accept at least 127 bytes of data.
|
||||
* Response SW = 0x9000 on success, 0x6A86 on undefined P1, 0x6A80 if the data is too long
|
||||
* Preconditions: Secure Channel must be opened, user PIN must be verified
|
||||
* Capabilities: NDEF for P1 = 0x01
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
id: apdu_unblockpin
|
||||
title: UNBLOCK PIN
|
||||
---
|
||||
|
||||
# UNBLOCK PIN
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0x22
|
||||
* P1 = 0x00
|
||||
* P2 = 0x00
|
||||
* Data = the PUK followed by the new PIN
|
||||
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid
|
||||
* Preconditions: Secure Channel must be opened, user PIN must be blocked
|
||||
* Capability: Credentials management
|
||||
|
||||
Used to unblock the user PIN. The data field must contain exactly 18 numeric digits, otherwise SW 0x6A80 is returned. The first 12 digits are the PUK and the last 6 are the new PIN. If the PUK is correct the PIN is changed to the supplied one, it is unblocked and authenticated for the rest of the session. The status code 0x9000 is returned. When the PUK is wrong, the number of remaining retries is decreased and the SW 0x63CX, where X is the number of available retries is returned. When the number of remaining retries reaches 0 the PUK is blocked. When the PUK is blocked this command always returns 0x63C0, even if the PUK is inserted correctly. In this case the wallet is effectively lost.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
id: apdu_unpair
|
||||
title: UNPAIR
|
||||
---
|
||||
|
||||
# UNPAIR
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0x13
|
||||
* P1 = the index to unpair
|
||||
* P2 = 0x00
|
||||
* Response SW = 0x9000 on success, 0x6985 if security conditions are not met, 0x6A86 if the index is higher than the
|
||||
highest possible pairing index.
|
||||
* Preconditions: Secure Channel must be opened, user PIN must be verified
|
||||
* Capability: Secure Channel
|
||||
|
||||
This APDU is sent to unpair a client. An existing secure channel session must be open. The application implementing this protocol may apply additional restrictions, such as the verification of a user PIN. On success the pairing slot at the given index will be freed and will be made available to pair other clients. If the index is already free nothing will happen.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
id: apdu_verifypin
|
||||
title: VERIFY PIN
|
||||
---
|
||||
|
||||
# VERIFY PIN
|
||||
|
||||
* CLA = 0x80
|
||||
* INS = 0x20
|
||||
* P1 = 0x00
|
||||
* P2 = 0x00
|
||||
* Data = the PIN to be verified
|
||||
* Response SW = 0x9000 on success, 0x63CX on failure, where X is the number of attempt remaining
|
||||
* Preconditions: Secure Channel must be opened
|
||||
* Capability: Credentials management
|
||||
|
||||
Used to verify the user PIN. On correct PIN entry the card returns 0x9000, the retry counter is reset and the PIN is marked as authenticated for the entire session (until the application is deselected or the card reset/teared). On error, the number of remaining retries is decreased and the SW 0x63CX, where X is the number of available retries is returned. When the number of remaining retries reaches 0 the PIN is blocked. When the PIN is blocked this command always returns 0x63C0, even if the PIN is inserted correctly.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
id: applet_installation
|
||||
title: Card Setup
|
||||
---
|
||||
|
||||
# Preparing the card
|
||||
|
||||
Keycard ships with all applets fully installed but not initialized. The most convenient way to initialize and manage the card is to install the [Keycard Connect](https://github.com/status-im/keycard-connect) Android app. It allows to fully initialize a card with pairing key, PIN, PUK and BIP39 seed. It also provides basic functionality like changing credentials, changing or removing keys and clear pairing slots. The last functionality is especially useful if you are integrating with Keycard and accidentally fill all pairing slots. Using the "Unpair others" button will remove all pairing except the Keycard Connect's one.
|
||||
|
||||
In some cases you might want to reset the applet status completely for testing purposes. This is done by reinstalling the applet, which can be accomplished using the [Keycard CLI](https://github.com/status-im/keycard-cli). To use the Keycard CLI, a USB reader is needed to be able to communicate with the card. The repository contains example scripts for applet reinstallation.
|
||||
|
||||
## Card requirements
|
||||
|
||||
The Keycard applet can be installed not only on our cards, but on any card which meets the following requirements:
|
||||
|
||||
* JavaCard 3.0.4 or later.
|
||||
* Cipher.ALG_AES_BLOCK_128_CBC_NOPAD
|
||||
* Cipher.ALG_AES_CBC_ISO9797_M2
|
||||
* KeyAgreement.ALG_EC_SVDP_DH_PLAIN
|
||||
* KeyAgreement.ALG_EC_SVDP_DH_PLAIN_XY (defined in JavaCard 3.0.5 but available on some 3.0.4 cards nonetheless)
|
||||
* KeyPair.ALG_EC_FP (generation of 256-bit keys)
|
||||
* MessageDigest.ALG_SHA_256
|
||||
* MessageDigest.ALG_SHA_512
|
||||
* RandomData.ALG_SECURE_RANDOM
|
||||
* Signature.ALG_AES_MAC_128_NOPAD
|
||||
* Signature.ALG_ECDSA_SHA_256
|
||||
|
||||
Best performance is achieved if the card supports:
|
||||
|
||||
* Signature.ALG_HMAC_SHA_512
|
||||
|
||||
Keycard requires about 10kb of NVM. All allocations, instantiations and checks are performed at installation time, so if installation succeeds, the applet will work fine.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: index
|
||||
title: Overview
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
Keycard provides to developer an hardware implementation of a [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) HD wallet. This means it supports key generation, derivation and signing. It also allows exporting keys defined in the context of [EIP-1581](https://eips.ethereum.org/EIPS/eip-1581).
|
||||
|
||||
Communication with the Keycard happens through a simple APDU interface, together with a Secure Channel guaranteeing confidentiality, authentication and integrity of all commands. It supports both NFC and ISO7816 physical interfaces, meaning that it is compatible with any Android phone equipped with NFC and all USB Smartcard readers.
|
||||
|
||||
A feature added in version 3.1 of the applet is a duress PIN, which is completely transparent in the API and undetectable. It changes the keys generated during derivation in a deterministic way. Wallets should make sure they account for this by hiding balance and wallet address until proper authentication, and eventually be able to transparently switch to using the addresses generated after inserting the duress PIN.
|
||||
|
||||
The most obvious case for integration of Keycard is crypto wallets (ETH, BTC, etc), however it can be used in other systems where a BIP-32 key tree is used and/or you perform authentication/identification.
|
||||
|
||||
To further simplify integration, we have developed a Java-based API which can be used on both desktop and Android systems. On the desktop it uses the javax.smartcardio to interface with the card, which is compatible with most USB readers. On Android it uses the on-board NFC reader. If you develop in Java or any other language available, this is the easiest way to use the Keycard. You can also find a Go and Swift SDK and more resources [here](resources)
|
||||
|
||||
[Read the SDK documentation](sdk/installation)
|
||||
|
||||
If you use a different language, please first refer to the [Java SDK](sdk/installation) documentation for a high level overview of how to perform different tasks with the Keycard. Then, please check the [protocol documentation](apdu) out for the low-level details.
|
||||
|
||||
Since Keycard 3.0, besides the hardware wallet functionality, you find a second applet with a different purpouse and security model. This applet, internally called Cash applet, does not share any data with the wallet and provides a single keypair which can be used to sign any piece of information. Unlike the wallet applet, the cash applet does not require a PIN or SecureChannel and can be tapped on 3rd party terminal. This enable convenient in-person interactions at the expense of security. For this reason the keypair in the Cash applet should not hold any funds at all, but should instead be used in concert with smartcontracts in scenarios with lower security requirements. It is especially suited for signing EIP-712 formatted metatx. Some examples can include ticketing, small payments, etc. It is worth repeating that the Wallet and Cash applet, while living on the same card, do not share any data or key and are completely independent from each other. In order to easily integrate Keycard with any dApp, we defined a small extension to the [Web3 API](web3).
|
||||
|
||||
## Versioning
|
||||
|
||||
The current version of the applet is 3.1. This documentation applies to said version. [Semantic versioning](https://semver.org) is used throughout the project, with the omission of the patch number for the applet, but not the SDK and related tools. The version of all components of the Keycard project are tied to the protocol version.
|
||||
|
||||
## Get in touch
|
||||
|
||||
You are at the right place to find informations about Keycard and get you going.
|
||||
|
||||
If you need more and want to discuss:
|
||||
- for live discussion and help, go to [Keycard Status channel](https://join.status.im/chat/public/status-keycard)
|
||||
- to check or raise issues/bugs/feature requests please do so in our [GitHub repos](https://keycard.tech/docs/resources.html)
|
||||
- check our latest Keycard news on [Discuss](https://discuss.status.im/c/keycard)
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
id: resources
|
||||
title: Resources
|
||||
---
|
||||
|
||||
# Resources
|
||||
|
||||
Here you find the links to all repositories and tools related to Keycard development.
|
||||
|
||||
## GitHub repositories
|
||||
|
||||
* [Javacard applet](https://github.com/status-im/status-keycard)
|
||||
* [Java SDK](https://github.com/status-im/status-keycard-java)
|
||||
* [Go SDK](https://github.com/status-im/keycard-go/)
|
||||
* [Swift SDK](https://github.com/status-im/Keycard.swift)
|
||||
* [Keycard on Ledger](https://github.com/status-im/keycard-ledger)
|
||||
* [React Native library](https://github.com/status-im/react-native-status-keycard)
|
||||
* [Go Status library](https://github.com/status-im/status-keycard-go)
|
||||
|
||||
## Tools
|
||||
|
||||
* [Keycard CLI](https://github.com/status-im/keycard-cli)
|
||||
* [Keycard Desktop](https://github.com/choppu/keycard-desktop)
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: sdk_authentication
|
||||
title: Authentication
|
||||
---
|
||||
|
||||
# User authentication
|
||||
|
||||
Most operations with the card (all involving operations with the wallet or credentials) require authenticating the user. After authentication, the user remains authenticated until the card is powered off or the application is re-selected.
|
||||
|
||||
Authentication is performed by verifying the user PIN. Note that this piece of information is sensitive and must be handled accordingly in the application. PIN verification is done with a single step
|
||||
|
||||
```java
|
||||
// pin is the user PIN as a string of 6 digits
|
||||
try {
|
||||
cmdSet.verifyPIN(pin).checkAuthOK();
|
||||
} catch(WrongPINException e) {
|
||||
System.out.println("Number of remaining attempts: " + e.getRetryAttempts());
|
||||
}
|
||||
```
|
||||
|
||||
if the PIN is wrong, you will receive an error SW in the format 0x63CX where X is the number of attempts remaining. When the number of remaining attempts is 0, the card is blocked. The user must then enter the PUK and a new PIN to restore access to the card. The maximum number of retries for the PUK is 5. To simplify things, the `APDUResponse.checkAuthOK()` method can be used to verify if the authentication was correct, and if not throw a `WrongPINException` which contains the number of remaining attempts.
|
||||
|
||||
```java
|
||||
cmdSet.unblockPIN(puk, newPIN).checkAuthOK();
|
||||
```
|
||||
|
||||
## Changing credentials
|
||||
|
||||
All credentials of the Keycard can be changed (PIN, PUK, pairing password). Changing the pairing password does not invalidate existing pairings, but applies to the ones which can be created in the future. Changing credentials, requires user authentication.
|
||||
|
||||
```java
|
||||
// Changes the user PIN
|
||||
cmdSet.changePIN("123456").checkOK();
|
||||
|
||||
// Changes the PUK
|
||||
cmdSet.changePUK("123456123456").checkOK();
|
||||
|
||||
// Changes the pairing password
|
||||
cmdSet.changePairingPassword("my pairing password").checkOK();
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
id: sdk_cash
|
||||
title: Cash
|
||||
---
|
||||
|
||||
# Cash applet
|
||||
|
||||
Since version 3.0 the Keycard package also includes a Cash applet. When installed, the applet immediately generates a keypair which cannot be changed, exported or be used for derivation. The applet only responds to the SELECT and SIGN commands and has no concept of PIN or SecureChannel. All in all, it is a very basic version of the main Keycard applet. This applet is completely separate from the Wallet applet and they do not share any key.
|
||||
|
||||
The use cases for this applet are burner wallets, payment cards (including prepaid ones). This applet can be installed alongside the main Keycard applet and does not interfere with it. As the name implies, the applet has roughly the same security level of cash in the pocket and should be used with this in mind. Security can be enhanced by making the applet a signer to a smartcontract (instead of holding funds directly) and applying anti-fraud policies on the network.
|
||||
|
||||
The SDK provides a separate command set to interact with this applet.
|
||||
|
||||
## Instatiating the command set
|
||||
|
||||
```java
|
||||
// cardChannel is our CardChannel instance
|
||||
CashCommandSet cashCmdSet = new CashCommandSet(cardChannel);
|
||||
```
|
||||
|
||||
## Selecting the CashApplet
|
||||
|
||||
```java
|
||||
// note that selecting the cash applet deselects the Keycard applet, so if you use both command sets make sure you invoke select every time you want to switch application
|
||||
CashApplicationInfo info = new CashApplicationInfo(cashCmdSet.select().checkOK().getData());
|
||||
|
||||
// Returns the public key of the wallet's keypair. This can be used to calculate the Ethereum address.
|
||||
info.getPubKey();
|
||||
|
||||
// Returns the version of the applet.
|
||||
info.getAppVersion();
|
||||
|
||||
// Returns arbitrary data which has been set either at applet installation or through the STORE DATA command in the Keycard applet
|
||||
info.getPubData();
|
||||
```
|
||||
|
||||
## Signing
|
||||
```java
|
||||
// hash is the hash to sign, for example the Keccak-256 hash of an Ethereum transaction
|
||||
// the signature object contains r, s, recId and the public key associated to this signature
|
||||
RecoverableSignature signature = new RecoverableSignature(hash, cashCmdSet.sign(hash).checkOK().getData());
|
||||
```
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
id: sdk_connection
|
||||
title: Card connection
|
||||
---
|
||||
|
||||
# Connecting to the card
|
||||
|
||||
Before you can send commands to the Keycard, you must first connect to the card. Connection handling is the only task which is handled differently between Android an the desktop.
|
||||
|
||||
## Android
|
||||
|
||||
On Android, the NFC connection handling must happen on a thread separate from the UI thread. The SDK provides the class `NFCCardManager` to handle this. This an example activity starting the NFC reader and handling the connection to the card. Refer to the comments in the example for more information.
|
||||
|
||||
```java
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private NfcAdapter nfcAdapter;
|
||||
private NFCCardManager cardManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
// Get the Android NFC default adapter
|
||||
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||
|
||||
// Create the NFCCardManager, this class is provided by the Keycard SDK and handles connections to the card
|
||||
cardManager = new NFCCardManager();
|
||||
|
||||
// The Card Listener receives the connected/disconnected events. These can happen at any time since the user can
|
||||
// introduce or remove the card to/from the field at any time. This is where your code goes.
|
||||
cardManager.setCardListener(new CardListener() {
|
||||
@Override
|
||||
public void onConnected(CardChannel cardChannel) {
|
||||
// Card is connected. Here you can start working with the Keycard. The CardChannel is what you will use to
|
||||
// communicate with the card.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected() {
|
||||
// Card is disconnected (was removed from the field). You can perform cleanup here.
|
||||
}
|
||||
});
|
||||
cardManager.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// We need to enable the reader on resume.
|
||||
if (nfcAdapter != null) {
|
||||
nfcAdapter.enableReaderMode(this, this.cardManager, NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
// We disable the reader on pause to allow other apps to use it.
|
||||
if (nfcAdapter != null) {
|
||||
nfcAdapter.disableReaderMode(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Desktop
|
||||
|
||||
On the desktop we use the javax.smartcardio library. There are several ways to handle connections, the important part is getting a CardChannel open. Below is an example of how this can be achieved (assumes that a single smartcard reader is connected).
|
||||
|
||||
```java
|
||||
// We create a TerminalFactory object
|
||||
TerminalFactory tf = TerminalFactory.getDefault();
|
||||
CardTerminal cardTerminal;
|
||||
|
||||
// We search a terminal with a card inside
|
||||
for (CardTerminal t : tf.terminals().list()) {
|
||||
if (t.isCardPresent()) {
|
||||
cardTerminal = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, we throw an exception. Of course you should decide how to handle this situation
|
||||
if (cardTerminal == null) {
|
||||
throw new RuntimeException("No terminal found");
|
||||
}
|
||||
|
||||
// If a terminal is found, we connect to it
|
||||
Card apduCard = cardTerminal.connect("*");
|
||||
|
||||
// We create a PCSCCardChannel, which is an implementation of CardChannel and can be used with the rest of the SDK.
|
||||
PCSCCardChannel apduChannel = new PCSCCardChannel(apduCard.getBasicChannel());
|
||||
```
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
id: sdk_create_wallet
|
||||
title: Wallet creation
|
||||
---
|
||||
|
||||
# Wallet creation
|
||||
|
||||
To actually use the Keycard, it needs to have a wallet. This can be achieved in several different ways, which one you choose depends on your usage scenario. Creating a wallet requires user authentication and is possible even if a wallet already exists on the card (the new wallet replaces the old one). Use the `ApplicationInfo.hasMasterKey()` method to determine if the card already has a wallet or not. Note that the response of the `KeycardCommandSet.loadKey` method contains the key UID of the created wallet. This UID can be stored to keep track of this specific wallet in the client. The UID is tied to the key itself (is derived from the public key) so it will change if the wallet on card is replaced. The key UID is also part of the response of the applet selection command, so the wallet can be identified immediately upon selection.
|
||||
|
||||
## Creating a BIP39 mnemonic phrase
|
||||
|
||||
This method is great for interoperability with other wallets. The card can assist in creating the mnemonic phrase, since it features a TRNG. Generating the mnemonic itself does not require user authentication (since it does not modify the card state), but loading the key derived from it does. Example of the entire procedure is below
|
||||
|
||||
```java
|
||||
// Generates a Mnemonic object from the card. You can choose between generating 12, 15, 18, 21 or 24 words
|
||||
Mnemonic mnemonic = new Mnemonic(cmdSet.generateMnemonic(KeycardCommandSet.GENERATE_MNEMONIC_12_WORDS).checkOK().getData());
|
||||
|
||||
// We need to set a wordlist if we plan using this object to derive the binary seed. We can set our own list or we can
|
||||
// fatch the official BIP39 english word list as shown below.
|
||||
mnemonic.fetchBIP39EnglishWordlist();
|
||||
|
||||
// If we did not verify the PIN before, we can do it now
|
||||
cmdSet.verifyPIN(pin).checkOK();
|
||||
|
||||
// Loads the key generated from the mnemonic phrase.
|
||||
cmdSet.loadKey(mnemonic.toBIP32KeyPair()).checkOK();
|
||||
```
|
||||
|
||||
## Importing a wallet from BIP39 mnemonic phrase
|
||||
|
||||
Importing an existing passphrase requires only the loading step.
|
||||
|
||||
```java
|
||||
// The passphrase is a string with space separated words. The password can be any non-null string, usually is empty.
|
||||
cmdSet.loadKey(Mnemonic.toBinarySeed(passphrase, password)).checkOK();
|
||||
```
|
||||
|
||||
## Generating keys on-card
|
||||
|
||||
This is the simplest and safest method, because the generated wallet never leaves the card and there is no "paper backup" to keep secure. Using a multi-sig wallet can solve the problem of backups, by having multiple cards signers for the wallet. Using the SDK, you simply do
|
||||
|
||||
```java
|
||||
cmdSet.generateKey().checkOK();
|
||||
```
|
||||
|
||||
## Importing an EC keypair
|
||||
|
||||
You can import on the keycard any EC keypair on the SECP256k1 curve, with or without the BIP32 extension. If your import a key without the BIP32 extension, then key derivation will not work, but you will still be able to use the Keycard for signing transactions using the imported key. This scenario can be useful if you are migrating from a wallet not using BIP39 passphrases or for wallets following some custom generation rules. It is however generally preferable to use one of the methods presented above.
|
||||
|
||||
An example of key import is
|
||||
|
||||
```java
|
||||
// privKey is the S component of the key, as a 32-byte long byte array
|
||||
// chainCode is the extension to the keypair defined by BIP32, this is another 32-byte long byte array. Can be null, in
|
||||
// which case the created wallet won't be BIP32 compatible.
|
||||
// pubKey is the DER encoded, uncompressed public key. Can be null, in which case it is automatically calculated from
|
||||
// the private key.
|
||||
BIP32KeyPair keypair = new BIP32KeyPair(privKey, chainCode, pubKey);
|
||||
|
||||
// Loads the keypair
|
||||
cmdSet.loadKey(keypair).checkOK();
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
id: sdk_derivation_signing
|
||||
title: Derivation & signing
|
||||
---
|
||||
|
||||
# Derivation & signing
|
||||
|
||||
Signing is the main goal of the Keycard and indeed any hardware wallet. The Keycard performs signing with the currently active key. Key derivation is used to select the active key to a specific key BIP32 key path.
|
||||
|
||||
## Key derivation
|
||||
|
||||
As mentioned before, the Keycard is a BIP32 compatible wallet. This means that it can perform key derivation as defined by the BIP32 specification in order to create a hierarchical deterministic wallet. When deriving a key, this key becomes active.
|
||||
|
||||
When creating or importing a wallet to the Keycard, the active key is the master key. Note that the maximum depth of the key path is 10, excluding the master key.
|
||||
|
||||
Key derivation requires user authentication
|
||||
|
||||
Since a line of code is worth a thousand words, below is an example of deriving a standard key path
|
||||
|
||||
```java
|
||||
cmdSet.deriveKey("m/44'/0'/0'/0/0").checkOK();
|
||||
```
|
||||
|
||||
You can also read the current active path with with
|
||||
|
||||
```java
|
||||
// you can then get is as a string with currentPath.toString()
|
||||
KeyPath currentPath = new KeyPath(cmdSet.getStatus(KeycardCommandSet.GET_STATUS_P1_KEY_PATH).checkOK().getData());
|
||||
```
|
||||
|
||||
## Signing
|
||||
|
||||
Your Keycard has been initialized, has a wallet and you have derived the keypath you need. You can now perform transactions by signing them with the card. Since the Keycard has no user input/output capabilities, it would be useless to transfer the entire transaction to the card for signing. You should instead calculate the transaction hash, according to the rules of the cryptocurrency you are handling and send that for signature instead. This also means, that you can handle anything which requires ECDSA signatures over SECP256k1 curve, regardless of the used hashing algorithm (at the condition that it output a 256-bit hash of course). This opens the door to signing transactions for the most common cryptocurrencies, but also makes it usable outside the realm of crypto transactions.
|
||||
|
||||
Signing is done as
|
||||
|
||||
```java
|
||||
// hash is the hash to sign, for example the Keccak-256 hash of an Ethereum transaction
|
||||
// the signature object contains r, s, recId and the public key associated to this signature
|
||||
RecoverableSignature signature = new RecoverableSignature(hash, cmdSet.sign(hash).checkOK().getData());
|
||||
```
|
||||
|
||||
Signing requires user authentication.
|
||||
|
||||
## Combined derivation and sign
|
||||
|
||||
Since version 2.2 of the Keycard API, it is possible to combine derivation and sign in a single step. Additionally, it is possible to choose whether the given path becomes the current path or not. Example
|
||||
|
||||
```java
|
||||
// hash is the hash to sign, the second argument is the path to use, in the same format as for the DERIVE KEY command
|
||||
// the third argument is a flag indicating whether the derived key should become the current key or not
|
||||
APDUResponse resp = cmdSet.signWithPath(hash, "m/44'/0'/0'/0/0", false);
|
||||
RecoverableSignature signature = new RecoverableSignature(hash, resp.checkOK().getData());
|
||||
```
|
||||
|
||||
this method of signing is now actually the recommended method since it does not rely on existing card state.
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
id: sdk_export
|
||||
title: Java SDK
|
||||
|
||||
---
|
||||
|
||||
# Exporting (public or EIP-1581 compliant) keys
|
||||
|
||||
Sorry for the long title, but let's make it immediately clear: the keys used to sign transactions never leave the card and cannot be exported. You can however export any public key as well as the private key of keypaths defined in the [EIP-1581 specifications](https://eips.ethereum.org/EIPS/eip-1581). Those keys, by design, are not to be used for transactions but are instead usable for operations with lower security concerns where caching or storing the key outside the card might be beneficial from an UX point of view. Of course, exporting a key always requires user authentication.
|
||||
|
||||
## Exporting the current key
|
||||
|
||||
```java
|
||||
// Exports the current public key. This is allowed for any key path
|
||||
BIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(KeycardCommandSet.EXPORT_KEY_P2_PUBLIC_ONLY).checkOK().getData());
|
||||
|
||||
// Exports the entire key pair. This is only allowed for key path following the EIP-1581 definition
|
||||
BIP32KeyPair keypair = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(KeycardCommandSet.EXPORT_KEY_P2_PRIVATE_AND_PUBLIC).checkOK().getData());
|
||||
```
|
||||
|
||||
## Derive & export
|
||||
|
||||
The export command is very powerful, since it allows you to derive & export a key in one step. You have the option to make the derived and exported key active or leave the active key untouched. You can also decide whether to export only the public key or the entire keypair (following the rules defined above).
|
||||
|
||||
A very convenient use case is deriving an account key and retrieving the public key in one step. This is faster than doing it with two commands (derive key and export public), because every command processed has some overhead. Example
|
||||
|
||||
```java
|
||||
// The first parameter is the keypath, the second tells whether that you want to make the derived & exported key current
|
||||
// and the third tells that you only want the public key to be exported.
|
||||
BIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportKey("m/44'/0'/0'/0/0", true, KeycardCommandSet.EXPORT_KEY_P2_PUBLIC_ONLY).checkOK().getData());
|
||||
|
||||
// The line above is equivalent to
|
||||
// cmdSet.deriveKey("m/44'/0'/0'/0/0").checkOK();
|
||||
// BIP32KeyPair publicKey = BIP32KeyPair.fromTLV(cmdSet.exportCurrentKey(KeycardCommandSet.EXPORT_KEY_P2_PUBLIC_ONLY).checkOK().getData());
|
||||
```
|
||||
|
||||
Another use case, is to export keys defined by EIP-1581 without changing the current active key, since you won't be signing with the exported key using the card
|
||||
|
||||
```java
|
||||
// Let's assume the current active path is "m/44'/0'/0'/0/0"
|
||||
|
||||
// The first parameter is the key path, the second tells that you do not want to make it current and the third that you
|
||||
// want the entire keypair, not only the public key
|
||||
BIP32KeyPair keypair = BIP32KeyPair.fromTLV(cmdSet.exportKey("m/43'/60'/1581'/0'/0", false, false).checkOK().getData());
|
||||
|
||||
// At this point, the current active path would still be "m/44'/0'/0'/0/0"
|
||||
```
|
||||
|
||||
For non EIP-1581 paths you can also export the extended public key, including the chain code. This allows, for non-hardened keys, to derive child public keys.
|
||||
|
||||
```java
|
||||
BIP32KeyPair keypair = BIP32KeyPair.fromTLV(cmdSet.exportKey("m/44'/0'/0'/0", false, KeycardCommandSet.EXPORT_KEY_P2_EXTENDED_PUBLIC).checkOK().getData());
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: skd_overview
|
||||
title: Java SDK
|
||||
---
|
||||
# Overview
|
||||
|
||||
This is an SKD essentially a Java library for development of applications for Status Keycard that can be installed via tools klike Gradle or Maven in your project.
|
||||
|
||||
See the sub-sections below for more details:
|
||||
|
||||
* [Installation](sdk/installation)
|
||||
* [Card Connection](sdk/connection)
|
||||
* [Preparation](sdk/preparation)
|
||||
* [Secure Channel](sdk/securechannel)
|
||||
* [Authentication](sdk/authentication)
|
||||
* [Wallet Creation](sdk/create_wallet)
|
||||
* [Derivation & Signing](sdk/derivation_sign)
|
||||
* [Pinless Signing](sdk/pinless)
|
||||
* [Exporting Keys](sdk/export)
|
||||
* [Cash Applet](sdk/cash)
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: sdk_installation
|
||||
title: Installation
|
||||
---
|
||||
|
||||
# Installation
|
||||
|
||||
You can import the SDK in your Gradle or Maven project using [Jitpack.io](https://jitpack.io). If using Gradle, to use
|
||||
JitPack all you have to do is insert these lines in you `build.gradle` file
|
||||
|
||||
```groovy
|
||||
allprojects {
|
||||
repositories {
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, you must import the correct dependency. In case you are building an Android-based project, you need to add this line
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
implementation 'com.github.status-im.status-keycard-java:android:3.0.2'
|
||||
}
|
||||
```
|
||||
|
||||
If you are working on the desktop, then you need this line instead
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
implementation 'com.github.status-im.status-keycard-java:desktop:3.0.2'
|
||||
}
|
||||
```
|
||||
|
||||
In both case, you will have the same SDK, except for the way connection with the card is established.
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
id: sdk_pinless
|
||||
title: Pinless signing
|
||||
---
|
||||
|
||||
# Pinless signing
|
||||
|
||||
The card offers the option to define a BIP32 path which can be used to sign transactions (or meta-transactions) without requiring a PIN. The **SIGN** command also has the option to sign with this pinless path (if defined) without having to know its value in advance. Since in such a scenario no sensitive data is being transferred, signing using the pinless path can be done without opening a Secure Channel.
|
||||
|
||||
There are several scenarios made possible by this feature. One example, is the ability to use the card for payments at POS terminals. Pairing with every terminal is not practical and inserting the PIN on an untrusted device (the terminal is owned by a 3rd party) is not safe, since it could be logged. Pinless signing with no Secure Channel solves both issues.
|
||||
|
||||
Since the wallet assigned to pinless signing is basically unprotected, it must not hold great value. One option is to consider the funds in that account like pocket cash. You put a little of your favourite cryptocurrency and should someone get hold of the card, you lost maybe the equivalent of $5. Another option, valid when using Ethereum, is to not hold any value in that wallet at all. Instead it can be used to sign meta-transactions and actual fund allocation can be handled by a smart contract through a dApp. Of course in this case the POS must be compatible with the specific payment scheme for this to work properly.
|
||||
|
||||
## Defining a pinless path
|
||||
|
||||
Defining which path is used for pinless signing, of course, requires PIN authentication and thus a Secure Channel. This means that only the owner can define which wallet is allocated for this operation. Then SDK call to set the pinless path is
|
||||
|
||||
```java
|
||||
cmdSet.setPinlessPath("m/44'/0'/0'/0/200").checkOK();
|
||||
```
|
||||
|
||||
## Signing with pinless path
|
||||
|
||||
When a pinless path is defined, a wallet (or POS terminal, or whatever) can invoke the `signPinless` method without having to know the path. If no pinless path was defined, the Status Word 0x6A88 will be returned. As mentioned before, opening a Secure Channel and authenticating with PIN before sending this command is allowed but not necessary.
|
||||
|
||||
```java
|
||||
cmdSet.signPinless(hashToSign).checkOK();
|
||||
```
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
id: sdk_preparation
|
||||
title: Preparation
|
||||
---
|
||||
|
||||
# Working with the applet
|
||||
|
||||
Regardless whether you are on Android or desktop, you should at this point have an implementation of the CardChannel interface (be it NFCCardChannel or PCSCCardChannel). You can now start working with the card. The first thing to do is creating a `KeycardCommandSet` instance. This class gives access to all of the applet functionality, wrapping the low-level APDUs in easy to use methods. All other classes in the SDK are helper to format parameters and parse responses from the card. To create a command set, just do
|
||||
|
||||
```java
|
||||
// cardChannel is our CardChannel instance
|
||||
KeycardCommandSet cmdSet = new KeycardCommandSet(cardChannel);
|
||||
```
|
||||
|
||||
## Applet selection
|
||||
|
||||
Modern SmartCards can have several applications installed, so after connection with the card you need to select the Keycard applet. This is easily done with
|
||||
|
||||
```java
|
||||
// The checkOK method can be called on any APDUResponse object to confirm that the
|
||||
cmdSet.select().checkOK();
|
||||
```
|
||||
|
||||
While this correctly selects the applet, it discards the card response, which contains information that can be useful to identify this specific card and its state. For this reason we could rewrite this as
|
||||
|
||||
```java
|
||||
ApplicationInfo info = new ApplicationInfo(cmdSet.select().checkOK().getData());
|
||||
|
||||
// This method tells if the card is initialized (has a PIN, PUK and pairing password). If it is not, it must be
|
||||
// initialized and no other operation is possible. Note that initialization touches only credentials to authenticate
|
||||
// the user or the client, but does not touch the creation of a wallet on the card
|
||||
info.isInitializedCard();
|
||||
|
||||
// Returns the instance UID of the applet. This can be used to identify this specific applet instance, very
|
||||
// useful when storing instance-specific data on the client (pairing info, cached data, etc).
|
||||
info.getInstanceUID();
|
||||
|
||||
// Returns the version of the applet.
|
||||
info.getAppVersion();
|
||||
|
||||
// Returns the number of free pairing slots. If you are not yet paired with the card, it helps you know if you can still
|
||||
// pair or not
|
||||
info.getFreePairingSlots();
|
||||
|
||||
// Tells if the card has a wallet or not. If no wallet is available, you must create once before you can perform most
|
||||
// operations on the card
|
||||
info.hasMasterKey();
|
||||
|
||||
// Returns the UID of the master key of the wallet. The UID is value generated starting from the public key and is
|
||||
// useful to identify if the card has the expected wallet.
|
||||
info.getKeyUID();
|
||||
```
|
||||
|
||||
After the applet is selected, you can start working with it. Note that the application remains selected until another applet is explicitly selected, or the card is powered off (for example is removed from the field)
|
||||
|
||||
## Applet initialization
|
||||
|
||||
This step is necessary to bring the initial credentials on the Keycard instance. When the card is not initialized, it cannot perform any operation. Initialization sets the initial PIN, PUK and pairing password and requires no authentication, but still uses a SecureChannel resistant to passive MITM attacks. Once the card is initialized, it cannot be initialized again (but credentials can be different with a different mechanism with previous authentication).
|
||||
|
||||
Initialization is done with
|
||||
|
||||
```java
|
||||
// Usually, you want to check if the card is initialized before trying to initialize it, otherwise you will receive an
|
||||
// error.
|
||||
if (!info.isInitializedCard()) {
|
||||
// The PIN must be 6 digits, the PUK 12 digits and the pairing password can be any password.
|
||||
// All parameters are strings
|
||||
cmdSet.init(pin, puk, pairingPassword).checkOK();
|
||||
}
|
||||
```
|
||||
|
||||
Since applet version 3.1 you can also specify the number of retries for PIN/PUK and a duress PIN
|
||||
|
||||
```java
|
||||
cmdSet.init(pin, puk, pairingPassword, 3, 5, duressPin).checkOK();
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
id: sdk_securechannel
|
||||
title: Secure Channel
|
||||
---
|
||||
|
||||
# Secure Channel
|
||||
|
||||
Communication with the card happens over a Secure Channel to protect sensitive information being transmitted. The Secure Channel relies on a pairing mechanism for mutual authentication.
|
||||
|
||||
## Verifying card genuinity
|
||||
Although this step is optional, it is highly recommended to verify that the applet on card is genuine. It is necessary to do this only once before pairing with the card. You can do this with
|
||||
|
||||
```java
|
||||
// challenge is a random 256-bit (32-bytes) challenge
|
||||
byte[] authData = cmdSet.identifyCard(challenge).checkOK().getData();
|
||||
// returns null if invalid or the public key of the signing authority/
|
||||
byte[] pubKey = Certificate.verifyIdentity(challenge, authData);
|
||||
```
|
||||
|
||||
You must verify that the public key returned by ```Certificate.verifyIdentity``` is that of a certification authority you trust.
|
||||
|
||||
## Pairing
|
||||
|
||||
Clients wishing to communicate with the card, need to pair with it first. This allows creating secure channels resistant not only to passive but also to active MITM attacks. Although pairing allows the card and the client to authenticate each other, the card does not grant access to any operation with the wallet until the user is authenticated (by verifying its PIN). To establish the pairing, the client needs to know the pairing password. After it is established, the pairing info (not the password) must be stored as securely as possible on the client for subsequent sessions. You should store the pairing information together with the instance UID to simplify handling of multiple cards.
|
||||
|
||||
Only 5 clients can be paired at once, but it is possible to unpair previously paired clients.
|
||||
|
||||
Using the SDK, pairing is a simple operation
|
||||
|
||||
```java
|
||||
// pairingPassword is usually provided by the user. This method throws an exception if pairing fails.
|
||||
cmdSet.autoPair(pairingPassword);
|
||||
// Retrieves the pairing object from the command set. This is what must be persisted (together with the instance UID)
|
||||
Pairing pairing = cmdSet.getPairing();
|
||||
// The pairing object can be serialized by calling
|
||||
pairing.toByteArray();
|
||||
// or the convenience method
|
||||
pairing.toBase64();
|
||||
```
|
||||
|
||||
If you have already paired, you should instead load the persisted pairing information in the command set
|
||||
|
||||
```java
|
||||
// serializedPairing can be either the byte array or base64 string representation
|
||||
Pairing pairing = new Pairing(serializedPairing);
|
||||
// Sets the pairing info in the command set. This must be done before further operation is possible
|
||||
cmdSet.setPairing(pairing);
|
||||
```
|
||||
|
||||
## Opening the Secure Channel
|
||||
|
||||
After a pairing has been established, a secure channel can be opened. Before opening a secure channel, the card won't allow sending any command. This guarantees secrecy, integrity and authenticity of the commands. Opening a secure channel must be performed every time the applet is selected (this means also after a power loss). After opening it, the SDK handles the secure channel transparently, encrypting and signing all command APDUs and decrypting and verifying the signature of all responses. To open a secure channel all you need to do is
|
||||
|
||||
```java
|
||||
cmdSet.autoOpenSecureChannel();
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
id: web3
|
||||
title: Web3 API Extensions
|
||||
---
|
||||
|
||||
# Web3 API Extensions
|
||||
|
||||
The Keycard wallet behaves like a regular hardware wallet and thus dApps can use it transparently as any other account. The Cash applet however, not being associated to a wallet and not being suitable to sign transactions (since it does not hold funds), must be specifically supported by dApps supporting it, usually to sign [EIP-712](https://eips.ethereum.org/EIPS/eip-712) formatted metatx. For this reason we defined some extensions to the Web3 API. At the moment, these are supported by the [Status](https://status.app) app. The specifications in this page are both for dApps developers wanting to support Keycard Cash in their dApp as well as for wallet app developers wanting to implement these extensions.
|
||||
|
||||
## EIP-712 signing
|
||||
|
||||
Support for this is provided by a single function: `keycard_signTypedData(data)`.
|
||||
|
||||
This function takes the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) data in the same format as `eth_signTypedData` and returns a signature generated by the tapped Keycard Cash. Unlike `eth_signTypedData` there is no `address` argument because the address will be that of the Cash applet of whatever Keycard is tapped by the user. The returned signature can then be sent, together with the message data, to an EIP-712 compatible smartcontract by the dApp. Assuming the Web 3.0 browser runs on a smartphone, calling this function will cause a pop-up to appear on the phone prompting to tap the Keycard. Since the function has all of the data being signed available, the screen can display this data in a user friendly way. In Status, messages for some specific smartcontracts are recognized and a custom UI is shown. These UX concern are not part of the specification itself, but implementers may want to consider this approach.
|
||||
|
||||
In order to generate the signature using Keycard Cash, browsers implementing this function should hash the message data using keccak256, listen on the NFC interface and send the hash to the Keycard Cash applet which will return a signature. More details can be found in the [Cash applet](sdk/cash) section of this site.
|
||||
|
||||
This function only returns a signature. If your dApp requires knowing the address of the signer you can use `recoverTypedSignature(data, sig)` from the [eth-sig-util](https://github.com/MetaMask/eth-sig-util) package.
|
||||
|
||||
### Implementation notes
|
||||
|
||||
This section explains some UX choices adopted by Status when implementing the `keycard_signTypedData(data)` in the browser. While these are not part of the specifications, it helps to visualize what happens when a dApp invokes this function and can be used by both web3 browser devs and dApp developers as a reference.
|
||||
|
||||
The idea behind's Status implementation, is to have a generic way to display these transactions, but if it finds specific fields in the data to sign then try to show a more user friendly interface. The generic screen looks like this
|
||||
|
||||

|
||||
|
||||
Clicking on the "Show transaction data" brings up something like this
|
||||
|
||||

|
||||
|
||||
When however the data to be signed contains `uint256 amount` and `address currency` among its fields this is interpreted as a payment transaction, and the following screen is shown instead.
|
||||
|
||||

|
||||
|
||||
If the currency field address is considered to be the address of an ERC20 token. If this address is zero, the amount is interpreted as plain ETH. This transaction format is used by the [Keycard Payment Network](https://github.com/status-im/payment-network-apps) project.
|
||||
|
||||
Another example of a custom screen, is the one displayed for the [Keycard Redeem](https://github.com/status-im/keycard-redeem) project. In this case we recognize the presence of the fields `address recipient` and `bytes32 code` field as a redeem request and we display the recipient field address clearly in the signature screen. If the recipient field in account managed by Status, instead of the plain address, the account name is shown. See screenshot below
|
||||
|
||||

|
||||
|
||||
One thing to keep in mind however, is that these custom screens are only there to provide better UX for some specific use case we are aware of. Any unrecognized message format can still be displayed generically and signed. If you are implementing a web3 browser you can adopt any strategy you prefer on this. Conversely, if you are developing SmartContract and dApps, you might want consider naming the fields of the message in a way that will trigger a specific interface on a browser. For example if your use case scenario involve transferring a certain `amount` of `currency` in a transaction, naming the message fields like that will show an easy to understand screen in Status.
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: Keycard Presale Website - Privacy Policy
|
||||
---
|
||||
|
||||
This Privacy Policy is intended to inform users of our approach to privacy in respect of this website (“**Website**”). In this regard, if you are visiting our Website, this Privacy Policy applies to you.
|
||||
|
||||
## 1) Who we are
|
||||
|
||||
For the purposes of this Privacy Policy and the collection and processing of personal data as a controller, the relevant entity is the Logos Collective Association, which has its registered office in Zug and its legal domicile address at
|
||||
|
||||
<ins>Logos Collective Association</ins>
|
||||
c/o PST Consulting GmbH
|
||||
Baarerstrasse 10
|
||||
6300 Zug
|
||||
Switzerland
|
||||
|
||||
Whenever we refer to “Logos”, “we” or other similar references, we are referring to the Logos Collective Association.
|
||||
|
||||
## 2) We limit the collection and processing of personal data from your use of the Website
|
||||
|
||||
We aim to limit the collection and collection and processing of personal data from users of the Website. We only collect and process certain personal data for specific purposes and where we have the legal basis to do so under applicable data protection law. We will not collect or process any personal data that we don’t need and where we do store any personal data, we will only store it for the least amount of time needed for the indicated purpose.
|
||||
|
||||
In this regard, we collect and process the following personal data from your use of the Website:
|
||||
|
||||
* **IP address**: As part of such use of the Website we briefly process your IP address but we have no way of identifying you. We however have a legitimate interest in processing such IP addresses to ensure the technical functionality and enhance the security measures of the Website. This IP address is not stored by us over time.
|
||||
|
||||
* **Collection of email addresses**: We only collect your email address on our website for the purpose of sending you information in relation to the development of and the potential sale of Keycard Pro. Your email address is securely stored on our self-hosted servers, and we have implemented measures to protect such personal data by securing it with appropriate technical and organisational measures. We will not share such personal data to third parties without your consent, except as required by law. We will only collect and process your email address on the basis of you providing your consent for us to do so. We will retain your email address for as long as necessary to provide you with updates about Keycard Pro, unless you withdraw your consent or request deletion earlier
|
||||
|
||||
## 3) Third party processing of personal data
|
||||
|
||||
In addition to our limited and collection of personal data, third parties may collect or process personal data as a result of the Website making use of certain features or to provide certain content. To the extent you interact with such third party content or features, their respective privacy policies will apply.
|
||||
|
||||
## 4) Security measures we take in respect of the Website
|
||||
|
||||
As a general approach, we take data security seriously and we have implemented a variety of security measures on the Website to maintain the safety of your personal data when you submit such information to us.
|
||||
|
||||
## 5) Exporting data outside the European Union and Switzerland
|
||||
|
||||
We are obliged to protect the privacy of personal data that you may have submitted in the unlikely event that we export your personal data to places outside the European Union or Switzerland. This means that personal data will only be processed in countries or by parties that provide an adequate level of protection as deemed by Switzerland or the European Commission. Otherwise, we will use other forms of protections, such as specific forms of contractual clauses to ensure such personal data is provided the same protection as required in Switzerland or Europe. In any event, the transmission of personal data outside the European Union and Switzerland will always occur in conformity with applicable data protection law.
|
||||
|
||||
## 6) Your choices and rights
|
||||
|
||||
As explained in this Privacy Policy, we limit our collection and processing of your personal data wherever possible. Nonetheless, you still have certain choices and rights in respect of the personal data which we do collect and process. As laid out in relevant applicable data protection law, you have the right to:
|
||||
|
||||
* Ask us to correct or update your personal data (where reasonably possible);
|
||||
* Ask us to remove your personal data from our systems;
|
||||
* Ask us for a copy of your personal data, which may also be transferred to another data controller at your request;
|
||||
* Withdraw your consent to process your personal data (only if consent was asked for a processing activity), which only affects processing activities that are based on your consent and doesn’t affect the validity of such processing activities before you have withdrawn your consent;
|
||||
* Object to the processing of your personal data; and
|
||||
* File a complaint with the Federal Data Protection and Information Commissioner (FDPIC), if you believe that your personal data has been processed unlawfully.
|
||||
|
||||
## 7) Third party links
|
||||
|
||||
On this Website, you may come across links to third party websites. These third party sites have separate and independent privacy policies. We therefore have no responsibility or liability for the content and activities of these third party websites.
|
||||
|
||||
## 8) This Privacy Policy might change
|
||||
|
||||
We may modify or replace any part of this Privacy Policy at any time and without notice. Please check the Website periodically for any changes. The new Privacy Policy will be effective immediately upon its posting on our Website.
|
||||
|
||||
## 9) Contact information
|
||||
|
||||
To the extent that you have any questions about the Privacy Policy, please contact us at [legal@free.technology](mailto:legal@free.technology).
|
||||
|
||||
This document is licensed under CC-BY-SA.
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: Keycard Presale - Terms of Use
|
||||
---
|
||||
|
||||
These website terms of use (“**Website Terms of Use**”) are entered into by you and us, and they govern your access and use of this Website, including any content and functionality contained in the Website.
|
||||
|
||||
It is your responsibility to read the Website Terms of Use carefully before your use of the Website and your use of the Website means you have agreed to be bound and comply with these Website Terms of Use.
|
||||
|
||||
If you do not agree with these Website Terms of Use, you must not access or use the Website.
|
||||
|
||||
## 1) Who we are
|
||||
|
||||
For the purposes of these Website Terms of Use, the relevant entity is the Logos Collective Association, which has its registered office in Zug and its legal domicile address at:
|
||||
|
||||
<ins>Logos Collective Association</ins>
|
||||
c/o PST Consulting GmbH
|
||||
Baarerstrasse 10
|
||||
6300 Zug
|
||||
Switzerland
|
||||
|
||||
Whenever we refer to “Logos”, “we”, “us” or any other similar references, we are referring to the Logos Collective Association.
|
||||
|
||||
## 2) Disclaimers
|
||||
|
||||
The Website is provided by us on an ‘as is’ basis and you use the Website at your own sole discretion and risk.
|
||||
|
||||
We disclaim all warranties of any kind, express or implied, including without limitation the warranties of merchantability, fitness for a particular purpose, and non-infringement of intellectual property or other violation of rights. We do not warrant or make any representations concerning the completeness, accuracy, legality, utility, reliability, suitability or availability of the use of the Website, the content on this Website or otherwise relating to the Website, such content or on any sites linked to this site. These disclaimers will apply to the maximum extent permitted by applicable law.
|
||||
|
||||
We make no claims that the Website or any of its content is accessible, legally compliant or appropriate in your jurisdiction. Your access or use of the Website is at your own sole discretion and you are solely responsible for complying with any applicable local laws.
|
||||
|
||||
The content herein or as accessible through the Website is intended to be made available for informational purposes only and should not be considered as creating any expectations or forming the basis of any contract, commitment or binding obligation with us. No information herein shall be considered to contain or be relied upon as a promise, representation, warranty or guarantee, whether express or implied and whether as to the past, present or the future in relation to the projects and matters described herein.
|
||||
|
||||
The information contained herein does not constitute financial, legal, tax, or other advice and should not be treated as such.
|
||||
|
||||
## 3) Forward looking statements
|
||||
|
||||
The Website may also contain forward-looking statements that are based on current expectations, estimates, forecasts, assumptions and projections about the technology, industry and markets in general.
|
||||
|
||||
The forward looking statements, which may include statements about the roadmap, project descriptions, technical details, functionalities, features, the development and use of tokens by projects, and any other statements related to such matters or as accessible through this website are subject to a high degree of risk and uncertainty. The forward looking statements are subject to change based on, among other things, market conditions, technical developments, and regulatory environment. The actual development and results, including the order and the timeline, might vary from what’s presented. The information contained herein is a summary and does not purport to be accurate, reliable or complete and we bear no responsibility for the accuracy, reliability or completeness of information contained herein. Because of the high degree of risk and uncertainty described above, you should not place undue reliance on any matters described in this website or as accessible through this website.
|
||||
|
||||
While we aim to update the Website regularly, all information, including the timeline and the specifics of each stage, is subject to change and may be amended or supplemented at any time, without notice and at our sole discretion.
|
||||
|
||||
## 4) Intellectual property rights
|
||||
|
||||
The Website and its contents are made available under Creative Commons Attribution 4.0 International license (CC-BY 4.0). In essence this licence allows users to copy, modify and distribute the content in any format for any purpose, including commercial use, subject to certain requirements such as attributing us. For the full terms of this licence, please refer to the following website: [https://creativecommons.org/licenses/by/4.0/](https://creativecommons.org/licenses/by/4.0/).
|
||||
|
||||
## 5) Third party website links
|
||||
|
||||
To the extent the Website provides any links to a third party website, then their terms and conditions, including privacy policies, govern your use of those third party websites. By linking such third party websites, we do not represent or imply that we endorse or support such third party websites or content therein, or that we believe such third party websites and content therein to be accurate, useful or non-harmful. We have no control over such third party websites and will not be liable for your use of or activities on any third party websites accessed through the Website. If you access such third party websites through the Website, it is at your own risk and you are solely responsible for your activities on such third party websites.
|
||||
|
||||
## 6) Limitation of liability
|
||||
|
||||
We will not be held liable to you under any contract, negligence, strict liability, or other legal or equitable theory for any lost profits, cost of procurement for substitute services, or any special, incidental, or consequential damages related to, arising from, or in any way connected with these Website Terms of Use, the Website, the content on the Website, or your use of the Website, even if we have been advised of the possibility of such damages. In any event, our aggregate liability for such claims is limited to EUR 100 (one hundred Euros). This limitation of liability will apply to the maximum extent permitted by applicable law.
|
||||
|
||||
## 7) Indemnity
|
||||
|
||||
You shall indemnify us and hold us harmless from and against any and all claims, damages and expenses, including attorneys’ fees, arising from or related to your use of the Website, the content on the Website, including without limitation your violation of these Website Terms of Use.
|
||||
|
||||
## 8) Modifications
|
||||
|
||||
We may modify or replace any part of this Website Terms of Use at any time and without notice. You are responsible for checking the Website periodically for any changes. The new Website Terms of Use will be effective immediately upon its posting on the Website.
|
||||
|
||||
## 9) Governing law
|
||||
|
||||
Swiss law governs these Website Terms of Use and any disputes between you and us, whether in court or arbitration, without regard to conflict of laws provisions.
|
||||
|
||||
## 10) Disputes
|
||||
|
||||
In these terms, “dispute” has the broadest meaning enforceable by law and includes any claim you make against or controversy you may have in relation to these Website Terms of Use, the Website, the content on the Website, or your use of the Website.
|
||||
|
||||
We prefer arbitration over litigation as we believe it meets our principle of resolving disputes in the most effective and cost effective manner. You are bound by the following arbitration clause, which waives your right to litigation and to be heard by a judge. Please note that court review of an arbitration award is limited. You also waive all your rights to a jury trial (if any) in any and all jurisdictions.
|
||||
|
||||
If a (potential) dispute arises, you must first use your reasonable efforts to resolve it amicably with us. If these efforts do not result in a resolution of such dispute, you shall then send us a written notice of dispute setting out (i) the nature of the dispute, and the claim you are making; and (ii) the remedy you are seeking.
|
||||
|
||||
If we and you are unable to further resolve this dispute within sixty (60) calendar days of us receiving this notice of dispute, then any such dispute will be referred to and finally resolved by you and us through an arbitration administered by the Swiss Chambers’ Arbitration Institution in accordance with the Swiss Rules of International Arbitration for the time being in force, which rules are deemed to be incorporated herein by reference. The arbitral decision may be enforced in any court. The arbitration will be held in Zug, Switzerland, and may be conducted via video conference virtual/online methods if possible. The tribunal will consist of one arbitrator, and all proceedings as well as communications between the parties will be kept confidential. The language of the arbitration will be in English. Payment of all relevant fees in respect of the arbitration, including filing, administration and arbitrator fees will be in accordance with the Swiss Rules of International Arbitration.
|
||||
|
||||
Regardless of any applicable statute of limitations, you must bring any claims within one year after the claim arose or the time when you should have reasonably known about the claim. You also waive the right to participate in a class action lawsuit or a classwide arbitration against us.
|
||||
|
||||
## 11) About these Website Terms of Use
|
||||
|
||||
These Website Terms of Use cover the entire agreement between you and us regarding the Website and supersede all prior and contemporaneous understandings, agreements, representations and warranties, both written and oral, with respect to the Website.
|
||||
|
||||
The captions and headings identifying sections and subsections of these Website Terms of Use are for reference only and do not define, modify, expand, limit, or affect the interpretation of any provisions of these Website Terms of Use.
|
||||
|
||||
If any part of these Website Terms of Use is held invalid or unenforceable, that part will be severable from these Website Terms of Use, and the remaining portions will remain in full force and effect. If we fail to enforce any of these Website Terms of Use, that does not mean that we have waived our right to enforce them.
|
||||
|
||||
If you have any specific questions about these Website Terms of Use, please contact us at [legal@free.technology](mailto:legal@free.technology).
|
||||
|
||||
This document is licensed under CC-BY-SA.
|
||||
@@ -0,0 +1,11 @@
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv extends Env {
|
||||
SHOPIFY_STORE_DOMAIN: string
|
||||
SHOPIFY_STOREFRONT_API_PUBLIC_ACCESS_TOKEN: string
|
||||
SHOPIFY_ADMIN_API_PRIVATE_ACCESS_TOKEN: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
@@ -1,4 +1,18 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const config = {}
|
||||
const config = {
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'demo.ghost.io',
|
||||
pathname: '/**',
|
||||
},
|
||||
],
|
||||
},
|
||||
transpilePackages: [
|
||||
// why: https://github.com/hashicorp/next-mdx-remote/issues/467#issuecomment-2432166413
|
||||
'next-mdx-remote',
|
||||
],
|
||||
}
|
||||
|
||||
export default config
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
"name": "keycard-website",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prepare": "husky",
|
||||
"dev": "pnpm install && next dev",
|
||||
"predev": "pnpm install",
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
@@ -10,27 +12,63 @@
|
||||
"check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@radix-ui/react-accordion": "^1.2.1",
|
||||
"@radix-ui/react-checkbox": "^1.1.2",
|
||||
"@radix-ui/react-dialog": "^1.1.2",
|
||||
"@radix-ui/react-tabs": "^1.1.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.3",
|
||||
"@tanstack/react-query": "^5.59.17",
|
||||
"@tryghost/content-api": "^1.11.21",
|
||||
"@types/unist": "^3.0.3",
|
||||
"cva": "1.0.0-beta.1",
|
||||
"next": "14.2.15",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"embla-carousel-react": "^8.3.1",
|
||||
"embla-carousel-wheel-gestures": "^8.0.1",
|
||||
"framer-motion": "^11.11.9",
|
||||
"github-slugger": "^2.0.0",
|
||||
"google-auth-library": "^9.14.2",
|
||||
"google-spreadsheet": "^4.1.4",
|
||||
"next": "15.0.2",
|
||||
"next-mdx-remote": "^5.0.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"react-children-utilities": "^2.10.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.53.1",
|
||||
"server-only": "^0.0.1",
|
||||
"ts-pattern": "^5.5.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.3.1",
|
||||
"@types/d3-time-format": "^4.0.3",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/tryghost__content-api": "^1.3.17",
|
||||
"@types/unist": "^3.0.3",
|
||||
"embla-carousel": "^8.3.1",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-next": "14.2.15",
|
||||
"eslint-config-next": "15.0.2",
|
||||
"eslint-plugin-tailwindcss": "^3.17.5",
|
||||
"husky": "^9.1.6",
|
||||
"lint-staged": "^15.2.10",
|
||||
"mdast-util-to-string": "^4.0.0",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||
"rehype-parse": "^8.0.4",
|
||||
"rehype-pretty-code": "^0.14.0",
|
||||
"rehype-react": "^7.2.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-mdx": "^3.1.0",
|
||||
"tailwind-scrollbar-utilities": "^0.2.0",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.10.0"
|
||||
"typescript-eslint": "^8.10.0",
|
||||
"unified": "^10.1.2",
|
||||
"unist-util-visit": "^5.0.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,mjs,ts,tsx}": [
|
||||
@@ -39,6 +77,9 @@
|
||||
],
|
||||
"*.css": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{md,mdx}": [
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 859 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 870 B |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 286 KiB |
|
After Width: | Height: | Size: 305 KiB |
|
After Width: | Height: | Size: 369 KiB |
|
After Width: | Height: | Size: 373 KiB |
|
After Width: | Height: | Size: 239 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 240 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.1 KiB |