11'use strict' ;
22
33const {
4+ ArrayIsArray,
45 ArrayPrototypeSlice,
56 FunctionPrototypeCall,
67 JSONParse,
78 JSONStringify,
89 ObjectDefineProperties,
9- PromisePrototypeThen,
10+ ObjectDefineProperty,
11+ ObjectKeys,
12+ ObjectSetPrototypeOf,
1013 PromiseReject,
1114 PromiseResolve,
1215 ReflectApply,
1316 ReflectConstruct,
17+ SafeArrayIterator,
1418 StringPrototypeRepeat,
1519 StringPrototypeSlice,
1620 StringPrototypeStartsWith,
21+ SymbolIterator,
1722 SymbolToStringTag,
1823 TypedArrayPrototypeGetBuffer,
1924} = primordials ;
@@ -26,7 +31,10 @@ const {
2631 kWebCryptoCipherDecrypt,
2732} = internalBinding ( 'crypto' ) ;
2833
29- const { TextDecoder, TextEncoder } = require ( 'internal/encoding' ) ;
34+ const {
35+ decodeUTF8,
36+ encodeUtf8String,
37+ } = internalBinding ( 'encoding_binding' ) ;
3038
3139const {
3240 codes : {
@@ -55,6 +63,7 @@ const {
5563
5664const {
5765 getBlockSize,
66+ jobPromiseThen,
5867 normalizeAlgorithm,
5968 normalizeHashName,
6069 validateMaxBufferLength,
@@ -71,13 +80,20 @@ const {
7180 randomUUID : _randomUUID ,
7281} = require ( 'internal/crypto/random' ) ;
7382
83+ const {
84+ isPromise,
85+ } = require ( 'internal/util/types' ) ;
86+
7487let webidl ;
7588
7689// WebCrypto methods return promises, including for synchronous validation
7790// failures. Keep that conversion in one place so method bodies stay readable.
7891function callSubtleCryptoMethod ( fn , receiver , args ) {
7992 try {
80- return PromiseResolve ( ReflectApply ( fn , receiver , args ) ) ;
93+ const result = ReflectApply ( fn , receiver , args ) ;
94+ // PromiseResolve(promise) reads promise.constructor. Avoid re-wrapping
95+ // native promises so Promise.prototype.constructor pollution is ignored.
96+ return isPromise ( result ) ? result : PromiseResolve ( result ) ;
8197 } catch ( err ) {
8298 return PromiseReject ( err ) ;
8399 }
@@ -384,7 +400,7 @@ function deriveKeyImpl(
384400 throw lazyDOMException ( 'Unrecognized algorithm name' , 'NotSupportedError' ) ;
385401 }
386402
387- return PromisePrototypeThen ( bits , ( bits ) => FunctionPrototypeCall (
403+ return jobPromiseThen ( bits , ( bits ) => FunctionPrototypeCall (
388404 importKeySync ,
389405 this ,
390406 'raw-secret' , bits , derivedKeyAlgorithm , extractable , keyUsages ,
@@ -569,32 +585,37 @@ function exportKeyRawSecret(key, format) {
569585 }
570586}
571587
588+ // Creates a descriptor for an own enumerable JWK parameter.
589+ function jwkPropertyDescriptor ( value ) {
590+ return {
591+ __proto__ : null ,
592+ configurable : true ,
593+ enumerable : true ,
594+ value,
595+ writable : true ,
596+ } ;
597+ }
598+
572599function exportKeyJWK ( key ) {
573600 const algorithm = getCryptoKeyAlgorithm ( key ) ;
574- const parameters = {
575- key_ops : ArrayPrototypeSlice ( getCryptoKeyUsages ( key ) , 0 ) ,
576- ext : getCryptoKeyExtractable ( key ) ,
577- } ;
601+ let alg ;
578602 switch ( algorithm . name ) {
579603 case 'RSASSA-PKCS1-v1_5' : {
580- const alg = normalizeHashName (
604+ alg = normalizeHashName (
581605 algorithm . hash . name ,
582606 normalizeHashName . kContextJwkRsa ) ;
583- if ( alg ) parameters . alg = alg ;
584607 break ;
585608 }
586609 case 'RSA-PSS' : {
587- const alg = normalizeHashName (
610+ alg = normalizeHashName (
588611 algorithm . hash . name ,
589612 normalizeHashName . kContextJwkRsaPss ) ;
590- if ( alg ) parameters . alg = alg ;
591613 break ;
592614 }
593615 case 'RSA-OAEP' : {
594- const alg = normalizeHashName (
616+ alg = normalizeHashName (
595617 algorithm . hash . name ,
596618 normalizeHashName . kContextJwkRsaOaep ) ;
597- if ( alg ) parameters . alg = alg ;
598619 break ;
599620 }
600621 case 'ECDSA' :
@@ -620,7 +641,7 @@ function exportKeyJWK(key) {
620641 case 'Ed25519' :
621642 // Fall through
622643 case 'Ed448' :
623- parameters . alg = algorithm . name ;
644+ alg = algorithm . name ;
624645 break ;
625646 case 'AES-CTR' :
626647 // Fall through
@@ -631,30 +652,40 @@ function exportKeyJWK(key) {
631652 case 'AES-OCB' :
632653 // Fall through
633654 case 'AES-KW' :
634- parameters . alg = require ( 'internal/crypto/aes' )
655+ alg = require ( 'internal/crypto/aes' )
635656 . getAlgorithmName ( algorithm . name , algorithm . length ) ;
636657 break ;
637658 case 'ChaCha20-Poly1305' :
638- parameters . alg = 'C20P' ;
659+ alg = 'C20P' ;
639660 break ;
640661 case 'HMAC' : {
641- const alg = normalizeHashName (
662+ alg = normalizeHashName (
642663 algorithm . hash . name ,
643664 normalizeHashName . kContextJwkHmac ) ;
644- if ( alg ) parameters . alg = alg ;
645665 break ;
646666 }
647667 case 'KMAC128' :
648- parameters . alg = 'K128' ;
668+ alg = 'K128' ;
649669 break ;
650670 case 'KMAC256' : {
651- parameters . alg = 'K256' ;
671+ alg = 'K256' ;
652672 break ;
653673 }
654674 default :
655675 return undefined ;
656676 }
657677
678+ const parameters = { } ;
679+ const descriptors = {
680+ __proto__ : null ,
681+ key_ops : jwkPropertyDescriptor ( ArrayPrototypeSlice ( getCryptoKeyUsages ( key ) , 0 ) ) ,
682+ ext : jwkPropertyDescriptor ( getCryptoKeyExtractable ( key ) ) ,
683+ } ;
684+ if ( alg !== undefined ) {
685+ descriptors . alg = jwkPropertyDescriptor ( alg ) ;
686+ }
687+ ObjectDefineProperties ( parameters , descriptors ) ;
688+
658689 return getCryptoKeyHandle ( key ) . exportJwk ( parameters , true ) ;
659690}
660691
@@ -748,6 +779,64 @@ function exportKeyImpl(format, key) {
748779 return exportKeySync ( format , key ) ;
749780}
750781
782+ // Parsed JWK arrays are detached from Array.prototype but still need to pass
783+ // WebIDL sequence conversion, which reads @@iterator from the value.
784+ function safeArrayIterator ( ) {
785+ return new SafeArrayIterator ( this ) ;
786+ }
787+
788+ // The WebCrypto spec parses and stringifies JWKs in a fresh global object.
789+ // Detach internal JSON values from the current global's mutable prototypes to
790+ // approximate those fresh-realm semantics without creating a new realm.
791+ function detachFromUserPrototypes ( value ) {
792+ if ( value === null || typeof value !== 'object' )
793+ return ;
794+
795+ ObjectSetPrototypeOf ( value , null ) ;
796+
797+ if ( ArrayIsArray ( value ) ) {
798+ ObjectDefineProperty ( value , SymbolIterator , {
799+ __proto__ : null ,
800+ configurable : true ,
801+ value : safeArrayIterator ,
802+ } ) ;
803+ for ( let n = 0 ; n < value . length ; n ++ )
804+ detachFromUserPrototypes ( value [ n ] ) ;
805+ return ;
806+ }
807+
808+ const keys = ObjectKeys ( value ) ;
809+ for ( let n = 0 ; n < keys . length ; n ++ )
810+ detachFromUserPrototypes ( value [ keys [ n ] ] ) ;
811+ }
812+
813+ // Parse wrapped JWK bytes according to WebCrypto's "parse a JWK" procedure.
814+ function parseJwk ( keyData ) {
815+ let key ;
816+ try {
817+ // WebCrypto parses JWKs in a fresh global. Detach parsed JSON values
818+ // from user-mutated prototypes before WebIDL dictionary conversion.
819+ // Wrapped JWKs may be produced outside WebCrypto, so parse using the
820+ // spec-required UTF-8.
821+ const json = decodeUTF8 ( keyData , false , true ) ;
822+ const result = JSONParse ( json ) ;
823+ detachFromUserPrototypes ( result ) ;
824+ key = webidl . converters . JsonWebKey ( result ) ;
825+ } catch ( err ) {
826+ throw lazyDOMException (
827+ 'Invalid wrapped JWK key' ,
828+ { name : 'DataError' , cause : err } ) ;
829+ }
830+
831+ if ( key . kty === undefined ) {
832+ throw lazyDOMException (
833+ 'Invalid wrapped JWK key' ,
834+ 'DataError' ) ;
835+ }
836+
837+ return key ;
838+ }
839+
751840function aliasKeyFormat ( format ) {
752841 switch ( format ) {
753842 case 'raw-public' :
@@ -967,15 +1056,21 @@ function wrapKeyImpl(format, key, wrappingKey, algorithm) {
9671056 let keyData = exportKeySync ( format , key ) ;
9681057
9691058 if ( format === 'jwk' ) {
970- const ec = new TextEncoder ( ) ;
971- const raw = JSONStringify ( keyData ) ;
1059+ // The WebCrypto spec stringifies JWKs in a new global object. Rather
1060+ // than create a new realm here, detach this internally generated JWK from
1061+ // user-mutated prototypes so JSON.stringify cannot read inherited toJSON
1062+ // hooks from the current global.
1063+ detachFromUserPrototypes ( keyData ) ;
1064+ const json = JSONStringify ( keyData ) ;
9721065 // As per the NOTE in step 13 https://w3c.github.io/webcrypto/#SubtleCrypto-method-wrapKey
9731066 // we're padding AES-KW wrapped JWK to make sure it is always a multiple of 8 bytes
9741067 // in length
975- if ( algorithm . name === 'AES-KW' && raw . length % 8 !== 0 ) {
976- keyData = ec . encode ( raw + StringPrototypeRepeat ( ' ' , 8 - ( raw . length % 8 ) ) ) ;
1068+ // The spec then UTF-8 encodes json.
1069+ if ( algorithm . name === 'AES-KW' && json . length % 8 !== 0 ) {
1070+ keyData = encodeUtf8String (
1071+ json + StringPrototypeRepeat ( ' ' , 8 - ( json . length % 8 ) ) ) ;
9771072 } else {
978- keyData = ec . encode ( raw ) ;
1073+ keyData = encodeUtf8String ( json ) ;
9791074 }
9801075 }
9811076
@@ -1067,17 +1162,9 @@ function unwrapKeyImpl(
10671162 wrappedKey ,
10681163 'unwrapKey' ) ;
10691164
1070- return PromisePrototypeThen ( keyData , ( keyData ) => {
1165+ return jobPromiseThen ( keyData , ( keyData ) => {
10711166 if ( format === 'jwk' ) {
1072- // The fatal: true option is only supported in builds that have ICU.
1073- const options = process . versions . icu !== undefined ?
1074- { fatal : true } : undefined ;
1075- const dec = new TextDecoder ( 'utf-8' , options ) ;
1076- try {
1077- keyData = JSONParse ( dec . decode ( keyData ) ) ;
1078- } catch {
1079- throw lazyDOMException ( 'Invalid wrapped JWK key' , 'DataError' ) ;
1080- }
1167+ keyData = parseJwk ( keyData ) ;
10811168 }
10821169
10831170 return FunctionPrototypeCall (
@@ -1453,7 +1540,7 @@ function encapsulateKeyImpl(
14531540 throw lazyDOMException ( 'Unrecognized algorithm name' , 'NotSupportedError' ) ;
14541541 }
14551542
1456- return PromisePrototypeThen ( encapsulateBits , ( encapsulateBits ) => {
1543+ return jobPromiseThen ( encapsulateBits , ( encapsulateBits ) => {
14571544 const sharedKey = FunctionPrototypeCall (
14581545 importKeySync ,
14591546 this ,
@@ -1593,7 +1680,7 @@ function decapsulateKeyImpl(
15931680 throw lazyDOMException ( 'Unrecognized algorithm name' , 'NotSupportedError' ) ;
15941681 }
15951682
1596- return PromisePrototypeThen ( decapsulatedBits , ( decapsulatedBits ) => FunctionPrototypeCall (
1683+ return jobPromiseThen ( decapsulatedBits , ( decapsulatedBits ) => FunctionPrototypeCall (
15971684 importKeySync ,
15981685 this ,
15991686 'raw-secret' , decapsulatedBits , normalizedSharedKeyAlgorithm , extractable ,
0 commit comments