Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitedd66d0

Browse files
authored
crypto: add tls.setDefaultCACertificates()
This API allows dynamically configuring CA certificates thatwill be used by the Node.js TLS clients by default.Once called, the provided certificates will become the default CAcertificate list returned by `tls.getCACertificates('default')` andused by TLS connections that don't specify their own CA certificates.This function only affects the current Node.js thread.PR-URL:#58822Reviewed-By: Matteo Collina <matteo.collina@gmail.com>Reviewed-By: Tim Perry <pimterry@gmail.com>Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
1 parenta22c9c4 commitedd66d0

File tree

21 files changed

+1128
-14
lines changed

21 files changed

+1128
-14
lines changed

‎doc/api/tls.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2260,6 +2260,54 @@ openssl pkcs12 -certpbe AES-256-CBC -export -out client-cert.pem \
22602260
The server can be tested by connecting to it using the example client from
22612261
[`tls.connect()`][].
22622262

2263+
##`tls.setDefaultCACertificates(certs)`
2264+
2265+
<!-- YAML
2266+
added: REPLACEME
2267+
-->
2268+
2269+
*`certs` {string\[]|ArrayBufferView\[]} An array of CA certificates in PEM format.
2270+
2271+
Sets the default CA certificates used by Node.js TLS clients. If the provided
2272+
certificates are parsed successfully, they will become the default CA
2273+
certificate list returned by[`tls.getCACertificates()`][] and used
2274+
by subsequent TLS connections that don't specify their own CA certificates.
2275+
The certificates will be deduplicated before being set as the default.
2276+
2277+
This function only affects the current Node.js thread. Previous
2278+
sessions cached by the HTTPS agent won't be affected by this change, so
2279+
this method should be called before any unwanted cachable TLS connections are
2280+
made.
2281+
2282+
To use system CA certificates as the default:
2283+
2284+
```cjs
2285+
consttls=require('node:tls');
2286+
tls.setDefaultCACertificates(tls.getCACertificates('system'));
2287+
```
2288+
2289+
```mjs
2290+
importtlsfrom'node:tls';
2291+
tls.setDefaultCACertificates(tls.getCACertificates('system'));
2292+
```
2293+
2294+
This function completely replaces the default CA certificate list. To add additional
2295+
certificates to the existing defaults, get the current certificates and append to them:
2296+
2297+
```cjs
2298+
consttls=require('node:tls');
2299+
constcurrentCerts=tls.getCACertificates('default');
2300+
constadditionalCerts= ['-----BEGIN CERTIFICATE-----\n...'];
2301+
tls.setDefaultCACertificates([...currentCerts,...additionalCerts]);
2302+
```
2303+
2304+
```mjs
2305+
importtlsfrom'node:tls';
2306+
constcurrentCerts=tls.getCACertificates('default');
2307+
constadditionalCerts= ['-----BEGIN CERTIFICATE-----\n...'];
2308+
tls.setDefaultCACertificates([...currentCerts,...additionalCerts]);
2309+
```
2310+
22632311
##`tls.getCACertificates([type])`
22642312

22652313
<!-- YAML

‎lib/tls.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const {
3737
ERR_TLS_CERT_ALTNAME_INVALID,
3838
ERR_OUT_OF_RANGE,
3939
ERR_INVALID_ARG_VALUE,
40+
ERR_INVALID_ARG_TYPE,
4041
}=require('internal/errors').codes;
4142
constinternalUtil=require('internal/util');
4243
internalUtil.assertCrypto();
@@ -51,6 +52,8 @@ const {
5152
getBundledRootCertificates,
5253
getExtraCACertificates,
5354
getSystemCACertificates,
55+
resetRootCertStore,
56+
getUserRootCertificates,
5457
getSSLCiphers,
5558
}=internalBinding('crypto');
5659
const{ Buffer}=require('buffer');
@@ -122,8 +125,17 @@ function cacheSystemCACertificates() {
122125
}
123126

124127
letdefaultCACertificates;
128+
lethasResetDefaultCACertificates=false;
129+
125130
functioncacheDefaultCACertificates(){
126131
if(defaultCACertificates){returndefaultCACertificates;}
132+
133+
if(hasResetDefaultCACertificates){
134+
defaultCACertificates=getUserRootCertificates();
135+
ObjectFreeze(defaultCACertificates);
136+
returndefaultCACertificates;
137+
}
138+
127139
defaultCACertificates=[];
128140

129141
if(!getOptionValue('--use-openssl-ca')){
@@ -171,6 +183,26 @@ function getCACertificates(type = 'default') {
171183
}
172184
exports.getCACertificates=getCACertificates;
173185

186+
functionsetDefaultCACertificates(certs){
187+
if(!ArrayIsArray(certs)){
188+
thrownewERR_INVALID_ARG_TYPE('certs','Array',certs);
189+
}
190+
191+
// Verify that all elements in the array are strings
192+
for(leti=0;i<certs.length;i++){
193+
if(typeofcerts[i]!=='string'&&!isArrayBufferView(certs[i])){
194+
thrownewERR_INVALID_ARG_TYPE(
195+
`certs[${i}]`,['string','ArrayBufferView'],certs[i]);
196+
}
197+
}
198+
199+
resetRootCertStore(certs);
200+
defaultCACertificates=undefined;// Reset the cached default certificates
201+
hasResetDefaultCACertificates=true;
202+
}
203+
204+
exports.setDefaultCACertificates=setDefaultCACertificates;
205+
174206
// Convert protocols array into valid OpenSSL protocols list
175207
// ("\x06spdy/2\x08http/1.1\x08http/1.0")
176208
functionconvertProtocols(protocols){

‎src/crypto/crypto_context.cc

Lines changed: 182 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#include<wincrypt.h>
2828
#endif
2929

30+
#include<set>
31+
3032
namespacenode {
3133

3234
using ncrypto::BignumPointer;
@@ -83,10 +85,28 @@ static std::atomic<bool> has_cached_bundled_root_certs{false};
8385
static std::atomic<bool> has_cached_system_root_certs{false};
8486
static std::atomic<bool> has_cached_extra_root_certs{false};
8587

88+
// Used for sets of X509.
89+
structX509Less {
90+
booloperator()(const X509* lhs,const X509* rhs)constnoexcept {
91+
returnX509_cmp(const_cast<X509*>(lhs),const_cast<X509*>(rhs)) <0;
92+
}
93+
};
94+
using X509Set = std::set<X509*, X509Less>;
95+
96+
// Per-thread root cert store. See NewRootCertStore() on what it contains.
97+
staticthread_local X509_STORE* root_cert_store =nullptr;
98+
// If the user calls tls.setDefaultCACertificates() this will be used
99+
// to hold the user-provided certificates, the root_cert_store and any new
100+
// copy generated by NewRootCertStore() will then contain the certificates
101+
// from this set.
102+
staticthread_local std::unique_ptr<X509Set> root_certs_from_users;
103+
86104
X509_STORE*GetOrCreateRootCertStore() {
87-
// Guaranteed thread-safe by standard, just don't use -fno-threadsafe-statics.
88-
static X509_STORE* store =NewRootCertStore();
89-
return store;
105+
if (root_cert_store !=nullptr) {
106+
return root_cert_store;
107+
}
108+
root_cert_store =NewRootCertStore();
109+
return root_cert_store;
90110
}
91111

92112
// Takes a string or buffer and loads it into a BIO.
@@ -227,14 +247,11 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,
227247
issuer);
228248
}
229249

230-
staticunsignedlongLoadCertsFromFile(// NOLINT(runtime/int)
250+
staticunsignedlongLoadCertsFromBIO(// NOLINT(runtime/int)
231251
std::vector<X509*>* certs,
232-
constchar* file) {
252+
BIOPointer bio) {
233253
MarkPopErrorOnReturn mark_pop_error_on_return;
234254

235-
auto bio =BIOPointer::NewFile(file,"r");
236-
if (!bio)returnERR_get_error();
237-
238255
while (X509* x509 =PEM_read_bio_X509(
239256
bio.get(),nullptr, NoPasswordCallback,nullptr)) {
240257
certs->push_back(x509);
@@ -250,6 +267,17 @@ static unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
250267
}
251268
}
252269

270+
staticunsignedlongLoadCertsFromFile(// NOLINT(runtime/int)
271+
std::vector<X509*>* certs,
272+
constchar* file) {
273+
MarkPopErrorOnReturn mark_pop_error_on_return;
274+
275+
auto bio =BIOPointer::NewFile(file,"r");
276+
if (!bio)returnERR_get_error();
277+
278+
returnLoadCertsFromBIO(certs,std::move(bio));
279+
}
280+
253281
// Indicates the trust status of a certificate.
254282
enumclassTrustStatus {
255283
// Trust status is unknown / uninitialized.
@@ -831,11 +859,24 @@ static std::vector<X509*>& GetExtraCACertificates() {
831859
// NODE_EXTRA_CA_CERTS are cached after first load. Certificates
832860
// from --use-system-ca are not cached and always reloaded from
833861
// disk.
862+
// 8. If users have reset the root cert store by calling
863+
// tls.setDefaultCACertificates(), the store will be populated with
864+
// the certificates provided by users.
834865
// TODO(joyeecheung): maybe these rules need a bit of consolidation?
835866
X509_STORE*NewRootCertStore() {
836867
X509_STORE* store =X509_STORE_new();
837868
CHECK_NOT_NULL(store);
838869

870+
// If the root cert store is already reset by users through
871+
// tls.setDefaultCACertificates(), just create a copy from the
872+
// user-provided certificates.
873+
if (root_certs_from_users !=nullptr) {
874+
for (X509* cert : *root_certs_from_users) {
875+
CHECK_EQ(1,X509_STORE_add_cert(store, cert));
876+
}
877+
return store;
878+
}
879+
839880
#ifdef NODE_OPENSSL_SYSTEM_CERT_PATH
840881
ifconstexpr (sizeof(NODE_OPENSSL_SYSTEM_CERT_PATH) >1) {
841882
ERR_set_mark();
@@ -903,14 +944,57 @@ void GetBundledRootCertificates(const FunctionCallbackInfo<Value>& args) {
903944
Array::New(env->isolate(), result,arraysize(root_certs)));
904945
}
905946

947+
boolArrayOfStringsToX509s(Local<Context> context,
948+
Local<Array> cert_array,
949+
std::vector<X509*>* certs) {
950+
ClearErrorOnReturn clear_error_on_return;
951+
Isolate* isolate = context->GetIsolate();
952+
Environment* env =Environment::GetCurrent(context);
953+
uint32_t array_length = cert_array->Length();
954+
955+
std::vector<v8::Global<Value>> cert_items;
956+
if (FromV8Array(context, cert_array, &cert_items).IsNothing()) {
957+
returnfalse;
958+
}
959+
960+
for (uint32_t i =0; i < array_length; i++) {
961+
Local<Value> cert_val = cert_items[i].Get(isolate);
962+
// Parse the PEM certificate.
963+
BIOPointerbio(LoadBIO(env, cert_val));
964+
if (!bio) {
965+
ThrowCryptoError(env,ERR_get_error(),"Failed to load certificate data");
966+
returnfalse;
967+
}
968+
969+
// Read all certificates from this PEM string
970+
size_t start = certs->size();
971+
auto err =LoadCertsFromBIO(certs,std::move(bio));
972+
if (err !=0) {
973+
size_t end = certs->size();
974+
// Clean up any certificates we've already parsed upon failure.
975+
for (size_t j = start; j < end; ++j) {
976+
X509_free((*certs)[j]);
977+
}
978+
ThrowCryptoError(env, err,"Failed to parse certificate");
979+
returnfalse;
980+
}
981+
}
982+
983+
returntrue;
984+
}
985+
986+
template<typename It>
906987
MaybeLocal<Array>X509sToArrayOfStrings(Environment* env,
907-
const std::vector<X509*>& certs) {
988+
It first,
989+
It last,
990+
size_t size) {
908991
ClearErrorOnReturn clear_error_on_return;
909992
EscapableHandleScopescope(env->isolate());
910993

911-
LocalVector<Value>result(env->isolate(), certs.size());
912-
for (size_t i =0; i < certs.size(); ++i) {
913-
X509Viewview(certs[i]);
994+
LocalVector<Value>result(env->isolate(), size);
995+
size_t i =0;
996+
for (It cur = first; cur != last; ++cur, ++i) {
997+
X509Viewview(*cur);
914998
auto pem_bio = view.toPEM();
915999
if (!pem_bio) {
9161000
ThrowCryptoError(env,ERR_get_error(),"X509 to PEM conversion");
@@ -935,10 +1019,87 @@ MaybeLocal<Array> X509sToArrayOfStrings(Environment* env,
9351019
return scope.Escape(Array::New(env->isolate(), result.data(), result.size()));
9361020
}
9371021

1022+
voidGetUserRootCertificates(const FunctionCallbackInfo<Value>& args) {
1023+
Environment* env =Environment::GetCurrent(args);
1024+
CHECK_NOT_NULL(root_certs_from_users);
1025+
Local<Array> results;
1026+
if (X509sToArrayOfStrings(env,
1027+
root_certs_from_users->begin(),
1028+
root_certs_from_users->end(),
1029+
root_certs_from_users->size())
1030+
.ToLocal(&results)) {
1031+
args.GetReturnValue().Set(results);
1032+
}
1033+
}
1034+
1035+
voidResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
1036+
Local<Context> context = args.GetIsolate()->GetCurrentContext();
1037+
CHECK(args[0]->IsArray());
1038+
Local<Array> cert_array = args[0].As<Array>();
1039+
1040+
if (cert_array->Length() ==0) {
1041+
// If the array is empty, just clear the user certs and reset the store.
1042+
if (root_cert_store !=nullptr) {
1043+
X509_STORE_free(root_cert_store);
1044+
root_cert_store =nullptr;
1045+
}
1046+
1047+
// Free any existing certificates in the old set.
1048+
if (root_certs_from_users !=nullptr) {
1049+
for (X509* cert : *root_certs_from_users) {
1050+
X509_free(cert);
1051+
}
1052+
}
1053+
root_certs_from_users = std::make_unique<X509Set>();
1054+
return;
1055+
}
1056+
1057+
// Parse certificates from the array
1058+
std::unique_ptr<std::vector<X509*>> certs =
1059+
std::make_unique<std::vector<X509*>>();
1060+
if (!ArrayOfStringsToX509s(context, cert_array, certs.get())) {
1061+
// Error already thrown by ArrayOfStringsToX509s
1062+
return;
1063+
}
1064+
1065+
if (certs->empty()) {
1066+
Environment* env =Environment::GetCurrent(context);
1067+
returnTHROW_ERR_CRYPTO_OPERATION_FAILED(
1068+
env,"No valid certificates found in the provided array");
1069+
}
1070+
1071+
auto new_set = std::make_unique<X509Set>();
1072+
for (X509* cert : *certs) {
1073+
auto [it, inserted] = new_set->insert(cert);
1074+
if (!inserted) {// Free duplicate certificates from the vector.
1075+
X509_free(cert);
1076+
}
1077+
}
1078+
1079+
// Free any existing certificates in the old set.
1080+
if (root_certs_from_users !=nullptr) {
1081+
for (X509* cert : *root_certs_from_users) {
1082+
X509_free(cert);
1083+
}
1084+
}
1085+
std::swap(root_certs_from_users, new_set);
1086+
1087+
// Reset the global root cert store and create a new one with the
1088+
// certificates.
1089+
if (root_cert_store !=nullptr) {
1090+
X509_STORE_free(root_cert_store);
1091+
}
1092+
1093+
// TODO(joyeecheung): we can probably just reset it to nullptr
1094+
// and let the next call to NewRootCertStore() create a new one.
1095+
root_cert_store =NewRootCertStore();
1096+
}
1097+
9381098
voidGetSystemCACertificates(const FunctionCallbackInfo<Value>& args) {
9391099
Environment* env =Environment::GetCurrent(args);
9401100
Local<Array> results;
941-
if (X509sToArrayOfStrings(env,GetSystemStoreCACertificates())
1101+
std::vector<X509*>& certs =GetSystemStoreCACertificates();
1102+
if (X509sToArrayOfStrings(env, certs.begin(), certs.end(), certs.size())
9421103
.ToLocal(&results)) {
9431104
args.GetReturnValue().Set(results);
9441105
}
@@ -950,7 +1111,9 @@ void GetExtraCACertificates(const FunctionCallbackInfo<Value>& args) {
9501111
return args.GetReturnValue().Set(Array::New(env->isolate()));
9511112
}
9521113
Local<Array> results;
953-
if (X509sToArrayOfStrings(env,GetExtraCACertificates()).ToLocal(&results)) {
1114+
std::vector<X509*>& certs =GetExtraCACertificates();
1115+
if (X509sToArrayOfStrings(env, certs.begin(), certs.end(), certs.size())
1116+
.ToLocal(&results)) {
9541117
args.GetReturnValue().Set(results);
9551118
}
9561119
}
@@ -1046,6 +1209,9 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) {
10461209
context, target,"getSystemCACertificates", GetSystemCACertificates);
10471210
SetMethodNoSideEffect(
10481211
context, target,"getExtraCACertificates", GetExtraCACertificates);
1212+
SetMethod(context, target,"resetRootCertStore", ResetRootCertStore);
1213+
SetMethodNoSideEffect(
1214+
context, target,"getUserRootCertificates", GetUserRootCertificates);
10491215
}
10501216

10511217
voidSecureContext::RegisterExternalReferences(
@@ -1088,6 +1254,8 @@ void SecureContext::RegisterExternalReferences(
10881254
registry->Register(GetBundledRootCertificates);
10891255
registry->Register(GetSystemCACertificates);
10901256
registry->Register(GetExtraCACertificates);
1257+
registry->Register(ResetRootCertStore);
1258+
registry->Register(GetUserRootCertificates);
10911259
}
10921260

10931261
SecureContext*SecureContext::Create(Environment* env) {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp