Coverage Report

Created: 2026-02-26 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/njs/external/njs_crypto_module.c
Line
Count
Source
1
2
/*
3
 * Copyright (C) Dmitry Volyntsev
4
 * Copyright (C) NGINX, Inc.
5
 */
6
7
8
#include <njs.h>
9
#include <njs_string.h>
10
#include <njs_buffer.h>
11
#include "njs_hash.h"
12
13
14
typedef void (*njs_hash_init)(njs_hash_t *ctx);
15
typedef void (*njs_hash_update)(njs_hash_t *ctx, const void *data, size_t size);
16
typedef void (*njs_hash_final)(u_char result[32], njs_hash_t *ctx);
17
18
typedef njs_int_t (*njs_digest_encode)(njs_vm_t *vm, njs_value_t *value,
19
    const njs_str_t *src);
20
21
22
typedef struct {
23
    njs_str_t           name;
24
25
    size_t              size;
26
    njs_hash_init       init;
27
    njs_hash_update     update;
28
    njs_hash_final      final;
29
} njs_hash_alg_t;
30
31
typedef struct {
32
    njs_hash_t          ctx;
33
    njs_hash_alg_t      *alg;
34
} njs_digest_t;
35
36
typedef struct {
37
    u_char              opad[64];
38
    njs_hash_t          ctx;
39
    njs_hash_alg_t      *alg;
40
} njs_hmac_t;
41
42
43
typedef struct {
44
    njs_str_t             name;
45
46
    njs_digest_encode     encode;
47
} njs_crypto_enc_t;
48
49
50
static njs_hash_alg_t *njs_crypto_algorithm(njs_vm_t *vm,
51
    njs_value_t *value);
52
static njs_crypto_enc_t *njs_crypto_encoding(njs_vm_t *vm,
53
    njs_value_t *value);
54
static njs_int_t njs_buffer_digest(njs_vm_t *vm, njs_value_t *value,
55
    const njs_str_t *src);
56
static njs_int_t njs_crypto_create_hash(njs_vm_t *vm, njs_value_t *args,
57
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
58
static njs_int_t njs_hash_prototype_update(njs_vm_t *vm, njs_value_t *args,
59
    njs_uint_t nargs, njs_index_t hmac, njs_value_t *retval);
60
static njs_int_t njs_hash_prototype_digest(njs_vm_t *vm, njs_value_t *args,
61
    njs_uint_t nargs, njs_index_t hmac, njs_value_t *retval);
62
static njs_int_t njs_hash_prototype_copy(njs_vm_t *vm, njs_value_t *args,
63
    njs_uint_t nargs, njs_index_t hmac, njs_value_t *retval);
64
static njs_int_t njs_crypto_create_hmac(njs_vm_t *vm, njs_value_t *args,
65
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
66
67
static njs_int_t njs_crypto_init(njs_vm_t *vm);
68
69
70
static njs_hash_alg_t njs_hash_algorithms[] = {
71
72
   {
73
     njs_str("md5"),
74
     16,
75
     njs_md5_init,
76
     njs_md5_update,
77
     njs_md5_final
78
   },
79
80
   {
81
     njs_str("sha1"),
82
     20,
83
     njs_sha1_init,
84
     njs_sha1_update,
85
     njs_sha1_final
86
   },
87
88
   {
89
     njs_str("sha256"),
90
     32,
91
     njs_sha2_init,
92
     njs_sha2_update,
93
     njs_sha2_final
94
   },
95
96
   {
97
    njs_null_str,
98
    0,
99
    NULL,
100
    NULL,
101
    NULL
102
   }
103
104
};
105
106
107
static njs_crypto_enc_t njs_encodings[] = {
108
109
   {
110
     njs_str("buffer"),
111
     njs_buffer_digest
112
   },
113
114
   {
115
     njs_str("hex"),
116
     njs_string_hex
117
   },
118
119
   {
120
     njs_str("base64"),
121
     njs_string_base64
122
   },
123
124
   {
125
     njs_str("base64url"),
126
     njs_string_base64url
127
   },
128
129
   {
130
    njs_null_str,
131
    NULL
132
   }
133
};
134
135
136
static njs_external_t  njs_ext_crypto_hash[] = {
137
138
    {
139
        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
140
        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
141
        .u.property = {
142
            .value = "Hash",
143
        }
144
    },
145
146
    {
147
        .flags = NJS_EXTERN_METHOD,
148
        .name.string = njs_str("update"),
149
        .writable = 1,
150
        .configurable = 1,
151
        .u.method = {
152
            .native = njs_hash_prototype_update,
153
            .magic8 = 0,
154
        }
155
    },
156
157
    {
158
        .flags = NJS_EXTERN_METHOD,
159
        .name.string = njs_str("digest"),
160
        .writable = 1,
161
        .configurable = 1,
162
        .u.method = {
163
            .native = njs_hash_prototype_digest,
164
            .magic8 = 0,
165
        }
166
    },
167
168
    {
169
        .flags = NJS_EXTERN_METHOD,
170
        .name.string = njs_str("copy"),
171
        .writable = 1,
172
        .configurable = 1,
173
        .u.method = {
174
            .native = njs_hash_prototype_copy,
175
        }
176
    },
177
178
    {
179
        .flags = NJS_EXTERN_METHOD,
180
        .name.string = njs_str("constructor"),
181
        .writable = 1,
182
        .configurable = 1,
183
        .u.method = {
184
            .native = njs_crypto_create_hash,
185
        }
186
    },
187
};
188
189
190
static njs_external_t  njs_ext_crypto_hmac[] = {
191
192
    {
193
        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
194
        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
195
        .u.property = {
196
            .value = "Hmac",
197
        }
198
    },
199
200
    {
201
        .flags = NJS_EXTERN_METHOD,
202
        .name.string = njs_str("update"),
203
        .writable = 1,
204
        .configurable = 1,
205
        .u.method = {
206
            .native = njs_hash_prototype_update,
207
            .magic8 = 1,
208
        }
209
    },
210
211
    {
212
        .flags = NJS_EXTERN_METHOD,
213
        .name.string = njs_str("digest"),
214
        .writable = 1,
215
        .configurable = 1,
216
        .u.method = {
217
            .native = njs_hash_prototype_digest,
218
            .magic8 = 1,
219
        }
220
    },
221
222
    {
223
        .flags = NJS_EXTERN_METHOD,
224
        .name.string = njs_str("constructor"),
225
        .writable = 1,
226
        .configurable = 1,
227
        .u.method = {
228
            .native = njs_crypto_create_hmac,
229
            .magic8 = 0,
230
        }
231
    },
232
};
233
234
235
static njs_external_t  njs_ext_crypto_crypto[] = {
236
237
    {
238
        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
239
        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
240
        .u.property = {
241
            .value = "crypto",
242
        }
243
    },
244
245
    {
246
        .flags = NJS_EXTERN_METHOD,
247
        .name.string = njs_str("createHash"),
248
        .writable = 1,
249
        .configurable = 1,
250
        .enumerable = 1,
251
        .u.method = {
252
            .native = njs_crypto_create_hash,
253
            .magic8 = 0,
254
        }
255
    },
256
257
    {
258
        .flags = NJS_EXTERN_METHOD,
259
        .name.string = njs_str("createHmac"),
260
        .writable = 1,
261
        .configurable = 1,
262
        .enumerable = 1,
263
        .u.method = {
264
            .native = njs_crypto_create_hmac,
265
            .magic8 = 0,
266
        }
267
    },
268
};
269
270
271
static njs_int_t    njs_crypto_hash_proto_id;
272
static njs_int_t    njs_crypto_hmac_proto_id;
273
274
275
njs_module_t  njs_crypto_module = {
276
    .name = njs_str("crypto"),
277
    .preinit = NULL,
278
    .init = njs_crypto_init,
279
};
280
281
282
static njs_int_t
283
njs_crypto_create_hash(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
284
    njs_index_t unused, njs_value_t *retval)
285
0
{
286
0
    njs_digest_t    *dgst;
287
0
    njs_hash_alg_t  *alg;
288
289
0
    alg = njs_crypto_algorithm(vm, njs_arg(args, nargs, 1));
290
0
    if (njs_slow_path(alg == NULL)) {
291
0
        return NJS_ERROR;
292
0
    }
293
294
0
    dgst = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_digest_t));
295
0
    if (njs_slow_path(dgst == NULL)) {
296
0
        njs_vm_memory_error(vm);
297
0
        return NJS_ERROR;
298
0
    }
299
300
0
    dgst->alg = alg;
301
302
0
    alg->init(&dgst->ctx);
303
304
0
    return njs_vm_external_create(vm, retval, njs_crypto_hash_proto_id,
305
0
                                  dgst, 0);
306
0
}
307
308
309
static njs_int_t
310
njs_hash_prototype_update(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
311
    njs_index_t hmac, njs_value_t *retval)
312
0
{
313
0
    njs_str_t                    data;
314
0
    njs_int_t                    ret;
315
0
    njs_hmac_t                   *ctx;
316
0
    njs_value_t                  *this, *value;
317
0
    njs_digest_t                 *dgst;
318
0
    njs_opaque_value_t           result;
319
0
    const njs_buffer_encoding_t  *enc;
320
321
0
    this = njs_argument(args, 0);
322
323
0
    if (!hmac) {
324
0
        dgst = njs_vm_external(vm, njs_crypto_hash_proto_id, this);
325
0
        if (njs_slow_path(dgst == NULL)) {
326
0
            njs_vm_type_error(vm, "\"this\" is not a hash object");
327
0
            return NJS_ERROR;
328
0
        }
329
330
0
        if (njs_slow_path(dgst->alg == NULL)) {
331
0
            njs_vm_error(vm, "Digest already called");
332
0
            return NJS_ERROR;
333
0
        }
334
335
0
        ctx = NULL;
336
337
0
    } else {
338
0
        ctx = njs_vm_external(vm, njs_crypto_hmac_proto_id, this);
339
0
        if (njs_slow_path(ctx == NULL)) {
340
0
            njs_vm_type_error(vm, "\"this\" is not a hmac object");
341
0
            return NJS_ERROR;
342
0
        }
343
344
0
        if (njs_slow_path(ctx->alg == NULL)) {
345
0
            njs_vm_error(vm, "Digest already called");
346
0
            return NJS_ERROR;
347
0
        }
348
349
0
        dgst = NULL;
350
0
    }
351
352
0
    value = njs_arg(args, nargs, 1);
353
354
0
    if (njs_value_is_string(value)) {
355
0
        enc = njs_buffer_encoding(vm, njs_arg(args, nargs, 2), 1);
356
0
        if (njs_slow_path(enc == NULL)) {
357
0
            return NJS_ERROR;
358
0
        }
359
360
0
        ret = njs_buffer_decode_string(vm, value, njs_value_arg(&result), enc);
361
0
        if (njs_slow_path(ret != NJS_OK)) {
362
0
            return NJS_ERROR;
363
0
        }
364
365
0
        njs_value_string_get(vm, njs_value_arg(&result), &data);
366
367
0
    } else if (njs_value_is_buffer(value)) {
368
0
        ret = njs_value_buffer_get(vm, value, &data);
369
0
        if (njs_slow_path(ret != NJS_OK)) {
370
0
            return NJS_ERROR;
371
0
        }
372
373
0
    } else {
374
0
        njs_vm_type_error(vm, "data is not a string or Buffer-like object");
375
376
0
        return NJS_ERROR;
377
0
    }
378
379
0
    if (!hmac) {
380
0
        dgst->alg->update(&dgst->ctx, data.start, data.length);
381
382
0
    } else {
383
0
        ctx->alg->update(&ctx->ctx, data.start, data.length);
384
0
    }
385
386
0
    njs_value_assign(retval, this);
387
388
0
    return NJS_OK;
389
0
}
390
391
392
static njs_int_t
393
njs_hash_prototype_digest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
394
    njs_index_t hmac, njs_value_t *retval)
395
0
{
396
0
    njs_str_t         str;
397
0
    njs_hmac_t        *ctx;
398
0
    njs_value_t       *this;
399
0
    njs_digest_t      *dgst;
400
0
    njs_hash_alg_t    *alg;
401
0
    njs_crypto_enc_t  *enc;
402
0
    u_char            hash1[32], digest[32];
403
404
0
    this = njs_argument(args, 0);
405
406
0
    if (!hmac) {
407
0
        dgst = njs_vm_external(vm, njs_crypto_hash_proto_id, this);
408
0
        if (njs_slow_path(dgst == NULL)) {
409
0
            njs_vm_type_error(vm, "\"this\" is not a hash object");
410
0
            return NJS_ERROR;
411
0
        }
412
413
0
        if (njs_slow_path(dgst->alg == NULL)) {
414
0
            goto exception;
415
0
        }
416
417
0
        ctx = NULL;
418
419
0
    } else {
420
0
        ctx = njs_vm_external(vm, njs_crypto_hmac_proto_id, this);
421
0
        if (njs_slow_path(ctx == NULL)) {
422
0
            njs_vm_type_error(vm, "\"this\" is not a hmac object");
423
0
            return NJS_ERROR;
424
0
        }
425
426
0
        if (njs_slow_path(ctx->alg == NULL)) {
427
0
            goto exception;
428
0
        }
429
430
0
        dgst = NULL;
431
0
    }
432
433
0
    enc = njs_crypto_encoding(vm, njs_arg(args, nargs, 1));
434
0
    if (njs_slow_path(enc == NULL)) {
435
0
        return NJS_ERROR;
436
0
    }
437
438
0
    if (!hmac) {
439
0
        alg = dgst->alg;
440
0
        alg->final(digest, &dgst->ctx);
441
0
        dgst->alg = NULL;
442
443
0
    } else {
444
0
        alg = ctx->alg;
445
0
        alg->final(hash1, &ctx->ctx);
446
447
0
        alg->init(&ctx->ctx);
448
0
        alg->update(&ctx->ctx, ctx->opad, 64);
449
0
        alg->update(&ctx->ctx, hash1, alg->size);
450
0
        alg->final(digest, &ctx->ctx);
451
0
        ctx->alg = NULL;
452
0
    }
453
454
0
    str.start = digest;
455
0
    str.length = alg->size;
456
457
0
    return enc->encode(vm, retval, &str);
458
459
0
exception:
460
461
0
    njs_vm_error(vm, "Digest already called");
462
463
0
    return NJS_ERROR;
464
0
}
465
466
467
static njs_int_t
468
njs_hash_prototype_copy(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
469
    njs_index_t unused, njs_value_t *retval)
470
0
{
471
0
    njs_digest_t  *dgst, *copy;
472
473
0
    dgst = njs_vm_external(vm, njs_crypto_hash_proto_id, njs_argument(args, 0));
474
0
    if (njs_slow_path(dgst == NULL)) {
475
0
        njs_vm_type_error(vm, "\"this\" is not a hash object");
476
0
        return NJS_ERROR;
477
0
    }
478
479
0
    if (njs_slow_path(dgst->alg == NULL)) {
480
0
        njs_vm_error(vm, "Digest already called");
481
0
        return NJS_ERROR;
482
0
    }
483
484
0
    copy = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_digest_t));
485
0
    if (njs_slow_path(copy == NULL)) {
486
0
        njs_vm_memory_error(vm);
487
0
        return NJS_ERROR;
488
0
    }
489
490
0
    memcpy(copy, dgst, sizeof(njs_digest_t));
491
492
0
    return njs_vm_external_create(vm, retval,
493
0
                                  njs_crypto_hash_proto_id, copy, 0);
494
0
}
495
496
497
static njs_int_t
498
njs_crypto_create_hmac(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
499
    njs_index_t unused, njs_value_t *retval)
500
0
{
501
0
    njs_int_t                    ret;
502
0
    njs_str_t                    key;
503
0
    njs_uint_t                   i;
504
0
    njs_hmac_t                   *ctx;
505
0
    njs_value_t                  *value;
506
0
    njs_hash_alg_t               *alg;
507
0
    njs_opaque_value_t           result;
508
0
    const njs_buffer_encoding_t  *enc;
509
0
    u_char                       digest[32], key_buf[64];
510
511
0
    alg = njs_crypto_algorithm(vm, njs_arg(args, nargs, 1));
512
0
    if (njs_slow_path(alg == NULL)) {
513
0
        return NJS_ERROR;
514
0
    }
515
516
0
    value = njs_arg(args, nargs, 2);
517
518
0
    if (njs_value_is_string(value)) {
519
0
        enc = njs_buffer_encoding(vm, njs_value_arg(&njs_value_undefined), 1);
520
0
        if (njs_slow_path(enc == NULL)) {
521
0
            return NJS_ERROR;
522
0
        }
523
524
0
        ret = njs_buffer_decode_string(vm, value, njs_value_arg(&result), enc);
525
0
        if (njs_slow_path(ret != NJS_OK)) {
526
0
            return NJS_ERROR;
527
0
        }
528
529
0
        njs_value_string_get(vm, njs_value_arg(&result), &key);
530
531
0
    } else if (njs_value_is_buffer(value)) {
532
0
        ret = njs_value_buffer_get(vm, value, &key);
533
0
        if (njs_slow_path(ret != NJS_OK)) {
534
0
            return NJS_ERROR;
535
0
        }
536
537
0
    } else {
538
0
        njs_vm_type_error(vm, "key is not a string or Buffer-like object");
539
540
0
        return NJS_ERROR;
541
0
    }
542
543
0
    ctx = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(njs_hmac_t));
544
0
    if (njs_slow_path(ctx == NULL)) {
545
0
        njs_vm_memory_error(vm);
546
0
        return NJS_ERROR;
547
0
    }
548
549
0
    ctx->alg = alg;
550
551
0
    if (key.length > sizeof(key_buf)) {
552
0
        alg->init(&ctx->ctx);
553
0
        alg->update(&ctx->ctx, key.start, key.length);
554
0
        alg->final(digest, &ctx->ctx);
555
556
0
        memcpy(key_buf, digest, alg->size);
557
0
        njs_explicit_memzero(key_buf + alg->size, sizeof(key_buf) - alg->size);
558
559
0
    } else {
560
0
        memcpy(key_buf, key.start, key.length);
561
0
        njs_explicit_memzero(key_buf + key.length,
562
0
                             sizeof(key_buf) - key.length);
563
0
    }
564
565
0
    for (i = 0; i < 64; i++) {
566
0
        ctx->opad[i] = key_buf[i] ^ 0x5c;
567
0
    }
568
569
0
    for (i = 0; i < 64; i++) {
570
0
         key_buf[i] ^= 0x36;
571
0
    }
572
573
0
    alg->init(&ctx->ctx);
574
0
    alg->update(&ctx->ctx, key_buf, 64);
575
576
0
    return njs_vm_external_create(vm, retval, njs_crypto_hmac_proto_id,
577
0
                                  ctx, 0);
578
0
}
579
580
581
static njs_hash_alg_t *
582
njs_crypto_algorithm(njs_vm_t *vm, njs_value_t *value)
583
0
{
584
0
    njs_str_t       name;
585
0
    njs_hash_alg_t  *e;
586
587
0
    if (njs_slow_path(!njs_value_is_string(value))) {
588
0
        njs_vm_type_error(vm, "algorithm must be a string");
589
0
        return NULL;
590
0
    }
591
592
0
    njs_value_string_get(vm, value, &name);
593
594
0
    for (e = &njs_hash_algorithms[0]; e->name.length != 0; e++) {
595
0
        if (njs_strstr_eq(&name, &e->name)) {
596
0
            return e;
597
0
        }
598
0
    }
599
600
0
    njs_vm_type_error(vm, "not supported algorithm: \"%V\"", &name);
601
602
0
    return NULL;
603
0
}
604
605
606
static njs_crypto_enc_t *
607
njs_crypto_encoding(njs_vm_t *vm, njs_value_t *value)
608
0
{
609
0
    njs_str_t         name;
610
0
    njs_crypto_enc_t  *e;
611
612
0
    if (njs_slow_path(!njs_value_is_string(value))) {
613
0
        if (!njs_value_is_undefined(value)) {
614
0
            njs_vm_type_error(vm, "encoding must be a string");
615
0
            return NULL;
616
0
        }
617
618
0
        return &njs_encodings[0];
619
0
    }
620
621
0
    njs_value_string_get(vm, value, &name);
622
623
0
    for (e = &njs_encodings[1]; e->name.length != 0; e++) {
624
0
        if (njs_strstr_eq(&name, &e->name)) {
625
0
            return e;
626
0
        }
627
0
    }
628
629
0
    njs_vm_type_error(vm, "Unknown digest encoding: \"%V\"", &name);
630
631
0
    return NULL;
632
0
}
633
634
635
static njs_int_t
636
njs_buffer_digest(njs_vm_t *vm, njs_value_t *value, const njs_str_t *src)
637
0
{
638
0
    return njs_buffer_new(vm, value, src->start, src->length);
639
0
}
640
641
642
static njs_int_t
643
njs_crypto_init(njs_vm_t *vm)
644
4.34k
{
645
4.34k
    njs_int_t           ret, proto_id;
646
4.34k
    njs_mod_t           *module;
647
4.34k
    njs_opaque_value_t  value;
648
649
4.34k
    njs_crypto_hash_proto_id =
650
4.34k
                     njs_vm_external_prototype(vm, njs_ext_crypto_hash,
651
4.34k
                                               njs_nitems(njs_ext_crypto_hash));
652
4.34k
    if (njs_slow_path(njs_crypto_hash_proto_id < 0)) {
653
0
        return NJS_ERROR;
654
0
    }
655
656
4.34k
    njs_crypto_hmac_proto_id =
657
4.34k
                     njs_vm_external_prototype(vm, njs_ext_crypto_hmac,
658
4.34k
                                               njs_nitems(njs_ext_crypto_hmac));
659
4.34k
    if (njs_slow_path(njs_crypto_hmac_proto_id < 0)) {
660
0
        return NJS_ERROR;
661
0
    }
662
663
4.34k
    proto_id = njs_vm_external_prototype(vm, njs_ext_crypto_crypto,
664
4.34k
                                         njs_nitems(njs_ext_crypto_crypto));
665
4.34k
    if (njs_slow_path(proto_id < 0)) {
666
0
        return NJS_ERROR;
667
0
    }
668
669
4.34k
    ret = njs_vm_external_create(vm, njs_value_arg(&value), proto_id, NULL, 1);
670
4.34k
    if (njs_slow_path(ret != NJS_OK)) {
671
0
        return NJS_ERROR;
672
0
    }
673
674
4.34k
    module = njs_vm_add_module(vm, &njs_str_value("crypto"),
675
4.34k
                               njs_value_arg(&value));
676
4.34k
    if (njs_slow_path(module == NULL)) {
677
0
        return NJS_ERROR;
678
0
    }
679
680
4.34k
    return NJS_OK;
681
4.34k
}