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; } }