├── README.md ├── images ├── demo-application.png ├── packet.json └── packet.svg ├── implementation.md ├── milestones.md └── tools ├── .gitignore ├── Makefile └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # Nitrokey WebSmartCard 2 | 3 | ### Smart Cards for the Web! 4 | 5 | 6 | ![Demo application view](images/demo-application.png) 7 | 8 | ## Summary 9 | 10 | The Nitrokey is an open-source hardware USB key that provides data encryption and two-factor authentication capabilities 11 | using FIDO standards. While FIDO is supported by web browsers, utilizing the Nitrokey as a secure key store for email 12 | and data encryption has traditionally required native software. As a result, email encryption within webmail using the 13 | Nitrokey has not been possible until now. 14 | 15 | Similarly, achieving strong end-to-end encryption in web applications has faced a common challenge: securely and 16 | conveniently storing users' private keys. This typically requires native software, such as an instant messenger app, or 17 | less secure methods, such as storing password-encrypted user keys on servers. To address these issues, Nitrokey aims to 18 | enable the use of Nitrokey with web applications. 19 | 20 | WebAuthn is a modern web authentication method that replaces passwords with public key cryptography. It uses FIDO (CTAP) 21 | protocol to communicate with user's security devices. It allows websites to use strong security features built into 22 | devices like Nitrokey 3. 23 | By creating a private-public keypair (credential) for each website, where the private key is securely stored on the 24 | user's device and the public key is sent to the server, WebAuthn enables servers to verify users' identities without 25 | handling any secrets, making it significantly more secure and less susceptible to hacking attempts 26 | 27 | The Nitrokey WebSmartCard project utilizes the FIDO (CTAP) protocol, shared by WebAuthn, to eliminate the need for device 28 | drivers, browser add-ons, or separate software. This integration allows the solution to seamlessly function with any 29 | modern browser supporting WebAuthn on various operating systems, including Android, and across different communication 30 | channels like Bluetooth, NFC, and USB. 31 | 32 | As a result, web applications gain the capability to locally store users' private keys on a Nitrokey, ensuring users 33 | retain full control over their keys. 34 | 35 | Note: Nitrokey WebSmartCard was formerly known as Nitrokey WebCrypt. 36 | 37 | A demo application is available under: 38 | - https://webcrypt.nitrokey.com 39 | 40 | It can be used with any Nitrokey 3 device, which uses a "test" firmware, e.g. [v1.5.0-test.20230704](https://github.com/Nitrokey/nitrokey-3-firmware/releases/tag/v1.5.0-test.20230704). 41 | 42 | See all the connected projects at: 43 | - https://github.com/topics/nitrokey-webcrypt 44 | - https://github.com/topics/nitrokey-websmartcard 45 | 46 | ## Terminology 47 | 48 | To ensure clarity and shared understanding, the following key terms are used throughout this documentation: 49 | 50 | 1. **Device**: Refers to a WebSmartCard-compliant device that is typically in the possession of a user and connected via 51 | USB, NFC, or Bluetooth. 52 | 2. **Web application**: A JavaScript-based application running in a web browser and capable of communicating with 53 | servers over the internet. 54 | 3. **Client software**: Any software that directly communicates with the WebSmartCard-compliant device, either directly or 55 | through a web browser. 56 | 4. **Browser**: One of the platforms on which the client software runs. 57 | 5. **Main key**: The primary secret key stored on the device, which can be represented by a Word Seed for backup 58 | purposes. 59 | 6. **Resident key**: While distinct from FIDO's resident keys (aka Discoverable Keys), the concept is similar. 60 | WebSmartCard's Resident Keys are stored on the device and can be created through importation or generation. 61 | 7. **Derived key**: Although different from FIDO's derived keys, the concept is similar. Derived keys are generated from 62 | the main secret key, along with service metadata such as the RPID (including the domain name) or the user's 63 | additional passphrase. 64 | 8. **Seed** or **Word Seed**: A phrase consisting of 24-30 words from a known, limited word count dictionary, enabling the restoration of 65 | the main key on any device. 66 | 9. **PIN**: A password or passphrase with a limited number of attempts, used to unlock the device and execute WebSmartCard 67 | operations. 68 | 10. **Backup**: A data structure that facilitates the restoration of the device's state on the same or a different 69 | instance. 70 | 11. **KDF**: Short for Key Derivation Function, it refers to a cryptographic hash function that derives a secret key 71 | from an input, such as a passphrase, using a pseudorandom function. 72 | 73 | Please refer to these defined terms throughout the documentation to ensure a consistent understanding of their meanings. 74 | 75 | # Solution 76 | 77 | ## Implementation Details 78 | 79 | The implementation of Nitrokey WebSmartCard incorporates the following design considerations: 80 | 81 | 1. **Inspiration from OnlyKey's WebCrypt**: The solution draws inspiration from OnlyKey's WebCrypt proof-of-concept, 82 | leveraging its concepts and ideas. 83 | 84 | 2. **CTAP2 (FIDO2) Compliance**: To ensure future-proofness and compatibility, Nitrokey WebSmartCard utilizes CTAP2 (FIDO2) 85 | and newer specifications. This choice mitigates potential incompatibilities in the long run. Additionally, for 86 | backward compatibility (optional), CTAP1 (FIDO U2F) may be included. 87 | 88 | 3. **Focus on ECC Algorithms**: To simplify usage and enhance usability, Nitrokey WebSmartCard primarily focuses on elliptic 89 | curve cryptography (ECC) algorithms. The support for RSA is provided as an optional feature, specifically for key 90 | importing and utilization of existing keys. 91 | 92 | 4. **OpenPGP Card Interface**: The OpenPGP Card interface is integrated to facilitate the implementation of OpenPGP.js 93 | with the support of a hardware Secure Element, ensuring secure operations for this JavaScript library. 94 | 95 | 5. **Support for USB, NFC, and Web Browsers**: Nitrokey WebSmartCard is designed to seamlessly work with WebSmartCard-compliant 96 | devices via USB and NFC connections. Furthermore, it offers compatibility with standard web browsers, enabling broad 97 | accessibility and usage across different platforms. 98 | 99 | By adhering to these implementation details, Nitrokey WebSmartCard aims to provide a robust, secure, and user-friendly 100 | experience for developers leveraging its functionalities. Please refer to the [implementation](./implementation.md) 101 | sections for more detailed information. 102 | 103 | ### Communication Channel 104 | 105 | The following sequence diagram illustrates the process of a high-level command exchange between Alice and a JavaScript Client 106 | Application (with an optional communication to the Web Service, which Alice visits), which communicates with the Nitrokey WebSmartCard API and the WebSmartCard Device (e.g. Nitrokey 3). The 107 | communication involves multiple requests and responses using Webauthn/FIDO2/CTAP2 and encoded in CBOR. Ultimately, the 108 | result of the command is sent back to Alice. 109 | 110 | ```mermaid 111 | sequenceDiagram 112 | autonumber 113 | actor Alice 114 | participant service as Web Service 115 | box Web Browser 116 | participant ClientApp as JavaScript Client Application 117 | participant API as Nitrokey WebSmartCard API
(JavaScript Library) 118 | end 119 | participant Device as WebSmartCard Device
(e.g. Nitrokey 3) 120 | 121 | Alice->>ClientApp: High-level command 122 | ClientApp-->>service: Additional processing or
data request (optional) 123 | service-->>ClientApp: Result 124 | ClientApp->>API: Request 125 | Loop Communicating with the device over multiple requests 126 | API->>Device: Request sent using Webauthn/FIDO2/CTAP2,
CBOR encoded 127 | Device->>API: Response sent using Webauthn/FIDO2/CTAP2,
CBOR encoded 128 | end 129 | API->>ClientApp: Response 130 | ClientApp->>Alice: Result 131 | ``` 132 | 133 | 134 | 135 | ### Example Use Case 136 | 137 | #### OpenPGP Encrypted Email Exchange 138 | 139 | The sequence diagram outlines the secure message exchange process between Alice and Bob using OpenPGP.js Client 140 | Applications, WebSmartCard API, and WebSmartCard Devices (e.g. Nitrokey 3). Both participants have initialized and 141 | ready-to-use WebSmartCard Devices. The exchange involves: 142 | 143 | 1. **Initialization**: Alice and Bob exchange their public keys once for secure communication. 144 | 145 | 2. **Message Exchange**: Alice sends an encrypted message to Bob using Bob's public key. The encryption occurs in the 146 | browser using the recipient's public key. Bob receives the ciphertext over an insecure channel. 147 | 148 | 3. **Decryption**: Bob decrypts the message using OpenPGP.js. The unpacked ciphertext is sent to the WebSmartCard Device 149 | for decryption using the private key. Finally, Bob successfully decrypts and reads the plaintext message. 150 | 151 | ```mermaid 152 | sequenceDiagram 153 | autonumber 154 | box Alice's Equipment 155 | participant ClientAppAlice as OpenPGP.js Client Application
(Web Browser) 156 | end 157 | box Participants 158 | actor Alice 159 | actor Bob 160 | end 161 | box Bob's Equipment 162 | participant ClientApp as OpenPGP.js Client Application
(Web Browser) 163 | participant API as Nitrokey WebSmartCard API
(JavaScript library) 164 | participant Device as WebSmartCard Device
(e.g. Nitrokey 3) 165 | end 166 | 167 | Note over Bob, Alice: Both participants have initialized
and ready-to-use WebSmartCard Devices 168 | 169 | Alice->>Bob: Hello Bob, let's exchange messages securely! 170 | activate Alice 171 | Alice->>Bob: Alice sends public key 172 | Bob->>Alice: Bob sends public key 173 | activate Bob 174 | 175 | Note over Bob, Alice: The exchange of public keys only needs to be done once 176 | 177 | 178 | Alice->>Bob: I want to send this encrypted message to you. 179 | Alice->>ClientAppAlice: Plaintext 180 | Note over ClientAppAlice: Encryption executed in the browser
using the recipient's Public Key 181 | ClientAppAlice->>Alice: Ciphertext within encoded OpenPGP message 182 | Alice->>Bob: Ciphertext within encoded OpenPGP message
sent over an insecure channel 183 | Bob->>ClientApp: Decrypt OpenPGP message using OpenPGP.js 184 | ClientApp->>API: Ciphertext decryption request 185 | API->>Device: Decrypt this ciphertext 186 | Note over Device: Decryption performed using the private key 187 | Device->>API: Plaintext 188 | API->>ClientApp: Plaintext 189 | ClientApp->>Bob: Plaintext 190 | 191 | Note over Bob: Message successfully decrypted 192 | 193 | ``` 194 | 195 | ## User Keys 196 | 197 | The user keys feature of this solution provides support for multiple keys, which can be derived dynamically from a main 198 | key or imported and securely stored as resident keys on the device. All key operations, such as signing and decryption, 199 | require the corresponding public key to be provided as a parameter. The key operations follow the following scheme: 200 | 201 | 1) First, the system checks if a public key or key handle matches any stored (resident) key and origin. If there is no 202 | match, the process continues with step 2. If there is a match, the process proceeds to step 3. 203 | 204 | 2) In step 2, the key is derived using the key derivation function (KDF) with the main key, key handle, and origin as 205 | inputs. The derived key is then verified for validity against the HMAC. 206 | 207 | 3) Once the key is found, the key operation is computed using the payload. 208 | 209 | 4) Finally, the result of the key operation is returned. 210 | 211 | Additional information: 212 | 213 | - Each key's attributes include a usage flag indicating whether it is meant for encryption/decryption, signing, or both. 214 | - The main key used in the derivation process is 256 bits in length. 215 | 216 | Note: The derivation algorithm is currently a work in progress and subject to ongoing development and refinement. 217 | 218 | #### Cross and Same Origin Keys 219 | 220 | Nitrokey WebSmartCard supports the configuration of keys for cross-origin or same-origin usage, allowing flexibility while 221 | addressing potential privacy risks. Use cases that involve sharing keys across different origins, such as email 222 | encryption, can choose to disable this option for their specific keys. 223 | 224 | For same-origin **resident** keys, the associated origin is securely stored along with the secret key on the device. When 225 | accessing a same-origin key, the device verifies the origin to ensure its validity. 226 | 227 | For same-origin **derived** keys, the origin is provided as input to the Key Derivation Function (KDF). 228 | As a result, same-origin and cross-origin scenarios yield distinct keys. 229 | 230 | When invoking a key operation, the web application specifies, via a parameter, whether a same-origin or cross-origin key 231 | should be utilized. The browser, rather than the web application, provides the actual origin information, ensuring 232 | accurate and secure origin determination. 233 | 234 | Additionally, cross-origin keys can be configured with an allowed domain list to ensure their scope is limited to specific services, providing enhanced user privacy protection. 235 | 236 | #### User Authentication and PIN Mechanism 237 | 238 | The PIN serves as a crucial authentication factor for users when authorizing key operations within Nitrokey WebSmartCard. It 239 | can be configured to operate in either of the following modes: 240 | 241 | 1. **PIN Required for Every Operation**: In this mode, the PIN is required for authentication before every key 242 | operation. This ensures heightened security but may involve repeated PIN entry for consecutive operations. 243 | 244 | 2. **PIN Required Once per Device Session**: Alternatively, the PIN can be configured to be required only once per 245 | device session. This option reduces the frequency of PIN entry during consecutive operations while maintaining the 246 | necessary level of security. The PIN has to be entered for each Origin separately. 247 | 248 | 3. **PIN Required After a Timeout**: Timed-out PIN requirement for enhanced security. Once the PIN is initially entered 249 | and 250 | authenticated, the device will accept all commands originating from the authenticated origin for a specified period 251 | of time. This period is determined by a timeout mechanism, which can be set as a constant value or calculated based 252 | on the duration since the last action. This approach strikes a balance between user convenience and security, 253 | minimizing the need for repetitive PIN entry while maintaining the necessary level of protection. This employs a 254 | similar approach to the one adopted in online banking access, where an auto-logout takes place after a specified 255 | period of inactivity. 256 | 257 | In either case, every sign and decrypt operation requires touch confirmation, typically in the form of a button press, 258 | to authorize the operation. This additional step ensures explicit user consent for sensitive operations. 259 | 260 | To ensure both interoperability and enhanced security, Nitrokey WebSmartCard utilizes WebAuthn's PIN mechanism. This choice 261 | provides the advantage of improved compatibility across different platforms and, importantly, prevents the exposure of 262 | the PIN to JavaScript, thereby bolstering security. 263 | 264 | For backward compatibility and to accommodate FIDO U2F handling, there may be an option to unlock a device with a PIN 265 | passed through the JavaScript application. However, it's important to note that this configuration option is designed to 266 | be user-controlled. 267 | However, due to the widespread adoption of FIDO2, we are considering removing support for FIDO U2F and its associated 268 | commands. As FIDO2 gains popularity, it has become the preferred standard for secure authentication. This shift allows 269 | us to streamline our implementation and focus solely on supporting FIDO2 functionalities. By aligning with the 270 | prevailing industry standard, we can ensure better compatibility and improved user experience for our users. 271 | 272 | #### Seed and Key Generation 273 | 274 | During device initialization, Nitrokey WebSmartCard establishes a seed (sourced randomly, or generated from the user-provided 24+ word seed), which serves as the foundation for deriving a main 275 | key and an encryption key. The main key is utilized to derive various keys, whereas encryption key functions as a 276 | source for encrypting resident keys. 277 | 278 | The seed holds significant importance as it enables the regeneration of all device-generated keys (except for the 279 | Resident Keys). By providing the seed, users can restore the entire derived keys hierarchy, effectively serving as a 280 | backup mechanism. It is important to note that this restoration capability applies exclusively to derived keys. Keys 281 | that are imported by the user, or generated to be stored on device (Resident Keys) are not restored through the seed; 282 | instead, it is assumed that a backup of such keys already exists. 283 | 284 | For generating ECC keys from a passphrase/seed and storing them in an OpenPGP format, you can refer to the Proof of 285 | Concept (POC) available [here](https://github.com/skeeto/passphrase2pgp). This POC demonstrates the process of 286 | generating ECC keys and their storage in an OpenPGP-compatible format. 287 | 288 | ## Commands 289 | 290 | This solution supports the following commands: 291 | 292 | 1) **Initialize or Restore from Seed**: This command allows for the initialization of the system or the restoration of 293 | its state from a seed. 294 | 295 | 2) **Generate Non-Resident Key**: With this command, you can generate a non-resident key dynamically. 296 | 297 | 3) **Write Resident Key**: This command is used to write resident keys to the device. It is primarily intended for 298 | externally existing keys. 299 | 300 | 4) **Read Public Key**: This command enables the retrieval of the public key associated with resident and derived keys. 301 | 302 | 5) **Sign**: The sign command allows for the signing of data. It requires parameters such as the data hash to be signed, 303 | the key handle, HMAC, and origin. 304 | 305 | 6) **Decrypt**: This command is used for decrypting data. It takes parameters such as the data to be decrypted, the key 306 | handle, HMAC, and origin. 307 | 308 | 7) **Status**: The status command provides information about the system, including whether it is unlocked, the version, 309 | and the number of available resident key slots. 310 | 311 | 8) **Configure**: This command is used to configure various settings related to the solution. Detailed configuration 312 | options can be found above. 313 | 314 | Optional commands for FIDO U2F compatibility (when FIDO2 method is not available; may be removed in the future): 315 | 316 | 9) **Unlock**: This command is provided for compatibility with FIDO U2F. It allows for unlocking the system when 317 | necessary. 318 | 319 | 10) **Reset**: This command is provided for compatibility with FIDO U2F. It allows for resetting the system when 320 | required. 321 | 322 | ### OpenPGP-like Commands 323 | 324 | In addition to the previously mentioned commands, this solution also provides a separate set of commands specifically 325 | designed for OpenPGP-like support, particularly for the OpenPGP.js case. These commands enable compatibility and 326 | integration with OpenPGP.js functionality. The specific commands available for OpenPGP-like support include: 327 | 328 | 1) **OpenPGP Initialize or Restore**: This command initializes the system or restores its state specifically for OpenPGP 329 | support using OpenPGP.js. 330 | 331 | 2) **OpenPGP Encrypt**: With this command, you can encrypt data using OpenPGP encryption standards. (planned) 332 | 333 | 3) **OpenPGP Decrypt**: This command allows for the decryption of OpenPGP encrypted data. 334 | 335 | 4) **OpenPGP Sign**: Use this command to sign data using OpenPGP signing mechanisms. 336 | 337 | 5) **OpenPGP Verify**: This command verifies the signature of OpenPGP signed data. 338 | 339 | 6) **OpenPGP Key Management**: This set of commands facilitates key management operations specific to OpenPGP, such as 340 | generating OpenPGP keys, importing and exporting keys, and managing key attributes. 341 | 342 | These OpenPGP-like commands are tailored to support seamless integration with OpenPGP.js and enable the utilization of 343 | OpenPGP encryption and signing capabilities. 344 | 345 | ## Use Cases 346 | 347 | With Nitrokey WebSmartCard, users can securely store all their web-related secrets on a compliant device, such as Nitrokey 3. Whether it's communication, passwords for FIDO-less web pages, or encrypted data storage, Nitrokey WebSmartCard has got you covered. Below is the comprehensive list of use-cases: 348 | 349 | 1. **User-to-User Communication** 350 | - **Email**: Nitrokey WebSmartCard supports encrypted and authenticated email, following a scheme similar to the OpenPGP smart 351 | card scheme. 352 | - **Chats**: Users can establish secure communication channels using keys per recipient, per channel, or per 353 | day/session. This functionality is comparable to popular secure messaging applications such as Signal and Element. 354 | - **Sharing Encrypted Files or Text**: Nitrokey WebSmartCard enables secure sharing of encrypted files or text, similar to 355 | encrypted pastebin services and Firefox Send. 356 | 357 | 2. **User Data Ownership** 358 | - Nitrokey WebSmartCard facilitates the operation of platforms that prioritize user data ownership. Instead of storing data on 359 | their own servers, platforms using this library store the data on the user's device. This allows for seamless data 360 | transfer to other services and simplifies the process of migrating an account between servers. By executing 361 | processing tasks on the user's device, such as a USB security key and JavaScript application, the service operator 362 | removes the responsibility of storing user data and mitigates the legal consequences of cross-border data 363 | transfers. 364 | 365 | 3. **Authentication** 366 | - Nitrokey WebSmartCard provides authentication capabilities similar to FIDO2, allowing users to establish secure and reliable 367 | authentication mechanisms. 368 | 369 | 4. **Secure Payments** 370 | - Users can leverage Nitrokey WebSmartCard to facilitate secure payment transactions, ensuring the confidentiality and 371 | integrity of sensitive financial information. 372 | 373 | 5. **Password Replacement** 374 | - Nitrokey WebSmartCard offers a password replacement mechanism by utilizing derived Public Keys and a single passphrase, 375 | which can serve as a backup seed phrase. This approach enhances security by reducing reliance on traditional 376 | passwords and simplifying the authentication process. 377 | 378 | 6. **Encrypted Internet** 379 | - With Nitrokey WebSmartCard, users can access encrypted web pages that are decrypted exclusively on their own devices 380 | through a JavaScript application. This ensures that the data remains encrypted throughout the transmission and is 381 | decrypted only on the user's host. 382 | 383 | 7. **Use of Insecure or Third-Party Platforms for Data Storage** 384 | - Nitrokey WebSmartCard empowers users to utilize insecure or third-party platforms for sending and storing data, including 385 | backups. By encrypting the data using Nitrokey WebSmartCard's functionality, users can maintain the security and privacy of 386 | their information, even when using platforms that may not provide inherent security measures. 387 | 388 | 8. **Sending Encrypted Messages through Any Channel using Webextension or Android Application** 389 | - Nitrokey WebSmartCard empowers users to establish their own secure end-to-end encrypted channels for sending messages 390 | through any channel using a Webextension or Android application. 391 | 392 | 9. **Hierarchical Deterministic Password Manager** 393 | 394 | - Nitrokey WebSmartCard includes a feature for a Hierarchical Deterministic Password Manager. This password management 395 | functionality generates passwords deterministically based on a main secret, domain name, and optionally, a login 396 | (for multiple accounts per service). By leveraging this capability, users can consolidate all their passwords on a 397 | single device and regenerate them just using the main secret. 398 | 399 | Additional features of the Hierarchical Deterministic Password Manager include: 400 | 401 | - **User-Chosen Number or Passphrase**: Users can provide a user-chosen number or passphrase to generate 402 | subsequent passphrases. This feature is particularly useful for complying with third-party services that 403 | require frequent password changes. 404 | 405 | - **Additional Passphrase for Enhanced Protection**: Users have the option to use an additional passphrase for 406 | extra protection. If an adversary attempts to force the passphrase from the user, not providing this 407 | additional passphrase will generate a different password, thereby safeguarding against unauthorized access. 408 | 409 | Please refer to the [implementation](./implementation.md) sections for detailed information on how to implement these use cases using Nitrokey 410 | WebSmartCard. 411 | 412 | ## Questions & Answers 413 | 414 | #### Advantages of Derived Keys 415 | 416 | The use of derived keys, as the default approach over resident keys, offers several significant advantages: 417 | 418 | 1. **Backup Mechanism**: For encryption use cases, such as those enabled by WebSmartCard, a backup mechanism is essential. 419 | To ensure ease of use, we provide a user-friendly backup mechanism. A seed phrase or backup phrase serves as a 420 | straightforward and accessible solution. From a technical perspective, a backup seed necessitates derived keys in ECC 421 | format, as opposed to RSA. Compared to traditional file backups, a seed-based backup offers the following advantages: 422 | * No separate storage or passphrase protection is required for a backup file. 423 | * Generating a new key does not necessitate creating and managing a new backup file repeatedly. 424 | * User experience is improved, as relying on backups alone often results in inadequate execution, potentially 425 | leading to frustration and data inaccessibility. 426 | 427 | 2. **Privacy Considerations**: The use of a single or a few resident keys could potentially enable malicious websites to 428 | track users' devices, compromising their privacy. By defaulting to derived keys, we mitigate this risk and prioritize 429 | user privacy. 430 | 431 | 3. **Expanded Key Usage**: With resident keys, there is a limitation on the number of keys that can be stored, imposing 432 | constraints on key storage. In contrast, derived keys allow for an unlimited number of keys, enabling more extensive 433 | usage scenarios and offering greater flexibility. 434 | 435 | By adopting derived keys as the default approach, Nitrokey WebSmartCard ensures a user-friendly backup mechanism, enhances 436 | privacy protection, and enables a wider range of key usage possibilities. 437 | -------------------------------------------------------------------------------- /images/demo-application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nitrokey/nitrokey-websmartcard/f5d23dbfe74fcb6da6275320c3efcd6f72d5679c/images/demo-application.png -------------------------------------------------------------------------------- /images/packet.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "name": "WC_CONST", "bits": 1, "attr": "=0x22" }, 3 | { "name": "MAGIC_VALUE", "bits": 4, "attr":"=0x8C2790F6" }, 4 | { "name": "COMM_ID", "bits": 1, "attr": ["READ","WRITE"] }, 5 | { "name": "PACKET_NUM", "bits": 1, "attr": "e.g. 0" }, 6 | { "name": "PACKET_CNT", "bits": 1 , "attr": "e.g. 1" }, 7 | { "name": "CHUNK_SIZE", "bits": 1 , "attr": "e.g. 200" }, 8 | { "name": "CHUNK_LEN", "bits": 1 , "attr": "e.g. 100" }, 9 | { "name": "CMD_ID", "bits": 1, "type": 4, "attr": ["data packet", "e.g. STATUS"] }, 10 | { "name": "DATA", "bits": 1, "type": 4, "attr": ["data packet", "e.g. 'CBOR({})'"] } 11 | ] 12 | -------------------------------------------------------------------------------- /images/packet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 0 23 | 24 | 25 | 1 26 | 27 | 28 | 4 29 | 30 | 31 | 5 32 | 33 | 34 | 35 | 36 | 37 | WC_CONST 38 | 39 | 40 | 41 | 42 | MAGIC_VALUE 43 | 44 | 45 | 46 | 47 | COMM_ID 48 | 49 | 50 | 51 | 52 | 53 | 54 | =0x22 55 | 56 | 57 | 58 | 59 | =0x8C2790F6 60 | 61 | 62 | 63 | 64 | 65 | READ 66 | 67 | 68 | 69 | 70 | WRITE 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 6 97 | 98 | 99 | 7 100 | 101 | 102 | 8 103 | 104 | 105 | 9 106 | 107 | 108 | 10 109 | 110 | 111 | 11 112 | 113 | 114 | 115 | 116 | 117 | PACKET_NUM 118 | 119 | 120 | 121 | 122 | PACKET_CNT 123 | 124 | 125 | 126 | 127 | CHUNK_SIZE 128 | 129 | 130 | 131 | 132 | CHUNK_LEN 133 | 134 | 135 | 136 | 137 | CMD_ID 138 | 139 | 140 | 141 | 142 | DATA 143 | 144 | 145 | 146 | 147 | 148 | 149 | e.g. 0 150 | 151 | 152 | 153 | 154 | e.g. 1 155 | 156 | 157 | 158 | 159 | e.g. 200 160 | 161 | 162 | 163 | 164 | e.g. 100 165 | 166 | 167 | 168 | 169 | 170 | data packet 171 | 172 | 173 | 174 | 175 | e.g. STATUS 176 | 177 | 178 | 179 | 180 | 181 | 182 | data packet 183 | 184 | 185 | 186 | 187 | e.g. 'CBOR({})' 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /implementation.md: -------------------------------------------------------------------------------- 1 | # Nitrokey Webcrypt Implementation Documentation 2 | 3 | This documentation provides an overview of the implemented Nitrokey Webcrypt interface in Nitrokey 3. It includes 4 | high-level descriptions of the commands and low-level protocol details. Please note that this implementation is in the 5 | early stages and may undergo changes in the future. 6 | 7 | ## Commands Overview Table 8 | 9 | 10 | 11 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 12 | |------|--------------------------|-----------------------------------|-----------------------------------------|-----|-----| 13 | | 0x0 | STATUS | None | `{UNLOCKED,VERSION,SLOTS,PIN_ATTEMPTS}` | - | - | 14 | | 0x1 | TEST_PING | Any / Raw | Any / Raw | - | - | 15 | | 0x2 | TEST_CLEAR | None | None | - | - | 16 | | 0x3 | TEST_REBOOT | None | None | - | - | 17 | | 0x4 | LOGIN | `{PIN}` | `{TP}` | - | + | 18 | | 0x5 | LOGOUT | None | None | - | - | 19 | | 0x6 | FACTORY_RESET | None | None | - | + | 20 | | 0x7 | *RESERVED* | - | - | - | + | 21 | | 0x8 | SET_CONFIGURATION | `{CONFIRMATION}` | None | - | + | 22 | | 0x9 | GET_CONFIGURATION | None | `{CONFIRMATION}` | - | + | 23 | | 0x0A | SET_PIN | `{PIN}` | None | - | + | 24 | | 0x0B | CHANGE_PIN | `{PIN,NEWPIN}` | None | - | + | 25 | | 0x10 | INITIALIZE_SEED | `{ENTROPY}` | `{MASTER,SALT}` | + | + | 26 | | 0x11 | RESTORE_FROM_SEED | `{MASTER,SALT}` | `{HASH}` | + | + | 27 | | 0x12 | GENERATE_KEY | None | `{PUBKEY,KEYHANDLE}` | + | + | 28 | | 0x13 | SIGN | `{HASH,KEYHANDLE}` | `{SIGNATURE,INHASH}` | + | + | 29 | | 0x14 | DECRYPT | `{DATA,KEYHANDLE,[HMAC,ECCEKEY]}` | `{DATA}` | + | + | 30 | | 0x15 | GENERATE_KEY_FROM_DATA | `{HASH}` | `{PUBKEY,KEYHANDLE}` | + | + | 31 | | 0x16 | GENERATE_RESIDENT_KEY | None | `{PUBKEY,KEYHANDLE}` | + | + | 32 | | 0x17 | READ_RESIDENT_KEY_PUBLIC | `{KEYHANDLE}` | `{PUBKEY,KEYHANDLE}` | + | + | 33 | | 0x18 | DISCOVER_RESIDENT_KEYS | TBD | TBD | + | + | 34 | | 0x19 | WRITE_RESIDENT_KEY | `{RAW_KEY_DATA,[KEY_TYPE]}` | `{PUBKEY,KEYHANDLE}` | + | + | 35 | 36 | 37 | 38 | Where for the given command: 39 | - ID is a hexadecimal integer; 40 | - `TEST_` prefixed commands are available only in the development firmware; 41 | - the TBD acronym means that the work for that command is planned 42 | - plus `+` sign means a requirement for operation named by this specific column, whereas minus `-` sign is the opposite; 43 | - column `Au`, short from authentication, marks authentication requirement with the `LOGIN` command, before using this command; 44 | - column `Bt`, short from button, marks touch-button press requirement after the command is called, to proceed further. 45 | 46 | Note that OpenPGP specific commands are missing from this description (to be updated). 47 | 48 | 49 | ## Initialize (INITIALIZE_SEED) 50 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 51 | | --- | ------ | ---------- | ---------- | --- | --- | 52 | | 0x10 | INITIALIZE_SEED | `{ENTROPY}` | `{MASTER,SALT}` | + | + | 53 | 54 | Sets random values (sourced from the HWRNG) to the Nitrokey Webcrypt's secrets - *Master Key* and *Salt* - and returns them to the caller for the backup purposes. The device produced random values are XOR'ed with the incoming ENTROPY field. 55 | 56 | On the client application side these binary secrets should be translated to human readable word-based representation, *Word Seed*, similarly to [BIP#39], e.g.: 57 | ``` 58 | witch collapse practice feed shame open despair creek road again ice least 59 | ``` 60 | 61 | In the future the secret will be returned in one field instead of two. 62 | 63 | [BIP#39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki 64 | 65 | Note: Nitrokey Webcrypt's secrets should be guaranteed to always be initialized. It should not be possible to use them however unless confirmed that user has his backup *Word Seed* saved. 66 | 67 | ``` 68 | random_data[40] = HWRNG(40) 69 | MASTER[32],SALT[8] = random_data[40] ^ ENTROPY[40] 70 | ``` 71 | 72 | 73 | ### Input description 74 | | Field | Size [B] | Description | 75 | |-----------|----------|----------------------------------------------------| 76 | | `ENTROPY` | 40 | Client-sourced bytes to be mixed with HWRNG result | 77 | 78 | ### Output description 79 | | Field | Size [B] | Description | 80 | |----------|----------|-------------------------------------| 81 | | `MASTER` | 32 | Nitrokey Webcrypt's *Master Secret* | 82 | | `SALT` | 8 | Salt | 83 | 84 | 85 | ### Errors 86 | | ID | Mnemonic | Description | 87 | |------|-------------------------|-------------------------------| 88 | | 0xF5 | ERR_FAILED_LOADING_DATA | Error during preparing output | 89 | 90 | 91 | 92 | 93 | 94 | ## Restore from seed (RESTORE_FROM_SEED) 95 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 96 | |------|-------------------|-----------------|----------|-----|-----| 97 | | 0x11 | RESTORE_FROM_SEED | `{MASTER,SALT}` | `{HASH}` | + | + | 98 | 99 | Sets Nitrokey Webcrypt's secret values as received from the caller. For verification calculates SHA256 hash of the input and returns as `HASH`. 100 | 101 | 102 | ``` 103 | HASH = SHA256(MASTER|SALT) 104 | ``` 105 | 106 | ### Input description 107 | | Field | Size [B] | Description | 108 | |----------|----------|-----------------------------------| 109 | | `MASTER` | 32 | Nitrokey Webcrypt's Master Secret | 110 | | `SALT` | 8 | Salt | 111 | 112 | ### Output description 113 | | Field | Size [B] | Description | 114 | |--------|----------|---------------------------------------| 115 | | `HASH` | 32 | SHA256 hash of the sent `MASTER+SALT` | 116 | 117 | ### Errors 118 | | ID | Mnemonic | Description | 119 | |------|-------------------------|-------------------------------| 120 | | 0xF3 | ERR_BAD_FORMAT | Incoming data are malformed | 121 | | 0xF5 | ERR_FAILED_LOADING_DATA | Error during preparing output | 122 | 123 | 124 | 125 | ## Key Handle description 126 | 127 | ### Wrapping 128 | 129 | The wrapping operation is reused from the fido-authenticator crate: 130 | 1. The private key is wrapped using a persistent wrapping key using ChaCha20-Poly1305 AEAD algorithm. 131 | 2. The wrapped key is embedded into a KeyHandle data structure, containing additional metadata (RP ID, Usage Flags). 132 | 3. The serialized KeyHandle structure is finally CBOR serialized and encrypted, resulting in a binary blob to be used with other commands. 133 | 134 | 135 | ```text 136 | key_private - private key structure 137 | Encrypt = ChaCha20-Poly1305 138 | Serialize = CBOR 139 | key_private_enc = Encrypt(Serialize(key_private)) 140 | key_handle = Encrypt(Serialize(key_private_enc)) 141 | key_pub[64] = ECC_compute_public_key(key_private) 142 | PUBKEY = key_pub 143 | KEYHANDLE = key_handle 144 | ``` 145 | 146 | ### Unwrapping 147 | 148 | The deserialization method of the KeyHandle is reused from the fido-authenticator project. 149 | 1. The encrypted KeyHandle is decrypted and deserialized to a KeyHandle structure using persistent encryption key. 150 | 2. From the resulting KeyHandle structure the wrapped private key is decrypted and deserialized 151 | 3. Finally, the wrapped private key is imported to the volatile in-memory keystore, and used for the further operations. 152 | 153 | ```text 154 | key_handle - serialized and encrypted KeyHandle structure 155 | Decrypt = ChaCha20-Poly1305 156 | Deserialize = CBOR 157 | key_private_enc = Decrypt(Deserialize(key_handle)) 158 | key_private = Decrypt(Deserialize(key_private_enc)) 159 | ``` 160 | 161 | ### Resident Keys 162 | 163 | The KeyHandles for Resident Keys are a serialized internal KeyID (16 B) identifier, along with some metadata fields reserved for the future use. This might change in the future. 164 | 165 | ## Generate non-resident key 166 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 167 | |------|------------------------|------------|----------------------|-------|-----| 168 | | 0x12 | GENERATE_KEY | None | `{PUBKEY,KEYHANDLE}` | + | + | 169 | | 0x15 | GENERATE_KEY_FROM_DATA | `{HASH}` | `{PUBKEY,KEYHANDLE}` | + | + | 170 | 171 | 172 | ### From hash (GENERATE_KEY_FROM_DATA) 173 | 174 | For the actual key generation the FIDO U2F / FIDO2 key generation and wrapping mechanism was reused. The passphrase is processed through a hash function (e.g. Argon2) with known parameters client side, and the hash result is sent to the device. The received hash is HMAC'ed with the Nitrokey Webcrypt's master key `WC_MASTER_KEY`. 175 | 176 | The hash function selection, use and parameters will be standardized in the future. 177 | 178 | ```text 179 | # Browser 180 | hash[32] = Argon2(passphrase) 181 | # Device 182 | key_data_raw[32] = HMAC256(hash) 183 | key_pub, key_priv = wc_new_keypair(key_data_raw, appid) 184 | ``` 185 | 186 | See the wrapping algorithm in the Key Handle description chapter. 187 | 188 | To discuss: 189 | - hash function selection and parameters; 190 | - introducing a KDF-DO like object, containing parameters needed to calculate the hash from the passphrase client side. 191 | 192 | #### Input description 193 | | Field | Size [B] | Description | 194 | | --- | ------ | ---------- | 195 | | `HASH` | 32 | Source data for key generation | 196 | 197 | #### Output description 198 | | Field | Size [B] | Description | 199 | |-------------|----------| ---------- | 200 | | `PUBKEY` | 64 | Raw ECC public key | 201 | | `KEYHANDLE` | 250 | Key handle | 202 | 203 | ### Errors 204 | Both commands return the same errors listed below. 205 | 206 | | ID | Mnemonic | Description | 207 | | --- | ------ | ---------- | 208 | | 0xF3 | ERR_BAD_FORMAT | Incoming data are malformed | 209 | | 0xF5 | ERR_FAILED_LOADING_DATA | Error during key generation or preparing output | 210 | | 0xFA | ERR_INTERNAL_ERROR | Unexpected condition encountered | 211 | 212 | 213 | ### Random (GENERATE_KEY) 214 | Random key generation follows the same path as from the hash, except that instead of the `key_data_raw` a randomized 32 bytes value is used, sourced from the device's HWRNG. Resulting `KEYHANDLE` can be stored off-device for the later use, e.g. locally in the browser (localStorage / cookie), or on a remote server. 215 | 216 | See *From hash (GENERATE_KEY_FROM_DATA)* chapter for the full pseudocode, specifically key wrapping. 217 | 218 | ```text 219 | # Device 220 | random_data[32] = HWRNG(32) 221 | key_pub, key_priv = wc_new_keypair(random_data, appid) 222 | ``` 223 | 224 | 225 | 226 | #### Input description 227 | None 228 | 229 | #### Output description 230 | | Field | Size [B] | Description | 231 | |-------------|----------| ---------- | 232 | | `PUBKEY` | 64 | Raw ECC public key | 233 | | `KEYHANDLE` | 250 | Key handle | 234 | 235 | ### Errors 236 | | ID | Mnemonic | Description | 237 | | --- | ------ | ---------- | 238 | | 0xF5 | ERR_FAILED_LOADING_DATA | Error during key generation or preparing output | 239 | | 0xFA | ERR_INTERNAL_ERROR | Unexpected condition encountered | 240 | 241 | 242 | 243 | 244 | ### General comments 245 | Work in progress. 246 | 247 | To implement: 248 | - Add cross-origin keys; 249 | - Key attributes; 250 | To discuss: 251 | - Replace HMAC with AES GCM or ChaCha20/ChaCha20-Poly1305; 252 | - Introduce MAC-then-encrypt/MAC-then-pad-then-encrypt if needed. 253 | 254 | 255 | 256 | ## Sign (SIGN) 257 | 258 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 259 | | --- | ------ | ---------- |---------------------| --- | --- | 260 | | 0x13 | SIGN | `{HASH,KEYHANDLE}` | `{SIGNATURE,INHASH}` | + | + | 261 | 262 | 263 | Returns `SIGNATURE` as a result, as well as the incoming hash `HASH`. 264 | `KEYHANDLE` authenticity (whether it was generated with given *Master Key* and to use for given *Origin*) is verified before use. 265 | Incoming `HASH` data is repeated on the output for signature confirmation. 266 | See the wrapping algorithm in the Key Handle description chapter. 267 | 268 | The type of the `SIGNATURE` signature depends on the used key algorithm, encoded in the keyhandle. 269 | 270 | In pseudocode: 271 | ```text 272 | SIGNATURE = Sign(KEYHANDLE, hash) 273 | INHASH = HASH 274 | ``` 275 | 276 | 277 | ### ECC keys 278 | Using key encoded in `KEYHANDLE` parameter command makes signature over the input hash `HASH` using ECDSA. 279 | The curve used by default is `secp256r1` (NIST P-256 Random). 280 | 281 | 282 | 283 | ### RSA keys 284 | Signing operation for RSA keys uses PKCSv15 padding and SHA256 as the hash. 285 | The only supported size for the RSA keys is RSA 2048. 286 | 287 | To implement: 288 | - Support `secp256k1` curve (NIST P-256 Koblitz). 289 | - Support other algorithms. 290 | 291 | 292 | ### Input description 293 | | Field | Size [B] | Description | 294 | |--------------|----------| ---------- | 295 | | `HASH` | 32 | Raw data to sign, typically SHA256 hash | 296 | | `KEYHANDLE` | 250 | Key handle | 297 | 298 | 299 | ### Output description 300 | | Field | Size [B] | Description | 301 | |-------------| ------ | ---------- | 302 | | `SIGNATURE` | 64 | ECC signature | 303 | | `INHASH` | 32 | Incoming raw data to sign | 304 | 305 | ### Errors 306 | | ID | Mnemonic | Description | 307 | | --- | ------ | ---------- | 308 | | 0xF3 | ERR_BAD_FORMAT | Incoming data are malformed | 309 | | 0xF5 | ERR_FAILED_LOADING_DATA | Error during preparing output | 310 | 311 | 312 | 313 | 314 | ## Decrypt (DECRYPT) 315 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 316 | |------|----------|-------------------------------------|----------|-----|-----| 317 | | 0x14 | DECRYPT | `{DATA,KEYHANDLE,[HMAC,ECCEKEY]}` | `{DATA}` | + | + | 318 | 319 | 320 | The type of the operation done on the `DATA` ciphertext depends on the used key algorithm, encoded in the keyhandle. 321 | `KEYHANDLE` authenticity (whether it was generated with given *Master Key* and to use for given *Origin*) is verified before use. 322 | See the wrapping algorithm in the Key Handle description chapter. 323 | 324 | ### ECC keys 325 | Decrypts data given in the `DATA` field, using `KEYHANDLE` *Key Handle* for regenerating the private key, and `ECCEKEY` ephemeral ECC public key for deriving the shared secret using ECDH. Before that this command verifies the data by calculating HMAC over all the fields and comparing with incoming `HMAC` field. 326 | 327 | Requires PKCS#7 ([RFC 5652]) padded data to the length of multiple of 32. 328 | 329 | Pseudocode: 330 | ```text 331 | shared_secret = ecc256_shared_secret(ECCEKEY) 332 | data_len = len(DATA) 333 | hmac_calc = HMAC256(shared_secret, DATA|ECCEKEY|data_len|KEYHANDLE) 334 | if hmac_calc != HMAC: abort 335 | plaintext = Decrypt(AES256, shared_secret, DATA) 336 | ``` 337 | 338 | [RFC 5652]: https://tools.ietf.org/html/rfc5652#section-6.3 339 | 340 | 341 | ### RSA keys 342 | Decrypts data given in the `DATA` field, using `KEYHANDLE` *Key Handle* for regenerating the private key. 343 | `KEYHANDLE` authenticity (whether it was generated with given *Master Key* and to use for given *Origin*) is verified before use. 344 | The ciphertext should be encoded with PKCS#1v15 padding. `HMAC` and `ECCEKEY` should not be provided. 345 | HMAC is not checked. 346 | 347 | Pseudocode: 348 | ```text 349 | plaintext = Decrypt(RSA2048, KEYHANDLE, DATA) 350 | DATA = plaintext 351 | ``` 352 | 353 | 354 | 355 | ### Input description 356 | | Field | Size [B] | Description | 357 | |--------------|----------|-------------------------------| 358 | | `DATA` | 32-128* | Data to decrypt | 359 | | `KEYHANDLE` | 250+ | Key handle | 360 | | `HMAC` | 32 | Calculated HMAC (ECC only) | 361 | | `ECCEKEY` | 64 | Raw ECC public key (ECC only) | 362 | 363 | ### Output description 364 | | Field | Size [B] | Description | 365 | | --- | ------ | ---------- | 366 | | `DATA` | 32-128* | Decrypted data | 367 | 368 | ### Errors 369 | | ID | Mnemonic | Description | 370 | | --- | ------ | ---------- | 371 | | 0xF3 | ERR_BAD_FORMAT | Incoming data are malformed | 372 | | 0xF5 | ERR_FAILED_LOADING_DATA | Error during preparing output | 373 | 374 | 375 | - `*` - work in progress: - maximum data length will be increased. 376 | 377 | 378 | ## Status (STATUS) 379 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 380 | | --- | ------ | ---------- | ---------- | --- | --- | 381 | | 0x0 | STATUS | None | `{UNLOCKED,VERSION,SLOTS,PIN_ATTEMPTS}` | - | - | 382 | 383 | 384 | Command requires authentication: no. 385 | 386 | #### To discuss 387 | - should `SLOTS` number not be hidden to avoid fingerprinting. 388 | 389 | ### Input description 390 | None 391 | 392 | ### Output description 393 | 394 | | Field | Size [B] | Description | 395 | | --- | ------ |---------------------------------------------------------| 396 | | `VERSION` | 1 | implemented Nitrokey Webcrypt's version | 397 | | `SLOTS` | 1 | number of left available Webcrypt's Resident Keys slots | 398 | | `PIN_ATTEMPTS` | 1 | PIN attempt counter's current value | 399 | | `UNLOCKED` | 1 | Return true if the session is open | 400 | 401 | ### Errors 402 | | ID | Mnemonic | Description | 403 | | --- | ------ | ---------- | 404 | | 0xF5 | ERR_FAILED_LOADING_DATA | Error during preparing output | 405 | 406 | 407 | 408 | 409 | 410 | ## Login (LOGIN) 411 | 412 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 413 | |--------|----------|---------------------|---------------------|------|------| 414 | | 0x4 | LOGIN | `{PIN}` | `{TP}` | - | + | 415 | 416 | 417 | This command allows to establish session by returning a session token upon presenting the correct PIN. 418 | If the PIN is invalid, the PIN attempt counter will be decreased. Once the latter reaches 0, the only further available 419 | operation will be FACTORY_RESET. 420 | 421 | ### Input description 422 | 423 | | Field | Size [B] | Description | 424 | |---------|----------|------------------------| 425 | | `PIN` | 4-64 | Current Webcrypt's PIN | 426 | 427 | ### Output description 428 | 429 | | Field | Size [B] | Description | 430 | |--------|----------|------------------------------------------| 431 | | `TP` | 32 | Session token, a.k.a. temporary password | 432 | 433 | ### Errors 434 | 435 | | ID | Mnemonic | Description | 436 | |----------|-------------------------|------------------------------------| 437 | | 0xF1 | ERR_INVALID_PIN | The presented PIN is invalid | 438 | | 0xF2 | ERR_NOT_ALLOWED | The PIN attempt counter is used up | 439 | | 0xF5 | ERR_FAILED_LOADING_DATA | Error during preparing output | 440 | 441 | 442 | 443 | ## Logout (LOGOUT) 444 | 445 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 446 | |--------|------------|---------------|-------------------------|------|------| 447 | | 0x5 | LOGOUT | None | None | - | - | 448 | 449 | Clear all session related data, and remove all secrets from the memory. 450 | 451 | ### Errors 452 | 453 | None 454 | 455 | ## Factory reset (FACTORY_RESET) 456 | 457 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 458 | |--------|---------------------|-------------|---------------------|------|------| 459 | | 0x6 | FACTORY_RESET | None | None | - | + | 460 | 461 | Removes all the currently stored user data, and prepares the device for the new use. 462 | 463 | Note: this command does not need PIN confirmation or session set. 464 | 465 | ### Errors 466 | None 467 | 468 | 469 | 470 | ## Get and Set configuration (GET_CONFIGURATION, SET_CONFIGURATION) 471 | 472 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 473 | |--------|-----------------------|-------------|-----------------|------|------| 474 | | 0x8 | SET_CONFIGURATION | `{CONFIRMATION}` | None | - | + | 475 | | 0x9 | GET_CONFIGURATION | None | `{CONFIRMATION}` | - | + | 476 | 477 | 478 | This command allows to change the user settings in Webcrypt. 479 | Work in progress. 480 | 481 | ### Input/output description 482 | 483 | | Field | Size [B] | Description | 484 | |-----------|----------|-------------------------| 485 | | `CONFIRMATION` | 1 | Confirmation mode (WIP) | 486 | 487 | ### Errors 488 | 489 | | ID | Mnemonic | Description | 490 | |---------|-------------------------|-------------------------------| 491 | | 0xF5 | ERR_FAILED_LOADING_DATA | Error during preparing output | 492 | 493 | 494 | 495 | 496 | 497 | ## PIN management (SET_PIN, CHANGE_PIN) 498 | 499 | 500 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 501 | |--------|-----------------|-----------------------|--------------------|------|------| 502 | | 0x0A | SET_PIN | `{PIN}` | None | - | + | 503 | | 0x0B | CHANGE_PIN | `{PIN,NEWPIN}` | None | - | + | 504 | 505 | 506 | The SET_PIN and CHANGE_PIN commands are for the PIN handling. The former allows to set the PIN, when there is none (e.g. just after factory reset operation), but afterwards it is not allowed to work. The further PIN changes require CHANGE_PIN command to be used. 507 | The PIN can be of length between 4 and 64 bytes. 508 | 509 | ### Input description 510 | 511 | | Field | Size [B] | Description | 512 | |-----------|----------|------------------------------------| 513 | | `PIN` | 4-64 | SET_PIN: the current PIN to be set | 514 | | `PIN` | 4-64 | CHANGE_PIN: the current PIN | 515 | | `NEWPIN` | 4-64 | CHANGE_PIN: the new PIN | 516 | 517 | ### Output description 518 | None 519 | 520 | ### Errors 521 | 522 | | ID | Mnemonic | Description | 523 | |---------|-------------------------|---------------------------------------------------------------------| 524 | | 0xF5 | ERR_FAILED_LOADING_DATA | Error during preparing output | 525 | | 0xF1 | INVALID_PIN | The provided PIN is invalid, or wrong length | 526 | | 0xF2 | ERR_NOT_ALLOWED | SET_PIN: command use is not allowed, because the PIN is already set | 527 | 528 | 529 | 530 | ## Resident Keys Handling 531 | 532 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 533 | |------|--------------------------|-----------------------------|----------------------|-----|-----| 534 | | 0x16 | GENERATE_RESIDENT_KEY | None | `{PUBKEY,KEYHANDLE}` | + | + | 535 | | 0x17 | READ_RESIDENT_KEY_PUBLIC | `{KEYHANDLE}` | `{PUBKEY,KEYHANDLE}` | + | + | 536 | | 0x18 | DISCOVER_RESIDENT_KEYS | TBD | TBD | + | + | 537 | | 0x19 | WRITE_RESIDENT_KEY | `{RAW_KEY_DATA,[KEY_TYPE]}` | `{PUBKEY,KEYHANDLE}` | + | + | 538 | 539 | 540 | Resident Keys (RK) are the keys stored on the device, allowing to be identified with a shorter keyhandle, or instead used as a storage means for the Relying Party, relieving it from the keeping of the secret material completely. 541 | Resident Keys can be generated, imported and used in the same way as their derived counterparts with the SIGN and DECRYPT commands. 542 | 543 | 544 | Detailed description: 545 | - GENERATE_RESIDENT_KEY - generates a RK on the device using local means, and returns a keyhandle to it (ECC only); 546 | - READ_RESIDENT_KEY_PUBLIC - allows to read a public key of the given RK; 547 | - DISCOVER_RESIDENT_KEYS - lists all the RKs available for the given Relying Party (work in progress); 548 | - WRITE_RESIDENT_KEY - writes raw key data, as received from the RP, and returns a keyhandle to it. 549 | 550 | 551 | ### Input description 552 | 553 | | Field | Size [B] | Description | 554 | |----------------|----------|---------------------------------------------------------------------------------------------| 555 | | `KEYHANDLE` | 250+ | The keyhandle bytes, allowing to either identify the Resident Key or the Derived Key | 556 | | `RAW_KEY_DATA` | 32+ | Raw key data, to be saved as a Resident Key. RSA raw keys have to be encoded in PKCS#8 DER. | 557 | | `KEY_TYPE` | 2 | Key type, encoded in int16: 0 = P256 , 1 = RSA 2K. Optional. Default: 0. | 558 | 559 | ### Output description 560 | 561 | | Field | Size [B] | Description | 562 | |----------------|----------|---------------------------------------------------------------------------------------------| 563 | | `PUBKEY` | 65+ | The calculated public key for the given keyhandle. RSA public key is encoded in PKCS#8 DER. | 564 | | `KEYHANDLE` | 250+ | The keyhandle bytes, allowing to either identify the Resident Key or the Derived Key | 565 | 566 | ### Errors 567 | 568 | | ID | Mnemonic | Description | 569 | |---------|-------------------------|-------------------------------| 570 | | 0xF5 | ERR_FAILED_LOADING_DATA | Error during preparing output | 571 | 572 | 573 | 574 | ## Test commands 575 | 576 | | ID | Mnemonic | Parameters | Returns | Au | Bt | 577 | | --- | ------ | ---------- | ---------- | --- | --- | 578 | | 0x1 | TEST_PING | Any / Raw | Any / Raw | - | - | 579 | | 0x2 | TEST_CLEAR | None | None | - | - | 580 | | 0x3 | TEST_REBOOT | None | None | - | - | 581 | 582 | These test commands are introduced to help in the development of the client applications, and are available only in the development version of the firmware: 583 | `TEST_PING` - send and receive data for transport tests (loopback). 584 | 585 | Not implemented at the moment: 586 | - `TEST_CLEAR` - clear the current Nitrokey Webcrypt's state; 587 | - `TEST_REBOOT` - reboot device. 588 | 589 | 590 | ## Common errors table 591 | Following errors are common to all commands requiring authorization. 592 | 593 | | ID | Mnemonic | Description | 594 | | --- |---------------------------|----------------------------------------------------------------| 595 | | 0xF0 | ERR_REQ_AUTH | Command needs to be authorized by PIN * | 596 | | 0xF1 | ERR_INVALID_PIN | Provided PIN is invalid | 597 | | 0xF2 | ERR_NOT_ALLOWED | The given key's origin does not match the one of the request | 598 | | 0xF3 | ERR_BAD_FORMAT | The given key's origin does not match the one of the request | 599 | | 0xF4 | ERR_USER_NOT_PRESENT | User has not pressed touch button in time | 600 | | 0xF5 | ERR_FAILED_LOADING_DATA | There was an error while preparing the result of the execution | 601 | | 0xFD | ERR_BAD_ORIGIN | The given key's origin does not match the one of the request | 602 | 603 | Notes: 604 | - (*) `ERR_REQ_AUTH` should be returned, when: for FIDO U2F the session token was not provided in the data, for FIDO2 the PIN was not requested from the user (`userVerification: "discouraged"`) 605 | 606 | 607 | # Protocol 608 | 609 | Communication is based on the [Webauthn] / [FIDO2] API, which by itself allows to communicate with FIDO Security Keys in FIDO2 enabled browsers on all platforms, as well as through NFC and Bluetooth. Such feature is here reused as a an universal communication tunnel to the Nitrokey Webcrypt enabled device, making it plug-and-play and working out of the box with many configurations. 610 | 611 | This chapter is still a work in progress. 612 | 613 | [Webauthn]: https://www.w3.org/TR/webauthn/ 614 | [FIDO2]: https://fidoalliance.org/specifications/ 615 | [CTAP]: https://fidoalliance.org/specs/fido2/fido-client-to-authenticator-protocol-v2.1-rd-20191217.html 616 | 617 | ## Overview 618 | 619 | Nitrokey Webcrypt's communication is based on the Request-Response message exchange pattern, where communication is initiated always by the host, and each data update requires sending the request. 620 | 621 | Each Nitrokey Webcrypt's request is sent over Webauthn using MakeAssertion operation. It allows to transfer 255 bytes to the device, and receive 73 bytes back. The data fields used are: 622 | - `key handle` for sending to device; 623 | - `signature` for receiving from device. 624 | 625 | 626 | ## Commands 627 | For low-level communication two commands are required: 628 | - `WRITE` - to write to the Nitrokey Webcrypt's incoming buffer on the device; 629 | - `READ` - to read from the Nitrokey Webcrypt's outgoing buffer on the device; 630 | 631 | Last packet of the `WRITE` protocol operation executes the command. If there are any results, these will be available in the outgoing buffer, from which client can download the content using `READ` commands. 632 | in the future this might be minimalized by removing the redundant first call to `READ` by moving first part of the results to the `WRITE` operation response (similarly to [CTAP]). 633 | 634 | 635 | ## Packet structure 636 | | Offset | Length | Mnemonic | Comments | 637 | |--------|-----------|----------------|-------------------------------------------------------------------| 638 | | 0 | 1 | WEBCRYPT_CONST | Always equal to `0x22`. | 639 | | 1 | 4 | __HEADER | Nitrokey Webcrypt's magic value to recognize extension over FIDO2 | 640 | | 5 | 1 | COMM_ID | Operation: WRITE (`0x01`) or READ (`0x02`) | 641 | | 6 | 1 | PACKET_NUM | This packet number, 0-255 | 642 | | 7 | 1 | PACKET_CNT | Total packet count, 0-255 | 643 | | 8 | 1 | CHUNK_SIZE | Size of the data chunk, 0-255 | 644 | | 9 | 1 | CHUNK_LEN | Length of the given data chunk, 0-CHUNK_SIZE | 645 | | 10 | CHUNK_LEN | DATA | Data to send | 646 | 647 | Notes: 648 | - Having dynamic `CHUNK_SIZE` allows to change the communication parameters on the fly, and depending on the platform conditions. 649 | - Introducing redundant information in the form of the packet number and count allows identifying potential transmission issues, like doubled packets (Windows 10 Webauthn handling issue). 650 | - Magic value is: `__HEADER = 0x8C2790F6`. 651 | - In the future packet format might be modified to be more compact by removing redundant information (e.g. removing packet sequence information and the current chunk length, but leaving the chunk size; similarly to [CTAP]). 652 | 653 | 654 | 655 | ## Data packet structure 656 | 657 | | Offset | Length | Mnemonic | Comments | 658 | | ------ | ------ | ------- | ------- | 659 | | 0 | 1 | COMMAND_ID | Command ID to execute | 660 | | 1 | CHUNK_LEN-1 | DATA | CBOR encoded arguments to the command | 661 | 662 | 663 | ## Incoming packet format for WRITE 664 | | Offset | Length | Mnemonic | Comments | 665 | | ------ | ------ | ------- | ------- | 666 | | 0 | 1 | RESULT | Result code | 667 | 668 | Execution's result code are described under each command description. 669 | 670 | ## Incoming packet format for READ 671 | | Offset | Length | Mnemonic | Comments | 672 | | ------ | ------ | ------- | ------- | 673 | | 0 | 2 | DATA_LEN | Data length N | 674 | | 2 | 1 | CMD_ID | Command ID that produced result | 675 | | 3 | DATA_LEN | DATA | Data received | 676 | 677 | 678 | ## Encoding 679 | 680 | All parameters to the commands sent in the `DATA` field of the data packet are [CBOR](Concise Binary Object Representation, RFC7049) encoded key-value maps. This method was chosen due to following: 681 | - FIDO2 requires CBOR encoded parameters as well, hence parser and encoder are provided already for FIDO2 supporting devices. 682 | - CBOR handling libraries are available for all major languages, including JavaScript, where the client applications are meant to be developed. 683 | 684 | [CBOR]: https://tools.ietf.org/html/rfc7049 685 | 686 | ## Full packet example 687 | 688 | Following is an example Nitrokey Webcrypt packet with `WRITE` operation for the `STATUS` command. 689 | This packet should be provided as an argument for the Webauthn MakeAssertion's `allowCredentials::id` parameter. 690 | 691 | ![Packet diagram](./images/packet.svg) 692 | 693 | 694 | # FIDO2 actions relationship 695 | The following are connections between the FIDO2 and Nitrokey Webcrypt: 696 | - On FIDO2 factory reset the Nitrokey Webcrypt's secrets should be reinitialized to random values. 697 | - The PIN is shared between the FIDO2 and Nitrokey Webcrypt. 698 | - The secrets are separated and never cross-used between FIDO2 and Nitrokey Webcrypt. 699 | - The FIDO2 PIN attempt counter should decrease on failed login over Nitrokey Webcrypt. 700 | - The FIDO2 use counter should not change during the use of Nitrokey Webcrypt. 701 | 702 | # Javascript Usage 703 | 704 | Below is an example of Javascript API usage with OpenPGP.js. 705 | 706 | ```typescript 707 | 708 | class WebCryptHardwareKeysPlugin { 709 | async serialNumber() { 710 | return new Uint8Array(16).fill('A'.charCodeAt(0)); 711 | } 712 | 713 | date() { 714 | return this.webcrypt_date ? new Date(this.webcrypt_date) : new Date(2019, 1, 1); 715 | } // the default WebCrypt date for the created keys 716 | 717 | async init() { 718 | if (this.public_sign === undefined) { 719 | await WEBCRYPT_LOGIN(WEBCRYPT_DEFAULT_PIN, statusCallback); 720 | const res = await WEBCRYPT_OPENPGP_INFO(statusCallback); 721 | this.public_encr = res.encr_pubkey; 722 | this.public_sign = res.sign_pubkey; 723 | this.webcrypt_date = res.date; 724 | } 725 | } 726 | 727 | async agree({ curve, V, Q, d }) { 728 | console.log({ curve, V, Q, d }); 729 | const agreed_secret = await WEBCRYPT_OPENPGP_DECRYPT(statusCallback, V); 730 | return { secretKey: d, sharedKey: agreed_secret }; 731 | } 732 | 733 | async sign({ oid, hashAlgo, data, Q, d, hashed }) { 734 | const res = await WEBCRYPT_OPENPGP_SIGN(statusCallback, data); 735 | const resb = hexStringToByte(res); 736 | const r = resb.slice(0, 32); 737 | const s = resb.slice(32, 64); 738 | const reso = { r, s }; 739 | return reso; 740 | } 741 | 742 | async generate({ algorithmName, curveName, rsaBits }) { 743 | let selected_pk = this.public_sign; 744 | if (algorithmName === openpgp.enums.publicKey.ecdh) { 745 | selected_pk = this.public_encr; 746 | console.warn(`Selecting subkey: ${selected_pk} for encryption`); 747 | } else if (algorithmName === openpgp.enums.publicKey.ecdsa) { 748 | console.warn(`Selecting main: ${selected_pk} for signing`); 749 | } else { 750 | console.error(`Not supported algorithm: ${algorithmName}`); 751 | throw new Error(`Not supported algorithm: ${algorithmName}`); 752 | } 753 | return { publicKey: selected_pk, privateKey: null }; 754 | } 755 | } 756 | 757 | const plugin = new WebCryptHardwareKeysPlugin(); 758 | 759 | WebcryptConnection(statusCallback){ 760 | await Webcrypt_Logout(statusCallback); 761 | await Webcrypt_FactoryReset(statusCallback); 762 | await Webcrypt_Status(statusCallback); 763 | await Webcrypt_SetPin(statusCallback, new CommandSetPinParams(new_pin)); 764 | await Webcrypt_Login(statusCallback, new CommandLoginParams(new_pin)); 765 | await plugin.init(); 766 | const { privateKey: webcrypt_privateKey, publicKey: webcrypt_publicKey } = await openpgp.generateKey({ 767 | curve: 'p256', 768 | userIDs: [{ name: 'Jon Smith', email: 'jon@example.com' }], 769 | format: 'object', 770 | date: plugin.date(), 771 | config: { hardwareKeys: plugin } 772 | }); 773 | } 774 | ``` 775 | -------------------------------------------------------------------------------- /milestones.md: -------------------------------------------------------------------------------- 1 | # Milestones 2 | 3 | ## Deliverables 4 | 5 | * Firmware containing the WebCrypt feature. 6 | * A JavaScript library to be used by arbitrary web applications to use Nitrokey WebCrypt. 7 | * Patch to openpgp.js, adding our WebCrypt library and make use of the device key store. 8 | * Documentation 9 | 10 | ## Options 11 | 12 | * OpenPGP Card interface 13 | * RSA support 14 | * OpenPGP.js integration 15 | 16 | ### A. Firmware 17 | 18 | #### I. Establishing PoC 19 | 20 | * Communication layer over FIDO2. Estimation assumes no code reuse. 21 | * Initial design and structure for commands - Setting up code structure and design for commands and implementations 22 | * Initialize and restore from seed - Master seed handling. Additional time for security analysis. Basic tests included. 23 | * Generate non-resident key - Key generation. Additional time for security analysis. Basic tests included. 24 | * Sign(to_be_signed_data_hash, key_handle, HMAC, origin) - Expecting simple implementation. Basic tests included. 25 | * Decrypt(to_be_decrypted_data, key_handle, HMAC, origin) - Expecting simple implementation. Basic tests included. 26 | * Status (Unlocked, version, available resident key slots) - Expecting simple implementation. Basic tests included. 27 | * Additional firmware tests - Tests for everyday usage, edge cases, invalid use cases. 28 | 29 | #### II. Encrypted storage 30 | 31 | * Encrypted resident keys and master key (for derived keys) - for all user data entities 32 | 33 | #### III. Resident keys feature 34 | 35 | * Write resident key - for external keys only - Expecting simple implementation. Basic tests included. 36 | * Read public key of resident keys - Expecting simple implementation. Basic tests included. 37 | 38 | #### IV. FIDO U2F support 39 | 40 | * CTAP1 transport layer (for backward compatibility) 41 | * Unlock - For U2F compatibility - Expecting simple implementation. Basic tests included. 42 | * Reset - For U2F compatibility - Expecting simple implementation. Basic tests included. 43 | * Configure - U2F and other options - Expecting simple implementation. Basic tests included. 44 | * PIN's use - each action, or once per session 45 | 46 | #### V. RSA support 47 | 48 | * RSA support - firmware side - tests included. For resident keys only. 49 | * RSA support - Nitrokey JS library - Add support to JS library 50 | * RSA support - OpenPGP.js - Add support to 3rd party JS library 51 | 52 | #### VI. NFC support 53 | 54 | * NFC tests and bug fixes - Tests for NFC interactions (PC/Mobile) 55 | 56 | #### B. JavaScript library for web applications 57 | 58 | * API design - API design for the JS Nitrokey WebCrypt library 59 | * API implementation - Library implementation 60 | * Tests: both automatic and manual Javascript tests 61 | * JS Demo Application - Demo application, similar to the OnlyKey’s demo + additional features if time permits 62 | 63 | #### C. Documentation 64 | 65 | * Firmware: Commands and Features - Examples of use (developers-centric, to pick-up framework) 66 | * JavaScript library - Focus on use cases 67 | 68 | ## Extensions 69 | 70 | #### D. OpenPGP Card Interface 71 | 72 | * Basic integration - CCID (GnuPG + etc.) and our custom access, assuming Solo’s OpenPGP card integration. 73 | * ECDSA support - Signing function already provided, but the format parsing is to be implemented. Usable for ECC 74 | encryption. 75 | * ECC decryption 76 | * Feature completion and improvements of given implementation 77 | * Security review and improvements 78 | 79 | #### E. OpenPGP.js patch 80 | 81 | Patch for OpenPGP.js to use Nitrokey WebCrypt extension (to use our device as key storage, instead of host storage) 82 | 83 | * First implementation – decryption operation - JavaScript implementation of the library 84 | * Tests Javascript automatic and manual tests 85 | * Signing 86 | * Key handling: 87 | * import 88 | * generation 89 | * Reserved time for additional corrections, developers input 90 | * Documentation of OpenPGP patch changes - Documentation for OpenPGP.js developers/users, to be included by them 91 | -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /tools/Makefile: -------------------------------------------------------------------------------- 1 | all: ../images/packet.svg 2 | 3 | BITFIELDD=node_modules/bit-field 4 | BITFIELD=$(BITFIELDD)/bin/bitfield.js 5 | 6 | $(BITFIELDD): package.json 7 | npm install 8 | 9 | %.svg: %.json $(BITFIELDD) 10 | $(BITFIELD) -i $< --vflip --hflip --lanes 2 --bits 12 > $@ 11 | 12 | .PHONY: clean 13 | clean: 14 | -rm -r ./node_modules/ 15 | 16 | -------------------------------------------------------------------------------- /tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nitrokey-webcrypt", 3 | "version": "0.1.0", 4 | "description": "## Summary", 5 | "main": "index.js", 6 | "dependencies": { 7 | "bit-field": "^1.3.6" 8 | }, 9 | "devDependencies": {}, 10 | "####devDependencies": { 11 | "mermaid": "^8.5.0", 12 | "mermaid-filter": "^1.4.5" 13 | }, 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/Nitrokey/nitrokey-webcrypt.git" 20 | }, 21 | "author": "", 22 | "bugs": { 23 | "url": "https://github.com/Nitrokey/nitrokey-webcrypt/issues" 24 | }, 25 | "homepage": "https://github.com/Nitrokey/nitrokey-webcrypt#readme" 26 | } 27 | --------------------------------------------------------------------------------