diff --git a/auth/client/src/main/java/org/wildfly/security/auth/client/ElytronXmlParser.java b/auth/client/src/main/java/org/wildfly/security/auth/client/ElytronXmlParser.java index 967df3725b8..039344daf48 100644 --- a/auth/client/src/main/java/org/wildfly/security/auth/client/ElytronXmlParser.java +++ b/auth/client/src/main/java/org/wildfly/security/auth/client/ElytronXmlParser.java @@ -154,7 +154,8 @@ private enum Version { VERSION_1_4("urn:elytron:client:1.4", VERSION_1_3), VERSION_1_5("urn:elytron:client:1.5", VERSION_1_4), VERSION_1_6("urn:elytron:client:1.6", VERSION_1_5), - VERSION_1_7("urn:elytron:client:1.7", VERSION_1_6); + VERSION_1_7("urn:elytron:client:1.7", VERSION_1_6), + VERSION_1_8("urn:elytron:client:1.8", VERSION_1_7); final String namespace; @@ -465,6 +466,7 @@ private static void parseSslContextType(final ConfigurationXMLStreamReader reade ExceptionSupplier trustStoreSupplier = null; DeferredSupplier providersSupplier = new DeferredSupplier<>(providers); TrustManagerBuilder trustManagerBuilder = new TrustManagerBuilder(providersSupplier, location); + boolean acceptOcspStapling = false; while (reader.hasNext()) { final int tag = reader.nextTag(); @@ -536,6 +538,13 @@ private static void parseSslContextType(final ConfigurationXMLStreamReader reade parseCertificateRevocationLists(reader, trustManagerBuilder, xmlVersion); break; } + case "accept-ocsp-stapling": { + if (isSet(foundBits, 10)) throw reader.unexpectedElement(); + foundBits = setBit(foundBits, 10); + if (!xmlVersion.isAtLeast(Version.VERSION_1_8)) throw reader.unexpectedElement(); + acceptOcspStapling = parseOcspStaplingType(reader, trustManagerBuilder, xmlVersion, keyStoresMap); + break; + } default: throw reader.unexpectedElement(); } } else if (tag != END_ELEMENT) { @@ -549,6 +558,8 @@ private static void parseSslContextType(final ConfigurationXMLStreamReader reade final ExceptionSupplier finalKeyManagerSupplier = keyManagerSupplier; final ExceptionSupplier finalTrustStoreSupplier = trustStoreSupplier; final boolean initTrustManager = finalTrustStoreSupplier != null || isSet(foundBits, 7); + final boolean finalAcceptOcspStapling = acceptOcspStapling; + sslContextsMap.putIfAbsent(name, () -> { final SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); sslContextBuilder.setClientMode(true); @@ -574,6 +585,7 @@ private static void parseSslContextType(final ConfigurationXMLStreamReader reade sslContextBuilder.setProviderName(finalProviderName); sslContextBuilder.setProviderSupplier(finalProvidersSupplier); sslContextBuilder.setUseCipherSuitesOrder(true); + sslContextBuilder.setAcceptOCSPStapling(finalAcceptOcspStapling); return sslContextBuilder.build(); }); return; @@ -582,6 +594,56 @@ private static void parseSslContextType(final ConfigurationXMLStreamReader reade throw reader.unexpectedDocumentEnd(); } + private static boolean parseOcspStaplingType(ConfigurationXMLStreamReader reader, TrustManagerBuilder builder, Version xmlVersion, Map> keyStoresMap) throws ConfigXMLParseException { + final int attributeCount = reader.getAttributeCount(); + boolean acceptOcspStapling = false; + boolean softFail = false; + boolean gotResponderCertAlias = false; + boolean gotResponderKeystore = false; + + for (int i = 0; i < attributeCount; i ++) { + checkAttributeNamespace(reader, i); + switch (reader.getAttributeLocalName(i)) { + case "accept-ocsp": { + if (acceptOcspStapling) throw reader.unexpectedAttribute(i); + if (!xmlVersion.isAtLeast(Version.VERSION_1_8)) throw reader.unexpectedAttribute(i); + acceptOcspStapling = reader.getBooleanAttributeValueResolved(i); + builder.setOcspStapling(acceptOcspStapling); + break; + } + case "soft-fail": { + if (softFail) throw reader.unexpectedAttribute(i); + if (!xmlVersion.isAtLeast(Version.VERSION_1_8)) throw reader.unexpectedAttribute(i); + softFail = reader.getBooleanAttributeValueResolved(i); + builder.setSoftFail(softFail); + break; + } + case "responder-certificate": { + if (gotResponderCertAlias) throw reader.unexpectedAttribute(i); + builder.setOcspRescponderCertAlias(reader.getAttributeValueResolved(i)); + gotResponderCertAlias = true; + break; + } + case "responder-keystore": { + if (gotResponderKeystore) throw reader.unexpectedAttribute(i); + builder.setOcspResponderCertKeystoreSupplier(keyStoresMap.get(reader.getAttributeValueResolved(i))); + gotResponderKeystore = true; + break; + } + default: throw reader.unexpectedAttribute(i); + } + } + while (reader.hasNext()) { + final int tag = reader.nextTag(); + if (tag == END_ELEMENT) { + return acceptOcspStapling; + } else { + throw reader.unexpectedContent(); + } + } + throw reader.unexpectedDocumentEnd(); + } + private static class TrustManagerBuilder { final Supplier providers; final Location xmlLocation; @@ -592,6 +654,7 @@ private static class TrustManagerBuilder { List crlStreams = new ArrayList<>(); int maxCertPath = 5; boolean ocsp = false; + boolean ocspStapling = false; boolean preferCrls = false; boolean onlyLeafCert = false; boolean softFail = false; @@ -638,6 +701,9 @@ boolean isMaxCertPathSet() { public void setOcsp() { this.ocsp = true; } + public void setOcspStapling(boolean ocspStapling) { + this.ocspStapling = ocspStapling; + } public void setPreferCrls(boolean preferCrls) { this.preferCrls = preferCrls; @@ -697,6 +763,15 @@ X509TrustManager build() throws NoSuchAlgorithmException, KeyStoreException, Con revocationBuilder.setOcspResponderCert((X509Certificate) responderStore.getCertificate(responderCertAlias)); } + return revocationBuilder.build(); + } else if (ocspStapling) { + X509RevocationTrustManager.Builder revocationBuilder = X509RevocationTrustManager.builder(); + revocationBuilder.setTrustManagerFactory(trustManagerFactory); + revocationBuilder.setTrustStore(trustStore); + revocationBuilder.setCheckRevocation(true); + revocationBuilder.setSoftFail(softFail); + KeyStore responderStore = responderStoreSupplier != null ? responderStoreSupplier.get() : trustStore; + revocationBuilder.setOcspResponderCert((X509Certificate) responderStore.getCertificate(responderCertAlias)); return revocationBuilder.build(); } else { trustManagerFactory.init(trustStore); diff --git a/auth/client/src/main/resources/schema/elytron-client-1_8.xsd b/auth/client/src/main/resources/schema/elytron-client-1_8.xsd index 104fecbb268..2b70e7c7dca 100644 --- a/auth/client/src/main/resources/schema/elytron-client-1_8.xsd +++ b/auth/client/src/main/resources/schema/elytron-client-1_8.xsd @@ -19,8 +19,8 @@ --> @@ -100,6 +100,13 @@ + + + + Indicated whether the client will accept OCSP stapled responses from the server. + + + @@ -121,6 +128,38 @@ + + + + + Indicates whether the client accepts ocsp stapled requests from the server. + + + + + + + Determines the client's behaviour upon receiving a certificate with unknown + revocation status from the server. + + + + + + + Alias of OCSP Responder certificate. + + + + + + + Keystore for OCSP Responder certificate. trust-manager keystore is used by default and responder-certificate has to be defined. + + + + + diff --git a/auth/client/src/test/java/org/wildfly/security/auth/client/ElytronXmlParserTest.java b/auth/client/src/test/java/org/wildfly/security/auth/client/ElytronXmlParserTest.java index ac33fd96f04..f7c259d9816 100644 --- a/auth/client/src/test/java/org/wildfly/security/auth/client/ElytronXmlParserTest.java +++ b/auth/client/src/test/java/org/wildfly/security/auth/client/ElytronXmlParserTest.java @@ -194,6 +194,14 @@ public void testCipherSuites() throws Exception { checkSSLContext(authContext, "http://names-only.org"); } + @Test + public void checkSSLContextWithOCSPStapling() throws Exception { + URL config = getClass().getResource("test-wildfly-config-v1_8.xml"); + SecurityFactory authContext = ElytronXmlParser.parseAuthenticationClientConfiguration(config.toURI()); + Assert.assertNotNull(authContext); + Assert.assertNotNull(authContext.create().getSslRules()); + } + private void checkSSLContext(SecurityFactory authContext, String uri) throws Exception { RuleNode> node = authContext.create().sslRuleMatching(new URI(uri), null, null); Assert.assertNotNull(node); diff --git a/auth/client/src/test/resources/org/wildfly/security/auth/client/test-wildfly-config-v1_8.xml b/auth/client/src/test/resources/org/wildfly/security/auth/client/test-wildfly-config-v1_8.xml new file mode 100644 index 00000000000..78abfa5715e --- /dev/null +++ b/auth/client/src/test/resources/org/wildfly/security/auth/client/test-wildfly-config-v1_8.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ssl/src/main/java/org/wildfly/security/ssl/ElytronMessages.java b/ssl/src/main/java/org/wildfly/security/ssl/ElytronMessages.java index 3f9efdbf2e7..6febc9d084e 100644 --- a/ssl/src/main/java/org/wildfly/security/ssl/ElytronMessages.java +++ b/ssl/src/main/java/org/wildfly/security/ssl/ElytronMessages.java @@ -193,4 +193,7 @@ interface ElytronMessages extends BasicLogger { @Message(id = 15001, value = "No '%s' provided by the configured providers") NoSuchAlgorithmException noSslContextProvided(String type); + + @Message(id = 15002, value = "ResponderURI needs to be provided to override AIA extension.") + IllegalArgumentException responderURIRequired(); } diff --git a/ssl/src/main/java/org/wildfly/security/ssl/SSLContextBuilder.java b/ssl/src/main/java/org/wildfly/security/ssl/SSLContextBuilder.java index 5ca5e0e24be..88dff502139 100644 --- a/ssl/src/main/java/org/wildfly/security/ssl/SSLContextBuilder.java +++ b/ssl/src/main/java/org/wildfly/security/ssl/SSLContextBuilder.java @@ -77,6 +77,13 @@ public final class SSLContextBuilder { private String providerName; private boolean wrap = true; private MechanismConfigurationSelector mechanismConfigurationSelector; + private int responseTimeout; + private int cacheSize; + private int cacheLifetime; + private String responderURI; + private boolean responderOverride; + private boolean ignoreExtensions; + private boolean acceptOCSPStapling; /** * Set the security domain to use to authenticate clients. @@ -309,7 +316,82 @@ public SSLContextBuilder setMechanismConfigurationSelector(final MechanismConfig return this; } + /** + * Set the response timeout, which will be used for OCSP Stapling. + * + * @param responseTimeout controls the maximum amount of time the server will use to obtain OCSP responses, whether from the cache or by contacting an OCSP responder. + * @return this builder + */ + public SSLContextBuilder setResponseTimeout(final int responseTimeout) { + this.responseTimeout = responseTimeout; + return this; + } + + /** + * Set the cache size, which will be used for OCSP Stapling. + * + * @param cacheSize controls the maximum cache size in entries. + * @return this builder + */ + public SSLContextBuilder setCacheSize(final int cacheSize) { + this.cacheSize = cacheSize; + return this; + } + + /** + * Set the cache lifetime, which will be used for OCSP Stapling. + * + * @param cacheLifetime controls the maximum life of a cached response. + * @return this builder + */ + public SSLContextBuilder setCacheLifetime(final int cacheLifetime) { + this.cacheLifetime = cacheLifetime; + return this; + } + + /** + * Set the responder URI, which will be used for OCSP Stapling. + * + * @param responderURI enables the administrator to set a default URI in the event that certificates used for TLS do not have the Authority Info Access (AIA) extension. + * @return this builder + */ + public SSLContextBuilder setResponderURI(final String responderURI) { + this.responderURI = responderURI; + return this; + } + + /** + * Set the responder override property, which will be used for OCSP Stapling. + * + * @param responderOverride enables a URI provided through the jdk.tls.stapling.responderURI property to override any AIA extension value. + * @return this builder + */ + public SSLContextBuilder setResponderOverride(final boolean responderOverride) { + this.responderOverride = responderOverride; + return this; + } + + /** + * Set the ignore extensions property, which will be used for OCSP Stapling. + * + * @param ignoreExtensions disables the forwarding of OCSP extensions specified in the status_request or status_request_v2 TLS extensions. + * @return this builder + */ + public SSLContextBuilder setIgnoreExtensions(final boolean ignoreExtensions) { + this.ignoreExtensions = ignoreExtensions; + return this; + } + /** + * Indicates whether the client will accept OCSP Stapled responses from the server. + * + * @param acceptOCSPStapling will enable or disable client side OCSP stapling. + * @return this builder + */ + public SSLContextBuilder setAcceptOCSPStapling(final boolean acceptOCSPStapling) { + this.acceptOCSPStapling = acceptOCSPStapling; + return this; + } /** * Build a security factory for the new context. The factory will cache the constructed instance. @@ -331,10 +413,40 @@ public SecurityFactory build() { final boolean needClientAuth = this.needClientAuth; final boolean useCipherSuitesOrder = this.useCipherSuitesOrder; final boolean wrap = this.wrap; + final int responseTimeout = this.responseTimeout; + final int cacheSize = this.cacheSize; + final String responderURI = this.responderURI; + final boolean responderOverride = this.responderOverride; + final boolean ignoreExtensions = this.ignoreExtensions; + final boolean acceptOCSPStapling = this.acceptOCSPStapling; final MechanismConfigurationSelector mechanismConfigurationSelector = this.mechanismConfigurationSelector != null ? this.mechanismConfigurationSelector : MechanismConfigurationSelector.constantSelector(MechanismConfiguration.EMPTY); + if (clientMode && acceptOCSPStapling) { // client-ssl-context + // enable client side OCSP fall back + Security.setProperty("ocsp.enable", Boolean.TRUE.toString()); + + // Enable client side support for accepting OCSP stapling + System.setProperty("jdk.tls.client.enableStatusRequestExtension", Boolean.TRUE.toString()); + + } else if (!clientMode && checkOCSPStaplingEnabled()) { //server-ssl-context + // Enable server side support for OCSP stapling + System.setProperty("jdk.tls.server.enableStatusRequestExtension", Boolean.TRUE.toString()); + + // Set additional properties related to the server side OCSP Stapling + System.setProperty("jdk.tls.stapling.responseTimeout", String.valueOf(responseTimeout)); + System.setProperty("jdk.tls.stapling.cacheSize", String.valueOf(cacheSize)); + System.setProperty("jdk.tls.stapling.cacheLifetime", String.valueOf(cacheLifetime)); + if (responderURI != null) + System.setProperty("jdk.tls.stapling.responderURI", responderURI); + if (responderOverride && responderURI.isEmpty()) { + throw ElytronMessages.log.responderURIRequired(); + } + System.setProperty("jdk.tls.stapling.responderOverride", String.valueOf(responderOverride)); + System.setProperty("jdk.tls.stapling.ignoreExtensions", String.valueOf(ignoreExtensions)); + } + return new OneTimeSecurityFactory<>(() -> { final SecurityFactory sslContextFactory = SSLUtils.createSslContextFactory(protocolSelector, providerSupplier, providerName); // construct the original context @@ -359,10 +471,18 @@ public SecurityFactory build() { " wantClientAuth = %s%n" + " needClientAuth = %s%n" + " useCipherSuitesOrder = %s%n" + - " wrap = %s%n", + " wrap = %s%n + " + + " acceptOCSPStapling = %s%n + " + + " responseTimeout = %s%n + " + + " cacheSize = %s%n + " + + " cacheLifetime = %s%n + " + + " responderURI = %s%n + " + + " responderOverride = %s%n + " + + " ignoreExtensions = %s%n:", securityDomain, canAuthPeers, cipherSuiteSelector, protocolSelector, x509TrustManager, x509KeyManager, providerSupplier, clientMode, authenticationOptional, sessionCacheSize, - sessionTimeout, wantClientAuth, needClientAuth, useCipherSuitesOrder, wrap); + sessionTimeout, wantClientAuth, needClientAuth, useCipherSuitesOrder, wrap, acceptOCSPStapling, + responseTimeout, cacheSize, cacheLifetime, responderURI, responderOverride, ignoreExtensions); } sslContext.init(x509KeyManager == null ? null : new KeyManager[]{ @@ -387,4 +507,8 @@ public SecurityFactory build() { return new DelegatingSSLContext(contextSpi); }); } + + private boolean checkOCSPStaplingEnabled() { + return (this.responseTimeout > 0) && (this.cacheSize != 0) && (this.cacheLifetime != 0); + } } diff --git a/ssl/src/main/java/org/wildfly/security/ssl/X509RevocationTrustManager.java b/ssl/src/main/java/org/wildfly/security/ssl/X509RevocationTrustManager.java index 8e14fa93089..d62f06650a2 100644 --- a/ssl/src/main/java/org/wildfly/security/ssl/X509RevocationTrustManager.java +++ b/ssl/src/main/java/org/wildfly/security/ssl/X509RevocationTrustManager.java @@ -73,44 +73,60 @@ private X509RevocationTrustManager(Builder builder) { try { PKIXBuilderParameters params = new PKIXBuilderParameters(builder.trustStore, new X509CertSelector()); - if (builder.crlStreams != null && ! builder.crlStreams.isEmpty()) { - CertStoreParameters csp = new CollectionCertStoreParameters(getCRLs(builder.crlStreams)); - CertStore store = CertStore.getInstance("Collection", csp); - params.addCertStore(store); - } + if (builder.checkRevocation) { // for ocspStapling + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX"); + PKIXRevocationChecker rc = (PKIXRevocationChecker) cpb.getRevocationChecker(); - CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX"); - PKIXRevocationChecker rc = (PKIXRevocationChecker) cpb.getRevocationChecker(); + if (builder.softFail) + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.SOFT_FAIL)); - if (builder.ocspResponderCert != null) { - rc.setOcspResponderCert(builder.ocspResponderCert); - } + if (builder.ocspResponderCert != null) { + rc.setOcspResponderCert(builder.ocspResponderCert); + } - EnumSet options = EnumSet.noneOf(PKIXRevocationChecker.Option.class); - if (builder.onlyEndEntity) { - options.add(PKIXRevocationChecker.Option.ONLY_END_ENTITY); - } - if (builder.preferCrls) { - options.add(PKIXRevocationChecker.Option.PREFER_CRLS); - } - if (builder.softFail) { - options.add(PKIXRevocationChecker.Option.SOFT_FAIL); - } - if (builder.noFallback) { - options.add(PKIXRevocationChecker.Option.NO_FALLBACK); - } + params.addCertPathChecker(rc); + } else { // for other revocation checking - rc.setOptions(options); - rc.setOcspResponder(builder.responderUri); - params.setRevocationEnabled(true); - params.addCertPathChecker(rc); + if (builder.crlStreams != null && ! builder.crlStreams.isEmpty()) { + CertStoreParameters csp = new CollectionCertStoreParameters(getCRLs(builder.crlStreams)); + CertStore store = CertStore.getInstance("Collection", csp); + params.addCertStore(store); + } - PKIXCertPathChecker maxPathLengthChecker = new MaxPathLengthChecker(builder.maxCertPath); - params.addCertPathChecker(maxPathLengthChecker); - params.setMaxPathLength(builder.maxCertPath); + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX"); + PKIXRevocationChecker rc = (PKIXRevocationChecker) cpb.getRevocationChecker(); - builder.trustManagerFactory.init(new CertPathTrustManagerParameters(params)); + if (builder.ocspResponderCert != null) { + rc.setOcspResponderCert(builder.ocspResponderCert); + } + EnumSet options = EnumSet.noneOf(PKIXRevocationChecker.Option.class); + if (builder.onlyEndEntity) { + options.add(PKIXRevocationChecker.Option.ONLY_END_ENTITY); + } + if (builder.preferCrls) { + options.add(PKIXRevocationChecker.Option.PREFER_CRLS); + } + if (builder.softFail) { + options.add(PKIXRevocationChecker.Option.SOFT_FAIL); + } + if (builder.noFallback) { + options.add(PKIXRevocationChecker.Option.NO_FALLBACK); + } + + rc.setOptions(options); + if (builder.responderUri != null) { + rc.setOcspResponder(builder.responderUri); + } + params.setRevocationEnabled(true); + params.addCertPathChecker(rc); + + PKIXCertPathChecker maxPathLengthChecker = new MaxPathLengthChecker(builder.maxCertPath); + params.addCertPathChecker(maxPathLengthChecker); + params.setMaxPathLength(builder.maxCertPath); + + } + builder.trustManagerFactory.init(new CertPathTrustManagerParameters(params)); X509TrustManager[] trustManagers = Stream.of(builder.trustManagerFactory.getTrustManagers()).map(trustManager -> trustManager instanceof X509TrustManager ? (X509TrustManager) trustManager : null).filter(Objects::nonNull).toArray(X509TrustManager[]::new); if (trustManagers.length == 0) { @@ -195,6 +211,7 @@ public static class Builder { private boolean onlyEndEntity = false; private boolean softFail = false; private boolean noFallback = false; + private boolean checkRevocation = false; private Builder() {} @@ -325,6 +342,17 @@ public Builder setNoFallback(boolean noFallback) { return this; } + /** + * Enables revocation checking on the client side. Default false + * + * @param checkRevocation determines whether revocation checking should be enabled on the client side + * @return this Builder for subsequent changes + */ + public Builder setCheckRevocation(boolean checkRevocation) { + this.checkRevocation = checkRevocation; + return this; + } + /** * Set OCSP responder's certificate. By default issuer certificate of certificate being validated is used. * diff --git a/tests/base/src/test/java/org/wildfly/security/ssl/SSLAuthenticationTest.java b/tests/base/src/test/java/org/wildfly/security/ssl/SSLAuthenticationTest.java index b1005514045..dd3fa4c00d4 100644 --- a/tests/base/src/test/java/org/wildfly/security/ssl/SSLAuthenticationTest.java +++ b/tests/base/src/test/java/org/wildfly/security/ssl/SSLAuthenticationTest.java @@ -104,12 +104,12 @@ public class SSLAuthenticationTest { private final int TESTING_PORT = 18201; private static final char[] PASSWORD = "Elytron".toCharArray(); - private static final String JKS_LOCATION = "./target/test-classes/pkcs12"; + private static final String PKCS_LOCATION = "./target/test-classes/pkcs12"; private static final String CA_CRL_LOCATION = "./target/test-classes/ca/crl"; private static final String ICA_CRL_LOCATION = "./target/test-classes/ica/crl"; private static final File WORKING_DIR_CACRL = new File(CA_CRL_LOCATION); private static final File WORKING_DIR_ICACRL = new File(ICA_CRL_LOCATION); - private static final File SHORTWINGED_FILE = new File(JKS_LOCATION, "shortwinged.keystore"); + private static final File SHORTWINGED_FILE = new File(PKCS_LOCATION, "shortwinged.keystore"); private static final File CA_BLANK_PEM_CRL = new File(WORKING_DIR_CACRL, "blank.pem"); private static final File ICA_BLANK_PEM_CRL = new File(WORKING_DIR_ICACRL, "blank.pem"); private static final File BLANK_BLANK_PEM_CRL = new File(WORKING_DIR_ICACRL, "blank-blank.pem"); @@ -168,7 +168,7 @@ public static void beforeTest() throws Exception { WORKING_DIR_ICACRL.mkdirs(); caGenerationTool = CAGenerationTool.builder() - .setBaseDir(JKS_LOCATION) + .setBaseDir(PKCS_LOCATION) .setRequestIdentities(Identity.values()) // Create all identities. .build(); @@ -182,6 +182,7 @@ public static void beforeTest() throws Exception { "ocsp-responder.keystore", new ExtendedKeyUsageExtension(false, Collections.singletonList(OID_KP_OCSP_SIGNING))); ocspResponderCertificate = responderIdentity.getCertificate(); + /* =================== CLIENT SIDE OCSP RELATED CERTS =================== */ // Generates GOOD certificate referencing the OCSP responder goodIdentity = intermediateCAIdentity.createIdentity("checked", new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedGood"), @@ -209,14 +210,39 @@ public static void beforeTest() throws Exception { X509Certificate greenJuneCertificate = caGenerationTool .getDefinedIdentity(Identity.GREENJUNE) .getCertificate(); + /* =================== OCSP STAPLING RELATED CERTS =================== */ + // Generates GOOD certificate referencing the OCSP responder + X509Certificate ocspCheckedGoodServerCertificate = caGenerationTool.createIdentity("checked", + new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedServerGood"), + "ocsp-stapling-checked-good.keystore", Identity.CA, new AuthorityInformationAccessExtension(Collections.singletonList( + new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp")) + ))); + + // Generates REVOKED certificate referencing the OCSP responder + X509Certificate ocspCheckedRevokedServerCertificate = caGenerationTool.createIdentity("checked", + new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedServerRevoked"), + "ocsp-stapling-checked-revoked.keystore", Identity.CA, (new AuthorityInformationAccessExtension(Collections.singletonList( + new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp")) + )))); + + // Generates UNKNOWN certificate referencing the OCSP responder + X509Certificate ocspCheckedUnknownServerCertificate = caGenerationTool.createIdentity("checked", + new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedServerUnknown"), + "ocsp-stapling-checked-unknown.keystore", Identity.CA, new AuthorityInformationAccessExtension(Collections.singletonList( + new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp")) + ))); + KeyStore beetlesKeyStore = caGenerationTool.getBeetlesKeyStore(); beetlesKeyStore.setCertificateEntry("ocspResponder", ocspResponderCertificate); beetlesKeyStore.setCertificateEntry("ocspCheckedGood", ocspCheckedGoodCertificate); + beetlesKeyStore.setCertificateEntry("ocspCheckedServerGood", ocspCheckedGoodServerCertificate); beetlesKeyStore.setCertificateEntry("ocspCheckedRevoked", ocspCheckedRevokedCertificate); + beetlesKeyStore.setCertificateEntry("ocspCheckedServerRevoked", ocspCheckedRevokedServerCertificate); beetlesKeyStore.setCertificateEntry("ocspCheckedUnknown", ocspCheckedUnknownCertificate); + beetlesKeyStore.setCertificateEntry("ocspCheckedServerUnknown", ocspCheckedUnknownServerCertificate); beetlesKeyStore.setCertificateEntry("green june", greenJuneCertificate); - createTemporaryKeyStoreFile(beetlesKeyStore, new File(JKS_LOCATION, "beetles.keystore"), PASSWORD); + createTemporaryKeyStoreFile(beetlesKeyStore, new File(PKCS_LOCATION, "beetles.keystore"), PASSWORD); // Adds trusted cert for shortwinged shortWingedKeyStore = createKeyStore(); @@ -352,7 +378,10 @@ public static void beforeTest() throws Exception { ocspServer.createCertificate(1, 1, intermediateCAIdentity.getCertificate()); ocspServer.createCertificate(2, 2, ocspCheckedGoodCertificate); ocspServer.createCertificate(3, 1, ocspCheckedRevokedCertificate); + ocspServer.createCertificate(4, 1, ocspCheckedGoodServerCertificate); + ocspServer.createCertificate(5, 1, ocspCheckedRevokedServerCertificate); ocspServer.revokeCertificate(3, 4); + ocspServer.revokeCertificate(5, 4); ocspServer.start(); } @@ -823,6 +852,94 @@ public void testClientSideOcsp() throws Throwable { } } + @Test + public void testOcspStaplingGood() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity ocspStaplingGood = caGenerationTool.getDefinedIdentity(Identity.OCSP_STAPLING_GOOD); + SSLContext serverContext = new SSLContextBuilder() + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(ocspStaplingGood.createKeyManager()) + .setTrustManager(ca.createTrustManager()) + .setNeedClientAuth(true) + .setResponseTimeout(5000) + .setCacheSize(256) + .setCacheLifetime(3600) + .build().create(); + + performConnectionTest(serverContext, "protocol://test-two-way-ocsp-stapling-good.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=ocspCheckedServerGood", + "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Ladybird", false); + } + + @Test + public void testOcspStaplingRevoked() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity ocspStaplingRevoked = caGenerationTool.getDefinedIdentity(Identity.OCSP_STAPLING_REVOKED); + SSLContext serverContext = new SSLContextBuilder() + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(ocspStaplingRevoked.createKeyManager()) + .setTrustManager(ca.createTrustManager()) + .setNeedClientAuth(true) + .setResponseTimeout(5000) + .setCacheSize(256) + .setCacheLifetime(3600) + .build().create(); + + performConnectionTest(serverContext, "protocol://test-two-way-ocsp-stapling-revoked.org", false, "OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedServerRevoked", + "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Ladybird", false); + } + + @Test + public void testOcspStaplingUnknownHardFail() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity ocspStaplingUnknown = caGenerationTool.getDefinedIdentity(Identity.OCSP_STAPLING_UNKNOWN); + SSLContext serverContext = new SSLContextBuilder() + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(ocspStaplingUnknown.createKeyManager()) + .setTrustManager(ca.createTrustManager()) + .setNeedClientAuth(true) + .setResponseTimeout(5000) + .setCacheSize(256) + .setCacheLifetime(3600) + .build().create(); + + performConnectionTest(serverContext, "protocol://test-two-way-ocsp-stapling-unknown.org", false, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=ocspCheckedServerUnknown", + "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Ladybird", false); + } + + @Test + public void testOcspStaplingUnknownSoftFail() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity ocspStaplingUnknown = caGenerationTool.getDefinedIdentity(Identity.OCSP_STAPLING_UNKNOWN); + SSLContext serverContext = new SSLContextBuilder() + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(ocspStaplingUnknown.createKeyManager()) + .setTrustManager(ca.createTrustManager()) + .setNeedClientAuth(true) + .setResponseTimeout(5000) + .setCacheSize(256) + .setCacheLifetime(3600) + .build().create(); + + performConnectionTest(serverContext, "protocol://test-two-way-ocsp-stapling-unknown-soft-fail.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=ocspCheckedServerUnknown", + "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Ladybird", false); + } + + @Test + public void testOcspStaplingOneWayGood() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity ocspStaplingGood = caGenerationTool.getDefinedIdentity(Identity.OCSP_STAPLING_GOOD); + SSLContext serverContext = new SSLContextBuilder() + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(ocspStaplingGood.createKeyManager()) + .setResponseTimeout(5000) + .setCacheSize(256) + .setCacheLifetime(3600) + .build().create(); + + performConnectionTest(serverContext, "protocol://test-one-way-ocsp-stapling-good.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=ocspCheckedServerGood", + null, true); + } + @Test public void testWantClientAuthWithCorrectCertificate() throws Throwable { DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); @@ -886,56 +1003,56 @@ private void testCommunication(SSLContext serverContext, SSLContext clientContex SSLSocket clientSocket = (SSLSocket) clientContext.getSocketFactory().createSocket("localhost", TESTING_PORT); SSLSocket serverSocket = (SSLSocket) listeningSocket.accept(); - ExecutorService serverExecutorService = Executors.newSingleThreadExecutor(); - Future serverFuture = serverExecutorService.submit(() -> { + ExecutorService clientExecutorService = Executors.newSingleThreadExecutor(); + Future clientFuture = clientExecutorService.submit(() -> { try { byte[] received = new byte[2]; - serverSocket.getInputStream().read(received); - serverSocket.getOutputStream().write(new byte[]{0x56, 0x78}); + clientSocket.getOutputStream().write(new byte[]{0x12, 0x34}); + clientSocket.getInputStream().read(received); - if (expectedClientPrincipal != null) { - assertEquals(expectedClientPrincipal, serverSocket.getSession().getPeerPrincipal().getName()); + if (expectedServerPrincipal != null) { + assertEquals(expectedServerPrincipal, clientSocket.getSession().getPeerPrincipal().getName()); } - SecurityIdentity identity = (SecurityIdentity) serverSocket.getSession().getValue(SSLUtils.SSL_SESSION_IDENTITY_KEY); if (oneWay) { - assertNull(identity); + assertNotEquals("TLSv1.3", clientSocket.getSession().getProtocol());// since TLS 1.3 is not enabled by default (ELY-1917) } else { - assertNotNull(identity); + assertNotEquals("TLSv1.3", serverSocket.getSession().getProtocol()); // since TLS 1.3 is not enabled by default + assertNotEquals("TLSv1.3", clientSocket.getSession().getProtocol()); // since TLS 1.3 is not enabled by default } - return received; } catch (Exception e) { - throw new RuntimeException("Server exception", e); + throw new RuntimeException("Client exception", e); } }); - ExecutorService clientExecutorService = Executors.newSingleThreadExecutor(); - Future clientFuture = clientExecutorService.submit(() -> { + ExecutorService serverExecutorService = Executors.newSingleThreadExecutor(); + Future serverFuture = serverExecutorService.submit(() -> { try { byte[] received = new byte[2]; - clientSocket.getOutputStream().write(new byte[]{0x12, 0x34}); - clientSocket.getInputStream().read(received); + serverSocket.getInputStream().read(received); + serverSocket.getOutputStream().write(new byte[]{0x56, 0x78}); - if (expectedServerPrincipal != null) { - assertEquals(expectedServerPrincipal, clientSocket.getSession().getPeerPrincipal().getName()); + if (expectedClientPrincipal != null) { + assertEquals(expectedClientPrincipal, serverSocket.getSession().getPeerPrincipal().getName()); } + SecurityIdentity identity = (SecurityIdentity) serverSocket.getSession().getValue(SSLUtils.SSL_SESSION_IDENTITY_KEY); if (oneWay) { - assertNotEquals("TLSv1.3", clientSocket.getSession().getProtocol());// since TLS 1.3 is not enabled by default (ELY-1917) + assertNull(identity); } else { - assertNotEquals("TLSv1.3", serverSocket.getSession().getProtocol()); // since TLS 1.3 is not enabled by default - assertNotEquals("TLSv1.3", clientSocket.getSession().getProtocol()); // since TLS 1.3 is not enabled by default + assertNotNull(identity); } + return received; } catch (Exception e) { - throw new RuntimeException("Client exception", e); + throw new RuntimeException("Server exception", e); } }); try { - assertArrayEquals(new byte[]{0x12, 0x34}, serverFuture.get()); assertArrayEquals(new byte[]{0x56, 0x78}, clientFuture.get()); + assertArrayEquals(new byte[]{0x12, 0x34}, serverFuture.get()); } catch (ExecutionException e) { if (e.getCause() != null && e.getCause() instanceof RuntimeException && e.getCause().getCause() != null) { throw e.getCause().getCause(); // unpack diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml index 2f5ab506492..e9a666a51db 100644 --- a/tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml +++ b/tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml @@ -20,7 +20,7 @@ - + @@ -62,6 +62,22 @@ + + + + + + + + + + + + + + + + @@ -206,6 +222,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -256,6 +319,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CAGenerationTool.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CAGenerationTool.java index e28f884d999..f0d81838e6b 100644 --- a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CAGenerationTool.java +++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CAGenerationTool.java @@ -431,7 +431,13 @@ public enum Identity { LADYBUG("OU=Wildfly, O=Wildfly, C=CA, ST=Wildfly, CN=Ladybug", SECOND_CA, false, "ladybug.keystore"), GREENJUNE("OU=Wildfly, O=Wildfly, C=CA, ST=Wildfly, CN=Green June", SECOND_CA, false, - "greenjune.keystore"); + "greenjune.keystore"), + OCSP_STAPLING_GOOD("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedServerGood", CA, false, + "ocsp-stapling-checked-good.keystore"), + OCSP_STAPLING_REVOKED("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedServerRevoked", CA, false, + "ocsp-stapling-checked-revoked.keystore"), + OCSP_STAPLING_UNKNOWN("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedServerUnknown", CA, false, + "ocsp-stapling-checked-unknown.keystore"); private final X500Principal principal; private final Identity signedBy;