mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Implement require('tls').getCACertificates() (#20364)
Co-authored-by: pfgithub <6010774+pfgithub@users.noreply.github.com>
This commit is contained in:
@@ -44,10 +44,7 @@ void *sni_find(void *sni, const char *hostname);
|
||||
#include <wolfssl/options.h>
|
||||
#endif
|
||||
|
||||
#include "./root_certs.h"
|
||||
|
||||
/* These are in root_certs.cpp */
|
||||
extern X509_STORE *us_get_default_ca_store();
|
||||
#include "./root_certs_header.h"
|
||||
|
||||
struct loop_ssl_data {
|
||||
char *ssl_read_input, *ssl_read_output;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// MSVC doesn't support C11 stdatomic.h propertly yet.
|
||||
// so we use C++ std::atomic instead.
|
||||
#include "./root_certs.h"
|
||||
#include "./root_certs_header.h"
|
||||
#include "./internal/internal.h"
|
||||
#include <atomic>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <string.h>
|
||||
static const int root_certs_size = sizeof(root_certs) / sizeof(root_certs[0]);
|
||||
|
||||
@@ -134,6 +133,23 @@ extern "C" int us_internal_raw_root_certs(struct us_cert_string_t **out) {
|
||||
return root_certs_size;
|
||||
}
|
||||
|
||||
struct us_default_ca_certificates {
|
||||
X509 *root_cert_instances[root_certs_size];
|
||||
STACK_OF(X509) *root_extra_cert_instances;
|
||||
};
|
||||
|
||||
us_default_ca_certificates* us_get_default_ca_certificates() {
|
||||
static us_default_ca_certificates default_ca_certificates = {{NULL}, NULL};
|
||||
|
||||
us_internal_init_root_certs(default_ca_certificates.root_cert_instances, default_ca_certificates.root_extra_cert_instances);
|
||||
|
||||
return &default_ca_certificates;
|
||||
}
|
||||
|
||||
STACK_OF(X509) *us_get_root_extra_cert_instances() {
|
||||
return us_get_default_ca_certificates()->root_extra_cert_instances;
|
||||
}
|
||||
|
||||
extern "C" X509_STORE *us_get_default_ca_store() {
|
||||
X509_STORE *store = X509_STORE_new();
|
||||
if (store == NULL) {
|
||||
@@ -145,10 +161,9 @@ extern "C" X509_STORE *us_get_default_ca_store() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static X509 *root_cert_instances[root_certs_size] = {NULL};
|
||||
static STACK_OF(X509) *root_extra_cert_instances = NULL;
|
||||
|
||||
us_internal_init_root_certs(root_cert_instances, root_extra_cert_instances);
|
||||
us_default_ca_certificates *default_ca_certificates = us_get_default_ca_certificates();
|
||||
X509** root_cert_instances = default_ca_certificates->root_cert_instances;
|
||||
STACK_OF(X509) *root_extra_cert_instances = default_ca_certificates->root_extra_cert_instances;
|
||||
|
||||
// load all root_cert_instances on the default ca store
|
||||
for (size_t i = 0; i < root_certs_size; i++) {
|
||||
|
||||
14
packages/bun-usockets/src/crypto/root_certs_header.h
Normal file
14
packages/bun-usockets/src/crypto/root_certs_header.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define CPPDECL extern "C"
|
||||
|
||||
STACK_OF(X509) *us_get_root_extra_cert_instances();
|
||||
|
||||
#else
|
||||
#define CPPDECL extern
|
||||
#endif
|
||||
|
||||
CPPDECL X509_STORE *us_get_default_ca_store();
|
||||
|
||||
@@ -6,22 +6,23 @@
|
||||
#include "libusockets.h"
|
||||
|
||||
#include "ZigGlobalObject.h"
|
||||
#include "ErrorCode.h"
|
||||
#include "openssl/base.h"
|
||||
#include "openssl/bio.h"
|
||||
#include "../../packages/bun-usockets/src/crypto/root_certs_header.h"
|
||||
|
||||
namespace Bun {
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
BUN_DECLARE_HOST_FUNCTION(Bun__canonicalizeIP);
|
||||
|
||||
JSC::JSValue createNodeTLSBinding(Zig::GlobalObject* globalObject)
|
||||
JSC_DEFINE_HOST_FUNCTION(getBundledRootCertificates, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto* obj = constructEmptyObject(globalObject);
|
||||
|
||||
struct us_cert_string_t* out;
|
||||
auto size = us_raw_root_certs(&out);
|
||||
if (size < 0) {
|
||||
return jsUndefined();
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
auto rootCertificates = JSC::JSArray::create(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), size);
|
||||
for (auto i = 0; i < size; i++) {
|
||||
@@ -29,12 +30,46 @@ JSC::JSValue createNodeTLSBinding(Zig::GlobalObject* globalObject)
|
||||
auto str = WTF::String::fromUTF8(std::span { raw.str, raw.len });
|
||||
rootCertificates->putDirectIndex(globalObject, i, JSC::jsString(vm, str));
|
||||
}
|
||||
obj->putDirect(
|
||||
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "rootCertificates"_s)), JSC::objectConstructorFreeze(globalObject, rootCertificates), 0);
|
||||
|
||||
obj->putDirect(
|
||||
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "canonicalizeIP"_s)), JSC::JSFunction::create(vm, globalObject, 1, "canonicalizeIP"_s, Bun__canonicalizeIP, ImplementationVisibility::Public, NoIntrinsic), 0);
|
||||
return obj;
|
||||
return JSValue::encode(JSC::objectConstructorFreeze(globalObject, rootCertificates));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(getExtraCACertificates, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
|
||||
VM& vm = globalObject->vm();
|
||||
|
||||
STACK_OF(X509)* root_extra_cert_instances = us_get_root_extra_cert_instances();
|
||||
|
||||
auto size = sk_X509_num(root_extra_cert_instances);
|
||||
if (size < 0) size = 0; // root_extra_cert_instances is nullptr
|
||||
|
||||
auto rootCertificates = JSC::JSArray::create(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), size);
|
||||
for (auto i = 0; i < size; i++) {
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
if (!bio) {
|
||||
throwOutOfMemoryError(globalObject, scope);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (PEM_write_bio_X509(bio, sk_X509_value(root_extra_cert_instances, i)) != 1) {
|
||||
BIO_free(bio);
|
||||
return throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "X509 to PEM conversion"_str);
|
||||
}
|
||||
|
||||
char* bioData = nullptr;
|
||||
long bioLen = BIO_get_mem_data(bio, &bioData);
|
||||
if (bioLen <= 0 || !bioData) {
|
||||
BIO_free(bio);
|
||||
return throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "Reading PEM data"_str);
|
||||
}
|
||||
|
||||
auto str = WTF::String::fromUTF8(std::span { bioData, static_cast<size_t>(bioLen) });
|
||||
rootCertificates->putDirectIndex(globalObject, i, JSC::jsString(vm, str));
|
||||
BIO_free(bio);
|
||||
}
|
||||
|
||||
return JSValue::encode(JSC::objectConstructorFreeze(globalObject, rootCertificates));
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
namespace Bun {
|
||||
|
||||
JSC::JSValue createNodeTLSBinding(Zig::GlobalObject*);
|
||||
BUN_DECLARE_HOST_FUNCTION(Bun__canonicalizeIP);
|
||||
JSC_DECLARE_HOST_FUNCTION(getBundledRootCertificates);
|
||||
JSC_DECLARE_HOST_FUNCTION(getExtraCACertificates);
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const fmtBinding = $bindgenFn("fmt.bind.ts", "fmtString");
|
||||
export const highlightJavaScript = (code: string) => fmtBinding(code, "highlight-javascript");
|
||||
export const escapePowershell = (code: string) => fmtBinding(code, "escape-powershell");
|
||||
|
||||
export const TLSBinding = $cpp("NodeTLS.cpp", "createNodeTLSBinding");
|
||||
export const canonicalizeIP = $newCppFunction("NodeTLS.cpp", "Bun__canonicalizeIP", 1);
|
||||
|
||||
export const SQL = $cpp("JSSQLStatement.cpp", "createJSSQLStatementConstructor");
|
||||
|
||||
|
||||
@@ -5,10 +5,13 @@ const { Duplex } = require("node:stream");
|
||||
const addServerName = $newZigFunction("socket.zig", "jsAddServerName", 3);
|
||||
const { throwNotImplemented } = require("internal/shared");
|
||||
const { throwOnInvalidTLSArray, DEFAULT_CIPHERS, validateCiphers } = require("internal/tls");
|
||||
const { validateString } = require("internal/validators");
|
||||
|
||||
const { Server: NetServer, Socket: NetSocket } = net;
|
||||
|
||||
const { rootCertificates, canonicalizeIP } = $cpp("NodeTLS.cpp", "createNodeTLSBinding");
|
||||
const getBundledRootCertificates = $newCppFunction("NodeTLS.cpp", "getBundledRootCertificates", 1);
|
||||
const getExtraCACertificates = $newCppFunction("NodeTLS.cpp", "getExtraCACertificates", 1);
|
||||
const canonicalizeIP = $newCppFunction("NodeTLS.cpp", "Bun__canonicalizeIP", 1);
|
||||
|
||||
const SymbolReplace = Symbol.replace;
|
||||
const RegExpPrototypeSymbolReplace = RegExp.prototype[SymbolReplace];
|
||||
@@ -31,6 +34,9 @@ const ArrayPrototypeForEach = Array.prototype.forEach;
|
||||
const ArrayPrototypePush = Array.prototype.push;
|
||||
const ArrayPrototypeSome = Array.prototype.some;
|
||||
const ArrayPrototypeReduce = Array.prototype.reduce;
|
||||
|
||||
const ObjectFreeze = Object.freeze;
|
||||
|
||||
function parseCertString() {
|
||||
// Removed since JAN 2022 Node v18.0.0+ https://github.com/nodejs/node/pull/41479
|
||||
throwNotImplemented("Not implemented");
|
||||
@@ -724,6 +730,59 @@ function convertALPNProtocols(protocols, out) {
|
||||
}
|
||||
}
|
||||
|
||||
let bundledRootCertificates: string[] | undefined;
|
||||
function cacheBundledRootCertificates(): string[] {
|
||||
bundledRootCertificates ||= getBundledRootCertificates() as string[];
|
||||
return bundledRootCertificates;
|
||||
}
|
||||
let defaultCACertificates: string[] | undefined;
|
||||
function cacheDefaultCACertificates() {
|
||||
if (defaultCACertificates) return defaultCACertificates;
|
||||
defaultCACertificates = [];
|
||||
|
||||
const bundled = cacheBundledRootCertificates();
|
||||
for (let i = 0; i < bundled.length; ++i) {
|
||||
ArrayPrototypePush.$call(defaultCACertificates, bundled[i]);
|
||||
}
|
||||
|
||||
if (process.env.NODE_EXTRA_CA_CERTS) {
|
||||
const extra = cacheExtraCACertificates();
|
||||
for (let i = 0; i < extra.length; ++i) {
|
||||
ArrayPrototypePush.$call(defaultCACertificates, extra[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ObjectFreeze(defaultCACertificates);
|
||||
return defaultCACertificates;
|
||||
}
|
||||
|
||||
function cacheSystemCACertificates(): string[] {
|
||||
throw new Error("getCACertificates('system') is not yet implemented in Bun");
|
||||
}
|
||||
|
||||
let extraCACertificates: string[] | undefined;
|
||||
function cacheExtraCACertificates(): string[] {
|
||||
extraCACertificates ||= getExtraCACertificates() as string[];
|
||||
return extraCACertificates;
|
||||
}
|
||||
|
||||
function getCACertificates(type = "default") {
|
||||
validateString(type, "type");
|
||||
|
||||
switch (type) {
|
||||
case "default":
|
||||
return cacheDefaultCACertificates();
|
||||
case "bundled":
|
||||
return cacheBundledRootCertificates();
|
||||
case "system":
|
||||
return cacheSystemCACertificates();
|
||||
case "extra":
|
||||
return cacheExtraCACertificates();
|
||||
default:
|
||||
throw $ERR_INVALID_ARG_VALUE("type", type);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
CLIENT_RENEG_LIMIT,
|
||||
CLIENT_RENEG_WINDOW,
|
||||
@@ -742,6 +801,7 @@ export default {
|
||||
TLSSocket,
|
||||
checkServerIdentity,
|
||||
get rootCertificates() {
|
||||
return rootCertificates;
|
||||
return cacheBundledRootCertificates();
|
||||
},
|
||||
getCACertificates,
|
||||
} as any as typeof import("node:tls");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
'use strict';
|
||||
const crypto = require('crypto');
|
||||
const net = require('net');
|
||||
const assert = require('assert');
|
||||
|
||||
exports.ccs = Buffer.from('140303000101', 'hex');
|
||||
|
||||
@@ -173,4 +174,16 @@ function P_hash(algo, secret, seed, size) {
|
||||
return result;
|
||||
}
|
||||
|
||||
exports.assertIsCAArray = function assertIsCAArray(certs) {
|
||||
assert(Array.isArray(certs));
|
||||
assert(certs.length > 0);
|
||||
|
||||
// The certificates looks PEM-encoded.
|
||||
for (const cert of certs) {
|
||||
const trimmed = cert.trim();
|
||||
assert.match(trimmed, /^-----BEGIN CERTIFICATE-----/);
|
||||
assert.match(trimmed, /-----END CERTIFICATE-----$/);
|
||||
}
|
||||
};
|
||||
|
||||
exports.TestTLSSocket = TestTLSSocket;
|
||||
|
||||
17
test/js/node/test/fixtures/tls-check-extra-ca-certificates.js
vendored
Normal file
17
test/js/node/test/fixtures/tls-check-extra-ca-certificates.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const tls = require('tls');
|
||||
const assert = require('assert');
|
||||
|
||||
const defaultSet = new Set(tls.getCACertificates('default'));
|
||||
const extraSet = new Set(tls.getCACertificates('extra'));
|
||||
console.log(defaultSet.size, 'default certificates');
|
||||
console.log(extraSet.size, 'extra certificates')
|
||||
|
||||
// Parent process is supposed to call this with
|
||||
// NODE_EXTRA_CA_CERTS set to test/fixtures/keys/ca1-cert.pem.
|
||||
assert.strictEqual(extraSet.size, 1);
|
||||
|
||||
// Check that default set is a super set of extra set.
|
||||
assert.deepStrictEqual(defaultSet.intersection(extraSet),
|
||||
extraSet);
|
||||
11
test/js/node/test/fixtures/tls-get-ca-certificates.js
vendored
Normal file
11
test/js/node/test/fixtures/tls-get-ca-certificates.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
// This fixture just writes tls.getCACertificates() outputs to process.env.CA_OUT
|
||||
const tls = require('tls');
|
||||
const fs = require('fs');
|
||||
const assert = require('assert');
|
||||
assert(process.env.CA_TYPE);
|
||||
assert(process.env.CA_OUT);
|
||||
|
||||
const certs = tls.getCACertificates(process.env.CA_TYPE);
|
||||
|
||||
fs.writeFileSync(process.env.CA_OUT, JSON.stringify(certs), 'utf8');
|
||||
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
// Flags: --no-use-openssl-ca
|
||||
// This tests that tls.getCACertificates() returns the bundled
|
||||
// certificates correctly.
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const tls = require('tls');
|
||||
|
||||
const defaultSet = new Set(tls.getCACertificates('default'));
|
||||
const bundledSet = new Set(tls.getCACertificates('bundled'));
|
||||
|
||||
// When --use-openssl-ca is false (i.e. bundled CA is sued),
|
||||
// default is a superset of bundled certificates.
|
||||
assert.deepStrictEqual(defaultSet.intersection(bundledSet), bundledSet);
|
||||
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
// This tests that tls.getCACertificates() returns the bundled
|
||||
// certificates correctly.
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const tls = require('tls');
|
||||
const { assertIsCAArray } = require('../common/tls');
|
||||
|
||||
const certs = tls.getCACertificates('bundled');
|
||||
assertIsCAArray(certs);
|
||||
|
||||
// It's the same as tls.rootCertificates - both are
|
||||
// Mozilla CA stores across platform.
|
||||
assert.strictEqual(certs, tls.rootCertificates);
|
||||
|
||||
// It's cached on subsequent accesses.
|
||||
assert.strictEqual(certs, tls.getCACertificates('bundled'));
|
||||
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
// This tests that tls.getCACertificates() returns the default
|
||||
// certificates correctly.
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const tls = require('tls');
|
||||
const { assertIsCAArray } = require('../common/tls');
|
||||
|
||||
const certs = tls.getCACertificates();
|
||||
assertIsCAArray(certs);
|
||||
|
||||
const certs2 = tls.getCACertificates('default');
|
||||
assert.strictEqual(certs, certs2);
|
||||
|
||||
// It's cached on subsequent accesses.
|
||||
assert.strictEqual(certs, tls.getCACertificates('default'));
|
||||
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
// This tests that tls.getCACertificates() throws error when being
|
||||
// passed an invalid argument.
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const tls = require('tls');
|
||||
|
||||
for (const invalid of [1, null, () => {}, true]) {
|
||||
assert.throws(() => tls.getCACertificates(invalid), {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
}
|
||||
|
||||
assert.throws(() => tls.getCACertificates('test'), {
|
||||
code: 'ERR_INVALID_ARG_VALUE'
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
// This tests that tls.getCACertificates('extra') returns an empty
|
||||
// array if NODE_EXTRA_CA_CERTS is empty.
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const fs = require('fs');
|
||||
|
||||
const assert = require('assert');
|
||||
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
tmpdir.refresh();
|
||||
const certsJSON = tmpdir.resolve('certs.json');
|
||||
|
||||
// If NODE_EXTRA_CA_CERTS is not set, it should be an empty array.
|
||||
spawnSyncAndExitWithoutError(process.execPath, [fixtures.path('tls-get-ca-certificates.js')], {
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_EXTRA_CA_CERTS: undefined,
|
||||
CA_TYPE: 'extra',
|
||||
CA_OUT: certsJSON,
|
||||
}
|
||||
});
|
||||
|
||||
const parsed = JSON.parse(fs.readFileSync(certsJSON, 'utf-8'));
|
||||
assert.deepStrictEqual(parsed, []);
|
||||
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
// This tests that tls.getCACertificates('defulat') returns a superset
|
||||
// of tls.getCACertificates('extra') when NODE_EXTRA_CA_CERTS is used.
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
spawnSyncAndExitWithoutError(process.execPath, [fixtures.path('tls-check-extra-ca-certificates.js')], {
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca1-cert.pem'),
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
// This tests that tls.getCACertificates('extra') returns the extra
|
||||
// certificates from NODE_EXTRA_CA_CERTS correctly.
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const fs = require('fs');
|
||||
const assert = require('assert');
|
||||
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
tmpdir.refresh();
|
||||
const certsJSON = tmpdir.resolve('certs.json');
|
||||
|
||||
// If NODE_EXTRA_CA_CERTS is set, it should contain a list of certificates.
|
||||
spawnSyncAndExitWithoutError(process.execPath, [fixtures.path('tls-get-ca-certificates.js')], {
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca1-cert.pem'),
|
||||
CA_TYPE: 'extra',
|
||||
CA_OUT: certsJSON,
|
||||
}
|
||||
});
|
||||
|
||||
const parsed = JSON.parse(fs.readFileSync(certsJSON, 'utf-8'));
|
||||
assert.deepStrictEqual(parsed, [fixtures.readKey('ca1-cert.pem', 'utf8')]);
|
||||
@@ -1,9 +1,8 @@
|
||||
import { TLSBinding } from "bun:internal-for-testing";
|
||||
import { canonicalizeIP } from "bun:internal-for-testing";
|
||||
import { createTest } from "node-harness";
|
||||
import { rootCertificates } from "tls";
|
||||
const { describe, expect } = createTest(import.meta.path);
|
||||
|
||||
const { canonicalizeIP, rootCertificates } = TLSBinding;
|
||||
|
||||
describe("NodeTLS.cpp", () => {
|
||||
test("canonicalizeIP", () => {
|
||||
expect(canonicalizeIP("127.0.0.1")).toBe("127.0.0.1");
|
||||
|
||||
Reference in New Issue
Block a user