1
+ #include " openssl/rsa.h"
1
2
#include " openssl/sha.h"
2
3
#include < iostream>
3
4
#include < span>
@@ -10,6 +11,83 @@ namespace builtins {
10
11
11
12
namespace {
12
13
14
+ const EVP_MD *createDigestAlgorithm (JSContext *cx, JS::HandleObject key) {
15
+
16
+ JS::RootedObject alg (cx, CryptoKey::get_algorithm (key));
17
+
18
+ JS::RootedValue hash_val (cx);
19
+ JS_GetProperty (cx, alg, " hash" , &hash_val);
20
+ JS::RootedObject hash (cx, &hash_val.toObject ());
21
+ JS::RootedValue name_val (cx);
22
+ JS_GetProperty (cx, hash, " name" , &name_val);
23
+ size_t name_length;
24
+ auto cc = encode (cx, name_val, &name_length);
25
+
26
+ std::string_view name (cc.get (), name_length);
27
+ if (name == " SHA-1" ) {
28
+ return EVP_sha1 ();
29
+ } else if (name == " SHA-224" ) {
30
+ return EVP_sha224 ();
31
+ } else if (name == " SHA-256" ) {
32
+ return EVP_sha256 ();
33
+ } else if (name == " SHA-384" ) {
34
+ return EVP_sha384 ();
35
+ } else if (name == " SHA-512" ) {
36
+ return EVP_sha512 ();
37
+ } else {
38
+ // TODO Rename error to NotSupportedError
39
+ JS_ReportErrorLatin1 (cx, " NotSupportedError" );
40
+ return nullptr ;
41
+ }
42
+ }
43
+ // This implements https://w3c.github.io/webcrypto/#sha-operations for all
44
+ // the SHA algorithms that we support.
45
+ std::optional<std::span<uint8_t >> rawDigest (JSContext *cx, std::span<uint8_t > data,
46
+ const EVP_MD *algorithm, size_t buffer_size) {
47
+ unsigned int size;
48
+ auto buf = static_cast <unsigned char *>(JS_malloc (cx, buffer_size));
49
+ if (!buf) {
50
+ JS_ReportOutOfMemory (cx);
51
+ return std::nullopt;
52
+ }
53
+ if (!EVP_Digest (data.data (), data.size (), buf, &size, algorithm, NULL )) {
54
+ // 2. If performing the operation results in an error, then throw an OperationError.
55
+ // TODO: Change to an OperationError DOMException
56
+ JS_ReportErrorUTF8 (cx, " SubtleCrypto.digest: failed to create digest" );
57
+ JS_free (cx, buf);
58
+ return std::nullopt;
59
+ }
60
+ return std::span<uint8_t >(buf, size);
61
+ };
62
+
63
+ // This implements https://w3c.github.io/webcrypto/#sha-operations for all
64
+ // the SHA algorithms that we support.
65
+ JSObject *digest (JSContext *cx, std::span<uint8_t > data, const EVP_MD *algorithm,
66
+ size_t buffer_size) {
67
+ unsigned int size;
68
+ auto buf = static_cast <unsigned char *>(JS_malloc (cx, buffer_size));
69
+ if (!buf) {
70
+ JS_ReportOutOfMemory (cx);
71
+ return nullptr ;
72
+ }
73
+ if (!EVP_Digest (data.data (), data.size (), buf, &size, algorithm, NULL )) {
74
+ // 2. If performing the operation results in an error, then throw an OperationError.
75
+ // TODO: Change to an OperationError DOMException
76
+ JS_ReportErrorUTF8 (cx, " SubtleCrypto.digest: failed to create digest" );
77
+ JS_free (cx, buf);
78
+ return nullptr ;
79
+ }
80
+ // 3. Return a new ArrayBuffer containing result.
81
+ JS::RootedObject array_buffer (cx);
82
+ array_buffer.set (JS::NewArrayBufferWithContents (cx, size, buf));
83
+ if (!array_buffer) {
84
+ JS_free (cx, buf);
85
+ JS_ReportOutOfMemory (cx);
86
+ return nullptr ;
87
+ }
88
+ return array_buffer;
89
+ };
90
+
13
91
// https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1
14
92
// 6.3.1. Parameters for RSA Public Keys
15
93
std::unique_ptr<CryptoKeyRSAComponents> createRSAPublicKeyFromJWK (JSContext *cx, JsonWebKey *jwk) {
@@ -436,6 +514,196 @@ std::unique_ptr<CryptoAlgorithmDigest> CryptoAlgorithmDigest::normalize(JSContex
436
514
}
437
515
};
438
516
517
+ std::unique_ptr<CryptoAlgorithmSignVerify>
518
+ CryptoAlgorithmSignVerify::normalize (JSContext *cx, JS::HandleValue value) {
519
+ // Do steps 1 through 5.1 of https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm
520
+ auto identifierResult = normalizeIdentifier (cx, value);
521
+ if (identifierResult.isErr ()) {
522
+ // If we are here, this means either the identifier could not be coerced to a String or was not recognized
523
+ // In both those scenarios an exception will have already been created, which is why we are not creating one here.
524
+ return nullptr ;
525
+ }
526
+ auto identifier = identifierResult.unwrap ();
527
+ JS::Rooted<JSObject *> params (cx);
528
+
529
+ // The value can either be a JS String or a JS Object with a 'name' property which is the algorithm identifier.
530
+ // Other properties within the object will be the parameters for the algorithm to use.
531
+ if (value.isString ()) {
532
+ auto obj = JS_NewPlainObject (cx);
533
+ params.set (obj);
534
+ if (!JS_SetProperty (cx, params, " name" , value)) {
535
+ return nullptr ;
536
+ }
537
+ } else if (value.isObject ()) {
538
+ params.set (&value.toObject ());
539
+ }
540
+
541
+ // The table listed at https://w3c.github.io/webcrypto/#h-note-15 is what defines which algorithms support which operations
542
+ // RSASSA-PKCS1-v1_5, RSA-PSS, ECDSA, HMAC, are the algorithms
543
+ // which support the sign operation
544
+ switch (identifier) {
545
+ case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: {
546
+ return std::make_unique<CryptoAlgorithmRSASSA_PKCS1_v1_5_Sign_Verify>();
547
+ break ;
548
+ }
549
+ case CryptoAlgorithmIdentifier::HMAC:
550
+ case CryptoAlgorithmIdentifier::ECDSA:
551
+ case CryptoAlgorithmIdentifier::RSA_PSS: {
552
+ MOZ_ASSERT (false );
553
+ JS_ReportErrorASCII (cx, " Supplied algorithm is not yet supported" );
554
+ convertErrorToNotSupported (cx);
555
+ return nullptr ;
556
+ }
557
+ default : {
558
+ return nullptr ;
559
+ }
560
+ }
561
+ };
562
+
563
+ JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Sign_Verify::sign (JSContext *cx, JS::HandleObject key,
564
+ std::span<uint8_t > data) {
565
+
566
+ // 1. If the [[type]] internal slot of key is not "private", then throw an InvalidAccessError.
567
+ if (CryptoKey::type (key) != CryptoKeyType::Private) {
568
+ // TODO: Change to an InvalidAccessError instance
569
+ JS_ReportErrorLatin1 (cx, " InvalidAccessError" );
570
+ return nullptr ;
571
+ }
572
+
573
+ MOZ_ASSERT (CryptoKey::is_instance (key));
574
+ if (CryptoKey::type (key) != CryptoKeyType::Private) {
575
+ // TODO Rename error to InvalidAccessError
576
+ JS_ReportErrorLatin1 (cx, " InvalidAccessError" );
577
+ return nullptr ;
578
+ }
579
+
580
+ const EVP_MD *algorithm = createDigestAlgorithm (cx, key);
581
+ if (!algorithm) {
582
+ // TODO Rename error to OperationError
583
+ JS_ReportErrorLatin1 (cx, " OperationError" );
584
+ return nullptr ;
585
+ }
586
+
587
+ auto digestOption = ::builtins::rawDigest (cx, data, algorithm, EVP_MD_size (algorithm));
588
+ if (!digestOption.has_value ()) {
589
+ // TODO Rename error to OperationError
590
+ JS_ReportErrorLatin1 (cx, " OperationError" );
591
+ return nullptr ;
592
+ }
593
+ auto digest = digestOption.value ();
594
+
595
+ // 2. Perform the signature generation operation defined in Section 8.2 of [RFC3447] with the
596
+ // key represented by the [[handle]] internal slot of key as the signer's private key and the
597
+ // contents of message as M and using the hash function specified in the hash attribute of the
598
+ // [[algorithm]] internal slot of key as the Hash option for the EMSA-PKCS1-v1_5 encoding
599
+ // method.
600
+ // 3. If performing the operation results in an error, then throw an OperationError.
601
+ auto ctx = EVP_PKEY_CTX_new (CryptoKey::key (key), nullptr );
602
+ if (!ctx) {
603
+ // TODO Rename error to OperationError
604
+ JS_ReportErrorLatin1 (cx, " OperationError" );
605
+ return nullptr ;
606
+ }
607
+
608
+ if (EVP_PKEY_sign_init (ctx) <= 0 ) {
609
+ // TODO Rename error to OperationError
610
+ JS_ReportErrorLatin1 (cx, " OperationError" );
611
+ return nullptr ;
612
+ }
613
+
614
+ if (EVP_PKEY_CTX_set_rsa_padding (ctx, RSA_PKCS1_PADDING) <= 0 ) {
615
+ // TODO Rename error to OperationError
616
+ JS_ReportErrorLatin1 (cx, " OperationError" );
617
+ return nullptr ;
618
+ }
619
+
620
+ if (EVP_PKEY_CTX_set_signature_md (ctx, algorithm) <= 0 ) {
621
+ // TODO Rename error to OperationError
622
+ JS_ReportErrorLatin1 (cx, " OperationError" );
623
+ return nullptr ;
624
+ }
625
+
626
+ size_t signature_length;
627
+ if (EVP_PKEY_sign (ctx, nullptr , &signature_length, digest.data (), digest.size ()) <= 0 ) {
628
+ // TODO Rename error to OperationError
629
+ JS_ReportErrorLatin1 (cx, " OperationError" );
630
+ return nullptr ;
631
+ }
632
+
633
+ // 4. Let signature be the value S that results from performing the operation.
634
+ uint8_t *signature = reinterpret_cast <uint8_t *>(calloc (signature_length, sizeof (uint8_t )));
635
+ if (EVP_PKEY_sign (ctx, signature, &signature_length, digest.data (), digest.size ()) <= 0 ) {
636
+ // TODO Rename error to OperationError
637
+ JS_ReportErrorLatin1 (cx, " OperationError" );
638
+ return nullptr ;
639
+ }
640
+
641
+ // 5. Return a new ArrayBuffer associated with the relevant global object of this [HTML], and
642
+ // containing the bytes of signature.
643
+ JS::RootedObject buffer (cx, JS::NewArrayBufferWithContents (cx, signature_length, signature));
644
+ if (!buffer) {
645
+ // We can be here is the array buffer was too large -- if that was the case then a
646
+ // JSMSG_BAD_ARRAY_LENGTH will have been created. No other failure scenarios in this path will
647
+ // create a JS exception and so we need to create one.
648
+ if (!JS_IsExceptionPending (cx)) {
649
+ // TODO Rename error to InternalError
650
+ JS_ReportErrorLatin1 (cx, " InternalError" );
651
+ }
652
+ JS_free (cx, signature);
653
+ return nullptr ;
654
+ }
655
+ return buffer;
656
+ }
657
+
658
+ JS::Result<bool > CryptoAlgorithmRSASSA_PKCS1_v1_5_Sign_Verify::verify (JSContext *cx, JS::HandleObject key,
659
+ std::span<uint8_t > signature,
660
+ std::span<uint8_t > data) {
661
+ MOZ_ASSERT (CryptoKey::is_instance (key));
662
+
663
+ if (CryptoKey::type (key) != CryptoKeyType::Public) {
664
+ // TODO Rename error to InvalidAccessError
665
+ JS_ReportErrorLatin1 (cx, " InvalidAccessError" );
666
+ return JS::Result<bool >(JS::Error ());
667
+ }
668
+ const EVP_MD *algorithm = createDigestAlgorithm (cx, key);
669
+
670
+ auto digestOption = ::builtins::rawDigest (cx, data, algorithm, EVP_MD_size (algorithm));
671
+ if (!digestOption.has_value ()) {
672
+ // TODO Rename error to OperationError
673
+ JS_ReportErrorLatin1 (cx, " OperationError" );
674
+ return JS::Result<bool >(JS::Error ());
675
+ }
676
+
677
+ auto digest = digestOption.value ();
678
+
679
+ auto ctx = EVP_PKEY_CTX_new (CryptoKey::key (key), nullptr );
680
+ if (!ctx) {
681
+ // TODO Rename error to OperationError
682
+ JS_ReportErrorLatin1 (cx, " OperationError" );
683
+ return JS::Result<bool >(JS::Error ());
684
+ }
685
+
686
+ if (EVP_PKEY_verify_init (ctx) != 1 ) {
687
+ // TODO Rename error to OperationError
688
+ JS_ReportErrorLatin1 (cx, " OperationError" );
689
+ return JS::Result<bool >(JS::Error ());
690
+ }
691
+
692
+ if (EVP_PKEY_CTX_set_rsa_padding (ctx, RSA_PKCS1_PADDING) != 1 ) {
693
+ // TODO Rename error to OperationError
694
+ JS_ReportErrorLatin1 (cx, " OperationError" );
695
+ return JS::Result<bool >(JS::Error ());
696
+ }
697
+
698
+ if (EVP_PKEY_CTX_set_signature_md (ctx, algorithm) != 1 ) {
699
+ // TODO Rename error to OperationError
700
+ JS_ReportErrorLatin1 (cx, " OperationError" );
701
+ return JS::Result<bool >(JS::Error ());
702
+ }
703
+
704
+ return EVP_PKEY_verify (ctx, signature.data (), signature.size (), digest.data (), digest.size ()) ==
705
+ 1 ;
706
+ }
439
707
440
708
std::unique_ptr<CryptoAlgorithmImportKey>
441
709
CryptoAlgorithmImportKey::normalize (JSContext *cx, JS::HandleValue value) {
@@ -460,7 +728,7 @@ CryptoAlgorithmImportKey::normalize(JSContext *cx, JS::HandleValue value) {
460
728
}
461
729
462
730
// The table listed at https://w3c.github.io/webcrypto/#h-note-15 is what defines which algorithms support which operations
463
- // RSASSA-PKCS1-v1_5, RSA-PSS, RSA-OAEP, ECDSA, ECDH, AES-CTR, AES-CBC, AES-GCM, AES-KW, HMAC, HKDF, PBKDF2 are the algorithms
731
+ // RSASSA-PKCS1-v1_5, RSA-PSS, RSA-OAEP, ECDSA, ECDH, AES-CTR, AES-CBC, AES-GCM, AES-KW, HMAC, HKDF, PBKDF2 are the algorithms
464
732
// which support the importKey operation
465
733
switch (identifier) {
466
734
case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: {
@@ -524,7 +792,7 @@ JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::importKey(JSContext *cx, Cryp
524
792
525
793
526
794
// 2.2 If the d field of jwk is present and usages contains an entry which
527
- // is not "sign", or, if the d field of jwk is not present and usages
795
+ // is not "sign", or, if the d field of jwk is not present and usages
528
796
// contains an entry which is not "verify" then throw a SyntaxError.
529
797
bool isUsagesAllowed = false ;
530
798
// public key
@@ -729,35 +997,6 @@ JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::toObject(JSContext *cx) {
729
997
return algorithm;
730
998
}
731
999
732
- namespace {
733
- // This implements https://w3c.github.io/webcrypto/#sha-operations for all
734
- // the SHA algorithms that we support.
735
- JSObject *digest (JSContext *cx, std::span<uint8_t > data, const EVP_MD * algorithm, size_t buffer_size) {
736
- unsigned int size;
737
- auto buf = static_cast <unsigned char *>(JS_malloc (cx, buffer_size));
738
- if (!buf) {
739
- JS_ReportOutOfMemory (cx);
740
- return nullptr ;
741
- }
742
- if (!EVP_Digest (data.data (), data.size (), buf, &size, algorithm, NULL )) {
743
- // 2. If performing the operation results in an error, then throw an OperationError.
744
- // TODO: Change to an OperationError DOMException
745
- JS_ReportErrorUTF8 (cx, " SubtleCrypto.digest: failed to create digest" );
746
- JS_free (cx, buf);
747
- return nullptr ;
748
- }
749
- // 3. Return a new ArrayBuffer containing result.
750
- JS::RootedObject array_buffer (cx);
751
- array_buffer.set (JS::NewArrayBufferWithContents (cx, size, buf));
752
- if (!array_buffer) {
753
- JS_free (cx, buf);
754
- JS_ReportOutOfMemory (cx);
755
- return nullptr ;
756
- }
757
- return array_buffer;
758
- };
759
- }
760
-
761
1000
JSObject *CryptoAlgorithmSHA1::digest (JSContext *cx, std::span<uint8_t > data) {
762
1001
return ::builtins::digest (cx, data, EVP_sha1 (), SHA_DIGEST_LENGTH);
763
1002
}
0 commit comments