package org.apereo.cas.adaptors.x509.authentication.handler.support;
import org.apereo.cas.adaptors.x509.authentication.ExpiredCRLException;
import org.apereo.cas.adaptors.x509.authentication.principal.X509CertificateCredential;
import org.apereo.cas.adaptors.x509.authentication.revocation.RevokedCertificateException;
import org.apereo.cas.adaptors.x509.authentication.revocation.checker.ResourceCRLRevocationChecker;
import org.apereo.cas.adaptors.x509.authentication.revocation.policy.ThresholdExpiredCRLRevocationPolicy;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.DefaultHandlerResult;
import org.apereo.cas.authentication.HandlerResult;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.principal.DefaultPrincipalFactory;
import org.apereo.cas.util.RegexUtils;
import org.cryptacular.util.CertUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.core.io.ClassPathResource;
import javax.security.auth.login.FailedLoginException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.X509Certificate;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import static org.junit.Assert.*;
/**
* Unit test for {@link X509CredentialsAuthenticationHandler} class.
*
* @author Scott Battaglia
* @author Marvin S. Addison
* @since 3.0.0
*/
@RunWith(Parameterized.class)
public class X509CredentialsAuthenticationHandlerTests {
private static final String USER_VALID_CRT = "user-valid.crt";
/**
* Subject of test.
*/
private final X509CredentialsAuthenticationHandler handler;
/**
* Test authentication credential.
*/
private final Credential credential;
/**
* Expected result of supports test.
*/
private final boolean expectedSupports;
/**
* Expected authentication result.
*/
private final Object expectedResult;
/**
* Creates a new test class instance with the given parameters.
*
* @param handler Test authentication handler.
* @param credential Test credential.
* @param supports Expected result of supports test.
* @param result Expected result of authentication test.
*/
public X509CredentialsAuthenticationHandlerTests(final X509CredentialsAuthenticationHandler handler, final Credential credential, final boolean supports,
final Object result) {
this.handler = handler;
this.credential = credential;
this.expectedSupports = supports;
this.expectedResult = result;
}
/**
* Gets the unit test parameters.
*
* @return Test parameter data.
* @throws Exception On test data setup errors.
*/
@Parameters
public static Collection<Object[]> getTestParameters() throws Exception {
final Collection<Object[]> params = new ArrayList<>();
X509CredentialsAuthenticationHandler handler;
X509CertificateCredential credential;
// Test case #1: Unsupported credential type
handler = new X509CredentialsAuthenticationHandler(RegexUtils.createPattern(".*"));
params.add(new Object[]{handler, new UsernamePasswordCredential(), false, null});
// Test case #2:Valid certificate
handler = new X509CredentialsAuthenticationHandler(RegexUtils.createPattern(".*"));
credential = new X509CertificateCredential(createCertificates(USER_VALID_CRT));
params.add(new Object[]{handler, credential, true, new DefaultHandlerResult(handler, credential,
new DefaultPrincipalFactory().createPrincipal(credential.getId())),
});
// Test case #3: Expired certificate
handler = new X509CredentialsAuthenticationHandler(RegexUtils.createPattern(".*"));
params.add(new Object[]{
handler,
new X509CertificateCredential(createCertificates("user-expired.crt")),
true,
new CertificateExpiredException(),
});
// Test case #4: Untrusted issuer
handler = new X509CredentialsAuthenticationHandler(
RegexUtils.createPattern("CN=\\w+,OU=CAS,O=Jasig,L=Westminster,ST=Colorado,C=US"),
true, false, false);
params.add(new Object[]{handler, new X509CertificateCredential(createCertificates("snake-oil.crt")),
true, new FailedLoginException(),
});
// Test case #5: Disallowed subject
handler = new X509CredentialsAuthenticationHandler(RegexUtils.createPattern(".*"),
true,
RegexUtils.createPattern("CN=\\w+,OU=CAS,O=Jasig,L=Westminster,ST=Colorado,C=US"));
params.add(new Object[]{
handler,
new X509CertificateCredential(createCertificates("snake-oil.crt")),
true,
new FailedLoginException(),
});
// Test case #6: Check key usage on a cert without keyUsage extension
handler = new X509CredentialsAuthenticationHandler(RegexUtils.createPattern(".*"),
false, true, false);
credential = new X509CertificateCredential(createCertificates(USER_VALID_CRT));
params.add(new Object[]{
handler,
credential,
true,
new DefaultHandlerResult(handler, credential, new DefaultPrincipalFactory().createPrincipal(credential.getId())),
});
// Test case #7: Require key usage on a cert without keyUsage extension
handler = new X509CredentialsAuthenticationHandler(RegexUtils.createPattern(".*"),
false, true, true);
params.add(new Object[]{
handler,
new X509CertificateCredential(createCertificates(USER_VALID_CRT)),
true, new FailedLoginException(),
});
// Test case #8: Require key usage on a cert with acceptable keyUsage extension values
handler = new X509CredentialsAuthenticationHandler(RegexUtils.createPattern(".*"),
false, true, true);
credential = new X509CertificateCredential(createCertificates("user-valid-keyUsage.crt"));
params.add(new Object[]{
handler,
credential,
true,
new DefaultHandlerResult(handler, credential, new DefaultPrincipalFactory().createPrincipal(credential.getId())),
});
// Test case #9: Require key usage on a cert with unacceptable keyUsage extension values
handler = new X509CredentialsAuthenticationHandler(RegexUtils.createPattern(".*"),
false, true, true);
params.add(new Object[]{
handler,
new X509CertificateCredential(createCertificates("user-invalid-keyUsage.crt")),
true,
new FailedLoginException(),
});
//===================================
// Revocation tests
//===================================
ResourceCRLRevocationChecker checker;
// Test case #10: Valid certificate with CRL checking
checker = new ResourceCRLRevocationChecker(new ClassPathResource("userCA-valid.crl"));
checker.init();
handler = new X509CredentialsAuthenticationHandler(RegexUtils.createPattern(".*"), checker);
credential = new X509CertificateCredential(createCertificates(USER_VALID_CRT));
params.add(new Object[]{
handler,
new X509CertificateCredential(createCertificates(USER_VALID_CRT)),
true,
new DefaultHandlerResult(handler, credential, new DefaultPrincipalFactory().createPrincipal(credential.getId())),
});
// Test case #11: Revoked end user certificate
checker = new ResourceCRLRevocationChecker(new ClassPathResource("userCA-valid.crl"));
checker.init();
handler = new X509CredentialsAuthenticationHandler(RegexUtils.createPattern(".*"), checker);
params.add(new Object[]{
handler,
new X509CertificateCredential(createCertificates("user-revoked.crt")),
true,
new RevokedCertificateException(ZonedDateTime.now(ZoneOffset.UTC), null),
});
// Test case #12: Valid certificate on expired CRL data
final ThresholdExpiredCRLRevocationPolicy zeroThresholdPolicy = new ThresholdExpiredCRLRevocationPolicy(0);
checker = new ResourceCRLRevocationChecker(new ClassPathResource("userCA-expired.crl"), null, zeroThresholdPolicy);
checker.init();
handler = new X509CredentialsAuthenticationHandler(RegexUtils.createPattern(".*"), checker);
params.add(new Object[]{
handler,
new X509CertificateCredential(createCertificates(USER_VALID_CRT)),
true,
new ExpiredCRLException(null, ZonedDateTime.now(ZoneOffset.UTC)),
});
return params;
}
/**
* Tests the {@link X509CredentialsAuthenticationHandler#authenticate(Credential)} method.
*/
@Test
public void verifyAuthenticate() {
try {
if (this.handler.supports(this.credential)) {
final HandlerResult result = this.handler.authenticate(this.credential);
if (this.expectedResult instanceof DefaultHandlerResult) {
assertEquals(this.expectedResult, result);
} else {
fail("Authentication succeeded when it should have failed with " + this.expectedResult);
}
}
} catch (final Exception e) {
if (this.expectedResult instanceof Exception) {
assertEquals(this.expectedResult.getClass(), e.getClass());
} else {
fail("Authentication failed when it should have succeeded: " + e.getMessage());
}
}
}
/**
* Tests the {@link X509CredentialsAuthenticationHandler#supports(Credential)} method.
*/
@Test
public void verifySupports() {
assertEquals(this.expectedSupports, this.handler.supports(this.credential));
}
protected static X509Certificate[] createCertificates(final String... files) {
final X509Certificate[] certs = new X509Certificate[files.length];
int i = 0;
for (final String file : files) {
try {
certs[i++] = CertUtil.readCertificate(new ClassPathResource(file).getInputStream());
} catch (final Exception e) {
throw new RuntimeException("Error creating certificate at " + file, e);
}
}
return certs;
}
}