├── 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 | 
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 |
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 | 
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 |
--------------------------------------------------------------------------------