1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
|
# Node.js `src/crypto` documentation
Welcome. You've found your way to the Node.js native crypto subsystem.
Do not be afraid.
While crypto may be a dark, mysterious, and forboding subject; and while
this directory may be filled with many `*.h` and `*.cc` files, finding
your way around is not too difficult. And I can promise you that a Gru
will not jump out of the shadows and eat you (well, "promise" may be a
bit too strong, a Gru may jump out of the shadows and eat you if you
live in a place where such things are possible).
## Finding your way around
All of the code in this directory is structured into units organized by
function or crypto protocol.
The following provide generalized utility declarations that are used throughout
the various other crypto files and other parts of Node.js:
* `crypto_util.h` / `crypto_util.cc` (Core crypto definitions)
* `crypto_common.h` / `crypto_common.h` (Shared TLS utility functions)
* `crypto_bio.c` / `crypto_bio.c` (Custom OpenSSL i/o implementation)
* `crypto_groups.h` (modp group definitions)
Of these, `crypto_util.h` and `crypto_util.cc` are the most important, as
they provide the core declarations and utility functions used most extensively
throughout the rest of the code.
The rest of the files are structured by their function, as detailed in the
following table:
| File (\*.h/\*.cc) | Description |
| -------------------- | -------------------------------------------------------------------------- |
| `crypto_aes` | AES Cipher support. |
| `crypto_cipher` | General Encryption/Decryption utilities. |
| `crypto_clienthello` | TLS/SSL client hello parser implementation. Used during SSL/TLS handshake. |
| `crypto_context` | Implementation of the `SecureContext` object. |
| `crypto_dh` | Diffie-Hellman Key Agreement implementation. |
| `crypto_dsa` | DSA (Digital Signature) Key Generation functions. |
| `crypto_ec` | Elliptic-curve cryptography implementation. |
| `crypto_hash` | Basic hash (e.g. SHA-256) functions. |
| `crypto_hkdf` | HKDF (Key derivation) implementation. |
| `crypto_hmac` | HMAC implementations. |
| `crypto_keys` | Utilities for using and generating secret, private, and public keys. |
| `crypto_pbkdf2` | PBKDF2 key / bit generation implementation. |
| `crypto_rsa` | RSA Key Generation functions. |
| `crypto_scrypt` | Scrypt key / bit generation implementation. |
| `crypto_sig` | General digital signature and verification utilities. |
| `crypto_spkac` | Netscape SPKAC certificate utilities. |
| `crypto_ssl` | Implementation of the `SSLWrap` object. |
| `crypto_timing` | Implementation of the TimingSafeEqual. |
When new crypto protocols are added, they will be added into their own
`crypto_` `*.h` and `*.cc` files.
## Helpful concepts
Node.js currently uses OpenSSL to provide it's crypto substructure.
(Some custom Node.js distributions -- such as Electron -- use BoringSSL
instead.)
This section aims to explain some of the utilities that have been
provided to make working with the OpenSSL APIs a bit easier.
### Pointer types
Most of the key OpenSSL types need to be explicitly freed when they are
no longer needed. Failure to do so introduces memory leaks. To make this
easier (and less error prone), the `crypto_util.h` defines a number of
smart-pointer aliases that should be used:
```cpp
using X509Pointer = DeleteFnPtr<X509, X509_free>;
using BIOPointer = DeleteFnPtr<BIO, BIO_free_all>;
using SSLCtxPointer = DeleteFnPtr<SSL_CTX, SSL_CTX_free>;
using SSLSessionPointer = DeleteFnPtr<SSL_SESSION, SSL_SESSION_free>;
using SSLPointer = DeleteFnPtr<SSL, SSL_free>;
using PKCS8Pointer = DeleteFnPtr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>;
using EVPKeyPointer = DeleteFnPtr<EVP_PKEY, EVP_PKEY_free>;
using EVPKeyCtxPointer = DeleteFnPtr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
using EVPMDPointer = DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free>;
using RSAPointer = DeleteFnPtr<RSA, RSA_free>;
using ECPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using BignumPointer = DeleteFnPtr<BIGNUM, BN_free>;
using NetscapeSPKIPointer = DeleteFnPtr<NETSCAPE_SPKI, NETSCAPE_SPKI_free>;
using ECGroupPointer = DeleteFnPtr<EC_GROUP, EC_GROUP_free>;
using ECPointPointer = DeleteFnPtr<EC_POINT, EC_POINT_free>;
using ECKeyPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using DHPointer = DeleteFnPtr<DH, DH_free>;
using ECDSASigPointer = DeleteFnPtr<ECDSA_SIG, ECDSA_SIG_free>;
using HMACCtxPointer = DeleteFnPtr<HMAC_CTX, HMAC_CTX_free>;
using CipherCtxPointer = DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
```
Examples of these being used are pervasive through the `src/crypto` code.
### `ByteSource`
The `ByteSource` class is a helper utility representing a _read-only_ byte
array. Instances can either wrap external ("foreign") data sources, such as
an `ArrayBuffer` (`v8::BackingStore`) or allocated data. If allocated data
is used, then the allocation is freed automatically when the `ByteSource` is
destroyed.
### `ArrayBufferOrViewContents`
The `ArrayBufferOfViewContents` class is a helper utility that abstracts
`ArrayBuffer`, `TypedArray`, or `DataView` inputs and provides access to
their underlying data pointers. It is used extensively through `src/crypto`
to make it easier to deal with inputs that allow any `ArrayBuffer`-backed
object.
### Key objects
Most crypto operations involve the use of keys -- cryptographic inputs
that protect data. There are three general types of keys:
* Secret Keys (Symmetric)
* Public Keys (Asymmetric)
* Private Keys (Asymmetric)
Secret keys consist of a variable number of bytes. They are "symmetrical"
in that the same key used to encrypt data, or generate a signature, must
be used to decrypt or validate that signature. If two people are exchanging
messages encrypted using a secret key, both of them must have access to the
same secret key data.
Public and Private keys always come in pairs. When one is used to encrypt
data or generate a signature, the other is used to decrypt or validate the
signature. The Public key is intended to be shared and can be shared openly.
The Private key must be kept secret and known only to the owner of the key.
The `src/crypto` subsystem uses several objects to represent keys. These
objects are structured in a way to allow key data to be shared across
multiple threads (the Node.js main thread, Worker Threads, and the libuv
threadpool).
Refer to `crypto_keys.h` and `crypto_keys.cc` for all code relating to the
core key objects.
#### `ManagedEVPPKey`
The `ManagedEVPPKey` class is a smart pointer for OpenSSL `EVP_PKEY`
structures. These manage the lifecycle of Public and Private key pairs.
#### `KeyObjectData`
`KeyObjectData` is an internal thread-safe structure used to wrap either
a `ManagedEVPPKey` (for Public or Private keys) or a `ByteSource` containing
a Secret key.
#### `KeyObjectHandle`
The `KeyObjectHandle` provides the interface between the native C++ code
handling keys and the public JavaScript `KeyObject` API.
#### `KeyObject`
A `KeyObject` is the public Node.js-specific API for keys. A single
`KeyObject` wraps exactly one `KeyObjectHandle`.
#### `CryptoKey`
A `CryptoKey` is the Web Crypto API's alternative to `KeyObject`. In the
Node.js implementation, `CryptoKey` is a thin wrapper around the
`KeyObject` and it is largely possible to use them interchangeably.
### `CryptoJob`
All operations that are not either Stream-based or single-use functions
are built around the `CryptoJob` class.
A `CryptoJob` encapsulates a single crypto operation that can be
invoked synchronously or asynchronously.
The `CryptoJob` class itself is a C++ template that takes a single
`CryptoJobTraits` struct as a parameter. The `CryptoJobTraits`
provides the implementation detail of the job.
There are (currently) four basic `CryptoJob` specializations:
* `CipherJob` (defined in `src/crypto_cipher.h`) -- Used for
encrypt and decrypt operations.
* `KeyGenJob` (defined in `src/crypto_keygen.h`) -- Used for
secret and key pair generation operations.
* `KeyExportJob` (defined in `src/crypto_keys.h`) -- Used for
key export operations.
* `DeriveBitsJob` (defined in `src/crypto_util.h`) -- Used for
key and byte derivation operations.
Every `CryptoJobTraits` provides two fundamental operations:
* Configuration -- Processes input arguments when a
`CryptoJob` instance is created.
* Implementation -- Provides the specific implementation of
the operation.
The Configuration is typically provided by an `AdditionalConfig()`
method, the signature of which is slightly different for each
of the above `CryptoJob` specializations. Despite the signature
differences, the purpose of the `AdditionalConfig()` function
remains the same: to process input arguments and set the properties
on the `CryptoJob`'s parameters object.
The parameters object is specific to each `CryptoJob` type, and
is stored with the `CryptoJob`. It holds all of the inputs that
are used by the Implementation. The inputs held by the parameters
must be threadsafe.
The `AdditionalConfig()` function is always called when the
`CryptoJob` instance is being created.
The Implementation function is unique to each of the `CryptoJob`
specializations and will either be called synchronously within
the current thread or from within the libuv threadpool.
Every `CryptoJob` instance exposes a `run()` function to the
JavaScript layer. When called, `run()` with either dispatch the
job to the libuv threadpool or invoke the Implementation
function synchronously. If invoked synchronously, run() will
return a JavaScript array. The first value in the array is
either an `Error` or `undefined`. If the operation was successful,
the second value in the array will contain the result of the
operation. Typically, the result is an `ArrayBuffer`, but
certain `CryptoJob` types can alter the output.
If the `CryptoJob` is processed asynchronously, then the job
must have an `ondone` property whose value is a function that
is invoked when the operation is complete. This function will
be called with two arguments. The first is either an `Error`
or `undefined`, and the second is the result of the operation
if successful.
For `CipherJob` types, the output is always an `ArrayBuffer`.
For `KeyExportJob` types, the output is either an `ArrayBuffer` or
a JavaScript object (for JWK output format);
For `KeyGenJob` types, the output is either a single KeyObject,
or an array containing a Public/Private key pair represented
either as a `KeyObjectHandle` object or a `Buffer`.
For `DeriveBitsJob` type output is typically an `ArrayBuffer` but
can be other values (`RandomBytesJob` for instance, fills an
input buffer and always returns `undefined`).
### Errors
#### `ThrowCryptoError` and the `THROW_ERR_CRYPTO_*` macros
The `ThrowCryptoError()` is a legacy utility that will throw a
JavaScript exception containing details collected from OpenSSL
about a failed operation. `ThrowCryptoError()` should only be
used when necessary to report low-level OpenSSL failures.
In `node_errors.h`, there are a number of `ERR_CRYPTO_*`
macro definitions that define semantically specific errors.
These can be called from within the C++ code as functions,
like `THROW_ERR_CRYPTO_INVALID_IV(env)`. These methods
should be used to throw JavaScript errors when necessary.
## Crypto API patterns
### Operation mode
All crypto functions in Node.js operate in one of three
modes:
* Synchronous single-call
* Asynchronous single-call
* Stream-oriented
It is often possible to perform various operations across
multiple modes. For instance, cipher and decipher operations
can be performed in any of the three modes.
Synchronous single-call operations are always blocking.
They perform their actions immediately.
```js
// Example synchronous single-call operation
const a = new Uint8Array(10);
const b = new Uint8Array(10);
crypto.timingSafeEqual(a, b);
```
Asynchronous single-call operations generally perform a
number of synchronous input validation steps, but then
defer the actual crypto-operation work to the libuv threadpool.
```js
// Example asynchronous single-call operation
const buf = new Uint8Array(10);
crypto.randomFill(buf, (err, buf) => {
console.log(buf);
});
```
For the legacy Node.js crypto API, asynchronous single-call
operations use the traditional Node.js callback pattern, as
illustrated in the previous `randomFill()` example. In the
Web Crypto API (accessible via `require('node:crypto').webcrypto`),
all asynchronous single-call operations are Promise-based.
```js
// Example Web Crypto API asynchronous single-call operation
const { subtle } = require('node:crypto').webcrypto;
subtle.generateKeys({ name: 'HMAC', length: 256 }, true, ['sign'])
.then((key) => {
console.log(key);
})
.catch((error) => {
console.error('an error occurred');
});
```
In nearly every case, asynchronous single-call operations make use
of the libuv threadpool to perform crypto operations off the main
event loop thread.
Stream-oriented operations use an object to maintain state
over multiple individual synchronous steps. The steps themselves
can be performed over time.
```js
// Example stream-oriented operation
const hash = crypto.createHash('sha256');
let updates = 10;
setTimeout(() => {
hash.update('hello world');
setTimeout(() => {
console.log(hash.digest();)
}, 1000);
}, 1000);
```
|