package org.apereo.cas.adaptors.x509.config; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.adaptors.x509.authentication.CRLFetcher; import org.apereo.cas.adaptors.x509.authentication.ResourceCRLFetcher; import org.apereo.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler; import org.apereo.cas.adaptors.x509.authentication.ldap.LdaptiveResourceCRLFetcher; import org.apereo.cas.adaptors.x509.authentication.principal.X509SerialNumberPrincipalResolver; import org.apereo.cas.adaptors.x509.authentication.principal.X509SubjectAlternativeNameUPNPrincipalResolver; import org.apereo.cas.adaptors.x509.authentication.principal.X509SubjectDNPrincipalResolver; import org.apereo.cas.adaptors.x509.authentication.principal.X509SubjectPrincipalResolver; import org.apereo.cas.adaptors.x509.authentication.principal.X509SerialNumberAndIssuerDNPrincipalResolver; import org.apereo.cas.adaptors.x509.authentication.revocation.checker.CRLDistributionPointRevocationChecker; import org.apereo.cas.adaptors.x509.authentication.revocation.checker.NoOpRevocationChecker; import org.apereo.cas.adaptors.x509.authentication.revocation.checker.ResourceCRLRevocationChecker; import org.apereo.cas.adaptors.x509.authentication.revocation.checker.RevocationChecker; import org.apereo.cas.adaptors.x509.authentication.revocation.policy.AllowRevocationPolicy; import org.apereo.cas.adaptors.x509.authentication.revocation.policy.DenyRevocationPolicy; import org.apereo.cas.adaptors.x509.authentication.revocation.policy.RevocationPolicy; import org.apereo.cas.adaptors.x509.authentication.revocation.policy.ThresholdExpiredCRLRevocationPolicy; import org.apereo.cas.authentication.AuthenticationEventExecutionPlan; import org.apereo.cas.authentication.AuthenticationHandler; import org.apereo.cas.authentication.principal.DefaultPrincipalFactory; import org.apereo.cas.authentication.principal.PrincipalFactory; import org.apereo.cas.authentication.principal.PrincipalResolver; import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.configuration.model.support.x509.X509Properties; import org.apereo.cas.configuration.support.Beans; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.util.RegexUtils; import org.apereo.services.persondir.IPersonAttributeDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; import net.sf.ehcache.Cache; /** * This is {@link X509AuthenticationConfiguration}. * * @author Misagh Moayyed * @since 5.0.0 */ @Configuration("x509AuthenticationConfiguration") @EnableConfigurationProperties(CasConfigurationProperties.class) public class X509AuthenticationConfiguration { private static final int HEX = 16; @Autowired private ResourceLoader resourceLoader; @Autowired @Qualifier("attributeRepository") private IPersonAttributeDao attributeRepository; @Autowired @Qualifier("servicesManager") private ServicesManager servicesManager; @Autowired private CasConfigurationProperties casProperties; @Bean public RevocationPolicy allowRevocationPolicy() { return new AllowRevocationPolicy(); } @Bean @RefreshScope public RevocationPolicy thresholdExpiredCRLRevocationPolicy() { return new ThresholdExpiredCRLRevocationPolicy(casProperties.getAuthn().getX509().getRevocationPolicyThreshold()); } @Bean public RevocationPolicy denyRevocationPolicy() { return new DenyRevocationPolicy(); } @Bean public RevocationChecker crlDistributionPointRevocationChecker() { final X509Properties x509 = casProperties.getAuthn().getX509(); final Cache cache = new Cache("CRL".concat(UUID.randomUUID().toString()), x509.getCacheMaxElementsInMemory(), x509.isCacheDiskOverflow(), x509.isCacheEternal(), x509.getCacheTimeToLiveSeconds(), x509.getCacheTimeToIdleSeconds()); return new CRLDistributionPointRevocationChecker( x509.isCheckAll(), getRevocationPolicy(x509.getCrlUnavailablePolicy()), getRevocationPolicy(x509.getCrlExpiredPolicy()), cache, crlFetcher(), x509.isThrowOnFetchFailure()); } @Bean public RevocationChecker noOpRevocationChecker() { return new NoOpRevocationChecker(); } @Bean public CRLFetcher resourceCrlFetcher() { return new ResourceCRLFetcher(); } @Bean public RevocationChecker resourceCrlRevocationChecker() { final X509Properties x509 = casProperties.getAuthn().getX509(); final Set<Resource> x509CrlResources = x509.getCrlResources() .stream() .map(s -> this.resourceLoader.getResource(s)) .collect(Collectors.toSet()); return new ResourceCRLRevocationChecker( x509.isCheckAll(), getRevocationPolicy(x509.getCrlResourceUnavailablePolicy()), getRevocationPolicy(x509.getCrlResourceExpiredPolicy()), x509.getRefreshIntervalSeconds(), crlFetcher(), x509CrlResources); } private RevocationPolicy getRevocationPolicy(final String policy) { switch (policy.trim().toLowerCase()) { case "allow": return new AllowRevocationPolicy(); case "threshold": return thresholdExpiredCRLRevocationPolicy(); case "deny": default: return new DenyRevocationPolicy(); } } @Bean public CRLFetcher crlFetcher() { final X509Properties x509 = casProperties.getAuthn().getX509(); switch (x509.getCrlFetcher().toLowerCase()) { case "ldap": return ldaptiveResourceCRLFetcher(); case "resource": default: return resourceCrlFetcher(); } } @Bean @RefreshScope public AuthenticationHandler x509CredentialsAuthenticationHandler() { final X509Properties x509 = casProperties.getAuthn().getX509(); final RevocationChecker revChecker; switch (x509.getRevocationChecker().trim().toLowerCase()) { case "resource": revChecker = resourceCrlRevocationChecker(); break; case "crl": revChecker = crlDistributionPointRevocationChecker(); break; case "none": default: revChecker = noOpRevocationChecker(); break; } return new X509CredentialsAuthenticationHandler( x509.getName(), servicesManager, x509PrincipalFactory(), StringUtils.isNotBlank(x509.getRegExTrustedIssuerDnPattern()) ? RegexUtils.createPattern(x509.getRegExTrustedIssuerDnPattern()) : null, x509.getMaxPathLength(), x509.isMaxPathLengthAllowUnspecified(), x509.isCheckKeyUsage(), x509.isRequireKeyUsage(), StringUtils.isNotBlank(x509.getRegExSubjectDnPattern()) ? RegexUtils.createPattern(x509.getRegExSubjectDnPattern()) : null, revChecker); } @Bean public CRLFetcher ldaptiveResourceCRLFetcher() { final X509Properties x509 = casProperties.getAuthn().getX509(); return new LdaptiveResourceCRLFetcher(Beans.newLdaptiveConnectionConfig(x509.getLdap()), Beans.newLdaptiveSearchExecutor(x509.getLdap().getBaseDn(), x509.getLdap().getSearchFilter()), x509.getCertificateAttribute()); } @Bean @RefreshScope public PrincipalResolver x509SubjectPrincipalResolver() { final X509Properties x509 = casProperties.getAuthn().getX509(); final X509SubjectPrincipalResolver r = new X509SubjectPrincipalResolver(x509.getPrincipalDescriptor()); r.setAttributeRepository(attributeRepository); r.setPrincipalAttributeName(x509.getPrincipal().getPrincipalAttribute()); r.setReturnNullIfNoAttributes(x509.getPrincipal().isReturnNull()); r.setPrincipalFactory(x509PrincipalFactory()); return r; } @Bean @RefreshScope public PrincipalResolver x509SubjectDNPrincipalResolver() { final X509Properties x509 = casProperties.getAuthn().getX509(); final X509SubjectDNPrincipalResolver r = new X509SubjectDNPrincipalResolver(); r.setAttributeRepository(attributeRepository); r.setPrincipalAttributeName(x509.getPrincipal().getPrincipalAttribute()); r.setReturnNullIfNoAttributes(x509.getPrincipal().isReturnNull()); r.setPrincipalFactory(x509PrincipalFactory()); return r; } @Bean @RefreshScope public PrincipalResolver x509SubjectAlternativeNameUPNPrincipalResolver() { final X509Properties x509 = casProperties.getAuthn().getX509(); final X509SubjectAlternativeNameUPNPrincipalResolver r = new X509SubjectAlternativeNameUPNPrincipalResolver(); r.setAttributeRepository(attributeRepository); r.setPrincipalAttributeName(x509.getPrincipal().getPrincipalAttribute()); r.setReturnNullIfNoAttributes(x509.getPrincipal().isReturnNull()); r.setPrincipalFactory(x509PrincipalFactory()); return r; } @Bean @RefreshScope public PrincipalResolver x509SerialNumberPrincipalResolver() { final X509Properties x509 = casProperties.getAuthn().getX509(); final X509SerialNumberPrincipalResolver r; final int radix = x509.getPrincipalSNRadix(); if (Character.MIN_RADIX <= radix && radix <= Character.MAX_RADIX) { if (radix == HEX) { r = new X509SerialNumberPrincipalResolver(radix, x509.isPrincipalHexSNZeroPadding()); } else { r = new X509SerialNumberPrincipalResolver(radix, false); } } else { r = new X509SerialNumberPrincipalResolver(); } r.setAttributeRepository(attributeRepository); r.setPrincipalAttributeName(x509.getPrincipal().getPrincipalAttribute()); r.setReturnNullIfNoAttributes(x509.getPrincipal().isReturnNull()); r.setPrincipalFactory(x509PrincipalFactory()); return r; } @ConditionalOnMissingBean(name = "x509PrincipalFactory") @Bean public PrincipalFactory x509PrincipalFactory() { return new DefaultPrincipalFactory(); } @Bean @RefreshScope public PrincipalResolver x509SerialNumberAndIssuerDNPrincipalResolver() { final X509Properties x509 = casProperties.getAuthn().getX509(); return new X509SerialNumberAndIssuerDNPrincipalResolver(x509.getSerialNumberPrefix(), x509.getValueDelimiter()); } /** * The type X 509 authentication event execution plan configuration. */ @Configuration("x509AuthenticationEventExecutionPlanConfiguration") public class X509AuthenticationEventExecutionPlanConfiguration implements AuthenticationEventExecutionPlanConfigurer { @Autowired @Qualifier("personDirectoryPrincipalResolver") private PrincipalResolver personDirectoryPrincipalResolver; @Override public void configureAuthenticationExecutionPlan(final AuthenticationEventExecutionPlan plan) { PrincipalResolver resolver = personDirectoryPrincipalResolver; if (casProperties.getAuthn().getX509().getPrincipalType() != null) { switch (casProperties.getAuthn().getX509().getPrincipalType()) { case SERIAL_NO: resolver = x509SerialNumberPrincipalResolver(); break; case SERIAL_NO_DN: resolver = x509SerialNumberAndIssuerDNPrincipalResolver(); break; case SUBJECT: resolver = x509SubjectPrincipalResolver(); break; case SUBJECT_ALT_NAME: resolver = x509SubjectAlternativeNameUPNPrincipalResolver(); break; default: resolver = x509SubjectDNPrincipalResolver(); break; } } plan.registerAuthenticationHandlerWithPrincipalResolver(x509CredentialsAuthenticationHandler(), resolver); } } }