Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions demo/src/main/resources/oiosaml.properties
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ oiosaml.servlet.idp.metadata.file=test-devtest4-idp-metadata.xml
oiosaml.servlet.revocation.crl.check.enabled=false
oiosaml.servlet.revocation.ocsp.check.enabled=false

# Send OCSP requests using POST instead of the RFC 5019 GET form (default true). The NemLog-in OCSP
# responders answer HTTP 404 to the GET form used by JDK 12+, so only disable this if the responders
# in use handle GET and the JVM wide setting of 'com.sun.security.ocsp.useget' is unwanted.
#oiosaml.servlet.revocation.ocsp.post.enabled=true

# NemLogin-in2 does not return OIOSAML 3 profile valid assertions
oiosaml.servlet.profile.validation.enabled=true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public class Configuration {
// Revocation check settings
private boolean crlCheckEnabled = true;
private boolean ocspCheckEnabled = true;
private boolean ocspPostEnabled = true;

// AppSwitch return URL settings
private String appSwitchReturnURLForAndroid;
Expand Down Expand Up @@ -327,6 +328,14 @@ public void setOcspCheckEnabled(boolean ocspCheckEnabled) {
this.ocspCheckEnabled = ocspCheckEnabled;
}

public boolean isOcspPostEnabled() {
return ocspPostEnabled;
}

public void setOcspPostEnabled(boolean ocspPostEnabled) {
this.ocspPostEnabled = ocspPostEnabled;
}

public String getAuditLoggerClassName() {
return this.auditLoggerClassName;
}
Expand Down
62 changes: 54 additions & 8 deletions oiosaml/src/main/java/dk/gov/oio/saml/service/CRLChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,26 @@

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.CRLException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.PKIXRevocationChecker;
import java.security.cert.TrustAnchor;
import java.security.cert.X509CRL;
import java.security.cert.X509CRLEntry;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
Expand Down Expand Up @@ -51,8 +54,42 @@
public class CRLChecker {
private static final Logger log = LoggerFactory.getLogger(CRLChecker.class);
private static final String AUTH_INFO_ACCESS = Extension.authorityInfoAccess.getId();

// JDK system property deciding whether the built-in OCSP client may use the RFC 5019 GET form
// (request base64-encoded into the URL path) for small requests, see sun.security.provider.certpath.OCSP
private static final String JDK_OCSP_USE_GET_PROPERTY = "com.sun.security.ocsp.useget";

private static Map<String, X509Certificate> certificateMap = new HashMap<String, X509Certificate>();


/**
* Make the JDK OCSP client POST its requests instead of using the RFC 5019 GET form.
*
* JDK 12 and later default to GET for requests that fit in 255 characters, but the NemLog-in OCSP
* responders answer HTTP 404 to that form. The resulting UNDETERMINED_REVOCATION_STATUS makes every
* OCSP check fail, which silently degrades the SP to the CRL fallback, or drops the IdP certificates
* entirely when CRL checking is disabled as well. POST is mandatory for responders (RFC 6960), so
* forcing it is safe. Java 8 and 11 always POST and are unaffected.
*
* The JDK reads the property once, when sun.security.provider.certpath.OCSP is initialized, so this
* must run before the first OCSP check in the JVM, hence the call from {@link OIOSAML3Service#init}.
* An explicit setting made by the deployer (-Dcom.sun.security.ocsp.useget=...) always wins.
*/
static void configureOcspTransport(Configuration configuration) {
if (!configuration.isOcspPostEnabled()) {
log.info("Leaving '{}' untouched, OCSP POST is disabled in the OIOSAML configuration", JDK_OCSP_USE_GET_PROPERTY);
return;
}

String existingValue = System.getProperty(JDK_OCSP_USE_GET_PROPERTY);
if (existingValue != null) {
log.info("Leaving '{}' at the value set by the deployer: {}", JDK_OCSP_USE_GET_PROPERTY, existingValue);
return;
}

System.setProperty(JDK_OCSP_USE_GET_PROPERTY, "false");
log.info("Setting '{}' to false, so OCSP requests are sent using POST", JDK_OCSP_USE_GET_PROPERTY);
}

public static Set<X509Certificate> checkCertificates(List<X509Certificate> x509Certificates, DateTime lastCRLCheck) throws ExternalException, InternalException, InitializationException {
Set<X509Certificate> result = new HashSet<>();
if (x509Certificates == null || x509Certificates.size() == 0) {
Expand Down Expand Up @@ -111,7 +148,7 @@ else if (config.isCRLCheckEnabled()) {
return validated;
}

private static boolean doOCSPCheck(X509Certificate certificate) throws CertificateException, CertPathValidatorException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
private static boolean doOCSPCheck(X509Certificate certificate) throws CertificateException, CertPathValidatorException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, URISyntaxException {
log.debug("Starting OCSP validation of certificate {}", certificate.getSubjectDN());

String ocspServer = getOCSPUrl(certificate);
Expand All @@ -131,17 +168,26 @@ private static boolean doOCSPCheck(X509Certificate certificate) throws Certifica
CertificateFactory cf = CertificateFactory.getInstance("X.509");
CertPath cp = cf.generateCertPath(certList);

Security.setProperty("ocsp.enable", "true");
Security.setProperty("ocsp.responderURL", ocspServer);

boolean revoked;
try {
TrustAnchor anchor = new TrustAnchor(issuer, null);
PKIXParameters params = new PKIXParameters(Collections.singleton(anchor));
params.setRevocationEnabled(true);

// Validate and obtain results
CertPathValidator cpv = CertPathValidator.getInstance("PKIX");

// Configure revocation checking on this validation only. The alternative, the JVM global
// "ocsp.enable"/"ocsp.responderURL" security properties, leaks the responder of the certificate
// being checked into every other PKIX validation in the JVM, and races with concurrent checks.
PKIXRevocationChecker revocationChecker = (PKIXRevocationChecker) cpv.getRevocationChecker();
revocationChecker.setOcspResponder(new URI(ocspServer));

// Fallback to CRL is handled by checkCertificate, according to the OIOSAML configuration
revocationChecker.setOptions(EnumSet.of(PKIXRevocationChecker.Option.NO_FALLBACK));

params.setRevocationEnabled(false);
params.addCertPathChecker(revocationChecker);

// Validate and obtain results
cpv.validate(cp, params);

log.debug("Certificate successfully validated during OCSP check.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public static void init(Configuration configuration) throws InitializationExcept
OIOSAML3Service.sessionHandlerFactory = new InternalSessionHandlerFactory();
OIOSAML3Service.sessionHandlerFactory.configure(configuration);

// Must happen before the first OCSP check is made in this JVM
CRLChecker.configureOcspTransport(configuration);

initialized = true;
} catch (Exception exception) {
log.error("Unable to initialize OIOSAML",exception);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ private void handleOptionalValues(Map<String, String> config, Configuration conf
configuration.setOcspCheckEnabled("true".equals(value));
}

value = config.get(Constants.OCSP_POST_ENABLED);
if (StringUtil.isNotEmpty(value)) {
configuration.setOcspPostEnabled("true".equals(value));
}

value = config.get(Constants.METADATA_NAMEID_FORMAT);
if (StringUtil.isNotEmpty(value)) {
configuration.setNameIDFormat(value);
Expand Down
1 change: 1 addition & 0 deletions oiosaml/src/main/java/dk/gov/oio/saml/util/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class Constants {
// Configuration constants for revocation check settings
public static final String CRL_CHECK_ENABLED = "oiosaml.servlet.revocation.crl.check.enabled";
public static final String OCSP_CHECK_ENABLED = "oiosaml.servlet.revocation.ocsp.check.enabled";
public static final String OCSP_POST_ENABLED = "oiosaml.servlet.revocation.ocsp.post.enabled";

// Configuration constants for AuthenticationFilter
public static final String IS_PASSIVE = "oiosaml.filter.ispassive.enabled";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import dk.gov.oio.saml.util.TestConstants;
Expand Down Expand Up @@ -51,6 +52,10 @@ public class CRLCheckerTest extends BaseServiceTest {
Assertions.assertEquals(1, validCertificates.size());
}

// Integration test: depends on the live NemLog-in test CA revocation infrastructure
// (the revoked certificate's real OCSP/CRL status). Tagged so it can be excluded from
// offline runs if needed, but it runs by default.
@Tag("integration")
@DisplayName("Test revocation check on revoked certificate using OCSP")
@Test public void testOcspCheckOnRevokedCertificate() throws Exception {
OIOSAML3Service.getConfig().setCRLCheckEnabled(false);
Expand All @@ -69,6 +74,10 @@ public class CRLCheckerTest extends BaseServiceTest {
Assertions.assertEquals(0, validCertificates.size());
}

// Integration test: depends on the live NemLog-in test CA revocation infrastructure
// (the revoked certificate's real OCSP/CRL status). Tagged so it can be excluded from
// offline runs if needed, but it runs by default.
@Tag("integration")
@DisplayName("Test revocation check on revoked certificate using CRL")
@Test public void testCrlCheckOnRevokedCertificate() throws Exception {
OIOSAML3Service.getConfig().setCRLCheckEnabled(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,5 @@ public class TestConstants {

public static final String VALID_CERTIFICATE = "MIIGkzCCBMegAwIBAgIUdxCsIBOOtB5tqNtriG1TMHD9dqYwQQYJKoZIhvcNAQEKMDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIDAgEgMGsxLTArBgNVBAMMJERlbiBEYW5za2UgU3RhdCBPQ0VTIHVkc3RlZGVuZGUtQ0EgMTETMBEGA1UECwwKVGVzdCAtIGN0aTEYMBYGA1UECgwPRGVuIERhbnNrZSBTdGF0MQswCQYDVQQGEwJESzAeFw0yMzA4MTgxMDI2NThaFw0yNjA4MTcxMDI2NTdaMIGpMSUwIwYDVQQDDBxqYXZhLnJlZmVyZW5jZWltcGxlbWVudGVyaW5nMTcwNQYDVQQFEy5VSTpESy1POkc6MmRjZjc5MTktYjI4Mi00NGQxLWI5ODAtM2I3MzcwMGE3ZGQ0MSEwHwYDVQQKDBhEaWdpdGFsaXNlcmluZ3NzdHlyZWxzZW4xFzAVBgNVBGEMDk5UUkRLLTM0MDUxMTc4MQswCQYDVQQGEwJESzCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKNAf9uAhuz3bEjPPFrBa39HCF6S64pSzGRr5yYm3lCBElYJvHzDr9lMKgbv8rKglIVgjWh+PzUjiwIlGjrqAbYa2Hg08Vw2H60GQSFP8rGsshgR+E5Ca2nb9kUcQXAQJl9ScG9squCPRNkdp8vSblRwv/3N0ksjxdZk1wdZ86bOqTsFEjpzhFdBXXSMl4tbhE7WOruKc0QqjUkzXJyp4qwyB2XA75+jsvtRHN/luOzCkUxLhEkFrbg+B6IWqjUuO132xC8d5+T8Y39K6rs4BYOIgQRJOg0OlA5844CC/WBLtAYgMiu1ucZ4mbVWOmm2F86WVRBdmwlN0CFORXihHiYNZfHpA0rPOSncDDMrrGZ7vuvvXxMfIiHlAniSw4eHaEqtaXqwDyNZbfcXgYkszQd7ZV7YMfAjkDo82Qn+Qz+Oc9qq0Syhd9pdUJ/Q26CjFDiaNSg+hDUUJTxowQAktX3AuwBcDeuMoc2yOGmg2xOf/3bIwJSNm8/b+y0wKfY2FwIDAQABo4IBhjCCAYIwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBR/KJ/ZcZlC4nXn1zV2Lk0IJW12XjB7BggrBgEFBQcBAQRvMG0wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYTEuY3RpLWdvdi5kay9vY2VzL2lzc3VpbmcvMS9jYWNlcnQvaXNzdWluZy5jZXIwJgYIKwYBBQUHMAGGGmh0dHA6Ly9jYTEuY3RpLWdvdi5kay9vY3NwMCEGA1UdIAQaMBgwCAYGBACPegEBMAwGCiqBUIEpAQEBAwcwOwYIKwYBBQUHAQMELzAtMCsGCCsGAQUFBwsCMB8GBwQAi+xJAQIwFIYSaHR0cHM6Ly91aWQuZ292LmRrMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jYTEuY3RpLWdvdi5kay9vY2VzL2lzc3VpbmcvMS9jcmwvaXNzdWluZy5jcmwwHQYDVR0OBBYEFNKFKxc70Coluez0ieqiVuK+a01oMA4GA1UdDwEB/wQEAwIFoDBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASADggGBAIkUbY8meqO9xQQ2gyMS4rfmqW3bV52YGs07DqG0zuVew7W7RMAJWqDLUj5ltMWK7wULcCBS1tjtxOrvMBCoAE42oQfF/EzLRYKr7VgsMyOgUiTk2t6LvyF5A1OGHOUP3lxQKX3viDURXUeoI4QZ3mxbHUg4sQXdXg2hOEhQOarOhWLdV3MzUkA9ZkwjmycXkbLBVdTbr/fODUU0jeDDlaixKXsGI66qg8Ou86nDkyW7wCxQ9QVwJ5YGogy9ZSc6sLt8XSv3+wFlXD/81EzWfqe5BdWX8cukLtSzdzg3SzJifB4IJ6GIQ58+NVLPEMezwZCLODzVkvdJfyWRxJrDijSVCza515qNW52yfYPYkTb+vdvKcFmwO1gCeK0vT21udVkp1grhNzwb8Cj/tq3OZ+IamZXkjL1go9GzSQQ31IbXHEI/oaPLEeX6j9E8X69wVtSti8SWPw0WgoeOglJM5A6fmlJIGhCBPk2klhH3IIU3+tjuz7iyFHZg7gbPhNHdow==";

public static final String REVOKED_CERTIFICATE = "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";
public static final String REVOKED_CERTIFICATE = "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";
}
Loading