Skip to content

Commit bc80880

Browse files
authored
Support SAN/dnsName for restricted trust (#91946) (#91978)
This commit extends the TLS restricted trust model to allow reading from alternative fields from the X509 certificate. Prior to this commit the only supported (hard coded) value that could be used with restricted trust is the SAN/otherName/CN value. This commit introduces support to read from other fields from the X509 certificate. This commit also introduces support to read from SAN/dnsName if configured. Any fields read from the certificate will be used to match against the restricted trust file and if any of the values match to the restricted trust file, then restricted trust is allowed. Only if none of the values match then the restricted trust denied. SAN/otherName/CN is the default, and SAN/dnsName can be used in addition or in place of SAN/otherName/CN. The possible configuration values are: ``` *.trust_restrictions.x509_fields: ["subjectAltName.otherName.commonName", "subjectAltName.dnsName"] ``` To help support testing, all of the existing certificates have been updated to include a SAN/dnsName that matches the SAN/otherName/CN. This allows the tests to randomize which field(s) are used to match for restricted trust. This also has the side effect of making this commit larger than expected in terms of lines of change. A readme has been included with copy-able commands to recreate the certificates as needed. Additionally, a CCS REST test has been introduced that uses the restricted trust. To support this new CCS REST test the private keys for the test certificates are also included in this commit as well as the gradle configuration needed to share those certificates across projects.
1 parent cd96706 commit bc80880

File tree

284 files changed

+6278
-1834
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

284 files changed

+6278
-1834
lines changed

docs/changelog/91946.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 91946
2+
summary: Support SAN/dnsName for restricted trust
3+
area: TLS
4+
type: enhancement
5+
issues: []

x-pack/plugin/core/build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ tasks.named("dependencyLicenses").configure {
2525
mapping from: /commons-.*/, to: 'commons' // pulled in by rest client
2626
}
2727

28+
configurations {
29+
signedCerts
30+
rootCert
31+
}
32+
2833
dependencies {
2934
compileOnly project(":server")
3035
api project(":libs:elasticsearch-ssl-config")
@@ -62,6 +67,8 @@ dependencies {
6267

6368
yamlRestTestImplementation project(':x-pack:plugin:core')
6469

70+
signedCerts fileTree("src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/nodes/ca-signed")
71+
rootCert files("src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/nodes/ca.crt")
6572
}
6673

6774
ext.expansions = [

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/RestrictedTrustConfig.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Collection;
2020
import java.util.List;
2121
import java.util.Objects;
22+
import java.util.Set;
2223

2324
import javax.net.ssl.X509ExtendedTrustManager;
2425

@@ -30,10 +31,15 @@
3031
public final class RestrictedTrustConfig extends TrustConfig {
3132

3233
private static final String RESTRICTIONS_KEY_SUBJECT_NAME = "trust.subject_name";
34+
public static final String SAN_OTHER_COMMON = "subjectAltName.otherName.commonName";
35+
public static final String SAN_DNS = "subjectAltName.dnsName";
36+
static final Set<String> SUPPORTED_X_509_FIELDS = org.elasticsearch.core.Set.of(SAN_OTHER_COMMON, SAN_DNS);
3337
private final String groupConfigPath;
3438
private final TrustConfig delegate;
39+
private final Set<String> configuredX509Fields;
3540

36-
RestrictedTrustConfig(String groupConfigPath, TrustConfig delegate) {
41+
RestrictedTrustConfig(String groupConfigPath, Set<String> configuredX509Fields, TrustConfig delegate) {
42+
this.configuredX509Fields = configuredX509Fields;
3743
this.groupConfigPath = Objects.requireNonNull(groupConfigPath);
3844
this.delegate = Objects.requireNonNull(delegate);
3945
}
@@ -43,7 +49,7 @@ RestrictedTrustManager createTrustManager(@Nullable Environment environment) {
4349
try {
4450
final X509ExtendedTrustManager delegateTrustManager = delegate.createTrustManager(environment);
4551
final CertificateTrustRestrictions trustGroupConfig = readTrustGroup(resolveGroupConfigPath(environment));
46-
return new RestrictedTrustManager(delegateTrustManager, trustGroupConfig);
52+
return new RestrictedTrustManager(delegateTrustManager, trustGroupConfig, configuredX509Fields);
4753
} catch (IOException e) {
4854
throw new ElasticsearchException("failed to initialize TrustManager for {}", e, toString());
4955
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/RestrictedTrustManager.java

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import java.security.cert.X509Certificate;
1818
import java.util.Collection;
1919
import java.util.Collections;
20+
import java.util.HashSet;
2021
import java.util.List;
22+
import java.util.Locale;
2123
import java.util.Objects;
2224
import java.util.Optional;
2325
import java.util.Set;
@@ -27,25 +29,39 @@
2729
import javax.net.ssl.SSLEngine;
2830
import javax.net.ssl.X509ExtendedTrustManager;
2931

32+
import static org.elasticsearch.xpack.core.ssl.RestrictedTrustConfig.SAN_DNS;
33+
import static org.elasticsearch.xpack.core.ssl.RestrictedTrustConfig.SAN_OTHER_COMMON;
34+
3035
/**
3136
* An X509 trust manager that only trusts connections from a restricted set of predefined network entities (nodes, clients, etc).
32-
* The trusted entities are defined as a list of predicates on {@link CertificateTrustRestrictions} that are applied to the
33-
* common-names of the certificate.
34-
* The common-names are read as subject-alternative-names with type 'Other' and a 'cn' OID.
35-
* The underlying certificate validation is delegated to another TrustManager.
37+
* The trusted entities are defined as a list of predicates on {@link CertificateTrustRestrictions} that built from the
38+
* configured restricted trust file. The values in the restricted trust file are compared to value(s) read from the X509 certificate.
39+
* If the value(s) read from the X509 certificate match values configured in restricted trust file then restricted trust is established.
40+
* If there is no match, then restricted trust is not established and the connection should be terminated. Restricted trust should be used
41+
* in conjunction with additional trust models and is intended to restrict, not provide trust.
42+
* The values read from the X509 certificate are configurable and the following are supported:
43+
* <ul>
44+
* <li>subjectAltName.otherName.commonName</li>
45+
* <li>subjectAltName.dnsName</li>
46+
* </ul>
47+
* see also: {@link RestrictedTrustConfig}
3648
*/
3749
public final class RestrictedTrustManager extends X509ExtendedTrustManager {
3850
private static final Logger logger = LogManager.getLogger(RestrictedTrustManager.class);
3951
private static final String CN_OID = "2.5.4.3";
4052
private static final int SAN_CODE_OTHERNAME = 0;
53+
private static final int SAN_CODE_DNS = 2;
4154

4255
private final X509ExtendedTrustManager delegate;
4356
private final CertificateTrustRestrictions trustRestrictions;
57+
private final Set<String> x509Fields;
4458

45-
public RestrictedTrustManager(X509ExtendedTrustManager delegate, CertificateTrustRestrictions restrictions) {
59+
public RestrictedTrustManager(X509ExtendedTrustManager delegate, CertificateTrustRestrictions restrictions, Set<String> x509Fields) {
4660
this.delegate = delegate;
4761
this.trustRestrictions = restrictions;
62+
this.x509Fields = x509Fields.stream().map(s -> s.toLowerCase(Locale.ROOT)).collect(Collectors.toSet());
4863
logger.debug("Configured with trust restrictions: [{}]", restrictions);
64+
logger.debug("Configured with x509 fields: [{}]", x509Fields);
4965
}
5066

5167
@Override
@@ -94,28 +110,32 @@ private void verifyTrust(X509Certificate[] chain) throws CertificateException {
94110
throw new CertificateException("No certificate presented");
95111
}
96112
final X509Certificate certificate = chain[0];
97-
Set<String> names = readCommonNames(certificate);
98-
if (verifyCertificateNames(names)) {
113+
Set<String> values = readX509Certificate(certificate);
114+
if (verifyCertificateNames(values)) {
99115
logger.debug(
100116
() -> new ParameterizedMessage(
101-
"Trusting certificate [{}] [{}] with common-names [{}]",
117+
"Trusting certificate [{}] [{}] with fields [{}] with values [{}]",
102118
certificate.getSubjectDN(),
103119
certificate.getSerialNumber().toString(16),
104-
names
120+
x509Fields,
121+
values
105122
)
106123
);
107124
} else {
108125
logger.info(
109-
"Rejecting certificate [{}] [{}] with common-names [{}]",
126+
"Rejecting certificate [{}] [{}] for fields [{}] with values [{}]",
110127
certificate.getSubjectDN(),
111128
certificate.getSerialNumber().toString(16),
112-
names
129+
x509Fields,
130+
values
113131
);
114132
throw new CertificateException(
115133
"Certificate for "
116134
+ certificate.getSubjectDN()
117-
+ " with common-names "
118-
+ names
135+
+ " with fields "
136+
+ x509Fields
137+
+ " with values "
138+
+ values
119139
+ " does not match the trusted names "
120140
+ trustRestrictions.getTrustedNames()
121141
);
@@ -133,13 +153,28 @@ private boolean verifyCertificateNames(Set<String> names) {
133153
return false;
134154
}
135155

136-
private Set<String> readCommonNames(X509Certificate certificate) throws CertificateParsingException {
137-
return getSubjectAlternativeNames(certificate).stream()
138-
.filter(pair -> ((Integer) pair.get(0)).intValue() == SAN_CODE_OTHERNAME)
139-
.map(pair -> pair.get(1))
140-
.map(value -> decodeDerValue((byte[]) value, certificate))
141-
.filter(Objects::nonNull)
142-
.collect(Collectors.toSet());
156+
private Set<String> readX509Certificate(X509Certificate certificate) throws CertificateParsingException {
157+
Collection<List<?>> sans = getSubjectAlternativeNames(certificate);
158+
Set<String> values = new HashSet<>();
159+
if (x509Fields.contains(SAN_DNS.toLowerCase(Locale.ROOT))) {
160+
Set<String> dnsNames = sans.stream()
161+
.filter(pair -> ((Integer) pair.get(0)).intValue() == SAN_CODE_DNS)
162+
.map(pair -> pair.get(1))
163+
.map(Object::toString)
164+
.filter(Objects::nonNull)
165+
.collect(Collectors.toSet());
166+
values.addAll(dnsNames);
167+
}
168+
if (x509Fields.contains(SAN_OTHER_COMMON.toLowerCase(Locale.ROOT))) {
169+
Set<String> otherNames = getSubjectAlternativeNames(certificate).stream()
170+
.filter(pair -> ((Integer) pair.get(0)).intValue() == SAN_CODE_OTHERNAME)
171+
.map(pair -> pair.get(1))
172+
.map(value -> decodeDerValue((byte[]) value, certificate))
173+
.filter(Objects::nonNull)
174+
.collect(Collectors.toSet());
175+
values.addAll(otherNames);
176+
}
177+
return values;
143178
}
144179

145180
/**

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfiguration.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,13 @@ private static KeyConfig createKeyConfig(Settings settings) {
168168
private static TrustConfig createTrustConfig(Settings settings, KeyConfig keyConfig) {
169169
final TrustConfig trustConfig = createCertChainTrustConfig(settings, keyConfig);
170170
return SETTINGS_PARSER.trustRestrictionsPath.get(settings)
171-
.map(path -> (TrustConfig) new RestrictedTrustConfig(path, trustConfig))
171+
.map(
172+
path -> (TrustConfig) new RestrictedTrustConfig(
173+
path,
174+
org.elasticsearch.core.Set.copyOf(SETTINGS_PARSER.trustRestrictionsX509Fields.get(settings)),
175+
trustConfig
176+
)
177+
)
172178
.orElse(trustConfig);
173179
}
174180

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.common.settings.Setting;
1212
import org.elasticsearch.common.settings.Setting.Property;
1313
import org.elasticsearch.common.settings.Settings;
14+
import org.elasticsearch.common.ssl.SslConfigException;
1415
import org.elasticsearch.common.util.CollectionUtils;
1516

1617
import java.security.KeyStore;
@@ -41,6 +42,7 @@ public class SSLConfigurationSettings {
4142
public final Setting<String> truststoreAlgorithm;
4243
public final Setting<Optional<String>> truststoreType;
4344
public final Setting<Optional<String>> trustRestrictionsPath;
45+
public final Setting<List<String>> trustRestrictionsX509Fields;
4446
public final Setting<List<String>> caPaths;
4547
public final Setting<Optional<SSLClientAuth>> clientAuth;
4648
public final Setting<Optional<VerificationMode>> verificationMode;
@@ -264,9 +266,30 @@ public class SSLConfigurationSettings {
264266
"xpack.security.ssl.trust_restrictions",
265267
TRUST_RESTRICTIONS_TEMPLATE
266268
);
269+
267270
public static final Function<String, Setting.AffixSetting<Optional<String>>> TRUST_RESTRICTIONS_REALM = realmType -> Setting
268271
.affixKeySetting("xpack.security.authc.realms." + realmType + ".", "ssl.trust_restrictions", TRUST_RESTRICTIONS_TEMPLATE);
269272

273+
public static final Function<String, Setting<List<String>>> TRUST_RESTRICTIONS_X509_FIELDS_TEMPLATE = key -> Setting.listSetting(
274+
key,
275+
org.elasticsearch.core.List.of("subjectAltName.otherName.commonName"),
276+
s -> {
277+
Optional<String> value = RestrictedTrustConfig.SUPPORTED_X_509_FIELDS.stream().filter(v -> v.equalsIgnoreCase(s)).findAny();
278+
if (value.isPresent() == false) {
279+
throw new SslConfigException(
280+
s
281+
+ " is not a supported x509 field for trust restrictions. "
282+
+ "Recognised values are ["
283+
+ String.join(",", RestrictedTrustConfig.SUPPORTED_X_509_FIELDS)
284+
+ "]"
285+
);
286+
}
287+
return s;
288+
},
289+
Property.NodeScope,
290+
Property.Filtered
291+
);
292+
270293
public static final Setting<SecureString> LEGACY_KEY_PASSWORD_PROFILES = Setting.affixKeySetting(
271294
"transport.profiles.",
272295
"xpack.security.ssl.key_passphrase",
@@ -371,6 +394,7 @@ private SSLConfigurationSettings(String prefix) {
371394
truststoreAlgorithm = TRUST_STORE_ALGORITHM_TEMPLATE.apply(prefix + "truststore.algorithm");
372395
truststoreType = TRUST_STORE_TYPE_TEMPLATE.apply(prefix + "truststore.type");
373396
trustRestrictionsPath = TRUST_RESTRICTIONS_TEMPLATE.apply(prefix + "trust_restrictions.path");
397+
trustRestrictionsX509Fields = TRUST_RESTRICTIONS_X509_FIELDS_TEMPLATE.apply(prefix + "trust_restrictions.x509_fields");
374398
caPaths = CAPATH_SETTING_TEMPLATE.apply(prefix + "certificate_authorities");
375399
clientAuth = CLIENT_AUTH_SETTING_TEMPLATE.apply(prefix + "client_authentication");
376400
verificationMode = VERIFICATION_MODE_SETTING_TEMPLATE.apply(prefix + "verification_mode");
@@ -383,6 +407,7 @@ private SSLConfigurationSettings(String prefix) {
383407
truststoreAlgorithm,
384408
truststoreType,
385409
trustRestrictionsPath,
410+
trustRestrictionsX509Fields,
386411
caPaths,
387412
clientAuth,
388413
verificationMode,

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/RestrictedTrustConfigTests.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
import javax.net.ssl.X509ExtendedTrustManager;
2626

27+
import static org.elasticsearch.xpack.core.ssl.RestrictedTrustConfig.SAN_OTHER_COMMON;
28+
2729
public class RestrictedTrustConfigTests extends ESTestCase {
2830

2931
public void testDelegationOfFilesToMonitor() throws Exception {
@@ -70,7 +72,11 @@ public int hashCode() {
7072
}
7173
};
7274

73-
final RestrictedTrustConfig restrictedTrustConfig = new RestrictedTrustConfig(groupConfigPath.toString(), delegate);
75+
final RestrictedTrustConfig restrictedTrustConfig = new RestrictedTrustConfig(
76+
groupConfigPath.toString(),
77+
org.elasticsearch.core.Set.of(SAN_OTHER_COMMON),
78+
delegate
79+
);
7480
List<Path> filesToMonitor = restrictedTrustConfig.filesToMonitor(environment);
7581
List<Path> expectedPathList = new ArrayList<>(otherFiles);
7682
expectedPathList.add(groupConfigPath);

0 commit comments

Comments
 (0)