diff --git a/src/main/java/org/elasticsearch/shield/authc/pki/PkiRealm.java b/src/main/java/org/elasticsearch/shield/authc/pki/PkiRealm.java index 3c25d702ec7..bde2b027007 100644 --- a/src/main/java/org/elasticsearch/shield/authc/pki/PkiRealm.java +++ b/src/main/java/org/elasticsearch/shield/authc/pki/PkiRealm.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.shield.authc.pki; +import org.elasticsearch.common.base.Strings; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; @@ -39,6 +40,7 @@ public class PkiRealm extends Realm { public static final String PKI_CERT_HEADER_NAME = "__SHIELD_CLIENT_CERTIFICATE"; public static final String TYPE = "pki"; + public static final String DEFAULT_USERNAME_PATTERN = "CN=(.*?)(?:,|$)"; // For client based cert validation, the auth type must be specified but UNKNOWN is an acceptable value public static final String AUTH_TYPE = "UNKNOWN"; @@ -50,7 +52,7 @@ public class PkiRealm extends Realm { public PkiRealm(RealmConfig config, DnRoleMapper roleMapper) { super(TYPE, config); this.trustManagers = trustManagers(config.settings(), config.env()); - this.principalPattern = Pattern.compile(config.settings().get("username_pattern", "CN=(.*?),"), Pattern.CASE_INSENSITIVE); + this.principalPattern = Pattern.compile(config.settings().get("username_pattern", DEFAULT_USERNAME_PATTERN), Pattern.CASE_INSENSITIVE); this.roleMapper = roleMapper; checkSSLEnabled(config, logger); } @@ -91,7 +93,7 @@ public class PkiRealm extends Realm { return null; } - String dn = certificates[0].getSubjectX500Principal().getName(); + String dn = certificates[0].getSubjectX500Principal().toString(); Matcher matcher = principalPattern.matcher(dn); if (!matcher.find()) { if (logger.isDebugEnabled()) { @@ -101,6 +103,12 @@ public class PkiRealm extends Realm { } String principal = matcher.group(1); + if (Strings.isNullOrEmpty(principal)) { + if (logger.isDebugEnabled()) { + logger.debug("certificate authentication succeeded for [{}] but extracted principal was empty", dn); + } + return null; + } return new X509AuthenticationToken(certificates, principal, dn); } diff --git a/src/test/java/org/elasticsearch/shield/authc/pki/PkiRealmTests.java b/src/test/java/org/elasticsearch/shield/authc/pki/PkiRealmTests.java index dbda8cba675..5fa5529ed8d 100644 --- a/src/test/java/org/elasticsearch/shield/authc/pki/PkiRealmTests.java +++ b/src/test/java/org/elasticsearch/shield/authc/pki/PkiRealmTests.java @@ -13,18 +13,21 @@ import org.elasticsearch.shield.authc.RealmConfig; import org.elasticsearch.shield.authc.support.DnRoleMapper; import org.elasticsearch.shield.authc.support.SecuredString; import org.elasticsearch.shield.authc.support.UsernamePasswordToken; +import org.elasticsearch.shield.support.NoOpLogger; import org.elasticsearch.test.ElasticsearchTestCase; import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.transport.TransportMessage; import org.junit.Before; import org.junit.Test; +import javax.security.auth.x500.X500Principal; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Collections; +import java.util.regex.Pattern; import static org.hamcrest.Matchers.*; import static org.mockito.Mockito.*; @@ -57,7 +60,7 @@ public class PkiRealmTests extends ElasticsearchTestCase { X509AuthenticationToken token = realm.token(restRequest); assertThat(token, is(notNullValue())); - assertThat(token.dn(), is("CN=Elasticsearch Test Node,OU=elasticsearch,O=org")); + assertThat(token.dn(), is("CN=Elasticsearch Test Node, OU=elasticsearch, O=org")); assertThat(token.principal(), is("Elasticsearch Test Node")); } @@ -70,7 +73,7 @@ public class PkiRealmTests extends ElasticsearchTestCase { X509AuthenticationToken token = realm.token(message); assertThat(token, is(notNullValue())); - assertThat(token.dn(), is("CN=Elasticsearch Test Node,OU=elasticsearch,O=org")); + assertThat(token.dn(), is("CN=Elasticsearch Test Node, OU=elasticsearch, O=org")); assertThat(token.principal(), is("Elasticsearch Test Node")); } @@ -160,6 +163,42 @@ public class PkiRealmTests extends ElasticsearchTestCase { } } + @Test + public void certificateWithOnlyCnExtractsProperly() throws Exception { + X509Certificate certificate = mock(X509Certificate.class); + X500Principal principal = new X500Principal("CN=PKI Client"); + when(certificate.getSubjectX500Principal()).thenReturn(principal); + + X509AuthenticationToken token = PkiRealm.token(new X509Certificate[]{certificate}, Pattern.compile(PkiRealm.DEFAULT_USERNAME_PATTERN), NoOpLogger.INSTANCE); + assertThat(token, notNullValue()); + assertThat(token.principal(), is("PKI Client")); + assertThat(token.dn(), is("CN=PKI Client")); + } + + @Test + public void certificateWithCnAndOuExtractsProperly() throws Exception { + X509Certificate certificate = mock(X509Certificate.class); + X500Principal principal = new X500Principal("CN=PKI Client, OU=Shield"); + when(certificate.getSubjectX500Principal()).thenReturn(principal); + + X509AuthenticationToken token = PkiRealm.token(new X509Certificate[]{certificate}, Pattern.compile(PkiRealm.DEFAULT_USERNAME_PATTERN), NoOpLogger.INSTANCE); + assertThat(token, notNullValue()); + assertThat(token.principal(), is("PKI Client")); + assertThat(token.dn(), is("CN=PKI Client, OU=Shield")); + } + + @Test + public void certificateWithCnInMiddle() throws Exception { + X509Certificate certificate = mock(X509Certificate.class); + X500Principal principal = new X500Principal("EMAILADDRESS=pki@elastic.co, CN=PKI Client, OU=Shield"); + when(certificate.getSubjectX500Principal()).thenReturn(principal); + + X509AuthenticationToken token = PkiRealm.token(new X509Certificate[]{certificate}, Pattern.compile(PkiRealm.DEFAULT_USERNAME_PATTERN), NoOpLogger.INSTANCE); + assertThat(token, notNullValue()); + assertThat(token.principal(), is("PKI Client")); + assertThat(token.dn(), is("EMAILADDRESS=pki@elastic.co, CN=PKI Client, OU=Shield")); + } + static X509Certificate readCert(Path path) throws Exception { try (InputStream in = Files.newInputStream(path)) { CertificateFactory factory = CertificateFactory.getInstance("X.509");