Skip to content

Commit fbfa985

Browse files
divybotlittledivy
andauthored
fix(ext/node): accept ArrayBufferView in tls.setDefaultCACertificates (#33700)
Enables `test-tls-set-default-ca-certificates-mixed-types` in node_compat suite. Co-authored-by: Divy Srivastava <dj.srivastava23@gmail.com>
1 parent 496e918 commit fbfa985

3 files changed

Lines changed: 63 additions & 14 deletions

File tree

ext/node/polyfills/tls.ts

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,19 @@ import {
1616
op_set_default_ca_certificates,
1717
} from "ext:core/ops";
1818
import { core, primordials } from "ext:core/mod.js";
19+
import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
20+
import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts";
1921

22+
const { isTypedArray } = core;
2023
const {
2124
ArrayIsArray,
2225
ArrayPrototypeIncludes,
2326
ArrayPrototypeForEach,
2427
ArrayPrototypeMap,
2528
ArrayPrototypePush,
29+
DataViewPrototypeGetBuffer,
30+
DataViewPrototypeGetByteLength,
31+
DataViewPrototypeGetByteOffset,
2632
ObjectDefineProperty,
2733
ObjectKeys,
2834
ObjectFreeze,
@@ -40,9 +46,36 @@ const {
4046
StringPrototypeSplit,
4147
StringPrototypeTrim,
4248
StringPrototypeToLowerCase,
43-
TypeError,
49+
TypedArrayPrototypeGetBuffer,
50+
TypedArrayPrototypeGetByteLength,
51+
TypedArrayPrototypeGetByteOffset,
52+
Uint8Array,
4453
} = primordials;
4554

55+
// Lazy-init: tls.ts is loaded into the startup snapshot before TextDecoder
56+
// is registered as a global, so a top-level `new TextDecoder()` would throw.
57+
let utf8Decoder: TextDecoder | null = null;
58+
59+
// deno-lint-ignore no-explicit-any
60+
function arrayBufferViewToString(view: any): string {
61+
// Use the matching prototype getter so a forged accessor on the view
62+
// can't redirect us to a different ArrayBuffer / out-of-bounds region.
63+
const isTA = isTypedArray(view);
64+
const buffer = isTA
65+
? TypedArrayPrototypeGetBuffer(view)
66+
: DataViewPrototypeGetBuffer(view);
67+
const byteOffset = isTA
68+
? TypedArrayPrototypeGetByteOffset(view)
69+
: DataViewPrototypeGetByteOffset(view);
70+
const byteLength = isTA
71+
? TypedArrayPrototypeGetByteLength(view)
72+
: DataViewPrototypeGetByteLength(view);
73+
if (utf8Decoder === null) {
74+
utf8Decoder = new TextDecoder("utf-8");
75+
}
76+
return utf8Decoder.decode(new Uint8Array(buffer, byteOffset, byteLength));
77+
}
78+
4679
// openssl -> rustls
4780
const cipherMap = {
4881
"__proto__": null,
@@ -187,28 +220,43 @@ export class CryptoStream {}
187220
export class SecurePair {}
188221
export const Server = tlsWrap.Server;
189222

190-
export function setDefaultCACertificates(certs: string[]) {
223+
export function setDefaultCACertificates(
224+
certs: (string | ArrayBufferView)[],
225+
) {
191226
if (!ArrayIsArray(certs)) {
192-
throw new TypeError(
193-
"The argument 'certs' must be an array of strings",
194-
);
227+
throw new ERR_INVALID_ARG_TYPE("certs", "Array", certs);
195228
}
196229

230+
const normalized: string[] = [];
197231
for (let i = 0; i < certs.length; ++i) {
198232
const cert = certs[i];
199-
if (typeof cert !== "string") {
200-
throw new TypeError(
201-
"Each certificate in 'certs' must be a string",
233+
if (typeof cert === "string") {
234+
ArrayPrototypePush(normalized, cert);
235+
} else if (isArrayBufferView(cert)) {
236+
ArrayPrototypePush(normalized, arrayBufferViewToString(cert));
237+
} else {
238+
throw new ERR_INVALID_ARG_TYPE(
239+
`certs[${i}]`,
240+
["string", "ArrayBufferView"],
241+
cert,
202242
);
203243
}
204244
}
205245

206-
op_set_default_ca_certificates(certs);
246+
op_set_default_ca_certificates(normalized);
207247

208-
lazyRootCertificates = null;
248+
// The bundled root certificates (`rootCertificates` proxy /
249+
// `lazyRootCertificates`) come from the webpki Mozilla bundle and don't
250+
// change here, so don't invalidate them: doing so would re-enter
251+
// `ensureLazyRootCertificates` and try to mutate a frozen target.
252+
// Only the 'default' cache reflects what we just wrote.
209253
ArrayPrototypeForEach(
210254
ObjectKeys(cachedCACertificates),
211-
(key) => delete cachedCACertificates[key],
255+
(key) => {
256+
if (key !== "bundled") {
257+
delete cachedCACertificates[key];
258+
}
259+
},
212260
);
213261
}
214262

tests/node_compat/config.jsonc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3313,6 +3313,7 @@
33133313
"parallel/test-tls-server-verify.js": {},
33143314
"parallel/test-tls-session-cache.js": {},
33153315
"parallel/test-tls-session-timeout-errors.js": {},
3316+
"parallel/test-tls-set-default-ca-certificates-mixed-types.js": {},
33163317
"parallel/test-tls-set-encoding.js": {},
33173318
"parallel/test-tls-snicallback-error.js": {},
33183319
"parallel/test-tls-socket-allow-half-open-option.js": {},

tests/unit_node/tls_test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -624,18 +624,18 @@ Deno.test("tls.setDefaultCACertificates validates input - must be array", () =>
624624
(tls as any).setDefaultCACertificates("not an array");
625625
},
626626
TypeError,
627-
"must be an array",
627+
"must be an instance of Array",
628628
);
629629
});
630630

631-
Deno.test("tls.setDefaultCACertificates validates input - array elements must be strings", () => {
631+
Deno.test("tls.setDefaultCACertificates validates input - array elements must be strings or ArrayBufferView", () => {
632632
assertThrows(
633633
() => {
634634
// deno-lint-ignore no-explicit-any
635635
(tls as any).setDefaultCACertificates([123, 456]);
636636
},
637637
TypeError,
638-
"must be a string",
638+
"must be of type string or an instance of ArrayBufferView",
639639
);
640640
});
641641

0 commit comments

Comments
 (0)