From 71dbf4bb5045bb82f4c71fea337cafba860adf61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Thu, 16 Nov 2023 13:15:35 +0100 Subject: [PATCH] [SYNCOPE-1789] WA: adding support for X509 auth --- .../common/lib/auth/AuthModuleConf.java | 2 + .../lib/auth/OAuth20AuthModuleConf.java | 4 +- .../common/lib/auth/X509AuthModuleConf.java | 483 ++++++++++++++++++ .../common/lib/types/X509PolicySetting.java | 35 ++ .../common/lib/types/X509PrincipalType.java | 59 +++ .../lib/types/X509RevocationCheckerType.java | 38 ++ .../lib/types/X509RevocationFetcherType.java | 32 ++ .../common/lib/types/X509SubjectDnFormat.java | 47 ++ .../concepts/authenticationmodules.adoc | 11 +- .../AuthModulePropertySourceMapper.java | 74 +++ wa/starter/pom.xml | 4 + 11 files changed, 783 insertions(+), 6 deletions(-) create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/X509AuthModuleConf.java create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509PolicySetting.java create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509PrincipalType.java create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509RevocationCheckerType.java create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509RevocationFetcherType.java create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509SubjectDnFormat.java diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AuthModuleConf.java index 4e0c7f0991..6d476ea748 100644 --- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AuthModuleConf.java +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AuthModuleConf.java @@ -52,6 +52,8 @@ interface Mapper { Map map(AuthModuleTO authModule, SyncopeAuthModuleConf conf); + Map map(AuthModuleTO authModule, X509AuthModuleConf conf); + Map map(AuthModuleTO authModule, GoogleMfaAuthModuleConf conf); Map map(AuthModuleTO authModule, DuoMfaAuthModuleConf conf); diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OAuth20AuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OAuth20AuthModuleConf.java index 9b04b1fe8c..8d7b97eb8d 100644 --- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OAuth20AuthModuleConf.java +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OAuth20AuthModuleConf.java @@ -27,9 +27,9 @@ public class OAuth20AuthModuleConf extends AbstractOAuth20AuthModuleConf impleme private static final long serialVersionUID = 299820485764241682L; protected String authUrl; - + protected String profileUrl; - + protected Map profileAttrs = new LinkedHashMap<>(); protected boolean withState; diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/X509AuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/X509AuthModuleConf.java new file mode 100644 index 0000000000..30f5159705 --- /dev/null +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/X509AuthModuleConf.java @@ -0,0 +1,483 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.syncope.common.lib.auth; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.apache.syncope.common.lib.AbstractLDAPConf; +import org.apache.syncope.common.lib.to.AuthModuleTO; +import org.apache.syncope.common.lib.types.X509PolicySetting; +import org.apache.syncope.common.lib.types.X509PrincipalType; +import org.apache.syncope.common.lib.types.X509RevocationCheckerType; +import org.apache.syncope.common.lib.types.X509RevocationFetcherType; +import org.apache.syncope.common.lib.types.X509SubjectDnFormat; + +public class X509AuthModuleConf implements AuthModuleConf { + + private static final long serialVersionUID = 1915254775199296906L; + + public static class LDAP extends AbstractLDAPConf implements Serializable { + + private static final long serialVersionUID = -7274446267090678730L; + + /** + * The LDAP attribute that holds the certificate revocation list. + */ + private String certificateAttribute = "certificateRevocationList"; + + public String getCertificateAttribute() { + return certificateAttribute; + } + + public void setCertificateAttribute(final String certificateAttribute) { + this.certificateAttribute = certificateAttribute; + } + } + + /** + * The authentication handler name. + */ + private String name; + + /** + * The order of the authentication handler in the chain. + */ + private int order = Integer.MAX_VALUE; + + /** + * Threshold value if expired CRL revocation policy is to be handled via threshold. + */ + private int revocationPolicyThreshold = 172_800; + + /** + * Whether revocation checking should check all resources, or stop at first one. + */ + private boolean checkAll; + + /** + * The refresh interval of the internal scheduler in cases where CRL revocation checking + * is done via resources. + */ + private int refreshIntervalSeconds = 3_600; + + /** + * When CRL revocation checking is done via distribution points, + * decide if fetch failures should throw errors. + */ + private boolean throwOnFetchFailure; + + private X509PrincipalType principalType = X509PrincipalType.SUBJECT_DN; + + /** + * Relevant for {@code CN_EDIPI}, {@code RFC822_EMAIL}, {@code SUBJECT}, {@code SUBJECT_ALT_NAME} principal types. + */ + private String principalAlternateAttribute; + + /** + * Relevant for {@code SUBJECT_DN} principal type. + */ + private X509SubjectDnFormat principalTypeSubjectDnFormat = X509SubjectDnFormat.DEFAULT; + + /** + * Relevant for {@code SERIAL_NO_DN} principal type. + * The serial number prefix used for principal resolution. + */ + private String principalTypeSerialNoDnSerialNumberPrefix = "SERIALNUMBER="; + + /** + * Relevant for {@code SERIAL_NO_DN} principal type. + * Value delimiter used for principal resolution. + */ + private String principalTypeSerialNoDnValueDelimiter = ", "; + + /** + * Relevant for {@code SERIAL_NO} principal type. + * Radix used. + */ + private int principalTypeSerialNoSNRadix; + + /** + * Relevant for {@code SERIAL_NO} principal type. + * If radix hex padding should be used. + */ + private boolean principalTypeSerialNoHexSNZeroPadding; + + /** + * Revocation certificate checking is carried out according to this setting. + */ + private X509RevocationCheckerType revocationChecker = X509RevocationCheckerType.NONE; + + /** + * Options to describe how to fetch CRL resources. + */ + private X509RevocationFetcherType crlFetcher = X509RevocationFetcherType.RESOURCE; + + /** + * List of CRL resources to use for fetching. + */ + private final List crlResources = new ArrayList<>(0); + + /** + * When CRLs are cached, indicate maximum number of elements kept in memory. + */ + private int cacheMaxElementsInMemory = 1_000; + + /** + * Determine whether X509 authentication should allow other forms of authentication such as username/password. + * If this setting is turned off, typically the ability to view the login form as the primary form of + * authentication is turned off. + */ + private boolean mixedMode = true; + + /** + * When CRLs are cached, indicate the time-to-live of cache items. + */ + private String cacheTimeToLiveSeconds = "PT4H"; + + /** + * If the CRL resource is unavailable, activate the this policy. + */ + private X509PolicySetting crlResourceUnavailablePolicy = X509PolicySetting.DENY; + + /** + * If the CRL resource has expired, activate the this policy. + * Activated if {@link #revocationChecker} is {@code RESOURCE}. + */ + private X509PolicySetting crlResourceExpiredPolicy = X509PolicySetting.DENY; + + /** + * If the CRL is unavailable, activate the this policy. + * Activated if {@link #revocationChecker} is {@code CRL}. + */ + private X509PolicySetting crlUnavailablePolicy = X509PolicySetting.DENY; + + /** + * If the CRL has expired, activate the this policy. + * Activated if {@link #revocationChecker} is {@code CRL}. + */ + private X509PolicySetting crlExpiredPolicy = X509PolicySetting.DENY; + + /** + * The compiled pattern supplied by the deployer. + */ + private String regExTrustedIssuerDnPattern; + + /** + * Deployer supplied setting for maximum pathLength in a SUPPLIED + * certificate. + */ + private int maxPathLength = 1; + + /** + * Deployer supplied setting to allow unlimited pathLength in a SUPPLIED + * certificate. + */ + private boolean maxPathLengthAllowUnspecified = false; + + /** + * Deployer supplied setting to check the KeyUsage extension. + */ + private boolean checkKeyUsage = false; + + /** + * Deployer supplied setting to force require the correct KeyUsage + * extension. + */ + private boolean requireKeyUsage = false; + + /** + * The pattern that authorizes an acceptable certificate by its subject dn. + */ + private String regExSubjectDnPattern = ".+"; + + /** + * Whether to extract certificate from request. + * The default implementation extracts certificate from header via Tomcat SSLValve parsing logic + * and using the {@link #DEFAULT_CERT_HEADER_NAME} header. + * Must be false by default because if someone enables it they need to make sure they are + * behind proxy that won't let the header arrive directly from the browser. + */ + private boolean extractCert; + + /** + * The name of the header to consult for an X509 cert (e.g. when behind proxy). + */ + private String sslHeaderName = "ssl_client_cert"; + + private LDAP ldap; + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public int getOrder() { + return order; + } + + public void setOrder(final int order) { + this.order = order; + } + + public int getRevocationPolicyThreshold() { + return revocationPolicyThreshold; + } + + public void setRevocationPolicyThreshold(final int revocationPolicyThreshold) { + this.revocationPolicyThreshold = revocationPolicyThreshold; + } + + public boolean isCheckAll() { + return checkAll; + } + + public void setCheckAll(final boolean checkAll) { + this.checkAll = checkAll; + } + + public int getRefreshIntervalSeconds() { + return refreshIntervalSeconds; + } + + public void setRefreshIntervalSeconds(final int refreshIntervalSeconds) { + this.refreshIntervalSeconds = refreshIntervalSeconds; + } + + public boolean isThrowOnFetchFailure() { + return throwOnFetchFailure; + } + + public void setThrowOnFetchFailure(final boolean throwOnFetchFailure) { + this.throwOnFetchFailure = throwOnFetchFailure; + } + + public X509PrincipalType getPrincipalType() { + return principalType; + } + + public void setPrincipalType(final X509PrincipalType principalType) { + this.principalType = principalType; + } + + public String getPrincipalAlternateAttribute() { + return principalAlternateAttribute; + } + + public void setPrincipalAlternateAttribute(final String principalAlternateAttribute) { + this.principalAlternateAttribute = principalAlternateAttribute; + } + + public X509SubjectDnFormat getPrincipalTypeSubjectDnFormat() { + return principalTypeSubjectDnFormat; + } + + public void setPrincipalTypeSubjectDnFormat(final X509SubjectDnFormat principalTypeSubjectDnFormat) { + this.principalTypeSubjectDnFormat = principalTypeSubjectDnFormat; + } + + public String getPrincipalTypeSerialNoDnSerialNumberPrefix() { + return principalTypeSerialNoDnSerialNumberPrefix; + } + + public void setPrincipalTypeSerialNoDnSerialNumberPrefix(final String principalTypeSerialNoDnSerialNumberPrefix) { + this.principalTypeSerialNoDnSerialNumberPrefix = principalTypeSerialNoDnSerialNumberPrefix; + } + + public String getPrincipalTypeSerialNoDnValueDelimiter() { + return principalTypeSerialNoDnValueDelimiter; + } + + public void setPrincipalTypeSerialNoDnValueDelimiter(final String principalTypeSerialNoDnValueDelimiter) { + this.principalTypeSerialNoDnValueDelimiter = principalTypeSerialNoDnValueDelimiter; + } + + public int getPrincipalTypeSerialNoSNRadix() { + return principalTypeSerialNoSNRadix; + } + + public void setPrincipalTypeSerialNoSNRadix(final int principalTypeSerialNoSNRadix) { + this.principalTypeSerialNoSNRadix = principalTypeSerialNoSNRadix; + } + + public boolean isPrincipalTypeSerialNoHexSNZeroPadding() { + return principalTypeSerialNoHexSNZeroPadding; + } + + public void setPrincipalTypeSerialNoHexSNZeroPadding(final boolean principalTypeSerialNoHexSNZeroPadding) { + this.principalTypeSerialNoHexSNZeroPadding = principalTypeSerialNoHexSNZeroPadding; + } + + public X509RevocationCheckerType getRevocationChecker() { + return revocationChecker; + } + + public void setRevocationChecker(final X509RevocationCheckerType revocationChecker) { + this.revocationChecker = revocationChecker; + } + + public X509RevocationFetcherType getCrlFetcher() { + return crlFetcher; + } + + public void setCrlFetcher(final X509RevocationFetcherType crlFetcher) { + this.crlFetcher = crlFetcher; + } + + public int getCacheMaxElementsInMemory() { + return cacheMaxElementsInMemory; + } + + public void setCacheMaxElementsInMemory(final int cacheMaxElementsInMemory) { + this.cacheMaxElementsInMemory = cacheMaxElementsInMemory; + } + + public boolean isMixedMode() { + return mixedMode; + } + + public void setMixedMode(final boolean mixedMode) { + this.mixedMode = mixedMode; + } + + public String getCacheTimeToLiveSeconds() { + return cacheTimeToLiveSeconds; + } + + public void setCacheTimeToLiveSeconds(final String cacheTimeToLiveSeconds) { + this.cacheTimeToLiveSeconds = cacheTimeToLiveSeconds; + } + + public X509PolicySetting getCrlResourceUnavailablePolicy() { + return crlResourceUnavailablePolicy; + } + + public void setCrlResourceUnavailablePolicy(final X509PolicySetting crlResourceUnavailablePolicy) { + this.crlResourceUnavailablePolicy = crlResourceUnavailablePolicy; + } + + public X509PolicySetting getCrlResourceExpiredPolicy() { + return crlResourceExpiredPolicy; + } + + public void setCrlResourceExpiredPolicy(final X509PolicySetting crlResourceExpiredPolicy) { + this.crlResourceExpiredPolicy = crlResourceExpiredPolicy; + } + + public X509PolicySetting getCrlUnavailablePolicy() { + return crlUnavailablePolicy; + } + + public void setCrlUnavailablePolicy(final X509PolicySetting crlUnavailablePolicy) { + this.crlUnavailablePolicy = crlUnavailablePolicy; + } + + public X509PolicySetting getCrlExpiredPolicy() { + return crlExpiredPolicy; + } + + public void setCrlExpiredPolicy(final X509PolicySetting crlExpiredPolicy) { + this.crlExpiredPolicy = crlExpiredPolicy; + } + + public List getCrlResources() { + return crlResources; + } + + public String getRegExTrustedIssuerDnPattern() { + return regExTrustedIssuerDnPattern; + } + + public void setRegExTrustedIssuerDnPattern(final String regExTrustedIssuerDnPattern) { + this.regExTrustedIssuerDnPattern = regExTrustedIssuerDnPattern; + } + + public int getMaxPathLength() { + return maxPathLength; + } + + public void setMaxPathLength(final int maxPathLength) { + this.maxPathLength = maxPathLength; + } + + public boolean isMaxPathLengthAllowUnspecified() { + return maxPathLengthAllowUnspecified; + } + + public void setMaxPathLengthAllowUnspecified(final boolean maxPathLengthAllowUnspecified) { + this.maxPathLengthAllowUnspecified = maxPathLengthAllowUnspecified; + } + + public boolean isCheckKeyUsage() { + return checkKeyUsage; + } + + public void setCheckKeyUsage(final boolean checkKeyUsage) { + this.checkKeyUsage = checkKeyUsage; + } + + public boolean isRequireKeyUsage() { + return requireKeyUsage; + } + + public void setRequireKeyUsage(final boolean requireKeyUsage) { + this.requireKeyUsage = requireKeyUsage; + } + + public String getRegExSubjectDnPattern() { + return regExSubjectDnPattern; + } + + public void setRegExSubjectDnPattern(final String regExSubjectDnPattern) { + this.regExSubjectDnPattern = regExSubjectDnPattern; + } + + public boolean isExtractCert() { + return extractCert; + } + + public void setExtractCert(final boolean extractCert) { + this.extractCert = extractCert; + } + + public String getSslHeaderName() { + return sslHeaderName; + } + + public void setSslHeaderName(final String sslHeaderName) { + this.sslHeaderName = sslHeaderName; + } + + public LDAP getLdap() { + return ldap; + } + + public void setLdap(final LDAP ldap) { + this.ldap = ldap; + } + + @Override + public Map map(final AuthModuleTO authModule, final Mapper mapper) { + return mapper.map(authModule, this); + } +} diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509PolicySetting.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509PolicySetting.java new file mode 100644 index 0000000000..40d09bb772 --- /dev/null +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509PolicySetting.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.syncope.common.lib.types; + +public enum X509PolicySetting { + /** + * Allow to proceed. + */ + ALLOW, + /** + * Deny and block. + */ + DENY, + /** + * Throttle the request whereby expired data is permitted up to a threshold period of time but not afterward. + */ + THRESHOLD; + +} diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509PrincipalType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509PrincipalType.java new file mode 100644 index 0000000000..a970e3baba --- /dev/null +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509PrincipalType.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.syncope.common.lib.types; + +public enum X509PrincipalType { + /** + * Create principal by common name and EDIPI. + */ + CN_EDIPI, + /** + * Create principal from the RFC822 type name (aka email address) in the subject alternative name field. + * The subject alternative name field contains a list of various types of names, one type is RFC822 e-mail + * address. This will return the first e-mail address that is found (if there are more than one). + */ + RFC822_EMAIL, + /** + * Create principal by serial no. + * Resolve the principal by the serial number with a configurable radix, ranging from 2 to 36. + * If {@code radix} is {@code 16}, then the serial number could be filled with leading zeros to even the number of + * digits. + */ + SERIAL_NO, + /** + * Create principal by serial no and DN. + */ + SERIAL_NO_DN, + /** + * Create principal by subject. + * Resolve the principal by extracting one or more attribute values from the + * certificate subject DN and combining them with intervening delimiters. + */ + SUBJECT, + /** + * Create principal by subject alternative name. + * Resolve the principal by the subject alternative name extension. (type: otherName) + */ + SUBJECT_ALT_NAME, + /** + * Create principal by subject DN. + */ + SUBJECT_DN; + +} diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509RevocationCheckerType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509RevocationCheckerType.java new file mode 100644 index 0000000000..cdf75f9416 --- /dev/null +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509RevocationCheckerType.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.syncope.common.lib.types; + +public enum X509RevocationCheckerType { + /** + * No revocation is performed. + */ + NONE, + /** + * The CRL URI(s) mentioned in the certificate cRLDistributionPoints extension field. + * + * Caches are available to prevent excessive IO against CRL endpoints. CRL data fetched if does not exist in the + * cache or if it is expired + */ + CRL, + /** + * A CRL hosted at a fixed location. The CRL is fetched at periodic intervals and cached. + */ + RESOURCE; + +} diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509RevocationFetcherType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509RevocationFetcherType.java new file mode 100644 index 0000000000..f0713ac6e5 --- /dev/null +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509RevocationFetcherType.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.syncope.common.lib.types; + +public enum X509RevocationFetcherType { + /** + * All revocation checks use fixed resources to fetch the CRL resource from the specified location. + */ + RESOURCE, + /** + * A CRL resource may be fetched from a pre-configured attribute, in the event that the CRL resource location is an + * LDAP URI. + */ + LDAP; + +} diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509SubjectDnFormat.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509SubjectDnFormat.java new file mode 100644 index 0000000000..50b631f43d --- /dev/null +++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/X509SubjectDnFormat.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.syncope.common.lib.types; + +public enum X509SubjectDnFormat { + /** + * Denigrated result of calling certificate.getSubjectDN() method. + * Javadocs designate this method as "denigrated" for not being portable and/or not being well defined. + * It is what has been used by CAS for a long time so it remains the default. + */ + DEFAULT, + /** + * RFC 1779 String format of Distinguished Names. + * Calls {@code X500Principal.getName("RFC1779")} which emits a subject DN with the attribute keywords defined + * in RFC 1779 (CN, L, ST, O, OU, C, STREET). Any other attribute type is emitted as an OID. + */ + RFC1779, + /** + * RFC 2253 String format of Distinguished Names. + * Calls {@code X500Principal.getName("RFC2253")} which emits a subject DN with the attribute keywords defined in + * RFC 2253 (CN, L, ST, O, OU, C, STREET, DC, UID). Any other attribute type is emitted as an OID. + */ + RFC2253, + /** + * Canonical String format of Distinguished Names. + * Calls X500Principal.getName("CANONICAL" which emits a subject DN that starts with RFC 2253 and applies + * additional canonicalizations described in the javadoc. + */ + CANONICAL; + +} diff --git a/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc b/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc index becac0352f..319a2ac3ae 100644 --- a/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc +++ b/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc @@ -27,12 +27,15 @@ Several authentication modules are provided: ** https://apereo.github.io/cas/6.6.x/authentication/Database-Authentication.html[Database^] ** https://apereo.github.io/cas/6.6.x/authentication/JAAS-Authentication.html[JAAS^] ** https://apereo.github.io/cas/6.6.x/authentication/LDAP-Authentication.html[LDAP^] - ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication.html[OpenID Connect^] - ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication.html[OAuth2^] - ** https://apereo.github.io/cas/6.6.x/authentication/Syncope-Authentication.html[Static^] + ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-Generic-OpenID-Connect.html[OpenID Connect^] + ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-OAuth20.html[OAuth2^] ** https://apereo.github.io/cas/6.6.x/authentication/Syncope-Authentication.html[Syncope^] - ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication.html[SAML^] + ** https://apereo.github.io/cas/6.6.x/authentication/X509-Authentication.html[X509^] + ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-SAML.htmll[SAML^] + ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-Apple.html[Apple Signin^] ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-Azure-AD.html[Azure Active Directory^] + ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-Google-OpenID-Connect.html[Google OpenID^] + ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-Keycloak.html[Keycloak^] * MFA: ** https://apereo.github.io/cas/6.6.x/mfa/DuoSecurity-Authentication.html[Duo Security^] ** https://apereo.github.io/cas/6.6.x/mfa/GoogleAuthenticator-Authentication.html[Google Authenticator^] diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java index dcd300afc6..7431fcefe9 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java @@ -41,6 +41,7 @@ import org.apache.syncope.common.lib.auth.SimpleMfaAuthModuleConf; import org.apache.syncope.common.lib.auth.StaticAuthModuleConf; import org.apache.syncope.common.lib.auth.SyncopeAuthModuleConf; +import org.apache.syncope.common.lib.auth.X509AuthModuleConf; import org.apache.syncope.common.lib.to.AuthModuleTO; import org.apache.syncope.common.lib.to.Item; import org.apache.syncope.common.lib.types.AuthModuleState; @@ -66,6 +67,10 @@ import org.apereo.cas.configuration.model.support.pac4j.oidc.Pac4jOidcClientProperties; import org.apereo.cas.configuration.model.support.pac4j.saml.Pac4jSamlClientProperties; import org.apereo.cas.configuration.model.support.syncope.SyncopeAuthenticationProperties; +import org.apereo.cas.configuration.model.support.x509.SubjectDnPrincipalResolverProperties.SubjectDnFormat; +import org.apereo.cas.configuration.model.support.x509.X509LdapProperties; +import org.apereo.cas.configuration.model.support.x509.X509Properties; +import org.apereo.cas.configuration.model.support.x509.X509Properties.PrincipalTypes; import org.apereo.cas.util.ResourceUtils; import org.apereo.cas.util.model.TriStateBoolean; @@ -288,6 +293,75 @@ public Map map(final AuthModuleTO authModuleTO, final SAML2IdPAu return prefix("cas.authn.pac4j.saml[].", WAConfUtils.asMap(props)); } + @Override + public Map map(final AuthModuleTO authModuleTO, final X509AuthModuleConf conf) { + X509Properties props = new X509Properties(); + props.setName(conf.getName()); + props.setOrder(conf.getOrder()); + props.setCacheMaxElementsInMemory(conf.getCacheMaxElementsInMemory()); + props.setCacheTimeToLiveSeconds(conf.getCacheTimeToLiveSeconds()); + props.setCheckAll(conf.isCheckAll()); + props.setCheckKeyUsage(conf.isCheckKeyUsage()); + props.setCrlExpiredPolicy(conf.getCrlExpiredPolicy().name()); + props.setCrlFetcher(conf.getCrlFetcher().name()); + props.setCrlResourceExpiredPolicy(conf.getCrlResourceExpiredPolicy().name()); + props.setCrlResourceUnavailablePolicy(conf.getCrlResourceUnavailablePolicy().name()); + props.setCrlResources(conf.getCrlResources()); + props.setCrlUnavailablePolicy(conf.getCrlUnavailablePolicy().name()); + props.setExtractCert(conf.isExtractCert()); + props.setMaxPathLength(conf.getMaxPathLength()); + props.setMaxPathLengthAllowUnspecified(conf.isMaxPathLengthAllowUnspecified()); + props.setMixedMode(conf.isMixedMode()); + props.setRefreshIntervalSeconds(conf.getRefreshIntervalSeconds()); + props.setRegExSubjectDnPattern(conf.getRegExSubjectDnPattern()); + props.setRegExTrustedIssuerDnPattern(conf.getRegExTrustedIssuerDnPattern()); + props.setRequireKeyUsage(conf.isRequireKeyUsage()); + props.setRevocationChecker(conf.getRevocationChecker().name()); + props.setRevocationPolicyThreshold(conf.getRevocationPolicyThreshold()); + props.setSslHeaderName(conf.getSslHeaderName()); + props.setThrowOnFetchFailure(conf.isThrowOnFetchFailure()); + + props.setPrincipalType(PrincipalTypes.valueOf(conf.getPrincipalType().name())); + if (StringUtils.isNotBlank(conf.getPrincipalAlternateAttribute())) { + switch (props.getPrincipalType()) { + case CN_EDIPI: + props.getCnEdipi().setAlternatePrincipalAttribute(conf.getPrincipalAlternateAttribute()); + break; + + case RFC822_EMAIL: + props.getRfc822Email().setAlternatePrincipalAttribute(conf.getPrincipalAlternateAttribute()); + break; + + case SUBJECT: + props.setPrincipalDescriptor(conf.getPrincipalAlternateAttribute()); + break; + + case SUBJECT_ALT_NAME: + props.getSubjectAltName().setAlternatePrincipalAttribute(conf.getPrincipalAlternateAttribute()); + break; + + case SUBJECT_DN: + case SERIAL_NO_DN: + case SERIAL_NO: + default: + } + } + props.getSubjectDn().setFormat(SubjectDnFormat.valueOf(conf.getPrincipalTypeSubjectDnFormat().name())); + props.getSerialNoDn().setSerialNumberPrefix(conf.getPrincipalTypeSerialNoDnSerialNumberPrefix()); + props.getSerialNoDn().setValueDelimiter(conf.getPrincipalTypeSerialNoDnValueDelimiter()); + props.getSerialNo().setPrincipalHexSNZeroPadding(conf.isPrincipalTypeSerialNoHexSNZeroPadding()); + props.getSerialNo().setPrincipalSNRadix(conf.getPrincipalTypeSerialNoSNRadix()); + + if (conf.getLdap() != null) { + X509LdapProperties ldapProps = new X509LdapProperties(); + ldapProps.setCertificateAttribute(conf.getLdap().getCertificateAttribute()); + fill(ldapProps, conf.getLdap()); + props.setLdap(ldapProps); + } + + return prefix("cas.authn.x509.", WAConfUtils.asMap(props)); + } + @Override public Map map(final AuthModuleTO authModuleTO, final SyncopeAuthModuleConf conf) { SyncopeClient syncopeClient = waRestClient.getSyncopeClient(); diff --git a/wa/starter/pom.xml b/wa/starter/pom.xml index e98faae7e4..0ccb677695 100644 --- a/wa/starter/pom.xml +++ b/wa/starter/pom.xml @@ -353,6 +353,10 @@ under the License. org.apereo.cas cas-server-support-passwordless-api + + org.apereo.cas + cas-server-support-x509-webflow + org.springframework.boot