KEYCLOAK-6056 Map user by Subject Alternative Name (otherName) when authenticating user with X509
This commit is contained in:
parent
cf35a4648b
commit
a48698caa3
18 changed files with 374 additions and 92 deletions
|
@ -59,6 +59,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
||||||
public static final String MAPPING_SOURCE_CERT_SUBJECTDN = "Match SubjectDN using regular expression";
|
public static final String MAPPING_SOURCE_CERT_SUBJECTDN = "Match SubjectDN using regular expression";
|
||||||
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL = "Subject's e-mail";
|
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL = "Subject's e-mail";
|
||||||
public static final String MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL = "Subject's Alternative Name E-mail";
|
public static final String MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL = "Subject's Alternative Name E-mail";
|
||||||
|
public static final String MAPPING_SOURCE_CERT_SUBJECTALTNAME_OTHERNAME = "Subject's Alternative Name otherName (UPN)";
|
||||||
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_CN = "Subject's Common Name";
|
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_CN = "Subject's Common Name";
|
||||||
public static final String MAPPING_SOURCE_CERT_ISSUERDN = "Match IssuerDN using regular expression";
|
public static final String MAPPING_SOURCE_CERT_ISSUERDN = "Match IssuerDN using regular expression";
|
||||||
public static final String MAPPING_SOURCE_CERT_ISSUERDN_EMAIL = "Issuer's e-mail";
|
public static final String MAPPING_SOURCE_CERT_ISSUERDN_EMAIL = "Issuer's e-mail";
|
||||||
|
@ -152,6 +153,9 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
|
||||||
case SUBJECTALTNAME_EMAIL:
|
case SUBJECTALTNAME_EMAIL:
|
||||||
extractor = UserIdentityExtractor.getSubjectAltNameExtractor(1);
|
extractor = UserIdentityExtractor.getSubjectAltNameExtractor(1);
|
||||||
break;
|
break;
|
||||||
|
case SUBJECTALTNAME_OTHERNAME:
|
||||||
|
extractor = UserIdentityExtractor.getSubjectAltNameExtractor(0);
|
||||||
|
break;
|
||||||
case ISSUERDN_CN:
|
case ISSUERDN_CN:
|
||||||
extractor = UserIdentityExtractor.getX500NameExtractor(BCStyle.CN, issuer);
|
extractor = UserIdentityExtractor.getX500NameExtractor(BCStyle.CN, issuer);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -44,6 +44,7 @@ import static org.keycloak.authentication.authenticators.x509.AbstractX509Client
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_ISSUERDN_EMAIL;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_ISSUERDN_EMAIL;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SERIALNUMBER;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SERIALNUMBER;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL;
|
||||||
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTALTNAME_OTHERNAME;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_CN;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_CN;
|
||||||
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL;
|
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL;
|
||||||
|
@ -72,6 +73,7 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
|
||||||
MAPPING_SOURCE_CERT_SUBJECTDN,
|
MAPPING_SOURCE_CERT_SUBJECTDN,
|
||||||
MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL,
|
MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL,
|
||||||
MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL,
|
MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL,
|
||||||
|
MAPPING_SOURCE_CERT_SUBJECTALTNAME_OTHERNAME,
|
||||||
MAPPING_SOURCE_CERT_SUBJECTDN_CN,
|
MAPPING_SOURCE_CERT_SUBJECTDN_CN,
|
||||||
MAPPING_SOURCE_CERT_ISSUERDN,
|
MAPPING_SOURCE_CERT_ISSUERDN,
|
||||||
MAPPING_SOURCE_CERT_ISSUERDN_EMAIL,
|
MAPPING_SOURCE_CERT_ISSUERDN_EMAIL,
|
||||||
|
|
|
@ -19,12 +19,18 @@
|
||||||
package org.keycloak.authentication.authenticators.x509;
|
package org.keycloak.authentication.authenticators.x509;
|
||||||
|
|
||||||
import freemarker.template.utility.NullArgumentException;
|
import freemarker.template.utility.NullArgumentException;
|
||||||
|
import org.bouncycastle.asn1.ASN1Encodable;
|
||||||
|
import org.bouncycastle.asn1.ASN1InputStream;
|
||||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||||
|
import org.bouncycastle.asn1.ASN1Sequence;
|
||||||
|
import org.bouncycastle.asn1.ASN1TaggedObject;
|
||||||
|
import org.bouncycastle.asn1.DERUTF8String;
|
||||||
import org.bouncycastle.asn1.x500.RDN;
|
import org.bouncycastle.asn1.x500.RDN;
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
import org.bouncycastle.asn1.x500.style.IETFUtils;
|
import org.bouncycastle.asn1.x500.style.IETFUtils;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.security.cert.CertificateParsingException;
|
import java.security.cert.CertificateParsingException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -101,6 +107,9 @@ public abstract class UserIdentityExtractor {
|
||||||
*/
|
*/
|
||||||
static class SubjectAltNameExtractor extends UserIdentityExtractor {
|
static class SubjectAltNameExtractor extends UserIdentityExtractor {
|
||||||
|
|
||||||
|
// User Principal Name. Used typically by Microsoft in certificates for Smart Card Login
|
||||||
|
private static final String UPN_OID = "1.3.6.1.4.1.311.20.2.3";
|
||||||
|
|
||||||
private final int generalName;
|
private final int generalName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,19 +136,79 @@ public abstract class UserIdentityExtractor {
|
||||||
|
|
||||||
Iterator<List<?>> iterator = subjectAlternativeNames.iterator();
|
Iterator<List<?>> iterator = subjectAlternativeNames.iterator();
|
||||||
|
|
||||||
while (iterator.hasNext()) {
|
boolean foundUpn = false;
|
||||||
|
String tempOtherName = null;
|
||||||
|
String tempOid = null;
|
||||||
|
|
||||||
|
while (iterator.hasNext() && !foundUpn) {
|
||||||
List<?> next = iterator.next();
|
List<?> next = iterator.next();
|
||||||
|
|
||||||
if (Integer.class.cast(next.get(0)) == generalName) {
|
if (Integer.class.cast(next.get(0)) == generalName) {
|
||||||
return next.get(1);
|
|
||||||
|
// We will try to find UPN_OID among the subjectAltNames of type 'otherName' . Just if not found, we will fallback to the other type
|
||||||
|
for (int i = 1 ; i<next.size() ; i++) {
|
||||||
|
Object obj = next.get(i);
|
||||||
|
|
||||||
|
// We have Subject Alternative Name of other type than 'otherName' . Just return it directly
|
||||||
|
if (generalName != 0) {
|
||||||
|
logger.tracef("Extracted identity '%s' from Subject Alternative Name of type '%d'", obj, generalName);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] otherNameBytes = (byte[]) obj;
|
||||||
|
|
||||||
|
try {
|
||||||
|
ASN1InputStream asn1Stream = new ASN1InputStream(new ByteArrayInputStream(otherNameBytes));
|
||||||
|
ASN1Encodable asn1otherName = asn1Stream.readObject();
|
||||||
|
asn1otherName = unwrap(asn1otherName);
|
||||||
|
|
||||||
|
ASN1Sequence asn1Sequence = ASN1Sequence.getInstance(asn1otherName);
|
||||||
|
|
||||||
|
if (asn1Sequence != null) {
|
||||||
|
ASN1Encodable encodedOid = asn1Sequence.getObjectAt(0);
|
||||||
|
ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(unwrap(encodedOid));
|
||||||
|
tempOid = oid.getId();
|
||||||
|
|
||||||
|
ASN1Encodable principalNameEncoded = asn1Sequence.getObjectAt(1);
|
||||||
|
DERUTF8String principalName = DERUTF8String.getInstance(unwrap(principalNameEncoded));
|
||||||
|
|
||||||
|
tempOtherName = principalName.getString();
|
||||||
|
|
||||||
|
// We found UPN among the 'otherName' principal. We don't need to look other
|
||||||
|
if (UPN_OID.equals(tempOid)) {
|
||||||
|
foundUpn = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to parse subjectAltName", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.tracef("Parsed otherName from subjectAltName. OID: '%s', Principal: '%s'", tempOid, tempOtherName);
|
||||||
|
|
||||||
|
return tempOtherName;
|
||||||
|
|
||||||
} catch (CertificateParsingException cause) {
|
} catch (CertificateParsingException cause) {
|
||||||
logger.errorf(cause, "Failed to obtain identity from subjectAltName extension");
|
logger.errorf(cause, "Failed to obtain identity from subjectAltName extension");
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ASN1Encodable unwrap(ASN1Encodable encodable) {
|
||||||
|
while (encodable instanceof ASN1TaggedObject) {
|
||||||
|
ASN1TaggedObject taggedObj = (ASN1TaggedObject) encodable;
|
||||||
|
encodable = taggedObj.getObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
return encodable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class PatternMatcher extends UserIdentityExtractor {
|
static class PatternMatcher extends UserIdentityExtractor {
|
||||||
|
|
|
@ -61,6 +61,7 @@ public class X509AuthenticatorConfigModel extends AuthenticatorConfigModel {
|
||||||
SUBJECTDN_CN(MAPPING_SOURCE_CERT_SUBJECTDN_CN),
|
SUBJECTDN_CN(MAPPING_SOURCE_CERT_SUBJECTDN_CN),
|
||||||
SUBJECTDN_EMAIL(MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL),
|
SUBJECTDN_EMAIL(MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL),
|
||||||
SUBJECTALTNAME_EMAIL(MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL),
|
SUBJECTALTNAME_EMAIL(MAPPING_SOURCE_CERT_SUBJECTALTNAME_EMAIL),
|
||||||
|
SUBJECTALTNAME_OTHERNAME(MAPPING_SOURCE_CERT_SUBJECTALTNAME_OTHERNAME),
|
||||||
SUBJECTDN(MAPPING_SOURCE_CERT_SUBJECTDN);
|
SUBJECTDN(MAPPING_SOURCE_CERT_SUBJECTDN);
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.authentication.authenticators.x509;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
import org.keycloak.common.util.StreamUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class SubjectAltNameIdentityExtractorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testX509SubjectAltName_otherName() throws Exception {
|
||||||
|
UserIdentityExtractor extractor = UserIdentityExtractor.getSubjectAltNameExtractor(0);
|
||||||
|
|
||||||
|
X509Certificate cert = getCertificate();
|
||||||
|
|
||||||
|
Object upn = extractor.extractUserIdentity(new X509Certificate[] { cert});
|
||||||
|
Assert.assertEquals("test-user@some-company-domain", upn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testX509SubjectAltName_email() throws Exception {
|
||||||
|
UserIdentityExtractor extractor = UserIdentityExtractor.getSubjectAltNameExtractor(1);
|
||||||
|
|
||||||
|
X509Certificate cert = getCertificate();
|
||||||
|
|
||||||
|
Object upn = extractor.extractUserIdentity(new X509Certificate[] { cert});
|
||||||
|
Assert.assertEquals("test@somecompany.com", upn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private X509Certificate getCertificate() throws Exception {
|
||||||
|
InputStream is = getClass().getResourceAsStream("/certs/UPN-cert.pem");
|
||||||
|
|
||||||
|
String s = StreamUtil.readString(is, Charset.defaultCharset());
|
||||||
|
|
||||||
|
return PemUtils.decodeCertificate(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
29
services/src/test/resources/certs/UPN-cert.pem
Normal file
29
services/src/test/resources/certs/UPN-cert.pem
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE6DCCA9CgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpzELMAkGA1UEBhMCVVMx
|
||||||
|
HTAbBgNVBAgMFERpc3RyaWN0IG9mIENvbHVtYmlhMRMwEQYDVQQHDApXYXNoaW5n
|
||||||
|
dG9uMRUwEwYDVQQKDAxTb21lIENvbXBhbnkxGDAWBgNVBAsMD1NvbWUgRGVwYXJ0
|
||||||
|
bWVudDEQMA4GA1UEAwwHVGVzdCBDQTEhMB8GCSqGSIb3DQEJARYSY2FAc29tZWNv
|
||||||
|
bXBhbnkuY29tMB4XDTE0MDYxMjE3MDkxMloXDTI0MDYxMjE3MDkxMlowgZYxCzAJ
|
||||||
|
BgNVBAYTAlVTMR0wGwYDVQQIDBREaXN0cmljdCBvZiBDb2x1bWJpYTEVMBMGA1UE
|
||||||
|
CgwMU29tZSBDb21wYW55MRgwFgYDVQQLDA9Tb21lIERlcGFydG1lbnQxEjAQBgNV
|
||||||
|
BAMMCVRlc3QgVXNlcjEjMCEGCSqGSIb3DQEJARYUdGVzdEBzb21lY29tcGFueS5j
|
||||||
|
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD42s0Q8YXv/rQrk4rF
|
||||||
|
4aHmuz8Tq9jWk3bU/tJoBgZTG2xCYfolT2z4j2Qa6kjXucEJuqOKihxMMZ1We0G4
|
||||||
|
I6tm5QJxqkEoUYmUZHu/QZSrH1gwgS0yjvfq+Kk+yvKqplXDUyxbLRuMBRgFRCy0
|
||||||
|
TUvdJPE4IQZQCcHir0Vqs667vj0UjSpI+y0BDZPY5CRePRSKcjM4ixoR9B8xj5kg
|
||||||
|
RcMxg4EszC2oK7z0IuuYKi0ZOdot1wVKP4OD/9evE2wjUYVeYCAV9y7tMlVsN0N5
|
||||||
|
dRplCSIa/CA5gTMod3C92t83VoPqfb0f71cNQAsx1V3dNtOKnTOoG5jX70RR4Rqk
|
||||||
|
8ItNAgMBAAGjggEsMIIBKDAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAL
|
||||||
|
BgNVHQ8EBAMCBeAwKwYJYIZIAYb4QgENBB4WHFNtYXJ0IENhcmQgTG9naW4gQ2Vy
|
||||||
|
dGlmaWNhdGUwHQYDVR0OBBYEFN1P5EBNqZ+MGrJQziiVMhkKAXr9MB8GA1UdIwQY
|
||||||
|
MBaAFCHrFg422S+AWOHXfPIdqxbRhXegME4GA1UdEQRHMEWBFHRlc3RAc29tZWNv
|
||||||
|
bXBhbnkuY29toC0GCisGAQQBgjcUAgOgHwwddGVzdC11c2VyQHNvbWUtY29tcGFu
|
||||||
|
eS1kb21haW4wHQYDVR0SBBYwFIESY2FAc29tZWNvbXBhbnkuY29tMB8GA1UdJQQY
|
||||||
|
MBYGCCsGAQUFBwMCBgorBgEEAYI3FAICMA0GCSqGSIb3DQEBCwUAA4IBAQCPI5Zk
|
||||||
|
DqGHkKfFhRjlzLLajUEveggs74x3roi6S0zlpXpbPA3iZ2N8COf/QZL3twKunbP9
|
||||||
|
XpmW/pcSji3+pil9aHPRn69S4cSuKdN5ZP9oQhkZxdk2UFS8Ts0WA+SUDJ3qTEtA
|
||||||
|
Q0HswBFBzWGOi0zkCtvAaBa8WSnDPtUN5RzmUtkKoMxBzu/MEMWNYXZyk/G2NHMJ
|
||||||
|
jh0N+ICpRNpnXGIZBwFymIRGH/PjtVkArVXy0hILWP/qfYzYMFgUBl0InyQzT0Hw
|
||||||
|
gzGdoeK7fVYrPWLK3ryRqqSR1XvfKFHwlnIg4XTBN4Cj7m5TmpntgVhO2JpNDjLr
|
||||||
|
K/NmtORHe6OhF17I
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -1,38 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
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
|
|
||||||
-----END CERTIFICATE-----
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
Binary file not shown.
|
@ -84,7 +84,8 @@ stateOrProvinceName_default = MA
|
||||||
localityName_default = Boston
|
localityName_default = Boston
|
||||||
0.organizationName_default = Red Hat
|
0.organizationName_default = Red Hat
|
||||||
organizationalUnitName_default = Keycloak
|
organizationalUnitName_default = Keycloak
|
||||||
emailAddress_default = contact@keycloak.org
|
commonName_default = test-user
|
||||||
|
emailAddress_default = test-user@localhost
|
||||||
|
|
||||||
[ v3_ca ]
|
[ v3_ca ]
|
||||||
# Extensions for a typical CA (`man x509v3_config`).
|
# Extensions for a typical CA (`man x509v3_config`).
|
||||||
|
@ -106,13 +107,21 @@ basicConstraints = CA:FALSE
|
||||||
nsCertType = client, email
|
nsCertType = client, email
|
||||||
nsComment = "OpenSSL Generated Client Certificate"
|
nsComment = "OpenSSL Generated Client Certificate"
|
||||||
subjectKeyIdentifier = hash
|
subjectKeyIdentifier = hash
|
||||||
authorityKeyIdentifier = keyid,issuer
|
#authorityKeyIdentifier = keyid,issuer
|
||||||
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
|
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
|
||||||
extendedKeyUsage = clientAuth, emailProtection
|
extendedKeyUsage = clientAuth, emailProtection
|
||||||
crlDistributionPoints = URI:http://localhost:8888/crl
|
crlDistributionPoints = URI:http://localhost:8888/crl
|
||||||
authorityInfoAccess = OCSP;URI:http://localhost:8888/oscp
|
authorityInfoAccess = OCSP;URI:http://localhost:8888/oscp
|
||||||
subjectAltName=email:copy
|
subjectAltName=@user_subject_alt_names
|
||||||
subjectAltName=email:move
|
|
||||||
|
[ user_subject_alt_names ]
|
||||||
|
email = test-user-altmail@localhost
|
||||||
|
IP = 192.168.7.1
|
||||||
|
DNS = www.example-test.com
|
||||||
|
URI = http://www.example-test.com
|
||||||
|
otherName.1 = 1.2.3.4;UTF8:my_test_user
|
||||||
|
otherName.2 = 1.3.6.1.4.1.311.20.2.3;UTF8:test_upn_name@localhost
|
||||||
|
|
||||||
|
|
||||||
[ server_cert ]
|
[ server_cert ]
|
||||||
# Extensions for server certificates (`man x509v3_config`).
|
# Extensions for server certificates (`man x509v3_config`).
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
1001
|
1007
|
||||||
|
|
|
@ -195,7 +195,7 @@
|
||||||
<resource>
|
<resource>
|
||||||
<directory>${common.resources}/pki/root/ca</directory>
|
<directory>${common.resources}/pki/root/ca</directory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>certs/clients/test-user-san-email@localhost.cert.pem</include>
|
<include>certs/clients/test-user-san@localhost.cert.pem</include>
|
||||||
<include>certs/clients/test-user@localhost.key.pem</include>
|
<include>certs/clients/test-user@localhost.key.pem</include>
|
||||||
</includes>
|
</includes>
|
||||||
</resource>
|
</resource>
|
||||||
|
|
|
@ -88,6 +88,12 @@
|
||||||
<resource>
|
<resource>
|
||||||
<directory>${common.resources}/keystore</directory>
|
<directory>${common.resources}/keystore</directory>
|
||||||
</resource>
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>${common.resources}/pki/root/ca</directory>
|
||||||
|
<includes>
|
||||||
|
<include>certs/clients/*</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
</resources>
|
</resources>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|
|
@ -273,7 +273,7 @@
|
||||||
<outputDirectory>${containers.home}/auth-server-undertow</outputDirectory>
|
<outputDirectory>${containers.home}/auth-server-undertow</outputDirectory>
|
||||||
</artifactItem>
|
</artifactItem>
|
||||||
</artifactItems>
|
</artifactItems>
|
||||||
<includes>*.jks,*.crt,*.truststore</includes>
|
<includes>*.jks,*.crt,*.truststore,*.crl,*.key,certs/clients/*</includes>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
|
|
@ -49,7 +49,6 @@ import org.keycloak.testsuite.util.AdminEventPaths;
|
||||||
import org.keycloak.testsuite.util.AssertAdminEvents;
|
import org.keycloak.testsuite.util.AssertAdminEvents;
|
||||||
import org.keycloak.testsuite.util.ClientBuilder;
|
import org.keycloak.testsuite.util.ClientBuilder;
|
||||||
import org.keycloak.testsuite.util.DroneUtils;
|
import org.keycloak.testsuite.util.DroneUtils;
|
||||||
import org.keycloak.testsuite.util.PhantomJSBrowser;
|
|
||||||
import org.keycloak.testsuite.util.RealmBuilder;
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
@ -68,6 +67,7 @@ import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorC
|
||||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.ISSUERDN;
|
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.ISSUERDN;
|
||||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.ISSUERDN_CN;
|
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.ISSUERDN_CN;
|
||||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTALTNAME_EMAIL;
|
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTALTNAME_EMAIL;
|
||||||
|
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTALTNAME_OTHERNAME;
|
||||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_CN;
|
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_CN;
|
||||||
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_EMAIL;
|
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_EMAIL;
|
||||||
|
|
||||||
|
@ -117,26 +117,41 @@ public abstract class AbstractX509AuthenticationTest extends AbstractTestRealmKe
|
||||||
Assume.assumeTrue(AUTH_SERVER_SSL_REQUIRED);
|
Assume.assumeTrue(AUTH_SERVER_SSL_REQUIRED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void onBeforeTestClass() {
|
public static void onBeforeTestClass() {
|
||||||
if (isAuthServerJBoss()) {
|
configurePhantomJS("/ca.crt", "/client.crt", "/client.key", "secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup phantom JS to be used for mutual TLS testing. All file paths are relative to "authServerHome"
|
||||||
|
*
|
||||||
|
* @param certificatesPath
|
||||||
|
* @param clientCertificateFile
|
||||||
|
* @param clientKeyFile
|
||||||
|
* @param clientKeyPassword
|
||||||
|
*/
|
||||||
|
protected static void configurePhantomJS(String certificatesPath, String clientCertificateFile, String clientKeyFile, String clientKeyPassword) {
|
||||||
String authServerHome = System.getProperty("auth.server.home");
|
String authServerHome = System.getProperty("auth.server.home");
|
||||||
|
|
||||||
if (authServerHome != null && System.getProperty("auth.server.ssl.required") != null) {
|
if (authServerHome != null && System.getProperty("auth.server.ssl.required") != null) {
|
||||||
|
if (isAuthServerJBoss()) {
|
||||||
authServerHome = authServerHome + "/standalone/configuration";
|
authServerHome = authServerHome + "/standalone/configuration";
|
||||||
|
}
|
||||||
|
|
||||||
StringBuilder cliArgs = new StringBuilder();
|
StringBuilder cliArgs = new StringBuilder();
|
||||||
|
|
||||||
cliArgs.append("--ignore-ssl-errors=true ");
|
cliArgs.append("--ignore-ssl-errors=true ");
|
||||||
cliArgs.append("--web-security=false ");
|
cliArgs.append("--web-security=false ");
|
||||||
cliArgs.append("--ssl-certificates-path=").append(authServerHome).append("/ca.crt ");
|
cliArgs.append("--ssl-certificates-path=").append(authServerHome).append(certificatesPath).append(" ");
|
||||||
cliArgs.append("--ssl-client-certificate-file=").append(authServerHome).append("/client.crt ");
|
cliArgs.append("--ssl-client-certificate-file=").append(authServerHome).append(clientCertificateFile).append(" ");
|
||||||
cliArgs.append("--ssl-client-key-file=").append(authServerHome).append("/client.key ");
|
cliArgs.append("--ssl-client-key-file=").append(authServerHome).append(clientKeyFile).append(" ");
|
||||||
cliArgs.append("--ssl-client-key-passphrase=secret ");
|
cliArgs.append("--ssl-client-key-passphrase=" + clientKeyPassword).append(" ");
|
||||||
|
|
||||||
System.setProperty("keycloak.phantomjs.cli.args", cliArgs.toString());
|
System.setProperty("keycloak.phantomjs.cli.args", cliArgs.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isAuthServerJBoss() {
|
private static boolean isAuthServerJBoss() {
|
||||||
return Boolean.parseBoolean(System.getProperty("auth.server.jboss"));
|
return Boolean.parseBoolean(System.getProperty("auth.server.jboss"));
|
||||||
|
@ -183,6 +198,8 @@ public abstract class AbstractX509AuthenticationTest extends AbstractTestRealmKe
|
||||||
userId = user.getId();
|
userId = user.getId();
|
||||||
|
|
||||||
user.singleAttribute("x509_certificate_identity","-");
|
user.singleAttribute("x509_certificate_identity","-");
|
||||||
|
user.singleAttribute("alternative_email", "test-user-altmail@localhost");
|
||||||
|
user.singleAttribute("upn", "test_upn_name@localhost");
|
||||||
updateUser(user);
|
updateUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,11 +360,20 @@ public abstract class AbstractX509AuthenticationTest extends AbstractTestRealmKe
|
||||||
.setUserIdentityMapperType(USERNAME_EMAIL);
|
.setUserIdentityMapperType(USERNAME_EMAIL);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static X509AuthenticatorConfigModel createLoginSubjectAltNameEmail2UsernameOrEmailConfig() {
|
protected static X509AuthenticatorConfigModel createLoginSubjectAltNameEmail2UserAttributeConfig() {
|
||||||
return new X509AuthenticatorConfigModel()
|
return new X509AuthenticatorConfigModel()
|
||||||
.setConfirmationPageAllowed(true)
|
.setConfirmationPageAllowed(true)
|
||||||
.setMappingSourceType(SUBJECTALTNAME_EMAIL)
|
.setMappingSourceType(SUBJECTALTNAME_EMAIL)
|
||||||
.setUserIdentityMapperType(USERNAME_EMAIL);
|
.setUserIdentityMapperType(USER_ATTRIBUTE)
|
||||||
|
.setCustomAttributeName("alternative_email");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static X509AuthenticatorConfigModel createLoginSubjectAltNameOtherName2UserAttributeConfig() {
|
||||||
|
return new X509AuthenticatorConfigModel()
|
||||||
|
.setConfirmationPageAllowed(true)
|
||||||
|
.setMappingSourceType(SUBJECTALTNAME_OTHERNAME)
|
||||||
|
.setUserIdentityMapperType(USER_ATTRIBUTE)
|
||||||
|
.setCustomAttributeName("upn");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static X509AuthenticatorConfigModel createLoginSubjectEmailWithKeyUsage(String keyUsage) {
|
protected static X509AuthenticatorConfigModel createLoginSubjectEmailWithKeyUsage(String keyUsage) {
|
||||||
|
|
|
@ -39,7 +39,7 @@ import org.openqa.selenium.WebDriver;
|
||||||
* @date 8/12/2016
|
* @date 8/12/2016
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class X509BrowserLoginSubjectAltNameEmailTest extends AbstractX509AuthenticationTest {
|
public class X509BrowserLoginSubjectAltNameTest extends AbstractX509AuthenticationTest {
|
||||||
|
|
||||||
@Page
|
@Page
|
||||||
@PhantomJSBrowser
|
@PhantomJSBrowser
|
||||||
|
@ -64,23 +64,8 @@ public class X509BrowserLoginSubjectAltNameEmailTest extends AbstractX509Authent
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void onBeforeTestClass() {
|
public static void onBeforeTestClass() {
|
||||||
if (Boolean.parseBoolean(System.getProperty("auth.server.jboss"))) {
|
configurePhantomJS("/ca.crt", "/certs/clients/test-user-san@localhost.cert.pem",
|
||||||
String authServerHome = System.getProperty("auth.server.home");
|
"/certs/clients/test-user@localhost.key.pem", "password");
|
||||||
|
|
||||||
if (authServerHome != null && System.getProperty("auth.server.ssl.required") != null) {
|
|
||||||
authServerHome = authServerHome + "/standalone/configuration";
|
|
||||||
StringBuilder cliArgs = new StringBuilder();
|
|
||||||
|
|
||||||
cliArgs.append("--ignore-ssl-errors=true ");
|
|
||||||
cliArgs.append("--web-security=false ");
|
|
||||||
cliArgs.append("--ssl-certificates-path=" + authServerHome + "/ca.crt ");
|
|
||||||
cliArgs.append("--ssl-client-certificate-file=" + authServerHome + "/certs/clients/test-user-san-email@localhost.cert.pem ");
|
|
||||||
cliArgs.append("--ssl-client-key-file=" + authServerHome + "/certs/clients/test-user@localhost.key.pem ");
|
|
||||||
cliArgs.append("--ssl-client-key-passphrase=password");
|
|
||||||
|
|
||||||
System.setProperty("keycloak.phantomjs.cli.args", cliArgs.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void login(X509AuthenticatorConfigModel config, String userId, String username, String attemptedUsername) {
|
private void login(X509AuthenticatorConfigModel config, String userId, String username, String attemptedUsername) {
|
||||||
|
@ -91,7 +76,7 @@ public class X509BrowserLoginSubjectAltNameEmailTest extends AbstractX509Authent
|
||||||
|
|
||||||
loginConfirmationPage.open();
|
loginConfirmationPage.open();
|
||||||
|
|
||||||
Assert.assertTrue(loginConfirmationPage.getSubjectDistinguishedNameText().equals("CN=test-user, OU=Keycloak, O=Red Hat, L=Boston, ST=MA, C=US"));
|
Assert.assertEquals("EMAILADDRESS=test-user@localhost, CN=test-user, OU=Keycloak, O=Red Hat, L=Boston, ST=MA, C=US", loginConfirmationPage.getSubjectDistinguishedNameText());
|
||||||
Assert.assertEquals(username, loginConfirmationPage.getUsernameText());
|
Assert.assertEquals(username, loginConfirmationPage.getUsernameText());
|
||||||
|
|
||||||
loginConfirmationPage.confirm();
|
loginConfirmationPage.confirm();
|
||||||
|
@ -107,7 +92,12 @@ public class X509BrowserLoginSubjectAltNameEmailTest extends AbstractX509Authent
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loginAsUserFromCertSubjectEmail() {
|
public void loginAsUserFromCertSANEmail() {
|
||||||
login(createLoginSubjectAltNameEmail2UsernameOrEmailConfig(), userId, "test-user@localhost", "test-user@localhost");
|
login(createLoginSubjectAltNameEmail2UserAttributeConfig(), userId, "test-user@localhost", "test-user-altmail@localhost");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loginAsUserFromCertSANUpn() {
|
||||||
|
login(createLoginSubjectAltNameOtherName2UserAttributeConfig(), userId, "test-user@localhost", "test_upn_name@localhost");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -39,9 +39,14 @@ import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
import org.keycloak.testsuite.util.cli.TestsuiteCLI;
|
import org.keycloak.testsuite.util.cli.TestsuiteCLI;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import org.xnio.Options;
|
||||||
|
import org.xnio.SslClientAuthMode;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import javax.servlet.DispatcherType;
|
import javax.servlet.DispatcherType;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
@ -380,7 +385,9 @@ public class KeycloakServer {
|
||||||
.setIoThreads(config.getWorkerThreads() / 8);
|
.setIoThreads(config.getWorkerThreads() / 8);
|
||||||
|
|
||||||
if (config.getPortHttps() != -1) {
|
if (config.getPortHttps() != -1) {
|
||||||
builder = builder.addHttpsListener(config.getPortHttps(), config.getHost(), createSSLContext());
|
builder = builder
|
||||||
|
.addHttpsListener(config.getPortHttps(), config.getHost(), createSSLContext())
|
||||||
|
.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, SslClientAuthMode.REQUESTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
server = new UndertowJaxrsServer();
|
server = new UndertowJaxrsServer();
|
||||||
|
@ -476,12 +483,29 @@ public class KeycloakServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private SSLContext createSSLContext() throws Exception {
|
private SSLContext createSSLContext() throws Exception {
|
||||||
|
KeyManager[] keyManagers = getKeyManagers();
|
||||||
|
|
||||||
|
if (keyManagers == null) {
|
||||||
|
return SSLContext.getDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
TrustManager[] trustManagers = getTrustManagers();
|
||||||
|
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
|
sslContext.init(keyManagers, trustManagers, null);
|
||||||
|
return sslContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private KeyManager[] getKeyManagers() throws Exception {
|
||||||
String keyStorePath = System.getProperty("keycloak.tls.keystore.path");
|
String keyStorePath = System.getProperty("keycloak.tls.keystore.path");
|
||||||
|
|
||||||
if (keyStorePath == null) {
|
if (keyStorePath == null) {
|
||||||
return SSLContext.getDefault();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.infof("Loading keystore from file: %s", keyStorePath);
|
||||||
|
|
||||||
InputStream stream = Files.newInputStream(Paths.get(keyStorePath));
|
InputStream stream = Files.newInputStream(Paths.get(keyStorePath));
|
||||||
|
|
||||||
if (stream == null) {
|
if (stream == null) {
|
||||||
|
@ -490,20 +514,41 @@ public class KeycloakServer {
|
||||||
|
|
||||||
try (InputStream is = stream) {
|
try (InputStream is = stream) {
|
||||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||||
|
|
||||||
char[] keyStorePassword = System.getProperty("keycloak.tls.keystore.password", "password").toCharArray();
|
char[] keyStorePassword = System.getProperty("keycloak.tls.keystore.password", "password").toCharArray();
|
||||||
|
|
||||||
keyStore.load(is, keyStorePassword);
|
keyStore.load(is, keyStorePassword);
|
||||||
|
|
||||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
|
|
||||||
keyManagerFactory.init(keyStore, keyStorePassword);
|
keyManagerFactory.init(keyStore, keyStorePassword);
|
||||||
|
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
return keyManagerFactory.getKeyManagers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
|
|
||||||
|
|
||||||
return sslContext;
|
private TrustManager[] getTrustManagers() throws Exception {
|
||||||
|
String trustStorePath = System.getProperty("keycloak.tls.truststore.path");
|
||||||
|
|
||||||
|
if (trustStorePath == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.infof("Loading truststore from file: %s", trustStorePath);
|
||||||
|
|
||||||
|
InputStream stream = Files.newInputStream(Paths.get(trustStorePath));
|
||||||
|
|
||||||
|
if (stream == null) {
|
||||||
|
throw new RuntimeException("Could not load truststore");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream is = stream) {
|
||||||
|
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||||
|
char[] keyStorePassword = System.getProperty("keycloak.tls.truststore.password", "password").toCharArray();
|
||||||
|
keyStore.load(is, keyStorePassword);
|
||||||
|
|
||||||
|
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
|
trustManagerFactory.init(keyStore);
|
||||||
|
|
||||||
|
return trustManagerFactory.getTrustManagers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,5 +118,39 @@
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
"login-protocol": {
|
||||||
|
"saml": {
|
||||||
|
"knownProtocols": [
|
||||||
|
"http=${auth.server.http.port}",
|
||||||
|
"https=${auth.server.https.port}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"x509cert-lookup": {
|
||||||
|
"provider": "${keycloak.x509cert.lookup.provider:default}",
|
||||||
|
"default": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"haproxy": {
|
||||||
|
"enabled": true,
|
||||||
|
"sslClientCert": "x-ssl-client-cert",
|
||||||
|
"sslCertChainPrefix": "x-ssl-client-cert-chain",
|
||||||
|
"certificateChainLength": 1
|
||||||
|
},
|
||||||
|
"apache": {
|
||||||
|
"enabled": true,
|
||||||
|
"sslClientCert": "x-ssl-client-cert",
|
||||||
|
"sslCertChainPrefix": "x-ssl-client-cert-chain",
|
||||||
|
"certificateChainLength": 1
|
||||||
|
},
|
||||||
|
"nginx": {
|
||||||
|
"enabled": true,
|
||||||
|
"sslClientCert": "x-ssl-client-cert",
|
||||||
|
"sslCertChainPrefix": "x-ssl-client-cert-chain",
|
||||||
|
"certificateChainLength": 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue