Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/crypto/crypto_tls.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,36 @@ namespace crypto {

namespace {

#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(4, 1)
int VerifyCertChain(X509_STORE_CTX* ctx, void*) {
X509_VERIFY_PARAM* param = X509_STORE_CTX_get0_param(ctx);
const unsigned int flags = X509_VERIFY_PARAM_get_hostflags(param);
if (!(flags & (X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT |
X509_CHECK_FLAG_NEVER_CHECK_SUBJECT))) {
// tls.checkServerIdentity() falls back to the CN when no DNS SAN exists.
// OpenSSL 4.1 requires this flag to apply name constraints to that CN.
ncrypto::DeleteFnPtr<GENERAL_NAMES, GENERAL_NAMES_free> names(
static_cast<GENERAL_NAMES*>(
X509_get_ext_d2i(X509_STORE_CTX_get0_cert(ctx),
NID_subject_alt_name,
nullptr,
nullptr)));
bool has_dns_san = false;
for (int i = 0; names && i < sk_GENERAL_NAME_num(names.get()); i++) {
if (sk_GENERAL_NAME_value(names.get(), i)->type == GEN_DNS) {
has_dns_san = true;
break;
}
}
if (!has_dns_san) {
X509_VERIFY_PARAM_set_hostflags(
param, flags | X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT);
}
}
return X509_verify_cert(ctx) > 0 ? 1 : 0;
}
#endif

// Our custom implementation of the certificate verify callback
// used when establishing a TLS handshake. Because we cannot perform
// I/O quickly enough with X509_STORE_CTX_ APIs in this callback,
Expand Down Expand Up @@ -330,6 +360,9 @@ int TLSExtStatusCallback(SSL* s, void* arg) {
void ConfigureSecureContext(SecureContext* sc) {
// OCSP stapling
sc->ctx().setStatusCallback(TLSExtStatusCallback);
#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(4, 1)
SSL_CTX_set_cert_verify_callback(sc->ctx().get(), VerifyCertChain, nullptr);
#endif
}

inline bool Set(
Expand Down
29 changes: 29 additions & 0 deletions test/fixtures/keys/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
all: \
name-constraints-ca-cert.pem \
name-constraints-permitted-cert.pem \
name-constraints-excluded-cert.pem \
name-constraints-dns-san-cert.pem \
name-constraints-email-san-cert.pem \
ca1-cert.pem \
ca2-cert.pem \
ca2-crl.pem \
Expand Down Expand Up @@ -1243,6 +1248,30 @@ irrelevant_san_correct_subject-cert.pem: irrelevant_san_correct_subject-key.pem
irrelevant_san_correct_subject-key.pem:
openssl ecparam -name prime256v1 -genkey -noout -out irrelevant_san_correct_subject-key.pem

# Certificates for DNS name constraints with CN fallback.
# Reuse the existing ca1 and agent1 private keys.
name-constraints-ca-cert.pem: name-constraints.cnf ca1-key.pem
openssl req -new -x509 -sha256 -days 99999 -set_serial 1 \
-config name-constraints.cnf -key ca1-key.pem \
-passin "pass:password" -out $@

name-constraints-permitted-cert.pem: CN = www.example.com
name-constraints-permitted-cert.pem: SERIAL = 2
name-constraints-excluded-cert.pem: SERIAL = 3
name-constraints-dns-san-cert.pem: SERIAL = 4
name-constraints-email-san-cert.pem: SERIAL = 5
name-constraints-%-cert.pem: CN = outside.invalid
name-constraints-%-cert.pem: name-constraints.cnf name-constraints-ca-cert.pem ca1-key.pem agent1-key.pem
openssl req -new -config name-constraints.cnf -key agent1-key.pem \
-subj "/CN=$(CN)/emailAddress=subject@example.com" \
-out name-constraints-$*-csr.pem
openssl x509 -req -sha256 -days 99999 -set_serial $(SERIAL) \
-in name-constraints-$*-csr.pem \
-CA name-constraints-ca-cert.pem -CAkey ca1-key.pem \
-passin "pass:password" -extfile name-constraints.cnf \
-extensions $* -out $@
rm name-constraints-$*-csr.pem

clean:
rm -f *.pfx *.pem *.srl ca2-database.txt ca2-crl-agent3-database.txt* ca2-crl-agent3.cnf ca2-serial fake-startcom-root-serial *.print *.old fake-startcom-root-issued-certs/*.pem
@> fake-startcom-root-database.txt
Expand Down
19 changes: 19 additions & 0 deletions test/fixtures/keys/name-constraints-ca-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDFjCCAf6gAwIBAgIBATANBgkqhkiG9w0BAQsFADArMSkwJwYDVQQDDCBOb2Rl
LmpzIG5hbWUgY29uc3RyYWludHMgdGVzdCBDQTAgFw0yNjA5MTAxMDE5MjZaGA8y
MzAwMDYyNTEwMTkyNlowKzEpMCcGA1UEAwwgTm9kZS5qcyBuYW1lIGNvbnN0cmFp
bnRzIHRlc3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNvf4O
GGep+ak+4DNjbuNgy0S/AZPxahEFp4gpbcvsi9YLOPZ31qpilQeQf7d27scIZ02Q
x1YBAzljxELB8H/ZxuYScQK0s+DNP22xhmgwMWznO7TezkHP5ujN2UkbfbUpfUxG
FgncXeZf9wR7yFWppeHiRWNBOgsvY7sTrS12kXjWGjqntF7xcEDHc7h+KyF6ZjVJ
ZJCnP6pJEQ+rUjd51eCZXt4WjowLnQiCS1VKzXiP83a++Ma1BKKkUitTR112/Uwd
5eGoiByhmLzb/BhxnHJN07GXjhlMItZRm/jfbZsx1mwnNOO3tx4r08l+DaqkinIa
dvazs+1ugCaKQn8xAgMBAAGjQzBBMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
BAQDAgEGMB4GA1UdHgEB/wQUMBKgEDAOggwuZXhhbXBsZS5jb20wDQYJKoZIhvcN
AQELBQADggEBAAJqIezf3QGn3IAiOgF8Psr5BlzRrz/9pQ7cGhm9T34+S+s9oaux
ooPWXF94Mp7zcv93kohhfP5KPRT5PDvMVkv420sQg2ATdkDV62pQwidsTSb2Uj0/
gQZwPDq5UAQE+uzQccP4fnEG7EEp6BdBm4SNU/6jnMdjFdOXX8l/DqR5nSC0ljiu
dFHyq0yL0hI51NLIfqVqfr5A7DAIbJhBu/TdAnuQaax0L8UVD0wXujxflwnXTxgh
SCoU+KnfbbWwJYrCE8DpmK9FKZYcVCb6xMQnEhC2P4tojqLYi31BBlTxSGcD4BQn
CEsF+KZ22uz8szGnCTFKmlGP4OEQlgsKqfk=
-----END CERTIFICATE-----
20 changes: 20 additions & 0 deletions test/fixtures/keys/name-constraints-dns-san-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDNzCCAh+gAwIBAgIBBDANBgkqhkiG9w0BAQsFADArMSkwJwYDVQQDDCBOb2Rl
LmpzIG5hbWUgY29uc3RyYWludHMgdGVzdCBDQTAgFw0yNjA5MTAxMDE5MjZaGA8y
MzAwMDYyNTEwMTkyNlowPjEYMBYGA1UEAwwPb3V0c2lkZS5pbnZhbGlkMSIwIAYJ
KoZIhvcNAQkBFhNzdWJqZWN0QGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA1FYyCvsg04Jwk9wsQoTtBN+6vVbh3a5Snii3kM1CVtsn
M0nzc1/9M3x6Y2Psylont/c9xwialsbYhtsMYjiPHN1qljr81ZnVgA5YehH5CJYP
hO1QuiWigwPs2m5oT757rtyc6IATJ7FpevJQl87j8XXkAJhMDbao64e+A7TPlHdL
pW//yMY8aNat62CrvmmnsUq2prnnuqibWtq46weJfAf21Po9Zg3/V0EH0o6PY0Z6
eIYkxXQZdpPpWc6hNi/64buhDIwNiIQKv+8QNjGy6PXDm1VIp+pX6KOfiSkYE/Ra
dsRIAzorfthAP0uqFHzzXi0lVKplzklpV5cJW/TcawIDAQABo1EwTzAMBgNVHRMB
Af8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAaBgNV
HREEEzARgg93d3cuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAKo9BvhK
WAx2dVStsX9V7tOEZu3vM3hNC8SvfolX0Bl+9F+hycElC3OQmM8Fgt1MegmRMT0R
dqoknu2MaiT4D+qX2i1mP2BhM9Zd0avA2OGhodMykNkMzqyDHyZWtCNmsvVB/708
PWGFoTMgKLfdYf6Kjifsyxt1hu1N3iI2XZOxR2aLJ/1xMFMOrsb/FWFpsumD+5SN
H2Fa5Ps/h3swXz+A/vzahnAHvbcARLjsbAYlJ+stUjgK6Mj21mOIkIiko2HHipz+
JjpI+dPARD/cvq4FyTWsmmFLe7IZUNPu8GTQaC2dNYUkiQWTUNZTZySwnYcF9DsL
3GNOJY+wfEMMA1U=
-----END CERTIFICATE-----
20 changes: 20 additions & 0 deletions test/fixtures/keys/name-constraints-email-san-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDNzCCAh+gAwIBAgIBBTANBgkqhkiG9w0BAQsFADArMSkwJwYDVQQDDCBOb2Rl
LmpzIG5hbWUgY29uc3RyYWludHMgdGVzdCBDQTAgFw0yNjA5MTAxMDE5MjZaGA8y
MzAwMDYyNTEwMTkyNlowPjEYMBYGA1UEAwwPb3V0c2lkZS5pbnZhbGlkMSIwIAYJ
KoZIhvcNAQkBFhNzdWJqZWN0QGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA1FYyCvsg04Jwk9wsQoTtBN+6vVbh3a5Snii3kM1CVtsn
M0nzc1/9M3x6Y2Psylont/c9xwialsbYhtsMYjiPHN1qljr81ZnVgA5YehH5CJYP
hO1QuiWigwPs2m5oT757rtyc6IATJ7FpevJQl87j8XXkAJhMDbao64e+A7TPlHdL
pW//yMY8aNat62CrvmmnsUq2prnnuqibWtq46weJfAf21Po9Zg3/V0EH0o6PY0Z6
eIYkxXQZdpPpWc6hNi/64buhDIwNiIQKv+8QNjGy6PXDm1VIp+pX6KOfiSkYE/Ra
dsRIAzorfthAP0uqFHzzXi0lVKplzklpV5cJW/TcawIDAQABo1EwTzAMBgNVHRMB
Af8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAaBgNV
HREEEzARgQ9zYW5AZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBABE6i1MH
8iQadmytlAExVgMkX85fDrS5YAQPjp37VfMkE0zlrsJ4hnNIz4EAIEgGBWUydPfO
xj//IBo0uHVzBT/tZKoL0/VrKrslj6tzGzDmaVjsjfReyEN+J1D0UAifBlJVY1sR
7C6zOtXjwX1UseScOqxtBtSJJMAMWmMMtKNbZ2mOzlmWrsqVSTFLI/aB/pdKsDol
sKOh9aKVLTYHQewZ3Na2bNIZgX0JiFRDoymz5ngJr1Kb6LuYj/h44svFMoNBRhpC
K9kVqdIAs5DPlANViVahHRNOp2oz0yWfatwau8gF6e0rdF3Zl1mjO2RbizfDlS4s
ceupch60bWYcsXA=
-----END CERTIFICATE-----
19 changes: 19 additions & 0 deletions test/fixtures/keys/name-constraints-excluded-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDGzCCAgOgAwIBAgIBAzANBgkqhkiG9w0BAQsFADArMSkwJwYDVQQDDCBOb2Rl
LmpzIG5hbWUgY29uc3RyYWludHMgdGVzdCBDQTAgFw0yNjA5MTAxMDE5MjZaGA8y
MzAwMDYyNTEwMTkyNlowPjEYMBYGA1UEAwwPb3V0c2lkZS5pbnZhbGlkMSIwIAYJ
KoZIhvcNAQkBFhNzdWJqZWN0QGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA1FYyCvsg04Jwk9wsQoTtBN+6vVbh3a5Snii3kM1CVtsn
M0nzc1/9M3x6Y2Psylont/c9xwialsbYhtsMYjiPHN1qljr81ZnVgA5YehH5CJYP
hO1QuiWigwPs2m5oT757rtyc6IATJ7FpevJQl87j8XXkAJhMDbao64e+A7TPlHdL
pW//yMY8aNat62CrvmmnsUq2prnnuqibWtq46weJfAf21Po9Zg3/V0EH0o6PY0Z6
eIYkxXQZdpPpWc6hNi/64buhDIwNiIQKv+8QNjGy6PXDm1VIp+pX6KOfiSkYE/Ra
dsRIAzorfthAP0uqFHzzXi0lVKplzklpV5cJW/TcawIDAQABozUwMzAMBgNVHRMB
Af8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkq
hkiG9w0BAQsFAAOCAQEAkkWCYQN4DrdqF1V3nkbpjgSu0rUlokj8pHlzOfKQpQAk
2bvGSIpnXlyK0+uYdgJDIH7feVhunVOWo2ySqxaQnjH05SFOlfSEyVqZBrpbDMiy
F5ulKs8fY2jS9o7nCiqHyog+oZWhQupTTTe120tmu8xesNK4T/bm3RiNEiPMVXYL
KS38VOZZE4A+REG9Pyd5zjJUOsOqngf8mEvKEmSsMh+6jEoGwJQGv4RYtCY3ZYA2
waIIw/0pw9pfncRTdcDgJBAe5jeDTjDZ7APNJSqA2kmMUNv8qcLqFbAWzTuK/dvW
7tE3Pa17FiTmcrVxZSc9StruPWgTZ7w8Fd1g6+v+Xg==
-----END CERTIFICATE-----
19 changes: 19 additions & 0 deletions test/fixtures/keys/name-constraints-permitted-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDGzCCAgOgAwIBAgIBAjANBgkqhkiG9w0BAQsFADArMSkwJwYDVQQDDCBOb2Rl
LmpzIG5hbWUgY29uc3RyYWludHMgdGVzdCBDQTAgFw0yNjA5MTAxMDE5MjZaGA8y
MzAwMDYyNTEwMTkyNlowPjEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMSIwIAYJ
KoZIhvcNAQkBFhNzdWJqZWN0QGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA1FYyCvsg04Jwk9wsQoTtBN+6vVbh3a5Snii3kM1CVtsn
M0nzc1/9M3x6Y2Psylont/c9xwialsbYhtsMYjiPHN1qljr81ZnVgA5YehH5CJYP
hO1QuiWigwPs2m5oT757rtyc6IATJ7FpevJQl87j8XXkAJhMDbao64e+A7TPlHdL
pW//yMY8aNat62CrvmmnsUq2prnnuqibWtq46weJfAf21Po9Zg3/V0EH0o6PY0Z6
eIYkxXQZdpPpWc6hNi/64buhDIwNiIQKv+8QNjGy6PXDm1VIp+pX6KOfiSkYE/Ra
dsRIAzorfthAP0uqFHzzXi0lVKplzklpV5cJW/TcawIDAQABozUwMzAMBgNVHRMB
Af8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkq
hkiG9w0BAQsFAAOCAQEAU9FR2kM66P1+k/RwQdzgeawPvxREJ36Q+JBdY0FQ50uW
ToFyUmCpm0Ts4eM5v5J2kfWVRgPlvj/oq1SVJ105d/CyJP3XDt/nd6aKgLUdVDCy
/AfbniwOqpmslij8MdKhwA2aFo828N/ecVKTBdX/mNZoA9/dhdu6ty1Q7DZzUv3e
hkF2XcWzTIEUUjxVPXUT6H1WjVgT/ViOjbqln30GmKSxgEG6qXbGLAwIQVX03UvI
Ta5uc4Jl9LYETu0iz9CVxhkzvS9ISXApBJD31sDuWBXZjmDQ1/Ky0Gd0WGogzYpz
LnEqspax4sylH8b+/nlYLbdiBgRNxkc5PzaZTYD/AA==
-----END CERTIFICATE-----
34 changes: 34 additions & 0 deletions test/fixtures/keys/name-constraints.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[ req ]
distinguished_name = req_distinguished_name
prompt = no
x509_extensions = ca

[ req_distinguished_name ]
CN = Node.js name constraints test CA

[ ca ]
basicConstraints = critical,CA:TRUE
keyUsage = critical,keyCertSign,cRLSign
nameConstraints = critical,permitted;DNS:.example.com

[ permitted ]
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth

[ excluded ]
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth

[ dns-san ]
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:www.example.com

[ email-san ]
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = email:san@example.com
55 changes: 55 additions & 0 deletions test/parallel/test-tls-name-constraints-cn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');

const { isBoringSSL } = require('../common/crypto');
// BoringSSL rejects DNS-like CNs without SANs under name constraints,
// even when the CN is permitted by those constraints.
if (isBoringSSL) common.skip('requires OpenSSL CN name constraints');

const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');

const ca = fixtures.readKey('name-constraints-ca-cert.pem');
const key = fixtures.readKey('agent1-key.pem');

// A DNS SAN prevents CN fallback; an email SAN does not. Name constraints
// must cover the CN whenever hostname verification can use it.
for (const version of ['TLSv1.2', 'TLSv1.3']) {
for (const [name, servername, valid] of [
['permitted', 'www.example.com', true],
['excluded', 'outside.invalid', false],
['dns-san', 'www.example.com', true],
['email-san', 'outside.invalid', false],
]) {
for (const rejectUnauthorized of [true, false]) {
const rejected = !valid && rejectUnauthorized;
const server = tls.createServer({
key,
cert: fixtures.readKey(`name-constraints-${name}-cert.pem`),
minVersion: version,
maxVersion: version,
}, (socket) => socket.end());
server.on('tlsClientError', () => {});
server.listen(0, common.mustCall(() => {
const client = tls.connect({
port: server.address().port,
ca,
servername,
rejectUnauthorized,
});
client.on('secureConnect', rejected ? common.mustNotCall() : common.mustCall(() => {
assert.strictEqual(client.authorized, valid);
if (!valid) assert.strictEqual(client.authorizationError, 'UNSPECIFIED');
client.end();
}));
client.on('error', rejected ? common.mustCall((err) => {
assert.strictEqual(err.code, 'UNSPECIFIED');
assert.match(err.message, /permitted subtree violation/);
}) : common.mustNotCall());
client.on('close', common.mustCall(() => server.close()));
}));
}
}
}
Loading