11'use strict' ;
22
3- // The four CryptoKey prototype getters (`type`, `extractable`,
4- // `algorithm`, `usages`) are user-configurable per Web IDL, so they
5- // can be invoked with an arbitrary `this`. The native callbacks that
6- // implement them must brand-check their receiver and throw cleanly
7- // (ERR_INVALID_THIS) rather than crashing the process or returning
8- // garbage. This test exercises four progressively more hostile
9- // receiver shapes, including subverting `instanceof` via
10- // `Symbol.hasInstance`, to make sure the C++ brand check holds.
3+ // CryptoKey prototype getters and methods can be invoked with an
4+ // arbitrary `this`. They must brand-check their receiver and throw
5+ // cleanly (ERR_INVALID_THIS) rather than crashing the process or
6+ // returning garbage. This test exercises invalid receiver shapes,
7+ // including subverting `instanceof` via `Symbol.hasInstance`.
118//
129// It also verifies that `util.types.isCryptoKey()` cannot be fooled
1310// by prototype spoofing.
@@ -17,7 +14,7 @@ if (!common.hasCrypto)
1714 common . skip ( 'missing crypto' ) ;
1815
1916const assert = require ( 'node:assert' ) ;
20- const { types : { isCryptoKey } } = require ( 'node:util' ) ;
17+ const { inspect , types : { isCryptoKey } } = require ( 'node:util' ) ;
2118const { subtle } = globalThis . crypto ;
2219
2320( async ( ) => {
@@ -29,22 +26,16 @@ const { subtle } = globalThis.crypto;
2926
3027 const CryptoKey = key . constructor ;
3128
32- // Capture the underlying prototype getters once, so that subsequent
29+ // Capture the underlying prototype members once, so that subsequent
3330 // tampering with `CryptoKey.prototype` cannot affect what we call.
34- const getters = {
35- type : Object . getOwnPropertyDescriptor ( CryptoKey . prototype , 'type' ) . get ,
36- extractable :
37- Object . getOwnPropertyDescriptor ( CryptoKey . prototype , 'extractable' ) . get ,
38- algorithm :
39- Object . getOwnPropertyDescriptor ( CryptoKey . prototype , 'algorithm' ) . get ,
40- usages :
41- Object . getOwnPropertyDescriptor ( CryptoKey . prototype , 'usages' ) . get ,
42- } ;
31+ const descriptors = Object . getOwnPropertyDescriptors ( CryptoKey . prototype ) ;
4332
4433 // Sanity: each getter works on a real CryptoKey.
45- Object . entries ( getters ) . forEach ( ( [ name , getter ] ) => {
46- assert . notStrictEqual ( getter . call ( key ) , undefined , `baseline ${ name } ` ) ;
47- } ) ;
34+ for ( const name of Reflect . ownKeys ( descriptors ) ) {
35+ const { get } = descriptors [ name ] ;
36+ if ( get !== undefined )
37+ Reflect . apply ( get , key , [ ] ) ;
38+ }
4839 assert . strictEqual ( isCryptoKey ( key ) , true ) ;
4940 assert . strictEqual ( Object . hasOwn ( CryptoKey , 'getSlots' ) , false ) ;
5041 const internalProto = Object . getPrototypeOf ( key ) ;
@@ -56,36 +47,51 @@ const { subtle } = globalThis.crypto;
5647 const invalidThis = { code : 'ERR_INVALID_THIS' , name : 'TypeError' } ;
5748 const invalidArgType = { code : 'ERR_INVALID_ARG_TYPE' , name : 'TypeError' } ;
5849
50+ async function assertInvalidReceiver ( receiver ) {
51+ for ( const name of Reflect . ownKeys ( descriptors ) ) {
52+ if ( name === 'constructor' ) continue ;
53+ const descriptor = descriptors [ name ] ;
54+ const args = name === inspect . custom ? [ 0 , { } ] : [ ] ;
55+ for ( const kind of [ 'get' , 'set' , 'value' ] ) {
56+ const member = descriptor [ kind ] ;
57+ if ( typeof member !== 'function' ) continue ;
58+ await assert . rejects (
59+ async ( ) => Reflect . apply ( member , receiver , args ) ,
60+ invalidThis ,
61+ `CryptoKey.${ String ( name ) } (${ kind } )` ,
62+ ) ;
63+ }
64+ }
65+ }
66+
5967 // Plain object receiver.
60- Object . entries ( getters ) . forEach ( ( [ , getter ] ) => {
61- assert . throws ( ( ) => getter . call ( { } ) , invalidThis ) ;
62- } ) ;
68+ await assertInvalidReceiver ( { } ) ;
6369
6470 // Null-prototype object receiver.
65- Object . entries ( getters ) . forEach ( ( [ , getter ] ) => {
66- assert . throws ( ( ) => getter . call ( { __proto__ : null } ) , invalidThis ) ;
67- } ) ;
71+ await assertInvalidReceiver ( { __proto__ : null } ) ;
6872
6973 // Primitive receiver.
70- Object . entries ( getters ) . forEach ( ( [ , getter ] ) => {
71- assert . throws ( ( ) => getter . call ( 1 ) , invalidThis ) ;
72- } ) ;
74+ await assertInvalidReceiver ( 1 ) ;
7375
7476 // Null.
75- Object . entries ( getters ) . forEach ( ( [ , getter ] ) => {
76- // eslint-disable-next-line no-useless-call
77- assert . throws ( ( ) => getter . call ( null ) , invalidThis ) ;
78- } ) ;
77+ await assertInvalidReceiver ( null ) ;
7978
8079 // Undefined.
81- Object . entries ( getters ) . forEach ( ( [ , getter ] ) => {
82- assert . throws ( ( ) => getter . call ( ) , invalidThis ) ;
83- } ) ;
80+ await assertInvalidReceiver ( undefined ) ;
8481
8582 // Function
86- Object . entries ( getters ) . forEach ( ( [ , getter ] ) => {
87- assert . throws ( ( ) => getter . call ( function ( ) { } ) , invalidThis ) ;
88- } ) ;
83+ await assertInvalidReceiver ( function ( ) { } ) ;
84+
85+ const revoked = Proxy . revocable ( key , { } ) ;
86+ revoked . revoke ( ) ;
87+ for ( const receiver of [
88+ { __proto__ : CryptoKey . prototype } ,
89+ { __proto__ : key } ,
90+ new Proxy ( key , { } ) ,
91+ revoked . proxy ,
92+ ] ) {
93+ await assertInvalidReceiver ( receiver ) ;
94+ }
8995
9096 // Prototype spoofing with InternalCryptoKey.prototype must not pass
9197 // util.types.isCryptoKey().
@@ -111,23 +117,19 @@ const { subtle } = globalThis.crypto;
111117 const fake = { foo : 'bar' } ;
112118 assert . strictEqual ( fake instanceof CryptoKey , true ) ;
113119 assert . strictEqual ( isCryptoKey ( fake ) , false ) ;
114- Object . entries ( getters ) . forEach ( ( [ , getter ] ) => {
115- assert . throws ( ( ) => getter . call ( fake ) , invalidThis ) ;
116- } ) ;
120+ await assertInvalidReceiver ( fake ) ;
117121
118122 // Subverted `instanceof` plus a real BaseObject of a different
119123 // kind (a Buffer) as the receiver. Without the C++ tag check
120124 // this would type-confuse `Unwrap<NativeCryptoKey>`.
121125 const buf = Buffer . alloc ( 16 ) ;
122126 assert . strictEqual ( buf instanceof CryptoKey , true ) ;
123127 assert . strictEqual ( isCryptoKey ( buf ) , false ) ;
124- Object . entries ( getters ) . forEach ( ( [ , getter ] ) => {
125- assert . throws ( ( ) => getter . call ( buf ) , invalidThis ) ;
126- } ) ;
128+ await assertInvalidReceiver ( buf ) ;
127129
128130 // The real CryptoKey continues to work after all of the above.
129- assert . strictEqual ( getters . type . call ( key ) , 'secret' ) ;
130- assert . strictEqual ( getters . extractable . call ( key ) , true ) ;
131- assert . strictEqual ( getters . algorithm . call ( key ) . name , 'HMAC' ) ;
132- assert . deepStrictEqual ( getters . usages . call ( key ) , [ 'sign' ] ) ;
131+ assert . strictEqual ( descriptors . type . get . call ( key ) , 'secret' ) ;
132+ assert . strictEqual ( descriptors . extractable . get . call ( key ) , true ) ;
133+ assert . strictEqual ( descriptors . algorithm . get . call ( key ) . name , 'HMAC' ) ;
134+ assert . deepStrictEqual ( descriptors . usages . get . call ( key ) , [ 'sign' ] ) ;
133135} ) ( ) . then ( common . mustCall ( ) ) ;
0 commit comments