diff --git a/.github/workflows/pr-ci.yaml b/.github/workflows/pr-ci.yaml index 32ffa815c71..eb720698df9 100644 --- a/.github/workflows/pr-ci.yaml +++ b/.github/workflows/pr-ci.yaml @@ -15,12 +15,13 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] + java: ['11', '17', '21'] steps: - uses: actions/checkout@v2 - - name: Set up JDK 11 + - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v1 with: - java-version: 11 + java-version: ${{ matrix.java }} # ELY-2204 - Temporarily preventing OidcTest from running on macOS since there # are intermittent issues with starting up the Docker container. #- if: matrix.os == 'macos-latest' diff --git a/.gitignore b/.gitignore index 2055c9d34e5..5e7aeeaaa67 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ target nbactions.xml nb-configuration.xml catalog.xml +# Ignore VSCode Files +.vscode # maven-ant-tasks.jar test-output diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 27c26b7ae87..b4979fa4e68 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,6 +81,8 @@ To run only a specific test, use: ```bash mvn clean install -Dtest=TestClassName ``` +Note: Some tests will fail if `localhost` is not listed first in `/etc/hosts` file for the loopback addresses (IPv4 and IPv6). + For more information, including details on how WildFly Elytron is integrated in WildFly Core and WildFly, check out our [developer guide](https://wildfly-security.github.io/wildfly-elytron/getting-started-for-developers/). ## Contributing Guidelines @@ -95,5 +97,17 @@ When submitting a PR, please keep the following guidelines in mind: For an example of a properly formatted PR, take a look at https://github.com/wildfly-security/wildfly-elytron/pull/1532 +## Code Reviews + +All submissions, including submissions by project members, need to be reviewed by at least two WildFly Elytron committers before being merged. + +The [GitHub Pull Request Review Process](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews) is followed for every pull request. + +## Maintenance Branches + +If you are working on a fix that's required for a maintenance branch (e.g., a fix for the 1.15.x or 2.2.x branches), please submit +your PR directly against the relevant maintenance branch. Once a fix for a maintenance branch is approved and merged, we then +merge the maintenance branch to the upstream branch to ensure changes are kept in sync. + ## Community For more information on how to get involved with WildFly Elytron, check out our [community](https://wildfly-security.github.io/wildfly-elytron/community/) page. diff --git a/ELY_Messages.txt b/ELY_Messages.txt index e3ecb9c83ef..00c3b509d31 100644 --- a/ELY_Messages.txt +++ b/ELY_Messages.txt @@ -126,7 +126,7 @@ 24000 - 24999 wildfly-elytron-jose-jwk 25000 - 25999 wildfly-elytron-jose-jws 26000 - 26999 wildfly-elytron-jose-util -27000 - 27999 +27000 - 27999 wildfly-elytron-dynamic-ssl 28000 - 28999 29000 - 29999 30000 - 30999 diff --git a/README.md b/README.md index c889a528d36..5b6459630a0 100644 --- a/README.md +++ b/README.md @@ -29,20 +29,25 @@ $ mvn clean install Issue Tracking -------------- -Bugs and features are tracked within the Elytron Jira project at https://issues.jboss.org/browse/ELY +Bugs and features are tracked within the Elytron Jira project at https://issues.redhat.com/browse/ELY Contributions ------------- -All new features and enhancements should be submitted to 1.x branch only. -Our [contribution guide](https://github.com/wildfly-security/wildfly-elytron/blob/1.x/CONTRIBUTING.md) will guide you through the steps for getting started on the WildFly Elytron project and will go through how to format and submit your first PR. +All new features and enhancements should be submitted to 2.x branch only. +Our [contribution guide](https://github.com/wildfly-security/wildfly-elytron/blob/2.x/CONTRIBUTING.md) will guide you through the steps for getting started on the WildFly Elytron project and will go through how to format and submit your first PR. For more details, check out our [getting started guide](https://wildfly-security.github.io/wildfly-elytron/getting-started-for-developers/) for developers. +Example Feature Demos +--------------------- + +Our [elytron-examples](https://github.com/wildfly-security-incubator/elytron-examples) repository contains example demos of WildFly Elytron features. + Get Help -------- There are a couple ways to get in touch with us. Feel free to ask questions on the WildFly user [forum](https://groups.google.com/g/wildfly). -The WildFly Elytron team also has an open chat room where you can listen in and ask questions. Join us on [Zulip chat](https://wildfly.zulipchat.com/#narrow/stream/173102-wildfly-elytron). \ No newline at end of file +The WildFly Elytron team also has an open chat room where you can listen in and ask questions. Join us on [Zulip chat](https://wildfly.zulipchat.com/#narrow/stream/173102-wildfly-elytron). diff --git a/SECURITY.md b/SECURITY.md index bd35dec3ce0..5cc4d52786d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,11 +1,23 @@ -# Security Policy - -## Security Contacts and Procedures - -The WildFly Elytron community takes security very seriously, and we aim to take immediate action to address serious security-related problems that involve our products or services. - -Please report any suspected security vulnerability in this project to Red Hat Product Security at secalert@redhat.com. You can use our GPG key to communicate with us securely. - -To report an issue in any Red Hat branded website or online service, please contact Red Hat Information Security at site-security@redhat.com. -https://access.redhat.com/security/team/contact +# Reporting of CVEs and Security Issues +## The WildFly Elytron community and our sponsor, Red Hat, take security bugs very seriously + +We aim to take immediate action to address serious security-related problems that involve our projects. + +Note that we will only fix such issues in the most recent minor release of WildFly Elytron.

+ +## Reporting of Security Issues + +When reporting a security vulnerability it is important to not accidentally broadcast to the world that the issue exists, as this makes it easier for people to exploit it. The software industry uses the term embargo to describe the time a security issue is known internally until it is public knowledge. + +Our preferred way of reporting security issues in WildFly Elytron and its related projects is listed below. + +### Email the mailing list + +The list at security@wildfly.org is the preferred mechanism for outside users to report security issues. A member of the WildFly Elytron team will open the required issues. + +### Other considerations + +If you would like to work with us on a fix for the security vulnerability, please include your GitHub username in the above email, and we will provide you access to a temporary private fork where we can collaborate on a fix without it being disclosed publicly, **including in your own publicly visible git repository**. + +Do not open a public issue, send a pull request, or disclose any information about the suspected vulnerability publicly, **including in your own publicly visible git repository**. If you discover any publicly disclosed security vulnerabilities, please notify us immediately through security@wildfly.org diff --git a/asn1/pom.xml b/asn1/pom.xml index daba6ac26ce..fb4be4b879d 100644 --- a/asn1/pom.xml +++ b/asn1/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT 4.0.0 diff --git a/audit/pom.xml b/audit/pom.xml index e41d7be291c..70673c851c8 100644 --- a/audit/pom.xml +++ b/audit/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT 4.0.0 diff --git a/audit/src/main/java/org/wildfly/security/audit/FileAuditEndpoint.java b/audit/src/main/java/org/wildfly/security/audit/FileAuditEndpoint.java index ea1605b22de..303c52f25ff 100644 --- a/audit/src/main/java/org/wildfly/security/audit/FileAuditEndpoint.java +++ b/audit/src/main/java/org/wildfly/security/audit/FileAuditEndpoint.java @@ -70,7 +70,7 @@ public class FileAuditEndpoint implements AuditEndpoint { } void setFile(final File file) throws IOException { - boolean ok = false; + boolean isFileSet = false; final FileOutputStream fos = new FileOutputStream(file, true); try { final Writer writer = new OutputStreamWriter(new BufferedOutputStream(fos), this.charset); @@ -78,14 +78,14 @@ void setFile(final File file) throws IOException { this.fileDescriptor = fos.getFD(); this.writer = writer; this.file = file; - ok = true; + isFileSet = true; } finally { - if (! ok) { + if (! isFileSet) { safeClose(writer); } } } finally { - if (! ok) { + if (! isFileSet) { safeClose(fos); } } diff --git a/audit/src/main/java/org/wildfly/security/audit/PeriodicRotatingFileAuditEndpoint.java b/audit/src/main/java/org/wildfly/security/audit/PeriodicRotatingFileAuditEndpoint.java index 52ac51b871f..c0aeede7342 100644 --- a/audit/src/main/java/org/wildfly/security/audit/PeriodicRotatingFileAuditEndpoint.java +++ b/audit/src/main/java/org/wildfly/security/audit/PeriodicRotatingFileAuditEndpoint.java @@ -233,9 +233,10 @@ public Builder setSuffix(String suffix) throws IllegalArgumentException { public AuditEndpoint build() throws IOException { return new PeriodicRotatingFileAuditEndpoint(this); } - } - private static > T min(T a, T b) { - return a.compareTo(b) <= 0 ? a : b; + private static > T min(T a, T b) { + return a.compareTo(b) <= 0 ? a : b; + } } + } \ No newline at end of file diff --git a/audit/src/main/java/org/wildfly/security/audit/SyslogAuditEndpoint.java b/audit/src/main/java/org/wildfly/security/audit/SyslogAuditEndpoint.java index 577c96f15e1..70d772f1856 100644 --- a/audit/src/main/java/org/wildfly/security/audit/SyslogAuditEndpoint.java +++ b/audit/src/main/java/org/wildfly/security/audit/SyslogAuditEndpoint.java @@ -66,7 +66,13 @@ public Integer run() { */ SyslogAuditEndpoint(Builder builder) throws IOException { maxReconnectAttempts = builder.maxReconnectAttempts; - protocol = builder.ssl ? Protocol.SSL_TCP : builder.tcp ? Protocol.TCP : Protocol.UDP; + if (builder.ssl) { + protocol = Protocol.SSL_TCP; + } else if (builder.tcp) { + protocol = Protocol.TCP; + } else { + protocol = Protocol.UDP; + } syslogHandler = new SyslogHandler(checkNotNullParam("serverAddress", builder.serverAddress), builder.port, Facility.SECURITY, builder.format, protocol, checkNotNullParam("hostName", builder.hostName)); diff --git a/auth/base/pom.xml b/auth/base/pom.xml index a527a409a87..764d0639c6e 100644 --- a/auth/base/pom.xml +++ b/auth/base/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/auth/base/src/main/java/org/wildfly/security/auth/principal/CompositePrincipal.java b/auth/base/src/main/java/org/wildfly/security/auth/principal/CompositePrincipal.java index 88a9b473d1e..8bd0383454c 100644 --- a/auth/base/src/main/java/org/wildfly/security/auth/principal/CompositePrincipal.java +++ b/auth/base/src/main/java/org/wildfly/security/auth/principal/CompositePrincipal.java @@ -62,7 +62,13 @@ public CompositePrincipal(Principal... principals) { } private CompositePrincipal(Principal[] principals, boolean clone) { - p = principals.length == 0 ? NO_PRINCIPALS : clone ? principals.clone() : principals; + if (principals.length == 0) { + p = NO_PRINCIPALS; + } else if (clone) { + p = principals.clone(); + } else { + p = principals; + } for (int i = 0; i < p.length; i++) { Assert.checkNotNullArrayParam("principals", i, p[i]); } diff --git a/auth/client/pom.xml b/auth/client/pom.xml index 97f7fabacf6..88375a5d569 100644 --- a/auth/client/pom.xml +++ b/auth/client/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml @@ -60,6 +60,10 @@ org.wildfly.security wildfly-elytron-mechanism + + org.wildfly.security + wildfly-elytron-mechanism-gssapi + org.wildfly.security wildfly-elytron-password-impl @@ -76,7 +80,6 @@ org.wildfly.security wildfly-elytron-ssh-util - org.jboss.logging jboss-logging-annotations diff --git a/auth/client/src/main/java/org/wildfly/security/auth/client/ActiveSessionsSSLContext.java b/auth/client/src/main/java/org/wildfly/security/auth/client/ActiveSessionsSSLContext.java new file mode 100644 index 00000000000..691066b170e --- /dev/null +++ b/auth/client/src/main/java/org/wildfly/security/auth/client/ActiveSessionsSSLContext.java @@ -0,0 +1,33 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.auth.client; + +/** + * An interface indicating active sessions of an SSLContext + */ +public interface ActiveSessionsSSLContext { + /** + * Indicates if the SSLContext has active sessions. + * + * @return true if SSLContext has active sessions. Otherwise, false + */ + default boolean hasActiveSessions() { + return false; + } +} diff --git a/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContext.java b/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContext.java index ba7e75a22bf..aadc6c7b8eb 100644 --- a/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContext.java +++ b/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContext.java @@ -361,6 +361,10 @@ public T runAsSupplierEx(ExceptionSupplier action return runExFunction(ExceptionSupplier::get, action); } + RuleNode> getSslRules() { + return this.sslRules; + } + public ContextManager getInstanceContextManager() { return getContextManager(); } diff --git a/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContextConfigurationClient.java b/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContextConfigurationClient.java index e1519a79179..c0f915766c8 100644 --- a/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContextConfigurationClient.java +++ b/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContextConfigurationClient.java @@ -30,7 +30,9 @@ import java.security.Principal; import java.security.PrivilegedAction; import java.security.Provider; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.function.Supplier; import java.util.function.UnaryOperator; @@ -196,6 +198,42 @@ private static AuthenticationConfiguration initializeConfiguration(final URI uri return configuration; } + /** + * Get all SSL contexts configured for this authentication context. + * + * @param authenticationContext the authentication context to examine (must not be {@code null}) + * @return List of all configured SSL contexts belonging to the provided authentication context + */ + public List getConfiguredSSLContexts(AuthenticationContext authenticationContext) throws GeneralSecurityException { + Assert.checkNotNullParam("authenticationContext", authenticationContext); + List sslContexts = new ArrayList<>(); + RuleNode> node = authenticationContext.getSslRules(); + while (node != null) { + sslContexts.add(node.getConfiguration().create()); + node = node.getNext(); + } + return sslContexts; + } + + /** + * Get the default SSL context that should be used when no other rules match, or {@link SSLContext#getDefault()} if there is none configured. + * + * @param authenticationContext the authentication context to examine (must not be {@code null}) + * @return the default SSL context configured if no other rules match + */ + public SSLContext getDefaultSSLContext(AuthenticationContext authenticationContext) throws GeneralSecurityException { + Assert.checkNotNullParam("authenticationContext", authenticationContext); + SSLContext defaultSSLContext = null; + RuleNode> node = authenticationContext.getSslRules(); + while (node != null) { + if (node.getRule().equals(MatchRule.ALL)) { + defaultSSLContext = node.getConfiguration().create(); + } + node = node.getNext(); + } + return defaultSSLContext == null ? SSLContext.getDefault() : defaultSSLContext; + } + /** * Get the configured SSL context which matches ALL rules from provided AuthenticationContext, or {@link SSLContext#getDefault()} if there is none. * 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 0a9702d1c87..8a933f4951c 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 @@ -714,13 +714,10 @@ private static void parseCertificateRevocationLists(ConfigurationXMLStreamReader while (reader.hasNext()) { final int tag = reader.nextTag(); if (tag == START_ELEMENT) { - switch (reader.getLocalName()) { - case "certificate-revocation-list": { - parseCertificateRevocationList(reader, builder, xmlVersion, true); - break; - } - default: - throw reader.unexpectedElement(); + if (reader.getLocalName().equals("certificate-revocation-list")) { + parseCertificateRevocationList(reader, builder, xmlVersion, true); + } else { + throw reader.unexpectedElement(); } } else if (tag != END_ELEMENT) { throw reader.unexpectedContent(); @@ -1084,12 +1081,10 @@ static ExceptionSupplier, ConfigXMLParseException> parseRulesTyp final int tag = reader.nextTag(); if (tag == START_ELEMENT) { checkElementNamespace(reader, xmlVersion); - switch (reader.getLocalName()) { - case "rule": { - rulesList.add(ruleParseFunction.apply(reader, configurations)); - break; - } - default: throw reader.unexpectedElement(); + if (reader.getLocalName().equals("rule")) { + rulesList.add(ruleParseFunction.apply(reader, configurations)); + } else { + throw reader.unexpectedElement(); } } else if (tag == END_ELEMENT) { return () -> { @@ -2523,10 +2518,12 @@ static String parseNameType(ConfigurationXMLStreamReader reader, boolean optiona String name = null; for (int i = 0; i < attributeCount; i ++) { checkAttributeNamespace(reader, i); - if (reader.getAttributeLocalName(i).equals("name")) { - name = reader.getAttributeValueResolved(i); - } else { - throw reader.unexpectedAttribute(i); + switch (reader.getAttributeLocalName(i)) { + case "name": { + name = reader.getAttributeValueResolved(i); + break; + } + default: throw reader.unexpectedAttribute(i); } } if (name == null && !optional) { @@ -2559,12 +2556,18 @@ static ExceptionSupplier parseResourceType(Configurati String module = null; for (int i = 0; i < attributeCount; i ++) { checkAttributeNamespace(reader, i); - if (reader.getAttributeLocalName(i).equals("name")) { - name = reader.getAttributeValueResolved(i); - } else if (reader.getAttributeLocalName(i).equals("module-name") && xmlVersion.isAtLeast(Version.VERSION_1_1)) { - module = reader.getAttributeValueResolved(i); - } else { - throw reader.unexpectedAttribute(i); + switch (reader.getAttributeLocalName(i)) { + case "name": { + name = reader.getAttributeValueResolved(i); + break; + } + case "module-name": { + if (xmlVersion.isAtLeast(Version.VERSION_1_1)) { + module = reader.getAttributeValueResolved(i); + break; + } + } + default: throw reader.unexpectedAttribute(i); } } if (name == null) { @@ -2602,18 +2605,20 @@ static int parsePortType(ConfigurationXMLStreamReader reader) throws ConfigXMLPa int number = -1; for (int i = 0; i < attributeCount; i ++) { checkAttributeNamespace(reader, i); - if (reader.getAttributeLocalName(i).equals("number")) { - String s = reader.getAttributeValueResolved(i); - try { - number = Integer.parseInt(s); - } catch (NumberFormatException ignored) { - throw invalidPortNumber(reader, i); - } - if (number < 1 || number > 65535) { - throw invalidPortNumber(reader, i); + switch (reader.getAttributeLocalName(i)) { + case "number": { + String s = reader.getAttributeValueResolved(i); + try { + number = Integer.parseInt(s); + } catch (NumberFormatException ignored) { + throw invalidPortNumber(reader, i); + } + if (number < 1 || number > 65535) { + throw invalidPortNumber(reader, i); + } + break; } - } else { - throw reader.unexpectedAttribute(i); + default: throw reader.unexpectedAttribute(i); } } if (number == -1) { @@ -2645,12 +2650,16 @@ static NameRewriter parseRegexSubstitutionType(ConfigurationXMLStreamReader read String replacement = null; for (int i = 0; i < attributeCount; i ++) { checkAttributeNamespace(reader, i); - if (reader.getAttributeLocalName(i).equals("pattern")) { - pattern = Pattern.compile(reader.getAttributeValueResolved(i)); - } else if (reader.getAttributeLocalName(i).equals("replacement")) { - replacement = reader.getAttributeValueResolved(i); - } else { - throw reader.unexpectedAttribute(i); + switch (reader.getAttributeLocalName(i)) { + case "pattern": { + pattern = Pattern.compile(reader.getAttributeValueResolved(i)); + break; + } + case "replacement": { + replacement = reader.getAttributeValueResolved(i); + break; + } + default: throw reader.unexpectedAttribute(i); } } if (pattern == null) { @@ -2684,11 +2693,13 @@ static String[] parseNamesType(ConfigurationXMLStreamReader reader) throws Confi String[] names = null; for (int i = 0; i < attributeCount; i ++) { checkAttributeNamespace(reader, i); - if (reader.getAttributeLocalName(i).equals("names")) { - String s = reader.getAttributeValueResolved(i); - names = s.trim().split("\\s+"); - } else { - throw reader.unexpectedAttribute(i); + switch (reader.getAttributeLocalName(i)) { + case "names": { + String s = reader.getAttributeValueResolved(i); + names = s.trim().split("\\s+"); + break; + } + default: throw reader.unexpectedAttribute(i); } } if (names == null) { @@ -2719,10 +2730,12 @@ static URI parseUriType(ConfigurationXMLStreamReader reader) throws ConfigXMLPar URI uri = null; for (int i = 0; i < attributeCount; i ++) { checkAttributeNamespace(reader, i); - if (reader.getAttributeLocalName(i).equals("uri")) { - uri = reader.getURIAttributeValueResolved(i); - } else { - throw reader.unexpectedAttribute(i); + switch (reader.getAttributeLocalName(i)) { + case "uri": { + uri = reader.getURIAttributeValueResolved(i); + break; + } + default: throw reader.unexpectedAttribute(i); } } if (uri == null) { @@ -2746,10 +2759,12 @@ static SaslMechanismSelector parseSaslMechanismSelectorType(ConfigurationXMLStre SaslMechanismSelector selector = null; for (int i = 0; i < attributeCount; i ++) { checkAttributeNamespace(reader, i); - if (reader.getAttributeLocalName(i).equals("selector")) { - selector = SaslMechanismSelector.fromString(reader.getAttributeValueResolved(i)); - } else { - throw reader.unexpectedAttribute(i); + switch (reader.getAttributeLocalName(i)) { + case "selector": { + selector = SaslMechanismSelector.fromString(reader.getAttributeValueResolved(i)); + break; + } + default: throw reader.unexpectedAttribute(i); } } if (selector == null) { @@ -2781,12 +2796,18 @@ static CipherSuiteSelector parseCipherSuiteSelectorType(ConfigurationXMLStreamRe CipherSuiteSelector names = null; for (int i = 0; i < attributeCount; i ++) { checkAttributeNamespace(reader, i); - if (reader.getAttributeLocalName(i).equals("selector")) { - selector = CipherSuiteSelector.fromString(reader.getAttributeValueResolved(i)); - } else if (xmlVersion.isAtLeast(Version.VERSION_1_5) && reader.getAttributeLocalName(i).equals("names")) { - names = CipherSuiteSelector.fromNamesString(reader.getAttributeValueResolved(i)); - } else { - throw reader.unexpectedAttribute(i); + switch (reader.getAttributeLocalName(i)) { + case "selector": { + selector = CipherSuiteSelector.fromString(reader.getAttributeValueResolved(i)); + break; + } + case "names": { + if (xmlVersion.isAtLeast(Version.VERSION_1_5)) { + names = CipherSuiteSelector.fromNamesString(reader.getAttributeValueResolved(i)); + break; + } + } + default: throw reader.unexpectedAttribute(i); } } if (selector == null && ! xmlVersion.isAtLeast(Version.VERSION_1_5)) { @@ -2841,10 +2862,12 @@ static String parseModuleRefType(ConfigurationXMLStreamReader reader) throws Con String moduleName = null; for (int i = 0; i < attributeCount; i ++) { checkAttributeNamespace(reader, i); - if (reader.getAttributeLocalName(i).equals("module-name")) { - moduleName = reader.getAttributeValueResolved(i); - } else { - throw reader.unexpectedAttribute(i); + switch (reader.getAttributeLocalName(i)) { + case "module-name": { + moduleName = reader.getAttributeValueResolved(i); + break; + } + default: throw reader.unexpectedAttribute(i); } } @@ -2874,10 +2897,12 @@ static ExceptionSupplier parseClearPassword(C char[] password = null; for (int i = 0; i < attributeCount; i ++) { checkAttributeNamespace(reader, i); - if (reader.getAttributeLocalName(i).equals("password")) { - password = reader.getAttributeValueResolved(i).toCharArray(); - } else { - throw reader.unexpectedAttribute(i); + switch (reader.getAttributeLocalName(i)) { + case "password": { + password = reader.getAttributeValueResolved(i).toCharArray(); + break; + } + default: throw reader.unexpectedAttribute(i); } } if (password == null) { @@ -3300,13 +3325,13 @@ static ExceptionSupplier mechanismOids = new LinkedList<>(); for (int i = 0; i < attributeCount; i ++) { checkAttributeNamespace(reader, i); - if (reader.getAttributeLocalName(i).equals("mechanism-names")) { - for (String name : reader.getListAttributeValueAsArrayResolved(i)) { - String oid = OidsUtil.attributeNameToOid(OidsUtil.Category.GSS, name); - if (oid == null) { - throw xmlLog.xmlInvalidGssMechanismName(reader, name); - } - try { - mechanismOids.add(new Oid(oid)); - } catch (GSSException e) { - throw xmlLog.xmlGssMechanismOidConversionFailed(reader, oid, e); + switch (reader.getAttributeLocalName(i)) { + case "mechanism-names": { + for (String name : reader.getListAttributeValueAsArrayResolved(i)) { + String oid = OidsUtil.attributeNameToOid(OidsUtil.Category.GSS, name); + if (oid == null) { + throw xmlLog.xmlInvalidGssMechanismName(reader, name); + } + try { + mechanismOids.add(new Oid(oid)); + } catch (GSSException e) { + throw xmlLog.xmlGssMechanismOidConversionFailed(reader, oid, e); + } } + break; } - } else if (reader.getAttributeLocalName(i).equals("mechanism-oids")) { - for (String oid : reader.getListAttributeValueAsArrayResolved(i)) { - try { - mechanismOids.add(new Oid(oid)); - } catch (GSSException e) { - throw xmlLog.xmlGssMechanismOidConversionFailed(reader, oid, e); + case "mechanism-oids": { + for (String oid : reader.getListAttributeValueAsArrayResolved(i)) { + try { + mechanismOids.add(new Oid(oid)); + } catch (GSSException e) { + throw xmlLog.xmlGssMechanismOidConversionFailed(reader, oid, e); + } } + break; } - } else { - throw reader.unexpectedAttribute(i); + default: throw reader.unexpectedAttribute(i); } } if (mechanismOids.size() == 0) { @@ -3620,6 +3649,9 @@ protected AbstractLoadingKeyStoreFactory(final ExceptionSupplier expectedClass, Class actualClass); + @LogMessage(level = INFO) + @Message(id = 1138, value = "No Keystore password specified \"%s\"") + void noKeystorePasswordSpecified(Location location); + @Message(id = 1139, value = "Failed to create credential store") ConfigXMLParseException xmlFailedToCreateCredentialStore(@Param Location location, @Cause Throwable cause); 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 58e29768184..ac33fd96f04 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 @@ -167,8 +167,8 @@ public void testWebservices() throws Exception { Assert.assertNotNull(node); String wsHttpMechanism = node.getConfiguration().getWsHttpMechanism(); String wsSecurityType = node.getConfiguration().getWsSecurityType(); - Assert.assertEquals(wsHttpMechanism, "BASIC"); - Assert.assertEquals(wsSecurityType, "UsernameToken"); + Assert.assertEquals("BASIC", wsHttpMechanism); + Assert.assertEquals("UsernameToken", wsSecurityType); } @Test diff --git a/auth/realm/base/pom.xml b/auth/realm/base/pom.xml index 8619afb6e45..7011a4cf695 100644 --- a/auth/realm/base/pom.xml +++ b/auth/realm/base/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml diff --git a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/AggregateSecurityRealm.java b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/AggregateSecurityRealm.java index dde13678819..198c5e8331c 100644 --- a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/AggregateSecurityRealm.java +++ b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/AggregateSecurityRealm.java @@ -79,18 +79,14 @@ public AggregateSecurityRealm(final SecurityRealm authenticationRealm, Function< public RealmIdentity getRealmIdentity(final Evidence evidence) throws RealmUnavailableException { boolean ok = false; final RealmIdentity authenticationIdentity = authenticationRealm.getRealmIdentity(evidence); - if (authenticationIdentity.exists()) { - log.tracef("Authentication identity for principal [%s] found.", evidence.getDecodedPrincipal()); - } + log.tracef("Authentication identity for principal [%s] obtained.", evidence.getDecodedPrincipal()); final RealmIdentity[] authorizationIdentities = new RealmIdentity[authorizationRealms.length]; try { for (int i = 0; i < authorizationIdentities.length; i++) { SecurityRealm authorizationRealm = authorizationRealms[i]; authorizationIdentities[i] = (authorizationRealm == authenticationRealm) ? authenticationIdentity : getAuthorizationIdentity(authorizationRealm, evidence, principalTransformer, authenticationIdentity); - if (authorizationIdentities[i].exists()) { - log.tracef("Authorization identity for principal [%s] found.", evidence.getDecodedPrincipal()); - } + log.tracef("Authorization identity for principal [%s] obtained.", evidence.getDecodedPrincipal()); } final Identity identity = new Identity(authenticationIdentity, authorizationIdentities); @@ -111,9 +107,7 @@ public RealmIdentity getRealmIdentity(final Evidence evidence) throws RealmUnava public RealmIdentity getRealmIdentity(final Principal principal) throws RealmUnavailableException { boolean ok = false; final RealmIdentity authenticationIdentity = authenticationRealm.getRealmIdentity(principal); - if (authenticationIdentity.exists()) { - log.tracef("Authentication identity for principal [%s] found.", principal); - } + log.tracef("Authentication identity for principal [%s] obtained.", principal); Principal authorizationPrincipal = principal; if (principalTransformer != null) { authorizationPrincipal = principalTransformer.apply(authorizationPrincipal); @@ -125,9 +119,7 @@ public RealmIdentity getRealmIdentity(final Principal principal) throws RealmUna for (int i = 0; i < authorizationIdentities.length; i++) { SecurityRealm authorizationRealm = authorizationRealms[i]; authorizationIdentities[i] = (authorizationRealm == authenticationRealm) && (principalTransformer == null) ? authenticationIdentity : authorizationRealm.getRealmIdentity(authorizationPrincipal); - if (authorizationIdentities[i].exists()) { - log.tracef("Authorization identity for principal [%s] found.", principal); - } + log.tracef("Authorization identity for principal [%s] obtained.", principal); } final Identity identity = new Identity(authenticationIdentity, authorizationIdentities); diff --git a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FailoverSecurityRealm.java b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FailoverSecurityRealm.java index 9264e6dad06..2edcbbdc5ff 100644 --- a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FailoverSecurityRealm.java +++ b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FailoverSecurityRealm.java @@ -134,7 +134,7 @@ protected abstract class FailoverRealmIdentity implements RealmIdentity { protected RealmIdentity delegate; protected boolean failed = false; - public FailoverRealmIdentity(final RealmIdentity identity) { + protected FailoverRealmIdentity(final RealmIdentity identity) { this.delegate = identity; } diff --git a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FileSystemSecurityRealm.java b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FileSystemSecurityRealm.java index 6ef7cc4cc9a..2b04bf1ca19 100644 --- a/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FileSystemSecurityRealm.java +++ b/auth/realm/base/src/main/java/org/wildfly/security/auth/realm/FileSystemSecurityRealm.java @@ -1000,7 +1000,7 @@ private Void replaceIdentityPrivileged(final LoadedIdentity newIdentity) throws } } - private Version requiredVersion(final LoadedIdentity identityToWrite) { + private Version requiredVersion() { // As new functionality is added we will identify if we need to use a later version // if new functionality is used then use the required schema version otherwise fallback // to an older version. @@ -1018,7 +1018,7 @@ private void writeIdentity(final XMLStreamWriter streamWriter, final LoadedIdent streamWriter.writeStartDocument(); streamWriter.writeCharacters("\n"); streamWriter.writeStartElement("identity"); - streamWriter.writeDefaultNamespace(requiredVersion(newIdentity).getNamespace()); + streamWriter.writeDefaultNamespace(requiredVersion().getNamespace()); if (integrityEnabled) { streamWriter.writeCharacters("\n "); @@ -1099,6 +1099,7 @@ private void writeIdentity(final XMLStreamWriter streamWriter, final LoadedIdent streamWriter.writeEndDocument(); } + @Override public void dispose() { // Release the lock for this realm identity IdentityLock identityLock = lock; diff --git a/auth/realm/base/src/test/java/org/wildfly/security/auth/realm/JaasSecurityRealmTest.java b/auth/realm/base/src/test/java/org/wildfly/security/auth/realm/JaasSecurityRealmTest.java index fe3f586ba8d..67950cd2adb 100644 --- a/auth/realm/base/src/test/java/org/wildfly/security/auth/realm/JaasSecurityRealmTest.java +++ b/auth/realm/base/src/test/java/org/wildfly/security/auth/realm/JaasSecurityRealmTest.java @@ -18,12 +18,6 @@ package org.wildfly.security.auth.realm; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertEquals; - import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; @@ -40,6 +34,12 @@ import org.wildfly.security.credential.PublicKeyCredential; import org.wildfly.security.evidence.PasswordGuessEvidence; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + /** * Testsuite for the {@link JaasSecurityRealm}. * diff --git a/auth/realm/jdbc/pom.xml b/auth/realm/jdbc/pom.xml index 1db6b19fd8f..d0092cd639b 100644 --- a/auth/realm/jdbc/pom.xml +++ b/auth/realm/jdbc/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml diff --git a/auth/realm/ldap/pom.xml b/auth/realm/ldap/pom.xml index f33bf97c93c..27a3cda086a 100644 --- a/auth/realm/ldap/pom.xml +++ b/auth/realm/ldap/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml diff --git a/auth/realm/ldap/src/main/java/org/wildfly/security/auth/realm/ldap/LdapSecurityRealm.java b/auth/realm/ldap/src/main/java/org/wildfly/security/auth/realm/ldap/LdapSecurityRealm.java index c5d83f14c1f..a805753057a 100644 --- a/auth/realm/ldap/src/main/java/org/wildfly/security/auth/realm/ldap/LdapSecurityRealm.java +++ b/auth/realm/ldap/src/main/java/org/wildfly/security/auth/realm/ldap/LdapSecurityRealm.java @@ -54,6 +54,7 @@ import javax.naming.NameNotFoundException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; +import javax.naming.PartialResultException; import javax.naming.ReferralException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; @@ -1083,7 +1084,7 @@ public boolean tryAdvance(Consumer action) { throw referralException; } - if ( ! result.hasMore()) { // end of page + if ( ! hasMore(result)) { // end of page if ( ! (pageSize != 0 && context instanceof LdapContext) ) { log.trace("Identity iterating - pagination not supported - end of list"); finished = true; @@ -1105,7 +1106,7 @@ public boolean tryAdvance(Consumer action) { result.close(); result = searchWithPagination(); - if ( ! result.hasMore()) { + if ( ! hasMore(result)) { log.trace("Identity iterating - even after page loading no results - end of list"); finished = true; return false; // no more elements @@ -1219,6 +1220,22 @@ private SearchControls createSearchControls() { private DirContext getContext() { return context; } + + /* + * wrapper of NamingEnumeration#hasMore() to ignore PartialResultException when referral-mode=ignore + */ + private boolean hasMore(NamingEnumeration result) throws PartialResultException, NamingException { + try { + return result.hasMore(); + } catch (PartialResultException e) { + if (getContext().getEnvironment().get(DirContext.REFERRAL).equals("ignore")) { + log.trace("Ignored PartialResultException with referral-mode=ignore: " + e.toString(false)); + return false; + } else { + throw e; + } + } + } } static class IdentityMapping { diff --git a/auth/realm/ldap/src/main/java/org/wildfly/security/auth/realm/ldap/SimpleDirContextFactoryBuilder.java b/auth/realm/ldap/src/main/java/org/wildfly/security/auth/realm/ldap/SimpleDirContextFactoryBuilder.java index aed4c548e77..5d53a119c83 100644 --- a/auth/realm/ldap/src/main/java/org/wildfly/security/auth/realm/ldap/SimpleDirContextFactoryBuilder.java +++ b/auth/realm/ldap/src/main/java/org/wildfly/security/auth/realm/ldap/SimpleDirContextFactoryBuilder.java @@ -64,7 +64,7 @@ public class SimpleDirContextFactoryBuilder { private static final String READ_TIMEOUT = "com.sun.jndi.ldap.read.timeout"; private static final String SOCKET_FACTORY = "java.naming.ldap.factory.socket"; - private static final int DEFAULT_CONNECT_TIMEOUT = 5000; // ms + private static final int DEFAULT_CONNECT_TIMEOUT = 10000; // ms private static final int DEFAULT_READ_TIMEOUT = 60000; // ms private static final String LDAPS_SCHEME = "ldaps"; diff --git a/auth/realm/ldap/src/main/java/org/wildfly/security/auth/realm/ldap/X509EvidenceVerifier.java b/auth/realm/ldap/src/main/java/org/wildfly/security/auth/realm/ldap/X509EvidenceVerifier.java index 8b2bd7bd61a..d2e2b1a15bf 100644 --- a/auth/realm/ldap/src/main/java/org/wildfly/security/auth/realm/ldap/X509EvidenceVerifier.java +++ b/auth/realm/ldap/src/main/java/org/wildfly/security/auth/realm/ldap/X509EvidenceVerifier.java @@ -176,10 +176,8 @@ public boolean verifyCertificate(X509Certificate certificate, Attributes attribu for (int i = 0; i < size; i++) { Object attrDigest = attribute.get(i); - if (attrDigest != null){ - if (digest.equalsIgnoreCase((String) attrDigest)) { - return true; - } + if (attrDigest != null && digest.equalsIgnoreCase((String) attrDigest)){ + return true; } } } catch (NoSuchAlgorithmException | CertificateEncodingException e) { @@ -217,10 +215,9 @@ public boolean verifyCertificate(X509Certificate certificate, Attributes attribu try { for (int i = 0; i < size; i++) { Object attrCertificate = attribute.get(i); - if (attrCertificate != null){ - if (MessageDigest.isEqual(certificate.getEncoded(), (byte[]) attrCertificate)) { - return true; - } + if (attrCertificate != null + && MessageDigest.isEqual(certificate.getEncoded(), (byte[]) attrCertificate)) { + return true; } } } catch (CertificateEncodingException e) { diff --git a/auth/realm/token/pom.xml b/auth/realm/token/pom.xml index 259c50e7395..e0cb18aa0f5 100644 --- a/auth/realm/token/pom.xml +++ b/auth/realm/token/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml @@ -85,7 +85,7 @@ - org.glassfish + org.eclipse.parsson jakarta.json test diff --git a/auth/realm/token/src/test/java/org/wildfly/security/auth/realm/token/OAuth2TokenSecurityRealmTest.java b/auth/realm/token/src/test/java/org/wildfly/security/auth/realm/token/OAuth2TokenSecurityRealmTest.java index 3ef7f9f9434..e2586601e37 100644 --- a/auth/realm/token/src/test/java/org/wildfly/security/auth/realm/token/OAuth2TokenSecurityRealmTest.java +++ b/auth/realm/token/src/test/java/org/wildfly/security/auth/realm/token/OAuth2TokenSecurityRealmTest.java @@ -187,7 +187,7 @@ public void testInErrorTokenIntrospectionEndpoint() throws Exception { RealmIdentity realmIdentity = securityRealm.getRealmIdentity(new BearerTokenEvidence(tokenBuilder.build().toString())); - assertFalse(realmIdentity.exists()); + realmIdentity.exists(); } @Test(expected = IllegalArgumentException.class) diff --git a/auth/server/base/pom.xml b/auth/server/base/pom.xml index a9ebc498a60..29a7ec9b566 100644 --- a/auth/server/base/pom.xml +++ b/auth/server/base/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml diff --git a/auth/server/base/src/main/java/org/wildfly/security/auth/server/PrincipalDecoder.java b/auth/server/base/src/main/java/org/wildfly/security/auth/server/PrincipalDecoder.java index bfa033f7477..7f48a45f3f0 100644 --- a/auth/server/base/src/main/java/org/wildfly/security/auth/server/PrincipalDecoder.java +++ b/auth/server/base/src/main/java/org/wildfly/security/auth/server/PrincipalDecoder.java @@ -74,8 +74,8 @@ default PrincipalDecoder withRewriter(NameRewriter nameRewriter) { } /** - * Create an aggregated credential decoder. The aggregated decoder will check each credential decoder until one - * matches the credential; this result will be returned. + * Create an aggregated principal decoder. The aggregated decoder will check each principal decoder until one + * matches the principal; this result will be returned. * * @param decoders the constituent decoders * @return the aggregated decoder diff --git a/auth/server/base/src/main/java/org/wildfly/security/auth/server/ServerAuthenticationContext.java b/auth/server/base/src/main/java/org/wildfly/security/auth/server/ServerAuthenticationContext.java index 7de3691e44d..598346366c0 100644 --- a/auth/server/base/src/main/java/org/wildfly/security/auth/server/ServerAuthenticationContext.java +++ b/auth/server/base/src/main/java/org/wildfly/security/auth/server/ServerAuthenticationContext.java @@ -895,19 +895,17 @@ private void handleOne(final Callback[] callbacks, final int idx) throws IOExcep // external method (e.g.: EXTERNAL SASL and TLS) where only authorization is necessary. We delay authentication // until we receive an authorization request. // In the future, we may want to support external methods other than TLS peer authentication - if (stateRef.get().canVerifyEvidence()) { - if (peerCerts != null) { - log.tracef("Authentication ID is null but SSL peer certificates are available. Trying to authenticate peer"); - // if SASL mechanism is used with skip-certificate-verification property then do not verifyEvidence against the security realm - if (saslSkipCertificateVerification) { - // Since evidence verification is being skipped here, ensure evidence decoding still takes place - X509PeerCertificateChainEvidence evidence = new X509PeerCertificateChainEvidence(peerCerts); - setDecodedEvidencePrincipal(evidence); - stateRef.get().setPrincipal(evidence.getDecodedPrincipal(), false); - } - else { - verifyEvidence(new X509PeerCertificateChainEvidence(peerCerts)); - } + if (stateRef.get().canVerifyEvidence() && peerCerts != null) { + log.tracef("Authentication ID is null but SSL peer certificates are available. Trying to authenticate peer"); + // if SASL mechanism is used with skip-certificate-verification property then do not verifyEvidence against the security realm + if (saslSkipCertificateVerification) { + // Since evidence verification is being skipped here, ensure evidence decoding still takes place + X509PeerCertificateChainEvidence evidence = new X509PeerCertificateChainEvidence(peerCerts); + setDecodedEvidencePrincipal(evidence); + stateRef.get().setPrincipal(evidence.getDecodedPrincipal(), false); + } + else { + verifyEvidence(new X509PeerCertificateChainEvidence(peerCerts)); } } } diff --git a/auth/server/base/src/test/java/org/wildfly/security/authz/RegexRoleMapperTest.java b/auth/server/base/src/test/java/org/wildfly/security/authz/RegexRoleMapperTest.java index 0dcd79c601a..87e660bad03 100644 --- a/auth/server/base/src/test/java/org/wildfly/security/authz/RegexRoleMapperTest.java +++ b/auth/server/base/src/test/java/org/wildfly/security/authz/RegexRoleMapperTest.java @@ -48,7 +48,7 @@ public void testRegexMapper() { iterator.next(); count++; } - assertEquals(count, 3); + assertEquals(3, count); } @Test @@ -69,7 +69,7 @@ public void testRegexMapperDoNotKeepNonMapped() { iterator.next(); count++; } - assertEquals(count, 2); + assertEquals(2,count); } @Test @@ -90,7 +90,7 @@ public void testRegexMapper2() { iterator.next(); count++; } - assertEquals(count, 2); + assertEquals(2,count); } @Test @@ -111,7 +111,7 @@ public void testRegexMapper3() { iterator.next(); count++; } - assertEquals(count, 3); + assertEquals(3,count); } @Test @@ -132,7 +132,7 @@ public void testRegexMapperEmailKeep() { iterator.next(); count++; } - assertEquals(count, 3); + assertEquals(3,count); } @Test @@ -153,7 +153,7 @@ public void testRegexMapperEmailDoNotKeep() { iterator.next(); count++; } - assertEquals(count, 2); + assertEquals( 2,count); } @Test @@ -174,7 +174,7 @@ public void testRegexMapperEmailDoNotKeepReplaceAll() { iterator.next(); count++; } - assertEquals(count, 2); + assertEquals( 2,count); } @Test(expected = IllegalArgumentException.class) @@ -238,7 +238,7 @@ public void testRegexMapperReplaceAllSubstrings() { iterator.next(); count++; } - assertEquals(count, 2); + assertEquals(2,count); } private Set createSet(String... values) { diff --git a/auth/server/base/src/test/java/org/wildfly/security/authz/RoleMappingTest.java b/auth/server/base/src/test/java/org/wildfly/security/authz/RoleMappingTest.java index 7bf83335c08..3cec301e615 100644 --- a/auth/server/base/src/test/java/org/wildfly/security/authz/RoleMappingTest.java +++ b/auth/server/base/src/test/java/org/wildfly/security/authz/RoleMappingTest.java @@ -259,6 +259,76 @@ public void testDifferenceRoles() { assertEquals(1, count); } + @Test + public void testIntersectionMappedRoles() { + Roles roles = createRoles("foo", "joe"); + + Map> mappingMap1 = new HashMap<>(); + mappingMap1.put("foo", createSet("bar", "role")); + + RoleMapper mapper1 = new MappedRoleMapper.Builder() + .setRoleMap(mappingMap1).build(); + + Map> mappingMap2 = new HashMap<>(); + mappingMap2.put("foo", createSet("bar", "test")); + + RoleMapper mapper2 = new MappedRoleMapper.Builder() + .setRoleMap(mappingMap2).build(); + + RoleMapper mapper3 = mapper1.and(mapper2); + + Roles mappedRoles = mapper3.mapRoles(roles); + + assertTrue(mappedRoles.contains("bar")); + assertFalse(mappedRoles.contains("role")); + assertFalse(mappedRoles.contains("test")); + assertFalse(mappedRoles.contains("foo")); + assertFalse(mappedRoles.contains("joe")); + + Iterator iterator = mappedRoles.iterator(); + int count = 0; + while (iterator.hasNext()) { + iterator.next(); + count++; + } + assertEquals(1, count); + } + + @Test + public void testUnionMappedRoles() { + Roles roles = createRoles("foo", "joe"); + + Map> mappingMap1 = new HashMap<>(); + mappingMap1.put("foo", createSet("bar", "role")); + + RoleMapper mapper1 = new MappedRoleMapper.Builder() + .setRoleMap(mappingMap1).build(); + + Map> mappingMap2 = new HashMap<>(); + mappingMap2.put("foo", createSet("bar", "test")); + + RoleMapper mapper2 = new MappedRoleMapper.Builder() + .setRoleMap(mappingMap2).build(); + + RoleMapper mapper3 = mapper1.or(mapper2); + + Roles mappedRoles = mapper3.mapRoles(roles); + + assertTrue(mappedRoles.contains("bar")); + assertTrue(mappedRoles.contains("role")); + assertTrue(mappedRoles.contains("test")); + assertFalse(mappedRoles.contains("foo")); + assertFalse(mappedRoles.contains("joe")); + + Iterator iterator = mappedRoles.iterator(); + int count = 0; + while (iterator.hasNext()) { + iterator.next(); + count++; + } + assertEquals(3, count); + } + private Set createSet(String... values) { HashSet set = new HashSet<>(); for (String s : values) set.add(s); diff --git a/auth/server/deprecated/pom.xml b/auth/server/deprecated/pom.xml index ec8d916d995..10b865ef5e6 100644 --- a/auth/server/deprecated/pom.xml +++ b/auth/server/deprecated/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml diff --git a/auth/server/deprecated/src/main/java/org/wildfly/security/auth/server/HttpAuthenticationFactory.java b/auth/server/deprecated/src/main/java/org/wildfly/security/auth/server/HttpAuthenticationFactory.java index 403bc2f8696..c07a8a3ed03 100644 --- a/auth/server/deprecated/src/main/java/org/wildfly/security/auth/server/HttpAuthenticationFactory.java +++ b/auth/server/deprecated/src/main/java/org/wildfly/security/auth/server/HttpAuthenticationFactory.java @@ -46,7 +46,7 @@ * {@link HttpServerAuthenticationMechanismFactory} for obtaining configured mechanisms. * * @author Darran Lofthouse - * @deprecated Use {@link org.wildfly.security.auth.server.http.HttpAuthenticationFactory} instead + * @deprecated Use {@link org.wildfly.security.auth.server.http.HttpAuthenticationFactory org.wildfly.security.auth.server.http.HttpAuthenticationFactory} instead */ @Deprecated public final class HttpAuthenticationFactory extends AbstractMechanismAuthenticationFactory { diff --git a/auth/server/deprecated/src/main/java/org/wildfly/security/auth/server/SaslAuthenticationFactory.java b/auth/server/deprecated/src/main/java/org/wildfly/security/auth/server/SaslAuthenticationFactory.java index 85636863d9b..e05351e9fcc 100644 --- a/auth/server/deprecated/src/main/java/org/wildfly/security/auth/server/SaslAuthenticationFactory.java +++ b/auth/server/deprecated/src/main/java/org/wildfly/security/auth/server/SaslAuthenticationFactory.java @@ -48,7 +48,7 @@ * A SASL server factory configuration. * * @author David M. Lloyd - * @deprecated Use {@link org.wildfly.security.auth.server.sasl.SaslAuthenticationFactory} instead + * @deprecated Use {@link org.wildfly.security.auth.server.sasl.SaslAuthenticationFactory org.wildfly.security.auth.server.sasl.SaslAuthenticationFactory} instead */ @Deprecated public final class SaslAuthenticationFactory extends AbstractMechanismAuthenticationFactory { diff --git a/auth/server/deprecated/src/main/java/org/wildfly/security/auth/server/SecurityIdentityServerMechanismFactory.java b/auth/server/deprecated/src/main/java/org/wildfly/security/auth/server/SecurityIdentityServerMechanismFactory.java index 430340ec0c1..9f63571f861 100644 --- a/auth/server/deprecated/src/main/java/org/wildfly/security/auth/server/SecurityIdentityServerMechanismFactory.java +++ b/auth/server/deprecated/src/main/java/org/wildfly/security/auth/server/SecurityIdentityServerMechanismFactory.java @@ -41,7 +41,7 @@ * the callback handler is returned instead. * * @author Darran Lofthouse - * @deprecated Use {@link org.wildfly.security.auth.server.http.SecurityIdentityServerMechanismFactory} instead + * @deprecated Use {@link org.wildfly.security.auth.server.http.SecurityIdentityServerMechanismFactory org.wildfly.security.auth.server.http.SecurityIdentityServerMechanismFactory} instead */ @Deprecated class SecurityIdentityServerMechanismFactory implements HttpServerAuthenticationMechanismFactory { diff --git a/auth/server/http/pom.xml b/auth/server/http/pom.xml index 184dd2fce67..cec7657b45d 100644 --- a/auth/server/http/pom.xml +++ b/auth/server/http/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml diff --git a/auth/server/sasl/pom.xml b/auth/server/sasl/pom.xml index 33457085a1b..8bf68a3ed45 100644 --- a/auth/server/sasl/pom.xml +++ b/auth/server/sasl/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml diff --git a/auth/server/sasl/src/main/java/org/wildfly/security/auth/server/sasl/SaslAuthenticationFactory.java b/auth/server/sasl/src/main/java/org/wildfly/security/auth/server/sasl/SaslAuthenticationFactory.java index 3e15fa92d7b..1b4cfebf6e3 100644 --- a/auth/server/sasl/src/main/java/org/wildfly/security/auth/server/sasl/SaslAuthenticationFactory.java +++ b/auth/server/sasl/src/main/java/org/wildfly/security/auth/server/sasl/SaslAuthenticationFactory.java @@ -41,6 +41,7 @@ import org.wildfly.security.sasl.util.AbstractDelegatingSaslServerFactory; import org.wildfly.security.sasl.util.AuthenticationCompleteCallbackSaslServerFactory; import org.wildfly.security.sasl.util.AuthenticationTimeoutSaslServerFactory; +import org.wildfly.security.sasl.util.PropertiesSaslServerFactory; import org.wildfly.security.sasl.util.SaslMechanismInformation; import org.wildfly.security.sasl.util.SecurityIdentitySaslServerFactory; import org.wildfly.security.sasl.util.SetMechanismInformationSaslServerFactory; @@ -121,6 +122,7 @@ public static Builder builder() { public static final class Builder extends AbstractMechanismAuthenticationFactory.Builder { private ScheduledExecutorService scheduledExecutorService; + private Map properties; /** * Construct a new instance. @@ -143,6 +145,11 @@ public Builder setFactory(final SaslServerFactory factory) { return this; } + public Builder setProperties(final Map properties) { + this.properties = properties; + return this; + } + /** * Set the scheduled executor service. * @@ -174,6 +181,10 @@ public SaslAuthenticationFactory build() { } factory = new AuthenticationTimeoutSaslServerFactory(factory, this.scheduledExecutorService); + if (this.properties != null && this.properties.size() > 0) { + factory = new PropertiesSaslServerFactory(factory, properties); + } + return new SaslAuthenticationFactory(getSecurityDomain(), getMechanismConfigurationSelector(), factory); } } diff --git a/auth/util/pom.xml b/auth/util/pom.xml index b608acd662f..6f39d9fa05e 100644 --- a/auth/util/pom.xml +++ b/auth/util/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml @@ -40,10 +40,6 @@ org.wildfly.security wildfly-elytron-auth - - org.wildfly.security - wildfly-elytron-base - org.wildfly.security wildfly-elytron-credential @@ -54,13 +50,10 @@ org.wildfly.security - wildfly-elytron-mechanism-gssapi - - - org.wildfly.security - wildfly-elytron-security-manager-action + wildfly-elytron-x500 + org.wildfly.common wildfly-common @@ -90,10 +83,5 @@ junit test - - org.jmockit - jmockit - test - diff --git a/auth/util/src/main/java/org/wildfly/security/auth/util/ElytronMessages.java b/auth/util/src/main/java/org/wildfly/security/auth/util/ElytronMessages.java index 9c374ce17ab..2d187ab0e6c 100644 --- a/auth/util/src/main/java/org/wildfly/security/auth/util/ElytronMessages.java +++ b/auth/util/src/main/java/org/wildfly/security/auth/util/ElytronMessages.java @@ -19,9 +19,6 @@ package org.wildfly.security.auth.util; import java.io.IOException; -import java.security.GeneralSecurityException; - -import javax.security.auth.login.LoginException; import org.jboss.logging.BasicLogger; import org.jboss.logging.Logger; @@ -51,32 +48,32 @@ interface ElytronMessages extends BasicLogger { ElytronMessages log = Logger.getMessageLogger(ElytronMessages.class, "org.wildfly.security"); - @Message(id = 3, value = "This builder has already been built") - IllegalStateException builderAlreadyBuilt(); + //@Message(id = 3, value = "This builder has already been built") + //IllegalStateException builderAlreadyBuilt(); @Message(id = 1065, value = "Pattern requires a capture group") IllegalArgumentException patternRequiresCaptureGroup(); - @Message(id = 1121, value = "Unable to perform initial JAAS login.") - GeneralSecurityException unableToPerformInitialLogin(@Cause LoginException cause); + //@Message(id = 1121, value = "Unable to perform initial JAAS login.") + //GeneralSecurityException unableToPerformInitialLogin(@Cause LoginException cause); - @Message(id = 1122, value = "No Kerberos principals found.") - GeneralSecurityException noKerberosPrincipalsFound(); + //@Message(id = 1122, value = "No Kerberos principals found.") + //GeneralSecurityException noKerberosPrincipalsFound(); - @Message(id = 1123, value = "Too many Kerberos principals found.") - GeneralSecurityException tooManyKerberosPrincipalsFound(); + //@Message(id = 1123, value = "Too many Kerberos principals found.") + //GeneralSecurityException tooManyKerberosPrincipalsFound(); - @Message(id = 1160, value = "KeyTab [%s] does not exists.") - IOException keyTabDoesNotExists(String keyTab); + //@Message(id = 1160, value = "KeyTab [%s] does not exists.") + //IOException keyTabDoesNotExists(String keyTab); - @Message(id = 1161, value = "No keys for Kerberos principal [%s] was found in KeyTab [%s].") - IOException noKeysForPrincipalInKeyTab(String principal, String keyTab); + //@Message(id = 1161, value = "No keys for Kerberos principal [%s] was found in KeyTab [%s].") + //IOException noKeysForPrincipalInKeyTab(String principal, String keyTab); - @Message(id = 1165, value = "Initial JAAS login skipped as it has failed in last %d seconds") - GeneralSecurityException initialLoginSkipped(long seconds); + //@Message(id = 1165, value = "Initial JAAS login skipped as it has failed in last %d seconds") + //GeneralSecurityException initialLoginSkipped(long seconds); - @Message(id = 3031, value = "Too many KerberosTicket instances in private credentials") - GeneralSecurityException tooManyKerberosTicketsFound(); + //@Message(id = 3031, value = "Too many KerberosTicket instances in private credentials") + //GeneralSecurityException tooManyKerberosTicketsFound(); @Message(id = 17000, value = "Failed to create credential") IOException xmlFailedToCreateCredential(@Cause Throwable cause); diff --git a/auth/util/src/main/java/org/wildfly/security/auth/util/GSSCredentialSecurityFactory.java b/auth/util/src/main/java/org/wildfly/security/auth/util/GSSCredentialSecurityFactory.java deleted file mode 100644 index 0d751766a44..00000000000 --- a/auth/util/src/main/java/org/wildfly/security/auth/util/GSSCredentialSecurityFactory.java +++ /dev/null @@ -1,560 +0,0 @@ -/* - * JBoss, Home of Professional Open Source. - * Copyright 2016 Red Hat, Inc., and individual contributors - * as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.wildfly.security.auth.util; - -import static javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag.REQUIRED; -import static org.wildfly.common.Assert.checkNotNullParam; -import static org.wildfly.security.auth.util.ElytronMessages.log; - -import java.io.File; -import java.io.IOException; -import java.security.AccessController; -import java.security.GeneralSecurityException; -import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.UnaryOperator; - -import javax.security.auth.RefreshFailedException; -import javax.security.auth.Subject; -import javax.security.auth.kerberos.KerberosPrincipal; -import javax.security.auth.kerberos.KerberosTicket; -import javax.security.auth.kerberos.KeyTab; -import javax.security.auth.login.AppConfigurationEntry; -import javax.security.auth.login.Configuration; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.ietf.jgss.GSSCredential; -import org.ietf.jgss.GSSException; -import org.ietf.jgss.GSSManager; -import org.ietf.jgss.GSSName; -import org.ietf.jgss.Oid; -import org.wildfly.common.function.ExceptionSupplier; -import org.wildfly.security.SecurityFactory; -import org.wildfly.security.auth.callback.FastUnsupportedCallbackException; -import org.wildfly.security.credential.GSSKerberosCredential; -import org.wildfly.security.manager.action.SetContextClassLoaderAction; - -/** - * A {@link SecurityFactory} implementation for obtaining a {@link GSSCredential}. - * - * @author Darran Lofthouse - * @deprecated Use {@link org.wildfly.security.mechanism.gssapi.GSSCredentialSecurityFactory} instead - */ -@Deprecated -public final class GSSCredentialSecurityFactory implements SecurityFactory { - - private static final String KRB5LoginModule = "com.sun.security.auth.module.Krb5LoginModule"; - private static final long ONE_SECOND = 1000; - - public static final Oid KERBEROS_V5; - public static final Oid SPNEGO; - - static { - try { - KERBEROS_V5 = new Oid("1.2.840.113554.1.2.2"); - SPNEGO = new Oid("1.3.6.1.5.5.2"); - } catch (GSSException e) { - throw new RuntimeException("Unable to initialise Oid", e); - } - } - - private final int minimumRemainingLifetime; - private final ExceptionSupplier rawSupplier; - - private final AtomicReference cachedCredentialReference = new AtomicReference<>(); - private final UnaryOperator credentialOperator; - - - GSSCredentialSecurityFactory(final int minimumRemainingLifetime, final ExceptionSupplier rawSupplier) { - this.minimumRemainingLifetime = minimumRemainingLifetime; - this.rawSupplier = rawSupplier; - credentialOperator = this::update; - } - - private GSSKerberosCredential update(GSSKerberosCredential original) { - GSSKerberosCredential result = null; - try { - if (original != null) { - if (testIsValid(original.getGssCredential()) && testIsValid(original.getKerberosTicket())) { - result = original; - } - } - - if (result == null) { - log.trace("No valid cached credential, obtaining new one..."); - result = rawSupplier.get(); - log.tracef("Obtained GSSCredentialCredential [%s]", result); - } else { - log.tracef("Used cached GSSCredential [%s]", result); - } - } catch (GeneralSecurityException e) { - throw new IllegalStateException(e); - } - - return result; - } - - private boolean testIsValid(GSSCredential gssCredential) throws GeneralSecurityException { - checkNotNullParam("gssCredential", gssCredential); - boolean stillValid; - try { - int remainingLifetime = gssCredential.getRemainingLifetime(); - log.tracef("Remaining GSSCredential Lifetime = %d", remainingLifetime); - stillValid = remainingLifetime >= minimumRemainingLifetime; - } catch (GSSException e) { - throw new GeneralSecurityException(e); - } - - log.tracef("testIsValid(GSSCredential)=%b", stillValid); - return stillValid; - } - - private boolean testIsValid(KerberosTicket ticket) { - if (ticket == null) { - log.trace("No cached KerberosTicket"); - return true; // If there is no ticket it is not "invalid". - } - - Date endTime = ticket.getEndTime(); - log.tracef("KerberosTicket.getEndTime()=%s", endTime); - boolean stillValid = endTime != null && System.currentTimeMillis() < endTime.getTime() - (minimumRemainingLifetime * ONE_SECOND); - - if (!stillValid) { - log.trace("Attempting to refresh existing KerberosTicket."); - try { - ticket.refresh(); - log.tracef("KerberosTicket refreshed until %s", ticket.getEndTime()); - stillValid = true; - } catch (RefreshFailedException e) { - log.tracef("Unable to refresh KerberosTicket.", e); - } - } - - log.tracef("testIsValid(KerberosTicket)=%b", stillValid); - return stillValid; - } - - @Override - public GSSKerberosCredential create() throws GeneralSecurityException { - try { - return cachedCredentialReference.updateAndGet(credentialOperator); - } catch (RuntimeException e) { - if (e.getCause() instanceof GSSException) { - throw new GeneralSecurityException(e.getCause()); - } else if (e.getCause() instanceof GeneralSecurityException) { - throw (GeneralSecurityException) e.getCause(); - } - - throw e; - } - } - - - /** - * Obtain a new {@link Builder} capable of building a {@link GSSCredentialSecurityFactory}. - * - * @return a new {@link Builder} capable of building a {@link GSSCredentialSecurityFactory}. - */ - public static Builder builder() { - return new Builder(); - } - - /** - * A builder for GSS credential security factories. - */ - public static class Builder { - - private boolean built = false; - private List mechanismOids = new ArrayList<>(); - private String principal; - private File keyTab; - private boolean isServer; - private boolean obtainKerberosTicket; - private int minimumRemainingLifetime; - private int requestLifetime; - private boolean debug; - private boolean wrapGssCredential; - private boolean checkKeyTab; - private volatile long lastFailTime = 0; - private long failCache = 0; - private Map options; - - Builder() { - } - - /** - * Set the keytab file to obtain the identity. - * - * @param keyTab the keytab file to obtain the identity. - * @return {@code this} to allow chaining. - */ - public Builder setKeyTab(final File keyTab) { - assertNotBuilt(); - this.keyTab = keyTab; - - return this; - } - - /** - * Set if the credential returned from the factory is representing the server side of the connection. - * - * @param isServer is the credential returned from the factory is representing the server side of the connection. - * @return {@code this} to allow chaining. - */ - public Builder setIsServer(final boolean isServer) { - assertNotBuilt(); - this.isServer = isServer; - - return this; - } - - /** - * Set if the KerberosTicket should also be obtained and associated with the Credential/ - * - * @param obtainKerberosTicket if the KerberosTicket should also be obtained and associated with the Credential/ - * @return {@code this} to allow chaining. - */ - public Builder setObtainKerberosTicket(final boolean obtainKerberosTicket) { - assertNotBuilt(); - this.obtainKerberosTicket = obtainKerberosTicket; - - return this; - } - - /** - * Once the factory has been called once it will cache the resulting {@link GSSCredential}, this setting - * defines how much life it must have left in seconds for it to be re-used. - * - * @param minimumRemainingLifetime the time in seconds of life a {@link GSSCredential} must have to be re-used. - * @return {@code this} to allow chaining. - */ - public Builder setMinimumRemainingLifetime(final int minimumRemainingLifetime) { - assertNotBuilt(); - this.minimumRemainingLifetime = minimumRemainingLifetime; - - return this; - } - - /** - * Set the lifetime to request newly created credentials are valid for. - * - * @param requestLifetime the lifetime to request newly created credentials are valid for. - * @return {@code this} to allow chaining. - */ - public Builder setRequestLifetime(final int requestLifetime) { - assertNotBuilt(); - this.requestLifetime = requestLifetime < 0 ? GSSCredential.INDEFINITE_LIFETIME : requestLifetime; - - return this; - } - - /** - * Add an {@link Oid} for a mechanism the {@link GSSCredential} should be usable with. - * - * @param oid the {@link Oid} for the mechanism the {@link GSSCredential} should be usable with. - * @return {@code this} to allow chaining. - */ - public Builder addMechanismOid(final Oid oid) { - assertNotBuilt(); - mechanismOids.add(checkNotNullParam("oid", oid)); - - return this; - } - - /** - * Set the principal name for the initial authentication from the KeyTab. - * - * @param principal the principal name for the initial authentication from the KeyTab. - * @return {@code this} to allow chaining. - */ - public Builder setPrincipal(final String principal) { - assertNotBuilt(); - this.principal = principal; - - return this; - } - - /** - * Set if debug logging should be enabled for the JAAS authentication portion of obtaining the {@link GSSCredential} - * - * @param debug if debug logging should be enabled for the JAAS authentication portion of obtaining the {@link GSSCredential} - * @return {@code this} to allow chaining. - */ - public Builder setDebug(final boolean debug) { - assertNotBuilt(); - this.debug = debug; - - return this; - } - - /** - * Set if the constructed {@link GSSCredential} should be wrapped to prevent improper credential disposal or not. - * - * @param value {@code true} if the constructed {@link GSSCredential} should be wrapped; {@code false} otherwise. - * @return {@code this} to allow chaining. - */ - public Builder setWrapGssCredential(final boolean value) { - assertNotBuilt(); - this.wrapGssCredential = value; - - return this; - } - - /** - * Set if keytab file existence and principal presence in it should be checked on factory build. - * - * @param value {@code true} if keytab file should be checked; {@code false} otherwise. - * @return {@code this} to allow chaining. - */ - public Builder setCheckKeyTab(final boolean value) { - assertNotBuilt(); - this.checkKeyTab = value; - - return this; - } - - /** - * Set other configuration options for {@code Krb5LoginModule} - * - * @param options the configuration options which will be appended to options passed into {@code Krb5LoginModule} - * @return {@code this} to allow chaining. - */ - public Builder setOptions(final Map options) { - assertNotBuilt(); - this.options = options; - - return this; - } - - /** - * Set amount of seconds before new try to obtain {@link GSSCredential} should be done if it has failed last time. - * Allows to prevent long waiting to unavailable KDC on every authentication. - * - * @param seconds amount of seconds to cache fail state of the credential factory; 0 if the cache should not be used. - * @return {@code this} to allow chaining. - */ - public Builder setFailCache(final long seconds) { - assertNotBuilt(); - this.failCache = seconds; - - return this; - } - - /** - * Construct a new {@link GSSKerberosCredential} security factory instance. - * - * @return the built factory instance - * @throws IOException when unable to use given KeyTab - */ - public SecurityFactory build() throws IOException { - assertNotBuilt(); - if (checkKeyTab) { - checkKeyTab(); - } - - final Configuration configuration = createConfiguration(); - - built = true; - return new GSSCredentialSecurityFactory(minimumRemainingLifetime > 0 ? minimumRemainingLifetime : 0, () -> createGSSCredential(configuration)); - } - - private GSSKerberosCredential createGSSCredential(Configuration configuration) throws GeneralSecurityException { - if (failCache != 0 && System.currentTimeMillis() - lastFailTime < failCache * 1000) { - throw log.initialLoginSkipped(failCache); - } - - final Subject subject = new Subject(); - - try { - final ClassLoader oldCl = doPrivileged(new SetContextClassLoaderAction(Builder.class.getClassLoader())); - final LoginContext lc; - try { - lc = new LoginContext("KDC", subject, (c) -> { - throw new FastUnsupportedCallbackException(c[0]); - }, configuration); - } finally { - doPrivileged(new SetContextClassLoaderAction(oldCl)); - } - log.tracef("Logging in using LoginContext and subject [%s]", subject); - lc.login(); - log.tracef("Logging in using LoginContext and subject [%s] succeed", subject); - - final KerberosTicket kerberosTicket; - if (obtainKerberosTicket) { - Set kerberosTickets = doPrivileged((PrivilegedAction>) () -> subject.getPrivateCredentials(KerberosTicket.class)); - if (kerberosTickets.size() > 1) { - throw log.tooManyKerberosTicketsFound(); - } - kerberosTicket = kerberosTickets.size() == 1 ? kerberosTickets.iterator().next() : null; - } else { - kerberosTicket = null; - } - - final GSSManager manager = GSSManager.getInstance(); - return Subject.doAs(subject, (PrivilegedExceptionAction) () -> { - Set principals = subject.getPrincipals(KerberosPrincipal.class); - if (principals.size() < 1) { - throw log.noKerberosPrincipalsFound(); - } else if (principals.size() > 1) { - throw log.tooManyKerberosPrincipalsFound(); - } - KerberosPrincipal principal = principals.iterator().next(); - log.tracef("Creating GSSName for Principal '%s'", principal); - GSSName name = manager.createName(principal.getName(), GSSName.NT_USER_NAME, KERBEROS_V5); - - if (wrapGssCredential) { - return new GSSKerberosCredential(wrapCredential(manager.createCredential(name, requestLifetime, mechanismOids.toArray(new Oid[mechanismOids.size()]), - isServer ? GSSCredential.ACCEPT_ONLY : GSSCredential.INITIATE_ONLY)), kerberosTicket); - } - return new GSSKerberosCredential(manager.createCredential(name, requestLifetime, mechanismOids.toArray(new Oid[mechanismOids.size()]), - isServer ? GSSCredential.ACCEPT_ONLY : GSSCredential.INITIATE_ONLY), kerberosTicket); - }); - - } catch (LoginException e) { - if (failCache != 0) { - lastFailTime = System.currentTimeMillis(); - } - throw log.unableToPerformInitialLogin(e); - } catch (PrivilegedActionException e) { - if (e.getCause() instanceof GeneralSecurityException) { - throw (GeneralSecurityException) e.getCause(); - } - throw new GeneralSecurityException(e.getCause()); - } - } - - private static T doPrivileged(final PrivilegedAction action) { - return System.getSecurityManager() != null ? AccessController.doPrivileged(action) : action.run(); - } - - private void checkKeyTab() throws IOException { - KeyTab kt = KeyTab.getInstance(keyTab); - if (!kt.exists()) { - throw log.keyTabDoesNotExists(keyTab.getAbsolutePath()); - } - if (kt.getKeys(new KerberosPrincipal(principal)).length == 0) { - throw log.noKeysForPrincipalInKeyTab(principal, keyTab.getAbsolutePath()); - } - } - - private Configuration createConfiguration() throws IOException { - Map options = new HashMap<>(); - if (debug) { - options.put("debug", "true"); - } - options.put("principal", principal); - - options.put("storeKey", "true"); - options.put("useKeyTab", "true"); - if (keyTab != null) options.put("keyTab", keyTab.getAbsolutePath()); - options.put("isInitiator", (isServer && !obtainKerberosTicket) ? "false" : "true"); - - if (this.options != null) { - options.putAll(this.options); - } - - log.tracef("Created LoginContext configuration: %s", options.toString()); - - final AppConfigurationEntry[] aceArray = new AppConfigurationEntry[] { - new AppConfigurationEntry(KRB5LoginModule, REQUIRED, options) - }; - - return new Configuration() { - - @Override - public AppConfigurationEntry[] getAppConfigurationEntry(String name) { - assert "KDC".equals(name); - return aceArray; - } - - }; - } - - private void assertNotBuilt() { - if (built) { - throw log.builderAlreadyBuilt(); - } - } - - } - - private static GSSCredential wrapCredential(final GSSCredential credential) { - return new GSSCredential() { - - @Override - public int getUsage(Oid mech) throws GSSException { - return credential.getUsage(mech); - } - - @Override - public int getUsage() throws GSSException { - return credential.getUsage(); - } - - @Override - public int getRemainingLifetime() throws GSSException { - return credential.getRemainingLifetime(); - } - - @Override - public int getRemainingInitLifetime(Oid mech) throws GSSException { - return credential.getRemainingInitLifetime(mech); - } - - @Override - public int getRemainingAcceptLifetime(Oid mech) throws GSSException { - return credential.getRemainingAcceptLifetime(mech); - } - - @Override - public GSSName getName(Oid mech) throws GSSException { - return credential.getName(mech); - } - - @Override - public GSSName getName() throws GSSException { - return credential.getName(); - } - - @Override - public Oid[] getMechs() throws GSSException { - return credential.getMechs(); - } - - @Override - public void dispose() throws GSSException { - // Prevent disposal of our credential. - } - - @Override - public void add(GSSName name, int initLifetime, int acceptLifetime, Oid mech, int usage) throws GSSException { - credential.add(name, initLifetime, acceptLifetime, mech, usage); - } - - }; - } -} diff --git a/base/pom.xml b/base/pom.xml index 5ff7e107802..1c30ef98cd9 100644 --- a/base/pom.xml +++ b/base/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT 4.0.0 diff --git a/credential/base/pom.xml b/credential/base/pom.xml index c85711ed292..2c98ff8943c 100644 --- a/credential/base/pom.xml +++ b/credential/base/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/credential/base/src/main/java/org/wildfly/security/key/KeyUtil.java b/credential/base/src/main/java/org/wildfly/security/key/KeyUtil.java index 234734c96bb..4ab3edd0236 100644 --- a/credential/base/src/main/java/org/wildfly/security/key/KeyUtil.java +++ b/credential/base/src/main/java/org/wildfly/security/key/KeyUtil.java @@ -37,6 +37,7 @@ import java.security.interfaces.RSAMultiPrimePrivateCrtKey; import java.security.interfaces.RSAPrivateKey; import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.DSAParameterSpec; import java.security.spec.ECParameterSpec; import java.util.Arrays; import java.util.Objects; @@ -99,7 +100,9 @@ public static

P getParameters(Key key, Class< } else if (key instanceof RSAKey && paramSpecClass.isAssignableFrom(RSAParameterSpec.class)) { return paramSpecClass.cast(new RSAParameterSpec((RSAKey) key)); } else if (key instanceof DSAKey && paramSpecClass.isAssignableFrom(DSAParams.class)) { - return paramSpecClass.cast(((DSAKey) key).getParams()); + final DSAKey dsaKey = (DSAKey) key; + final DSAParams dsaParams = dsaKey.getParams(); + return paramSpecClass.cast(new DSAParameterSpec(dsaParams.getP(), dsaParams.getQ(), dsaParams.getG())); } else if (key instanceof ECKey && paramSpecClass.isAssignableFrom(ECParameterSpec.class)) { return paramSpecClass.cast(((ECKey) key).getParams()); } else if (key instanceof DHKey && paramSpecClass.isAssignableFrom(DHParameterSpec.class)) { diff --git a/credential/base/src/main/java/org/wildfly/security/keystore/PasswordKeyStoreSpi.java b/credential/base/src/main/java/org/wildfly/security/keystore/PasswordKeyStoreSpi.java index b5c592f6303..3cf4959452a 100644 --- a/credential/base/src/main/java/org/wildfly/security/keystore/PasswordKeyStoreSpi.java +++ b/credential/base/src/main/java/org/wildfly/security/keystore/PasswordKeyStoreSpi.java @@ -91,7 +91,9 @@ public void engineSetEntry(final String alias, final KeyStore.Entry entry, final if (protParam != null) { throw log.keyCannotBeProtected(alias); } - HashMap map, newMap; + HashMap map; + HashMap newMap; + do { map = pwRef.get(); if (map == null) { diff --git a/credential/source/deprecated/pom.xml b/credential/source/deprecated/pom.xml index 2f625828242..2c91de751de 100644 --- a/credential/source/deprecated/pom.xml +++ b/credential/source/deprecated/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml diff --git a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CallbackHandlerCredentialSource.java b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CallbackHandlerCredentialSource.java index 509491c2252..deb676a58e8 100644 --- a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CallbackHandlerCredentialSource.java +++ b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CallbackHandlerCredentialSource.java @@ -37,7 +37,7 @@ * A credential source which is backed by a callback handler. * * @author David M. Lloyd - * @deprecated Use {@link org.wildfly.security.credential.source.impl.CallbackHandlerCredentialSource} instead + * @deprecated Use {@link org.wildfly.security.credential.source.impl.CallbackHandlerCredentialSource org.wildfly.security.credential.source.impl.CallbackHandlerCredentialSource} instead */ @Deprecated public final class CallbackHandlerCredentialSource implements CredentialSource { diff --git a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CommandCredentialSource.java b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CommandCredentialSource.java index b5914bda876..2265e3da231 100644 --- a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CommandCredentialSource.java +++ b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CommandCredentialSource.java @@ -52,7 +52,7 @@ * A credential source which acquires a credential from the command line. * * @author David M. Lloyd - * @deprecated Use {@link org.wildfly.security.credential.source.impl.CommandCredentialSource} instead + * @deprecated Use {@link org.wildfly.security.credential.source.impl.CommandCredentialSource org.wildfly.security.credential.source.impl.CommandCredentialSource} instead */ @Deprecated public final class CommandCredentialSource implements CredentialSource { diff --git a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CredentialStoreCredentialSource.java b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CredentialStoreCredentialSource.java index 76e35fd81f9..234b0d32d66 100644 --- a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CredentialStoreCredentialSource.java +++ b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CredentialStoreCredentialSource.java @@ -37,7 +37,7 @@ * * @author David M. Lloyd * @author Peter Skopek - * @deprecated Use {@link org.wildfly.security.credential.source.impl.CredentialStoreCredentialSource} instead + * @deprecated Use {@link org.wildfly.security.credential.source.impl.CredentialStoreCredentialSource org.wildfly.security.credential.source.impl.CredentialStoreCredentialSource} instead */ @Deprecated public final class CredentialStoreCredentialSource implements CredentialSource { diff --git a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/FactoryCredentialSource.java b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/FactoryCredentialSource.java index 3e5944f3673..088ff37a36e 100644 --- a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/FactoryCredentialSource.java +++ b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/FactoryCredentialSource.java @@ -31,7 +31,7 @@ * A credential source which is backed by a credential security factory. * * @author Martin Mazanek - * @deprecated Use {@link org.wildfly.security.credential.source.impl.FactoryCredentialSource} instead + * @deprecated Use {@link org.wildfly.security.credential.source.impl.FactoryCredentialSource org.wildfly.security.credential.source.impl.FactoryCredentialSource} instead */ @Deprecated public class FactoryCredentialSource implements CredentialSource { diff --git a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/KeyStoreCredentialSource.java b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/KeyStoreCredentialSource.java index 5b12588b703..40087d618b4 100644 --- a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/KeyStoreCredentialSource.java +++ b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/KeyStoreCredentialSource.java @@ -43,7 +43,7 @@ * A credential source which is backed by a key store entry. * * @author David M. Lloyd - * @deprecated Use {@link org.wildfly.security.credential.source.impl.KeyStoreCredentialSource} instead + * @deprecated Use {@link org.wildfly.security.credential.source.impl.KeyStoreCredentialSource org.wildfly.security.credential.source.impl.KeyStoreCredentialSource} instead */ @Deprecated public final class KeyStoreCredentialSource implements CredentialSource { diff --git a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/LocalKerberosCredentialSource.java b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/LocalKerberosCredentialSource.java index 11e521dc5ed..c27d20012b0 100644 --- a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/LocalKerberosCredentialSource.java +++ b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/LocalKerberosCredentialSource.java @@ -40,7 +40,7 @@ * * Successful obtaining from cache requires set system property {@code javax.security.auth.useSubjectCredsOnly} to {@code false}. * - * @deprecated Kerberos based authentication mechanism obtains credential himself, see {@link org.wildfly.security.credential.source.impl.LocalKerberosCredentialSource} to use with the new wildfly-elytron-credential-source-impl module + * @deprecated Kerberos based authentication mechanism obtains credential himself, see {@link org.wildfly.security.credential.source.impl.LocalKerberosCredentialSource org.wildfly.security.credential.source.impl.LocalKerberosCredentialSource} to use with the new wildfly-elytron-credential-source-impl module * * @author Jan Kalina */ diff --git a/credential/source/impl/pom.xml b/credential/source/impl/pom.xml index d87ea7011e5..64e2fb398a1 100644 --- a/credential/source/impl/pom.xml +++ b/credential/source/impl/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml diff --git a/credential/store/pom.xml b/credential/store/pom.xml index b0dda23a278..ac6bc803d5c 100644 --- a/credential/store/pom.xml +++ b/credential/store/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/credential/store/src/main/java/org/wildfly/security/credential/store/impl/VaultObjectInputStream.java b/credential/store/src/main/java/org/wildfly/security/credential/store/impl/VaultObjectInputStream.java index c71e9202ee2..566a144e16c 100644 --- a/credential/store/src/main/java/org/wildfly/security/credential/store/impl/VaultObjectInputStream.java +++ b/credential/store/src/main/java/org/wildfly/security/credential/store/impl/VaultObjectInputStream.java @@ -31,9 +31,10 @@ final class VaultObjectInputStream extends ObjectInputStream { protected Class resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException { final String name = desc.getName(); - switch (name) { - case SecurityVaultData.PICKETBOX_CLASS_NAME: return SecurityVaultData.class; - default: return super.resolveClass(desc); + if (name.equals(SecurityVaultData.PICKETBOX_CLASS_NAME)) { + return SecurityVaultData.class; + } else { + return super.resolveClass(desc); } } } diff --git a/digest/pom.xml b/digest/pom.xml index 506d3b75783..fd791d6cfde 100644 --- a/digest/pom.xml +++ b/digest/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT 4.0.0 diff --git a/dynamic-ssl/pom.xml b/dynamic-ssl/pom.xml new file mode 100644 index 00000000000..0149158d921 --- /dev/null +++ b/dynamic-ssl/pom.xml @@ -0,0 +1,82 @@ + + + + org.wildfly.security + wildfly-elytron-parent + 2.6.1.CR1-SNAPSHOT + + + 4.0.0 + + wildfly-elytron-dynamic-ssl + + WildFly Elytron - Dynamic SSL + WildFly Security Dynamic SSL Implementation + + + org.jboss.logging + jboss-logging-annotations + provided + + + org.jboss.logging + jboss-logging + provided + + + org.jboss.logging + jboss-logging-processor + provided + + + org.jboss.logmanager + jboss-logmanager + provided + + + org.wildfly.security + wildfly-elytron-client + + + org.wildfly.security + wildfly-elytron-tests-common + test + test-jar + + + org.kohsuke.metainf-services + metainf-services + provided + + + org.wildfly.common + wildfly-common + compile + + + + + junit + junit + test + + + + com.squareup.okhttp3 + mockwebserver + test + + + org.mock-server + mockserver-netty + test + + + org.wildfly.client + wildfly-client-config + test + + + diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContext.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContext.java new file mode 100644 index 00000000000..500d03badc2 --- /dev/null +++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContext.java @@ -0,0 +1,56 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.dynamic.ssl; + +import org.wildfly.security.auth.client.ActiveSessionsSSLContext; + +import javax.net.ssl.SSLContext; +import java.security.NoSuchAlgorithmException; + +/** + * SSLContext that resolves which SSLContext to use based on peer's host and port information. + * + * @author Diana Krepinska + */ +public final class DynamicSSLContext extends SSLContext implements ActiveSessionsSSLContext { + + private static SSLContext resolverSSLContext(DynamicSSLContextSPI dynamicSSLContextSPIImpl) throws NoSuchAlgorithmException, DynamicSSLContextException { + return dynamicSSLContextSPIImpl.getConfiguredDefault() == null ? + SSLContext.getDefault() : dynamicSSLContextSPIImpl.getConfiguredDefault(); + } + + /** + * This constructor uses ServiceLoader to find provider of DynamicSSLContextSPI on classpath. + */ + public DynamicSSLContext() throws NoSuchAlgorithmException { + // this does not use provider and protocol from DynamicSSLContextSPI implementation found on classpath + // to avoid this ServiceLoader.load would have to be called 3 times in separate static method + super(new DynamicSSLContextSpiImpl(), SSLContext.getDefault().getProvider(), SSLContext.getDefault().getProtocol()); + } + + /** + * This constructor uses received DynamicSSLContextSPI implementation or finds it on classpath if received is null. + * + * @param dynamicSSLContextSPIImpl DynamicSSLContextSPI implementation to use. If null then ServiceLoader is used to locate it on classpath. + */ + public DynamicSSLContext(DynamicSSLContextSPI dynamicSSLContextSPIImpl) throws NoSuchAlgorithmException, DynamicSSLContextException { + super(new DynamicSSLContextSpiImpl(dynamicSSLContextSPIImpl), + resolverSSLContext(dynamicSSLContextSPIImpl).getProvider(), + resolverSSLContext(dynamicSSLContextSPIImpl).getProtocol()); + } +} diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextException.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextException.java new file mode 100644 index 00000000000..a47dbea76b0 --- /dev/null +++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextException.java @@ -0,0 +1,42 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.dynamic.ssl; + +/** + * Exception to indicate a failure related to the DynamicSSLContext. + * + * @author Diana Krepinska + */ +public class DynamicSSLContextException extends Exception { + private static final long serialVersionUID = 894798122053539237L; + + public DynamicSSLContextException() { + } + + public DynamicSSLContextException(String msg) { + super(msg); + } + + public DynamicSSLContextException(String message, Throwable cause) { + super(message, cause); + } + + public DynamicSSLContextException(Throwable cause) { + super(cause); + } +} diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextImpl.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextImpl.java new file mode 100644 index 00000000000..153f59c5a0d --- /dev/null +++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextImpl.java @@ -0,0 +1,91 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.dynamic.ssl; + +import org.kohsuke.MetaInfServices; +import org.wildfly.security.auth.client.AuthenticationContext; +import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient; + +import javax.net.ssl.SSLContext; +import java.net.URI; +import java.security.AccessController; +import java.security.GeneralSecurityException; +import java.security.PrivilegedAction; +import java.util.List; + +import static org.wildfly.common.Assert.checkNotNullParam; + +/** + * Elytron client implementation of DynamicSSLContextSPI. It uses configuration from either provided instance of AuthenticationContext + * or from current AuthenticationContext if a configuration was not provided. + * + * @author Diana Krepinska (Vilkolakova) + */ +@MetaInfServices(value = DynamicSSLContextSPI.class) +public class DynamicSSLContextImpl implements DynamicSSLContextSPI { + + private final AuthenticationContextConfigurationClient AUTH_CONTEXT_CLIENT = + AccessController.doPrivileged((PrivilegedAction) AuthenticationContextConfigurationClient::new); + private AuthenticationContext authenticationContext; + private SSLContext configuredDefaultSSLContext; + private List configuredSSLContexts; + + public DynamicSSLContextImpl() throws GeneralSecurityException { + } + + public DynamicSSLContextImpl(AuthenticationContext authenticationContext) throws GeneralSecurityException { + checkNotNullParam("authenticationContext", authenticationContext); + this.authenticationContext = authenticationContext; + this.configuredSSLContexts = AUTH_CONTEXT_CLIENT.getConfiguredSSLContexts(authenticationContext); + this.configuredDefaultSSLContext = AUTH_CONTEXT_CLIENT.getDefaultSSLContext(authenticationContext); + } + + @Override + public SSLContext getConfiguredDefault() throws DynamicSSLContextException { + if (this.configuredDefaultSSLContext != null) { + return this.configuredDefaultSSLContext; + } + try { + return AUTH_CONTEXT_CLIENT.getDefaultSSLContext(AuthenticationContext.captureCurrent()); + } catch (GeneralSecurityException e) { + throw ElytronMessages.log.cannotObtainDefaultSSLContext(e); + } + } + + @Override + public List getConfiguredSSLContexts() throws DynamicSSLContextException { + if (this.configuredSSLContexts != null) { + return this.configuredSSLContexts; + } + try { + return AUTH_CONTEXT_CLIENT.getConfiguredSSLContexts(AuthenticationContext.captureCurrent()); + } catch (GeneralSecurityException e) { + throw ElytronMessages.log.cannotObtainConfiguredSSLContexts(e); + } + } + + @Override + public SSLContext getSSLContext(URI uri) throws DynamicSSLContextException { + try { + return AUTH_CONTEXT_CLIENT.getSSLContext(uri, authenticationContext == null ? AuthenticationContext.captureCurrent() : authenticationContext); + } catch (GeneralSecurityException e) { + throw ElytronMessages.log.cannotObtainSSLContextForGivenURI(e); + } + } +} diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSPI.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSPI.java new file mode 100644 index 00000000000..91985a60fe3 --- /dev/null +++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSPI.java @@ -0,0 +1,52 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.dynamic.ssl; + +import javax.net.ssl.SSLContext; +import java.net.URI; +import java.util.List; + +/** + * This interface provides configuration that is used by DynamicSSLContext. + * + * @author Diana Krepinska + */ +public interface DynamicSSLContextSPI { + + /** + * Get SSLContext that will be used as a default, eg. when no URI is provided. + * + * @return configured default SSLContext + */ + SSLContext getConfiguredDefault() throws DynamicSSLContextException; + + /** + * Get list of all configured SSLContexts. This is used to obtain cipher suites supported by all SSLContexts. + * + * @return list of all configured SSLContexts + */ + List getConfiguredSSLContexts() throws DynamicSSLContextException; + + /** + * Get the SSLContext that matches the given URI. + * + * @return SSLContext that matches the given URI + */ + SSLContext getSSLContext(URI uri) throws DynamicSSLContextException; +} diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSpiImpl.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSpiImpl.java new file mode 100644 index 00000000000..d3085b96366 --- /dev/null +++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSpiImpl.java @@ -0,0 +1,145 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.dynamic.ssl; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLContextSpi; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Iterator; +import java.util.ServiceLoader; + +/** + * SSLContextSpi that uses ServiceLoader to find implementations of DynamicSSLContextSPI. + * DynamicSSLContextSPI implementation is being used to obtain authentication configuration for DynamicSSLContext. + * if no provider is found then SSLContext.getDefault() is used. + * + * @author Diana Krepinska + */ +final class DynamicSSLContextSpiImpl extends SSLContextSpi { + + private final DynamicSSLContextSPI dynamicSSLContextImpl; + private volatile SSLSocketFactory sslSocketFactory; + + DynamicSSLContextSpiImpl() { + this(null); + } + + DynamicSSLContextSpiImpl(DynamicSSLContextSPI dynamicSSLContextSPIImpl) { + if (dynamicSSLContextSPIImpl != null) { + dynamicSSLContextImpl = dynamicSSLContextSPIImpl; + } else { + Iterator dynamicSSLContextSPIIterator = ServiceLoader.load(DynamicSSLContextSPI.class).iterator(); + if (dynamicSSLContextSPIIterator.hasNext()) { + dynamicSSLContextImpl = dynamicSSLContextSPIIterator.next(); + } else { + dynamicSSLContextImpl = null; + } + } + } + + private SSLContext getConfiguredDefaultSSLContext() { + try { + if (dynamicSSLContextImpl != null) { + SSLContext configuredDefault = dynamicSSLContextImpl.getConfiguredDefault(); + if (configuredDefault != null) { + return configuredDefault; + } + } + return SSLContext.getDefault(); + } catch (NoSuchAlgorithmException | DynamicSSLContextException e) { + throw ElytronMessages.log.cannotObtainConfiguredDefaultSSLContext(); + } + } + + @Override + protected void engineInit(KeyManager[] keyManagers, TrustManager[] trustManagers, SecureRandom secureRandom) { + // initialization of SSL context is delegated to providers of {@link org.wildfly.security.dynamic.ssl.DynamicSSLContextSPI} + } + + @Override + protected SSLSocketFactory engineGetSocketFactory() { + if (dynamicSSLContextImpl == null) { + return this.getConfiguredDefaultSSLContext().getSocketFactory(); + } + if (sslSocketFactory == null) { + synchronized (this) { + if (sslSocketFactory == null) { + sslSocketFactory = new DynamicSSLSocketFactory(this.getConfiguredDefaultSSLContext().getSocketFactory(), dynamicSSLContextImpl); + } + } + } + return sslSocketFactory; + } + + @Override + protected SSLServerSocketFactory engineGetServerSocketFactory() { + return this.getConfiguredDefaultSSLContext().getServerSocketFactory(); + } + + @Override + protected SSLEngine engineCreateSSLEngine() { + return this.getConfiguredDefaultSSLContext().createSSLEngine(); + } + + @Override + protected SSLEngine engineCreateSSLEngine(String host, int port) throws IllegalStateException { + try { + if (dynamicSSLContextImpl == null) { + return this.getConfiguredDefaultSSLContext().createSSLEngine(host, port); + } + SSLContext sslContext = dynamicSSLContextImpl + .getSSLContext(new URI(null, null, host, port, null, null, null)); + if (sslContext == null) { + throw ElytronMessages.log.receivedSSLContextFromDynamicSSLContextProviderWasNull(); + } + if (sslContext instanceof DynamicSSLContext && sslContext.getSocketFactory().equals(this.engineGetSocketFactory())) { + throw ElytronMessages.log.dynamicSSLContextCreatesLoop(); + } + return sslContext.createSSLEngine(host, port); + } catch (URISyntaxException e) { + throw ElytronMessages.log.couldNotCreateURI(); + } catch (DynamicSSLContextException e) { + throw ElytronMessages.log.couldNotCreateDynamicSSLContextEngine(); + } + } + + @Override + protected SSLSessionContext engineGetServerSessionContext() { + throw new UnsupportedOperationException(ElytronMessages.log.dynamicSSLContextDoesNotSupportSessions()); + } + + @Override + protected SSLSessionContext engineGetClientSessionContext() { + throw new UnsupportedOperationException(ElytronMessages.log.dynamicSSLContextDoesNotSupportSessions()); + } + + @Override + protected SSLParameters engineGetSupportedSSLParameters() { + return this.getConfiguredDefaultSSLContext().getSupportedSSLParameters(); + } +} diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLSocketFactory.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLSocketFactory.java new file mode 100644 index 00000000000..a06badab0ce --- /dev/null +++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLSocketFactory.java @@ -0,0 +1,160 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.dynamic.ssl; + +import org.wildfly.common.Assert; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * SSLSocketFactory that is being used by DynamicSSLContext. + * + * @author Diana Krepinska + */ +final class DynamicSSLSocketFactory extends SSLSocketFactory { + + private DynamicSSLContextSPI dynamicSSLContextImpl; + private volatile String[] intersectionCipherSuite; + private SSLSocketFactory configuredDefaultSslSocketFactory; + + DynamicSSLSocketFactory(SSLSocketFactory configuredDefaultSslSocketFactory, DynamicSSLContextSPI dynamicSSLContextImpl) { + super(); + Assert.assertNotNull(configuredDefaultSslSocketFactory); + Assert.assertNotNull(dynamicSSLContextImpl); + this.configuredDefaultSslSocketFactory = configuredDefaultSslSocketFactory; + this.dynamicSSLContextImpl = dynamicSSLContextImpl; + } + + @Override + public Socket createSocket() throws IOException { + return configuredDefaultSslSocketFactory.createSocket(); + } + + @Override + public Socket createSocket(InetAddress address, int port) throws IOException { + return createSocketBasedOnPeerInfo(null, port, address, null, null, null, null); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + return createSocketBasedOnPeerInfo(host, port, null, null, null, null, null); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException { + return createSocketBasedOnPeerInfo(host, port, null, localAddress, localPort, null, null); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return createSocketBasedOnPeerInfo(null, port, address, localAddress, localPort, null, null); + } + + @Override + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { + return createSocketBasedOnPeerInfo(host, port, null, null, null, socket, autoClose); + } + + @Override + public String[] getDefaultCipherSuites() { + return configuredDefaultSslSocketFactory.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + String[] val = intersectionCipherSuite; + if (val == null) { + synchronized (this) { + val = intersectionCipherSuite; + if (intersectionCipherSuite == null) { + val = intersectionCipherSuite = getIntersection(); + } + } + } + return val; + } + + private Socket createSocketBasedOnPeerInfo(String hostname, Integer port, InetAddress address, InetAddress localAddress, Integer localPort, Socket socket, Boolean autoClose) throws IOException { + try { + SSLContext sslContext = this.dynamicSSLContextImpl.getSSLContext(new URI(null, null, hostname == null ? address.getHostName() : hostname, port, null, null, null)); + if (sslContext == null) { + throw ElytronMessages.log.configuredSSLContextIsNull(); + } + SSLSocketFactory socketFactory = sslContext.getSocketFactory(); + if (socketFactory instanceof DynamicSSLSocketFactory && socketFactory.equals(this)) { + throw ElytronMessages.log.dynamicSSLContextCreatesLoop(); + } + // resolve socket + if (socket != null && autoClose != null) { + return socketFactory.createSocket(socket, hostname, port, autoClose); + } + + // resolves InetAddresses callbacks + if (address != null) { + return localAddress == null ? + socketFactory.createSocket(address, port) : socketFactory.createSocket(address, port, localAddress, localPort); + } + if (localAddress != null && localPort != null) { + return socketFactory.createSocket(hostname, port, localAddress, localPort); + } + return socketFactory.createSocket(hostname, port); + } catch (URISyntaxException e) { + throw new UnknownHostException(e.getMessage()); + } catch (DynamicSSLContextException e) { + throw new IOException(e); + } + } + + private String[] getIntersection() { + List sslContexts; + try { + sslContexts = dynamicSSLContextImpl.getConfiguredSSLContexts(); + } catch (DynamicSSLContextException e) { + throw ElytronMessages.log.unableToGetConfiguredSSLContexts(); + } + if (sslContexts == null) { + throw ElytronMessages.log.configuredSSLContextsAreNull(); + } + Map counts = new HashMap<>(); + List intersection = new ArrayList<>(); + sslContexts.forEach(c -> { + String[] cipherSuites = c.getSocketFactory().getSupportedCipherSuites(); + for (String cipherSuite : cipherSuites) { + counts.merge(cipherSuite, 1, (a, b) -> a + b); + } + }); + List finalSslContexts = sslContexts; + counts.forEach((c, v) -> { + if (finalSslContexts.size() == v) { + intersection.add(c); + } + }); + return intersection.toArray(new String[0]); + } +} diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/ElytronMessages.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/ElytronMessages.java new file mode 100644 index 00000000000..feab5f75ee2 --- /dev/null +++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/ElytronMessages.java @@ -0,0 +1,77 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.dynamic.ssl; + +import org.jboss.logging.BasicLogger; +import org.jboss.logging.Logger; +import org.jboss.logging.annotations.Cause; +import org.jboss.logging.annotations.Message; +import org.jboss.logging.annotations.MessageLogger; +import org.jboss.logging.annotations.ValidIdRange; +import org.jboss.logging.annotations.ValidIdRanges; + +/** + * Log messages and exceptions for Elytron. + * + * @author David M. Lloyd + * @author Darran Lofthouse + */ +@MessageLogger(projectCode = "ELY", length = 5) +@ValidIdRanges({ + @ValidIdRange(min = 21000, max = 21999) +}) +interface ElytronMessages extends BasicLogger { + + ElytronMessages log = Logger.getMessageLogger(ElytronMessages.class, "org.wildfly.security"); + + @Message(id = 21000, value = "DynamicSSLContext creates loop") + IllegalStateException dynamicSSLContextCreatesLoop(); + + @Message(id = 21001, value = "Received SSLContext from DynamicSSLContextProvider was null") + IllegalStateException receivedSSLContextFromDynamicSSLContextProviderWasNull(); + + @Message(id = 21002, value = "Dynamic SSLContext does not support sessions") + UnsupportedOperationException dynamicSSLContextDoesNotSupportSessions(); + + @Message(id = 21003, value = "Provider for DynamicSSLContextSPI threw an exception when getting configured SSLContexts") + IllegalStateException unableToGetConfiguredSSLContexts(); + + @Message(id = 21004, value = "Provider for DynamicSSLContextSPI returned null configured SSLContexts") + IllegalStateException configuredSSLContextsAreNull(); + + @Message(id = 21005, value = "Cannot obtain default SSLContext from DynamicSSLContext implementation") + IllegalStateException cannotObtainConfiguredDefaultSSLContext(); + + @Message(id = 21006, value = "Could not create URI from host and port") + IllegalStateException couldNotCreateURI(); + + @Message(id = 21007, value = "Could not create dynamic ssl context engine") + IllegalStateException couldNotCreateDynamicSSLContextEngine(); + + @Message(id = 21008, value = "Provider for DynamicSSLContextSPI returned null SSLContext") + IllegalStateException configuredSSLContextIsNull(); + + @Message(id = 21009, value = "Obtaining of the default SSLContext from current authentication context resulted in exception.") + DynamicSSLContextException cannotObtainDefaultSSLContext(@Cause Throwable cause); + + @Message(id = 21010, value = "Obtaining of all configured SSLContexts from current authentication context resulted in exception.") + DynamicSSLContextException cannotObtainConfiguredSSLContexts(@Cause Throwable cause); + + @Message(id = 21011, value = "Obtaining of the SSLContext from current authentication context and provided URI resulted in exception.") + DynamicSSLContextException cannotObtainSSLContextForGivenURI(@Cause Throwable cause); +} diff --git a/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java new file mode 100644 index 00000000000..6c4378f34f6 --- /dev/null +++ b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java @@ -0,0 +1,454 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.dynamic.ssl; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.wildfly.security.auth.client.AuthenticationContext; +import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient; +import org.wildfly.security.auth.client.ElytronXmlParser; +import org.wildfly.security.auth.client.InvalidAuthenticationConfigurationException; +import org.wildfly.security.auth.client.MatchRule; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; +import java.net.URL; +import java.security.AccessController; +import java.security.GeneralSecurityException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivilegedAction; +import java.security.cert.CertificateException; + +import static java.security.AccessController.doPrivileged; +import static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance.ServerThread.STATUS_OK; + +/** + * Functional tests of DynamicSSLContext. + * + * @author Diana Krepinska (Vilkolakova) + */ +public class DynamicSSLContextTest { + static final String RESOURCES = "./target/keystores/"; + private static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance sslServerSocketTestInstancePort10001; + private static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance sslServerSocketTestInstancePort10002; + private static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance sslServerSocketTestInstancePort10003; + private static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance sslServerSocketTestInstancePort10000Default; + + @BeforeClass + public static void before() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { + DynamicSSLTestUtils.createKeystores(); + sslServerSocketTestInstancePort10001 = new SSLServerSocketTestInstance(RESOURCES + "server1.keystore.jks", RESOURCES + "server1.truststore.jks", 10001); + sslServerSocketTestInstancePort10002 = new SSLServerSocketTestInstance(RESOURCES + "server2.keystore.jks", RESOURCES + "server2.truststore.jks", 10002); + sslServerSocketTestInstancePort10003 = new SSLServerSocketTestInstance(RESOURCES + "server3.keystore.jks", RESOURCES + "server3.truststore.jks", 10003); + sslServerSocketTestInstancePort10000Default = new SSLServerSocketTestInstance(RESOURCES + "default-server.keystore.jks", RESOURCES + "default-server.truststore.jks", 10000); + + sslServerSocketTestInstancePort10001.run(); + sslServerSocketTestInstancePort10002.run(); + sslServerSocketTestInstancePort10003.run(); + sslServerSocketTestInstancePort10000Default.run(); + } + + @AfterClass + public static void after() { + sslServerSocketTestInstancePort10001.stop(); + sslServerSocketTestInstancePort10002.stop(); + sslServerSocketTestInstancePort10003.stop(); + sslServerSocketTestInstancePort10000Default.stop(); + org.wildfly.security.dynamic.ssl.DynamicSSLTestUtils.deleteKeystores(); + } + + @Test + public void smokeTestWith4Servers() throws NoSuchAlgorithmException { + SSLContext dynamicSSLContext = new DynamicSSLContext(); + SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory(); + getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> { + try { + SSLSocket clientSslSocket1 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10001); + clientSslSocket1.setUseClientMode(true); + clientSslSocket1.setReuseAddress(true); + checkOutputIsOK(clientSslSocket1); + clientSslSocket1.close(); + + SSLSocket clientSslSocket2 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10002); + clientSslSocket2.setReuseAddress(true); + checkOutputIsOK(clientSslSocket2); + clientSslSocket2.close(); + + SSLSocket clientSslSocket3 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10003); + clientSslSocket3.setReuseAddress(true); + checkOutputIsOK(clientSslSocket3); + clientSslSocket3.close(); + + SSLSocket clientSslSocket4 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10000); + clientSslSocket4.setReuseAddress(true); + checkOutputIsOK(clientSslSocket4); + clientSslSocket4.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + }); + } + + @Test + public void smokeTestAuthenticationContextPassedExplicitly() throws DynamicSSLContextException, GeneralSecurityException { + SSLContext dynamicSSLContext = new DynamicSSLContext(new DynamicSSLContextImpl(getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml"))); + SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory(); + try { + SSLSocket clientSslSocket1 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10001); + clientSslSocket1.setUseClientMode(true); + clientSslSocket1.setReuseAddress(true); + checkOutputIsOK(clientSslSocket1); + clientSslSocket1.close(); + + SSLSocket clientSslSocket2 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10002); + clientSslSocket2.setReuseAddress(true); + checkOutputIsOK(clientSslSocket2); + clientSslSocket2.close(); + + SSLSocket clientSslSocket3 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10003); + clientSslSocket3.setReuseAddress(true); + checkOutputIsOK(clientSslSocket3); + clientSslSocket3.close(); + + SSLSocket clientSslSocket4 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10000); + clientSslSocket4.setReuseAddress(true); + checkOutputIsOK(clientSslSocket4); + clientSslSocket4.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + } + + @Test(expected = SocketException.class) + public void smokeTestWithoutElytronClientContextWillFail() throws NoSuchAlgorithmException, IOException { + SSLContext dynamicSSLContext = new DynamicSSLContext(); + SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory(); + SSLSocket clientSslSocket1 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10002); + clientSslSocket1.setUseClientMode(true); + clientSslSocket1.setReuseAddress(true); + checkOutputIsOK(clientSslSocket1); + clientSslSocket1.close(); + } + + @Test + public void testCreateSocketByInetAddressPort() throws NoSuchAlgorithmException { + SSLContext dynamicSSLContext = new DynamicSSLContext(); + SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory(); + getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> { + try { + SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(InetAddress.getByName("localhost"), 10002); + clientSslSocket.setReuseAddress(true); + checkOutputIsOK(clientSslSocket); + clientSslSocket.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + }); + } + + @Test + public void testCreateSocketByHostPortLocalAddressLocalPort() throws NoSuchAlgorithmException { + SSLContext dynamicSSLContext = new DynamicSSLContext(); + SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory(); + getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> { + try { + SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10001, InetAddress.getByName("localhost"), 0); + clientSslSocket.setReuseAddress(true); + checkOutputIsOK(clientSslSocket); + clientSslSocket.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + }); + } + + @Test + public void testCreateSocketByAddressPortLocalAddressLocalPort() throws NoSuchAlgorithmException { + SSLContext dynamicSSLContext = new DynamicSSLContext(); + SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory(); + getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> { + try { + SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(InetAddress.getByName("localhost"), 10001, InetAddress.getByName("localhost"), 12555); + clientSslSocket.setReuseAddress(true); + checkOutputIsOK(clientSslSocket); + clientSslSocket.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + }); + } + + @Test + public void testCreateSocketBySocketHostPortAutoCloseTrue() throws NoSuchAlgorithmException { + SSLContext dynamicSSLContext = new DynamicSSLContext(); + SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory(); + getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> { + try { + Socket plainSocket = new Socket(); + plainSocket.connect(new InetSocketAddress("localhost", 10001)); + SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(plainSocket, "localhost", 10001, true); + clientSslSocket.setReuseAddress(true); + checkOutputIsOK(clientSslSocket); + clientSslSocket.close(); + plainSocket.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + }); + } + + @Test + public void testCreateSocketsBySocketHostPortAutoCloseFalse() throws NoSuchAlgorithmException { + SSLContext dynamicSSLContext = new DynamicSSLContext(); + SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory(); + getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> { + try { + Socket plainSocket = new Socket(); + plainSocket.connect(new InetSocketAddress("localhost", 10001)); + SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(plainSocket, "localhost", 10001, false); + clientSslSocket.setReuseAddress(true); + checkOutputIsOK(clientSslSocket); + clientSslSocket.close(); + plainSocket.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + }); + } + + @Test + public void testCreateSocketbyHostAndPortAndConfiguredSSLParams2() throws NoSuchAlgorithmException { + DynamicSSLContext dynamicSSLContext = new DynamicSSLContext(); + AuthenticationContext context = getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml"); + context.run(() -> { + try { + DynamicSSLSocketFactory dynamicSSLContextSocketFactory = (DynamicSSLSocketFactory) dynamicSSLContext.getSocketFactory(); + dynamicSSLContext.getDefaultSSLParameters().setCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256"}); + SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(); + SSLParameters sslParameters = clientSslSocket.getSSLParameters(); + sslParameters.setCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256"}); + clientSslSocket.setSSLParameters(sslParameters); + dynamicSSLContext.getDefaultSSLParameters().setCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256"}); + clientSslSocket.connect(new InetSocketAddress("localhost", 10000)); + clientSslSocket.startHandshake(); + Assert.assertEquals("TLS_RSA_WITH_AES_128_CBC_SHA256", clientSslSocket.getSession().getCipherSuite()); + checkOutputIsOK(clientSslSocket); + clientSslSocket.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + }); + } + + @Test(expected = UnsupportedOperationException.class) + public void checkExceptionThrownClientSessionContext() throws Exception { + SSLContext sslContext = new DynamicSSLContext(); + sslContext.getClientSessionContext(); + } + + @Test(expected = UnsupportedOperationException.class) + public void checkExceptionThrownServerSessionContext() throws Exception { + SSLContext sslContext = new DynamicSSLContext(); + sslContext.getServerSessionContext(); + } + + // thorough testing of sslEngine would need a lot of code with socket implementation that is pretty low level + // it is reasonable to assume that it is being tested anyway since sockets created by SSLSocketFactory seem to always use this SSLEngine + // here I at least test that the SSLEngine was created with correct host and port + @Test + public void smokeTestCorrectSSLEngineIsUsed() throws NoSuchAlgorithmException { + DynamicSSLContext dynamicSSLContext = new DynamicSSLContext(); + SSLEngine sslEngine = dynamicSSLContext.createSSLEngine("localhost", 10000); + Assert.assertEquals("localhost", sslEngine.getPeerHost()); + Assert.assertEquals(10000, sslEngine.getPeerPort()); + + SSLEngine sslEngine2 = dynamicSSLContext.createSSLEngine(); + Assert.assertNull(sslEngine2.getPeerHost()); + Assert.assertEquals(-1, sslEngine2.getPeerPort()); + } + + @Test + public void smokeTestIntersectionOfCipherSuites() throws NoSuchAlgorithmException { + DynamicSSLContext dynamicSSLContext = new DynamicSSLContext(); + SSLServerSocketTestInstance testSSLServerSingleCipherSuite = + new SSLServerSocketTestInstance(RESOURCES + "default-server.keystore.jks", RESOURCES + "default-server.truststore.jks", 10004); + testSSLServerSingleCipherSuite.setConfiguredEnabledCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA256"}); + testSSLServerSingleCipherSuite.run(); + AuthenticationContext context = getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml"); + context.run(() -> { + try { + SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContext.getSocketFactory().createSocket(); + SSLParameters sslParameters = clientSslSocket.getSSLParameters(); + sslParameters.setCipherSuites(new String[]{"TLS_RSA_WITH_AES_256_CBC_SHA256"}); + clientSslSocket.setSSLParameters(sslParameters); + clientSslSocket.connect(new InetSocketAddress("localhost", 10000)); + clientSslSocket.startHandshake(); + Assert.assertEquals("TLS_RSA_WITH_AES_256_CBC_SHA256", clientSslSocket.getSession().getCipherSuite()); + checkOutputIsOK(clientSslSocket); + clientSslSocket.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + }); + } + + @Test + public void testChangingAuthenticationContextsTest() throws NoSuchAlgorithmException { + DynamicSSLContext dynamicSSLContext = new DynamicSSLContext(); + SSLSocketFactory socketFactory = dynamicSSLContext.getSocketFactory(); + + AuthenticationContext.empty().withSsl(MatchRule.ALL.matchPort(10001), () -> DynamicSSLTestUtils + .createSSLContext(RESOURCES + "client1.keystore.jks", RESOURCES + "client1.truststore.jks", "Elytron")).run(() -> { + try { + Socket clientSslSocket = socketFactory.createSocket("localhost", 10001); + checkOutputIsOK((SSLSocket) clientSslSocket); + clientSslSocket.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + }); + + AuthenticationContext.empty().withSsl(MatchRule.ALL.matchPort(10002), () -> DynamicSSLTestUtils + .createSSLContext(RESOURCES + "client2.keystore.jks", RESOURCES + "client2.truststore.jks", "Elytron")).run(() -> { + try { + Socket clientSslSocket = socketFactory.createSocket("localhost", 10002); + checkOutputIsOK((SSLSocket) clientSslSocket); + clientSslSocket.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + }); + + AuthenticationContext.empty().withSsl(MatchRule.ALL.matchPort(10003), () -> DynamicSSLTestUtils + .createSSLContext(RESOURCES + "client3.keystore.jks", RESOURCES + "client3.truststore.jks", "Elytron")).run(() -> { + try { + Socket clientSslSocket = socketFactory.createSocket("localhost", 10003); + checkOutputIsOK((SSLSocket) clientSslSocket); + clientSslSocket.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + }); + } + + @Test(expected = IllegalStateException.class) + public void testThrowAnExceptionWhenLoop() throws NoSuchAlgorithmException { + + DynamicSSLContext dynamicSSLContext = new DynamicSSLContext(); + SSLSocketFactory socketFactory = dynamicSSLContext.getSocketFactory(); + SSLContext previousDefaultSSLContext = SSLContext.getDefault(); + SSLContext.setDefault(dynamicSSLContext); + AuthenticationContext.empty().withSsl(MatchRule.ALL.matchPort(10000), () -> DynamicSSLTestUtils + .createSSLContext(RESOURCES + "client1.keystore.jks", RESOURCES + "client1.truststore.jks", "Elytron")).run(() -> { + try { + Socket clientSslSocket = socketFactory.createSocket("localhost", 12345); + checkOutputIsOK((SSLSocket) clientSslSocket); + clientSslSocket.close(); + } catch (IOException e) { + Assert.assertEquals("fine", e.getMessage()); + } finally { + SSLContext.setDefault(previousDefaultSSLContext); + } + }); + } + + + @Test + public void testPreconfiguredDefault() throws NoSuchAlgorithmException { + DynamicSSLContext dynamicSSLContext = new DynamicSSLContext(); + final AuthenticationContextConfigurationClient AUTH_CONTEXT_CLIENT = + AccessController.doPrivileged((PrivilegedAction) AuthenticationContextConfigurationClient::new); + try { + + AuthenticationContext contextWithConfiguredDefault = getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml"); + AuthenticationContext contextWithoutConfiguredDefault = getAuthenticationContext("wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml"); + + SSLContext preconfiguredDefault = AUTH_CONTEXT_CLIENT.getDefaultSSLContext(contextWithConfiguredDefault); + SSLContext jvmDefault = AUTH_CONTEXT_CLIENT.getDefaultSSLContext(contextWithoutConfiguredDefault); + + Assert.assertEquals(jvmDefault, SSLContext.getDefault()); + + // AuthenticationContextConfigurationClient always creates new instances. So we can check that preconfigured SSLContext was received + // correctly by successful connection to the host and port that requires that ssl context. + + // We first test configured default by using createSocket(host, port) with port not specified in any match rules. + // Second we use empty createSocket method that will later connect to the same host and port successfully. + + contextWithConfiguredDefault.run(() -> { + try { + SSLSocket clientSslSocket1 = (SSLSocket) preconfiguredDefault.getSocketFactory().createSocket("localhost", 10000); + clientSslSocket1.setReuseAddress(true); + checkOutputIsOK(clientSslSocket1); + clientSslSocket1.close(); + //preconfigured default will be used to create socket since no host and port was provided + SSLSocket clientSocketWithDynamicDefaultSSLContext = (SSLSocket) dynamicSSLContext.getSocketFactory().createSocket(); + clientSocketWithDynamicDefaultSSLContext.setUseClientMode(true); + // configured default is the one which passes for this host and port + clientSocketWithDynamicDefaultSSLContext.connect(new InetSocketAddress("localhost", 10000)); + checkOutputIsOK(clientSocketWithDynamicDefaultSSLContext); + clientSocketWithDynamicDefaultSSLContext.close(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + } + ); + + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + } + } + + private void checkOutputIsOK(SSLSocket clientSslSocket) throws IOException { + PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(clientSslSocket.getOutputStream())); + printWriter.println("Client Hello"); + printWriter.flush(); + InputStream inputStream = clientSslSocket.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String line = bufferedReader.readLine().trim(); + Assert.assertEquals(STATUS_OK, line); + } + + private AuthenticationContext getAuthenticationContext(String path) { + return doPrivileged((PrivilegedAction) () -> { + URL config = getClass().getResource(path); + try { + return ElytronXmlParser.parseAuthenticationClientConfiguration(config.toURI()).create(); + } catch (Exception e) { + Assert.assertEquals("fine", e.getMessage()); + throw new InvalidAuthenticationConfigurationException(e); + } + }); + } +} diff --git a/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java new file mode 100644 index 00000000000..1f60cbdc64a --- /dev/null +++ b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java @@ -0,0 +1,185 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.wildfly.security.dynamic.ssl; + +import org.junit.Assert; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.security.auth.x500.X500Principal; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import org.wildfly.security.ssl.test.util.CAGenerationTool; +import org.wildfly.security.x500.cert.X509CertificateExtension; + +/** + * Utility class for DynamicSSLContextTest class. + * + * @author Diana Krepinska (Vilkolakova) + */ +public class DynamicSSLTestUtils { + + private static final String CLIENT_ALIAS = "client"; + private static final String LOCALHOST_ALIAS = "localhost"; + private static final String KEYSTORE_TYPE = "JKS"; + private static final String TLS_PROTOCOL_VERSION = "TLSv1.2"; + public static final String KEY_MANAGER_FACTORY_ALGORITHM = "SunX509"; + private static char[] PASSWORD = "Elytron".toCharArray(); + private static File KEYSTORES_DIR = new File("./target/keystores"); + + private static String CLIENT1_KEYSTORE_FILENAME = "client1.keystore.jks"; + private static String CLIENT1_TRUSTSTORE_FILENAME ="client1.truststore.jks"; + private static String SERVER1_KEYSTORE_FILENAME = "server1.keystore.jks"; + private static String SERVER1_TRUSTSTORE_FILENAME = "server1.truststore.jks"; + + private static String CLIENT2_KEYSTORE_FILENAME = "client2.keystore.jks"; + private static String CLIENT2_TRUSTSTORE_FILENAME ="client2.truststore.jks"; + private static String SERVER2_KEYSTORE_FILENAME = "server2.keystore.jks"; + private static String SERVER2_TRUSTSTORE_FILENAME = "server2.truststore.jks"; + + private static String CLIENT3_KEYSTORE_FILENAME = "client3.keystore.jks"; + private static String CLIENT3_TRUSTSTORE_FILENAME ="client3.truststore.jks"; + private static String SERVER3_KEYSTORE_FILENAME = "server3.keystore.jks"; + private static String SERVER3_TRUSTSTORE_FILENAME = "server3.truststore.jks"; + + private static String DEFAULT_CLIENT_KEYSTORE_FILENAME = "default-client.keystore.jks"; + private static String DEFAULT_CLIENT_TRUSTSTORE_FILENAME ="default-client.truststore.jks"; + private static String DEFAULT_SERVER_KEYSTORE_FILENAME = "default-server.keystore.jks"; + private static String DEFAULT_SERVER_TRUSTSTORE_FILENAME = "default-server.truststore.jks"; + + static SSLContext createSSLContext(String keystorePath, String truststorePath, String password) { + try { + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE); + keyStore.load(new FileInputStream(keystorePath), password.toCharArray()); + + // Create key manager + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM); + keyManagerFactory.init(keyStore, password.toCharArray()); + KeyManager[] km = keyManagerFactory.getKeyManagers(); + + KeyStore trustStore = KeyStore.getInstance(KEYSTORE_TYPE); + trustStore.load(new FileInputStream(truststorePath), password.toCharArray()); + + // Create trust manager + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM); + trustManagerFactory.init(trustStore); + TrustManager[] tm = trustManagerFactory.getTrustManagers(); + + // Initialize SSLContext + SSLContext sslContext = SSLContext.getInstance(TLS_PROTOCOL_VERSION); + sslContext.init(km, tm, null); + + return sslContext; + } catch (Exception ex) { + Assert.fail(); + } + return null; + } + + static void createKeystores() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { + if (!KEYSTORES_DIR.exists()) { + KEYSTORES_DIR.mkdirs(); + } + + generateTwoWaySSLKeystoresAndTruststores(CLIENT1_KEYSTORE_FILENAME, SERVER1_KEYSTORE_FILENAME, CLIENT1_TRUSTSTORE_FILENAME, SERVER1_TRUSTSTORE_FILENAME); + generateTwoWaySSLKeystoresAndTruststores(CLIENT2_KEYSTORE_FILENAME, SERVER2_KEYSTORE_FILENAME, CLIENT2_TRUSTSTORE_FILENAME, SERVER2_TRUSTSTORE_FILENAME); + generateTwoWaySSLKeystoresAndTruststores(CLIENT3_KEYSTORE_FILENAME, SERVER3_KEYSTORE_FILENAME, CLIENT3_TRUSTSTORE_FILENAME, SERVER3_TRUSTSTORE_FILENAME); + generateTwoWaySSLKeystoresAndTruststores(DEFAULT_CLIENT_KEYSTORE_FILENAME, DEFAULT_SERVER_KEYSTORE_FILENAME, DEFAULT_CLIENT_TRUSTSTORE_FILENAME, DEFAULT_SERVER_TRUSTSTORE_FILENAME); + } + + private static void generateTwoWaySSLKeystoresAndTruststores(String clientKeystoreFilename, String serverKeystoreFilename, + String clientTruststoreFilename, String serverTruststoreFilename) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { + CAGenerationTool caGenerationTool = null; + try { + caGenerationTool = CAGenerationTool.builder() + .setBaseDir(KEYSTORES_DIR.getCanonicalPath()) + .setRequestIdentities(CAGenerationTool.Identity.values()) + .build(); + } catch(Exception e) { + e.printStackTrace(); + Assert.fail(); + } + + // Generates client certificate + X509Certificate clientCertificate = caGenerationTool.createIdentity(CLIENT_ALIAS, + new X500Principal("OU=Elytron"), + clientKeystoreFilename, + CAGenerationTool.Identity.CA, + new X509CertificateExtension[]{}); + + // Generates server certificate + X509Certificate serverCertificate = caGenerationTool.createIdentity(LOCALHOST_ALIAS, + new X500Principal("OU=Elytron"), + serverKeystoreFilename, + CAGenerationTool.Identity.CA, + new X509CertificateExtension[]{}); + + // create truststores + KeyStore clientTrustStore = KeyStore.getInstance(KEYSTORE_TYPE); + clientTrustStore.load(null, null); + + KeyStore serverTrustStore = KeyStore.getInstance(KEYSTORE_TYPE); + serverTrustStore.load(null, null); + + clientTrustStore.setCertificateEntry(LOCALHOST_ALIAS, serverCertificate); + serverTrustStore.setCertificateEntry(CLIENT_ALIAS, clientCertificate); + + File clientTrustFile = new File(KEYSTORES_DIR, clientTruststoreFilename); + try (FileOutputStream clientStream = new FileOutputStream(clientTrustFile)) { + clientTrustStore.store(clientStream, PASSWORD); + } + + File serverTrustFile = new File(KEYSTORES_DIR, serverTruststoreFilename); + try (FileOutputStream serverStream = new FileOutputStream(serverTrustFile)) { + serverTrustStore.store(serverStream, PASSWORD); + } + } + + public static void deleteKeystores() { + new File(KEYSTORES_DIR, CLIENT1_KEYSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, CLIENT1_TRUSTSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, CLIENT2_KEYSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, CLIENT2_TRUSTSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, CLIENT3_KEYSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, CLIENT3_TRUSTSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, DEFAULT_CLIENT_KEYSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, DEFAULT_CLIENT_TRUSTSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, SERVER1_KEYSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, SERVER1_TRUSTSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, SERVER2_KEYSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, SERVER2_TRUSTSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, SERVER3_KEYSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, SERVER3_TRUSTSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, DEFAULT_SERVER_KEYSTORE_FILENAME).delete(); + new File(KEYSTORES_DIR, DEFAULT_SERVER_TRUSTSTORE_FILENAME).delete(); + KEYSTORES_DIR.delete(); + } +} diff --git a/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java new file mode 100644 index 00000000000..b69715f1437 --- /dev/null +++ b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java @@ -0,0 +1,138 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.dynamic.ssl; + +import okhttp3.TlsVersion; +import org.junit.Assert; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSocket; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.InetSocketAddress; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Utility class for running SSLServerSocket instance for testing. + * + * @author Diana Krepinska (Vilkolakova) + */ +public class SSLServerSocketTestInstance { + + private int port; + private String keystorePath; + private String truststorePath; + private String[] configuredEnabledCipherSuites; + private SSLServerSocket sslServerSocket; + private AtomicBoolean running = new AtomicBoolean(false); + private Thread serverThread; + + public SSLServerSocketTestInstance(String pathToKeystore, String pathToTruststore, int port) { + this.keystorePath = pathToKeystore; + this.truststorePath = pathToTruststore; + this.port = port; + } + + void setConfiguredEnabledCipherSuites(String[] configuredEnabledCipherSuite) { + this.configuredEnabledCipherSuites = configuredEnabledCipherSuite; + } + + public void run() { + String password = "Elytron"; + SSLContext sslContext = DynamicSSLTestUtils.createSSLContext(this.keystorePath, this.truststorePath, password); + try { + SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory(); + sslServerSocket = (javax.net.ssl.SSLServerSocket) sslServerSocketFactory.createServerSocket(); + sslServerSocket.setNeedClientAuth(true); + sslServerSocket.setUseClientMode(false); + sslServerSocket.setWantClientAuth(true); + sslServerSocket.setEnabledProtocols(new String[]{ + TlsVersion.TLS_1_2.javaName(), + TlsVersion.TLS_1_3.javaName() + }); + if (configuredEnabledCipherSuites != null) { + sslServerSocket.setEnabledCipherSuites(configuredEnabledCipherSuites); + } + sslServerSocket.bind(new InetSocketAddress("localhost", port)); + serverThread = new Thread(() -> { + running.set(true); + while (running.get()) { + SSLSocket sslSocket; + try { + sslSocket = (SSLSocket) sslServerSocket.accept(); + new Thread(new ServerThread(sslSocket)).start(); + } catch (Exception e) { + Assert.fail(); + } + } + }); + serverThread.start(); + } catch (Exception ex) { + Assert.fail(); + } finally { + running.set(false); + } + } + + public void stop() { + running.set(false); + } + + // Thread handling the socket from client + public static class ServerThread implements Runnable { + public static final String STATUS_OK = "HTTP/1.1 200 OK"; + private SSLSocket sslSocket; + AtomicBoolean running = new AtomicBoolean(false); + + ServerThread(SSLSocket sslSocket) { + this.sslSocket = sslSocket; + } + + public void run() { + try { + // wait for client's message first so that the first client message will trigger handshake. + // This way client can set its preferences in SSLParams after creation of bound createSocket(host,port) without server triggering handshake before. + running.set(true); + sslSocket.startHandshake(); + InputStream inputStream = sslSocket.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + while (running.get()) { + if ((bufferedReader.readLine()).equals("Client Hello")) { + break; + } + } + // if successful return 200 + PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(sslSocket.getOutputStream())); + printWriter.println(STATUS_OK); + printWriter.flush(); + sslSocket.close(); + } catch (Exception ex) { + ex.printStackTrace(); + Assert.fail(); + } finally { + running.set(false); + } + } + } +} diff --git a/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml new file mode 100644 index 00000000000..4bfe9365125 --- /dev/null +++ b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml new file mode 100644 index 00000000000..e857cbb9d08 --- /dev/null +++ b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/encryption/pom.xml b/encryption/pom.xml index 52c3cf2f704..de9ed3fea5f 100644 --- a/encryption/pom.xml +++ b/encryption/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT 4.0.0 diff --git a/http/base/pom.xml b/http/base/pom.xml index dca3c82da80..5fb11808f14 100644 --- a/http/base/pom.xml +++ b/http/base/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/http/basic/pom.xml b/http/basic/pom.xml index 316fb0c6e5a..382d0f54c79 100644 --- a/http/basic/pom.xml +++ b/http/basic/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/http/bearer/pom.xml b/http/bearer/pom.xml index 7c3c0c4312c..c7ca60a5060 100644 --- a/http/bearer/pom.xml +++ b/http/bearer/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml @@ -68,6 +68,11 @@ org.wildfly.common wildfly-common + + junit + junit + test + diff --git a/http/bearer/src/test/java/org/wildfly/security/http/bearer/BearerMechanismFactoryTest.java b/http/bearer/src/test/java/org/wildfly/security/http/bearer/BearerMechanismFactoryTest.java new file mode 100644 index 00000000000..08c09f0dc90 --- /dev/null +++ b/http/bearer/src/test/java/org/wildfly/security/http/bearer/BearerMechanismFactoryTest.java @@ -0,0 +1,127 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.http.bearer; + + +import org.junit.Test; +import org.junit.Assert; +import org.wildfly.security.http.HttpAuthenticationException; +import org.wildfly.security.http.HttpServerAuthenticationMechanism; + +import javax.security.auth.callback.CallbackHandler; +import java.util.HashMap; + +import static org.wildfly.security.http.HttpConstants.BASIC_NAME; +import static org.wildfly.security.http.HttpConstants.BEARER_TOKEN; + +/** + * This test class contains unit tests for the {@link BearerMechanismFactory}. + * + * @author Marek Jusko + */ +public class BearerMechanismFactoryTest { + + private final BearerMechanismFactory bearerMechanismFactory = new BearerMechanismFactory(); + private final HashMap emptyProperties = new HashMap<>(); + + CallbackHandler dummyCallbackHandler = callbacks -> {}; + + /** + * Unit test for the {@link BearerMechanismFactory#getMechanismNames} method with a {@code null} properties map. + * Verifies that the method returns a non-null array containing the Bearer mechanism name. + */ + @Test + public void testGetMechanismNamesWithNullProperties() { + BearerMechanismFactory factory = new BearerMechanismFactory(); + String[] mechanismNames = factory.getMechanismNames(null); + + Assert.assertNotNull("Array of mechanism names cannot be null.", mechanismNames); + Assert.assertEquals(1, mechanismNames.length); + Assert.assertEquals(BEARER_TOKEN, mechanismNames[0]); + } + + /** + * Unit test for the {@link BearerMechanismFactory#getMechanismNames} method with an empty properties map. + * Verifies that the method returns a non-null array containing the Bearer mechanism name. + */ + @Test + public void testGetMechanismNamesWithEmptyProperties() { + BearerMechanismFactory factory = new BearerMechanismFactory(); + String[] mechanismNames = factory.getMechanismNames(emptyProperties); + + Assert.assertNotNull("Array of mechanism names cannot be null.", mechanismNames); + Assert.assertEquals(1, mechanismNames.length); + Assert.assertEquals(BEARER_TOKEN, mechanismNames[0]); + } + + /** + * Verifies that creating an authentication mechanism with a null mechanism name results in an IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void testCreateAuthenticationMechanismMechanismNameNull() throws HttpAuthenticationException { + bearerMechanismFactory.createAuthenticationMechanism(null, emptyProperties, dummyCallbackHandler); + Assert.fail("IllegalArgumentException expected for null mechanismName."); + } + + /** + * Verifies that creating an authentication mechanism with null properties results in an IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void testCreateAuthenticationMechanismPropertiesNull() throws HttpAuthenticationException { + bearerMechanismFactory.createAuthenticationMechanism(BEARER_TOKEN, null, dummyCallbackHandler); + Assert.fail("IllegalArgumentException expected for null properties."); + } + + /** + * Verifies that creating an authentication mechanism with a null callback handler results in an IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void testCreateAuthenticationMechanismCallbackHandlerNull() throws HttpAuthenticationException { + bearerMechanismFactory.createAuthenticationMechanism(BEARER_TOKEN, emptyProperties, null); + Assert.fail("IllegalArgumentException expected for null callbackHandler."); + } + + /** + * Verifies that creating an authentication mechanism with the BASIC mechanism name returns null. + */ + @Test + public void testCreateAuthenticationMechanismBasicMechanismName() throws HttpAuthenticationException { + HttpServerAuthenticationMechanism mechanism = bearerMechanismFactory.createAuthenticationMechanism(BASIC_NAME, emptyProperties, dummyCallbackHandler); + Assert.assertNull("Expected null mechanism for the BASIC mechanism name.", mechanism); + } + + /** + * Verifies that creating an authentication mechanism with an incorrect mechanism name returns null. + */ + @Test + public void testCreateAuthenticationMechanismIncorrectMechanismName() throws HttpAuthenticationException { + HttpServerAuthenticationMechanism mechanism = bearerMechanismFactory.createAuthenticationMechanism("INCORRECT_NAME", emptyProperties, dummyCallbackHandler); + Assert.assertNull("Expected null mechanism for an incorrect mechanism name.", mechanism); + } + + /** + * Tests that creating a Bearer authentication mechanism with valid parameters returns a non-null mechanism. + */ + @Test + public void testCreateValidBearerAuthenticationMechanism() throws HttpAuthenticationException{ + HttpServerAuthenticationMechanism httpServerAuthenticationMechanism = bearerMechanismFactory.createAuthenticationMechanism(BEARER_TOKEN, emptyProperties, dummyCallbackHandler); + Assert.assertNotNull("HttpServerAuthenticationMechanism cannot be null.",httpServerAuthenticationMechanism); + } + +} diff --git a/http/cert/pom.xml b/http/cert/pom.xml index 7b68887af5f..669a855053d 100644 --- a/http/cert/pom.xml +++ b/http/cert/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml @@ -71,7 +71,19 @@ org.wildfly.common wildfly-common - + + + + + org.jmockit + jmockit + test + + + junit + junit + test + diff --git a/http/cert/src/test/java/org.wildfly.security.http.cert/ClientCertAuthenticationMechanismFactoryTest.java b/http/cert/src/test/java/org.wildfly.security.http.cert/ClientCertAuthenticationMechanismFactoryTest.java new file mode 100644 index 00000000000..041ac99e7a5 --- /dev/null +++ b/http/cert/src/test/java/org.wildfly.security.http.cert/ClientCertAuthenticationMechanismFactoryTest.java @@ -0,0 +1,124 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.http.cert; + +import org.wildfly.security.http.HttpAuthenticationException; +import org.wildfly.security.http.HttpServerAuthenticationMechanism; +import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.junit.Assert; +import org.junit.Test; + +public class ClientCertAuthenticationMechanismFactoryTest { + private HttpServerAuthenticationMechanismFactory clientCertMechanismFactory = new ClientCertMechanismFactory(); + + CallbackHandler dummyCallbackHandler = new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + } + }; + + /** + * Tests that {@link ServerMechanismFactoryImpl#getMechanismNames(Map)} correctly + * handles null or empty properties parameter as possible value. + */ + @Test + public void testGetMechanismNamesPropertiesNull() { + clientCertMechanismFactory.getMechanismNames(null); + clientCertMechanismFactory.getMechanismNames(new HashMap()); + } + + /** + * Tests that {@link ServerMechanismFactoryImpl#getMechanismNames(Map)} does not return null. + */ + @Test + public void testGetMechanismNamesReturnNotNull() { + String[] mechanismNames = clientCertMechanismFactory.getMechanismNames(null); + Assert.assertNotNull("Array of mechanism names is not null.", mechanismNames); + } + + /** + * Tests that {@link ServerMechanismFactoryImpl#createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)} + * does handle null mechanism name parameter correctly - does not allow. + * @throws HttpAuthenticationException + */ + @Test + public void testCreateAuthenticationMechanismMechanismNameNull() throws HttpAuthenticationException { + try { + clientCertMechanismFactory.createAuthenticationMechanism(null, new HashMap(), dummyCallbackHandler); + Assert.fail("Mechanism name could not be null"); + } catch (IllegalArgumentException e) { + // OK - expected exception state + } + } + + /** + * Tests that {@link ServerMechanismFactoryImpl#createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)} + * does handle null properties parameter correctly - does not allow. + */ + @Test + public void testCreateAuthenticationMechanismPropertiesNull() throws HttpAuthenticationException { + try { + clientCertMechanismFactory.createAuthenticationMechanism("CLIENT_CERT", null, dummyCallbackHandler); + Assert.fail("Properties could not be null"); + } catch (IllegalArgumentException e) { + // OK - expected exception state + } + } + + /** + * Tests that {@link ServerMechanismFactoryImpl#createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)} + * does handle wrong mechanism ("BASIC") - returns null. + */ + @Test + public void testCreateAuthenticationMechanismBasicMechanismName() throws HttpAuthenticationException{ + HttpServerAuthenticationMechanism httpServerAuthenticationMechanism = clientCertMechanismFactory.createAuthenticationMechanism("BASIC",new HashMap(),dummyCallbackHandler); + Assert.assertNull("Provided mechanism must be null.", httpServerAuthenticationMechanism); + } + + /** + * Tests that {@link ServerMechanismFactoryImpl#createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)} + * does handle null properties parameter correctly - does not allow. + */ + @Test + public void testCreateAuthenticationMechanismCallbackHandlerNull() throws HttpAuthenticationException { + try { + clientCertMechanismFactory.createAuthenticationMechanism("CLIENT_CERT", new HashMap(), null); + Assert.fail("CallbackHandler could not be null"); + } catch (IllegalArgumentException e) { + // OK - expected exception state + } + } + + /** + * Tests that {@link ServerMechanismFactoryImpl#createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)} + * does handle wrong mechanism name correctly - returns null. + */ + @Test + public void testCreateAuthenticationMechanismWrongMechanismName() throws HttpAuthenticationException { + HttpServerAuthenticationMechanism httpServerAuthenticationMechanism = clientCertMechanismFactory.createAuthenticationMechanism("MECHANISM_NAME_DOES_NOT_EXISTS", new HashMap(), dummyCallbackHandler); + Assert.assertNull("Provided mechanism must be null.", httpServerAuthenticationMechanism); + } +} diff --git a/http/deprecated/pom.xml b/http/deprecated/pom.xml index 3a104a68875..190c1e7adc2 100644 --- a/http/deprecated/pom.xml +++ b/http/deprecated/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/http/digest/pom.xml b/http/digest/pom.xml index 552e00155d7..8f6d77deb59 100644 --- a/http/digest/pom.xml +++ b/http/digest/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml @@ -76,6 +76,18 @@ org.wildfly.common wildfly-common + + + + org.jmockit + jmockit + test + + + junit + junit + test + diff --git a/http/digest/src/main/java/org/wildfly/security/http/digest/DigestAuthenticationMechanism.java b/http/digest/src/main/java/org/wildfly/security/http/digest/DigestAuthenticationMechanism.java index 7d7e2f4f7db..97f2a53a857 100644 --- a/http/digest/src/main/java/org/wildfly/security/http/digest/DigestAuthenticationMechanism.java +++ b/http/digest/src/main/java/org/wildfly/security/http/digest/DigestAuthenticationMechanism.java @@ -23,6 +23,7 @@ import static org.wildfly.security.http.HttpConstants.AUTHORIZATION; import static org.wildfly.security.http.HttpConstants.BAD_REQUEST; import static org.wildfly.security.http.HttpConstants.CNONCE; +import static org.wildfly.security.http.HttpConstants.DIGEST_NAME; import static org.wildfly.security.http.HttpConstants.NC; import static org.wildfly.security.http.HttpConstants.QOP; import static org.wildfly.security.http.HttpConstants.URI; @@ -49,6 +50,7 @@ import java.security.Provider; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.function.Supplier; import javax.security.auth.callback.Callback; @@ -326,10 +328,19 @@ private byte[] calculateResponseDigest(MessageDigest messageDigest, byte[] hA1, } private byte[] getH_A1(final MessageDigest messageDigest, final String username, final String messageRealm) throws AuthenticationMechanismException { - PasswordDigestObtainer obtainer = new PasswordDigestObtainer(callbackHandler, username, messageRealm, httpDigest, DigestPassword.ALGORITHM_DIGEST_MD5, messageDigest, providers, null, true, false); + PasswordDigestObtainer obtainer = new PasswordDigestObtainer(callbackHandler, username, messageRealm, httpDigest, getCredentialAlgorithm(getMechanismName()), messageDigest, providers, null, true, false); return obtainer.handleUserRealmPasswordCallbacks(); } + private String getCredentialAlgorithm(String mechanismName) { + switch (mechanismName) { + case DIGEST_NAME: + return DigestPassword.ALGORITHM_DIGEST_MD5; + default: + return mechanismName.toLowerCase(Locale.ROOT); + } + } + private String convertToken(final String name, final byte[] value) throws AuthenticationMechanismException { if (value == null) { throw httpDigest.mechMissingDirective(name); diff --git a/http/digest/src/main/java/org/wildfly/security/http/digest/DigestMechanismFactory.java b/http/digest/src/main/java/org/wildfly/security/http/digest/DigestMechanismFactory.java index 85ba265ba63..3a64c3fe130 100644 --- a/http/digest/src/main/java/org/wildfly/security/http/digest/DigestMechanismFactory.java +++ b/http/digest/src/main/java/org/wildfly/security/http/digest/DigestMechanismFactory.java @@ -109,8 +109,9 @@ public HttpServerAuthenticationMechanism createAuthenticationMechanism(String me return new DigestAuthenticationMechanism(callbackHandler, nonceManager, (String) properties.get(CONFIG_REALM), (String) properties.get(CONFIG_CONTEXT_PATH), DIGEST_SHA256_NAME, SHA256, providers, (String) properties.get(HttpConstants.CONFIG_VALIDATE_DIGEST_URI)); case DIGEST_SHA512_256_NAME: return new DigestAuthenticationMechanism(callbackHandler, nonceManager, (String) properties.get(CONFIG_REALM), (String) properties.get(CONFIG_CONTEXT_PATH), DIGEST_SHA512_256_NAME, SHA512_256, providers, (String) properties.get(HttpConstants.CONFIG_VALIDATE_DIGEST_URI)); + default: + return null; } - return null; } /* diff --git a/http/digest/src/test/java/org/wildfly/security/http/digest/DigestMechanismFactoryTest.java b/http/digest/src/test/java/org/wildfly/security/http/digest/DigestMechanismFactoryTest.java new file mode 100644 index 00000000000..307bff709b7 --- /dev/null +++ b/http/digest/src/test/java/org/wildfly/security/http/digest/DigestMechanismFactoryTest.java @@ -0,0 +1,142 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.http.digest; + +import org.junit.Assert; +import org.junit.Test; +import org.wildfly.security.http.HttpAuthenticationException; +import org.wildfly.security.http.HttpServerAuthenticationMechanism; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import java.io.IOException; +import java.util.HashMap; + +/** + * Tests of DigestMechanismFactory Class. + * + * @author Keshav Kumar + */ + +public class DigestMechanismFactoryTest { + + private DigestMechanismFactory digestMechanismFactory = new DigestMechanismFactory(); + + CallbackHandler dummyCallbackHandler = new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + } + }; + + /** + * Tests that getMechanismNames(Map) correctly + * handles null or empty properties parameter as possible value. + */ + @Test + public void testGetMechanismNamesPropertiesNull(){ + String[] mechanismNames1 = digestMechanismFactory.getMechanismNames(null); + Assert.assertNotNull("Array of mechanism names cannot be null.",mechanismNames1); + + String[] mechanismNames2 = digestMechanismFactory.getMechanismNames(new HashMap()); + Assert.assertNotNull("Array of mechanism names cannot be null.",mechanismNames2); + } + + /** + * Tests that getMechanismNames(Map) does not return null. + */ + @Test + public void testGetMechanismNamesReturnNotNull(){ + String[] mechanismNames = digestMechanismFactory.getMechanismNames(null); + Assert.assertNotNull("Array of mechanism names cannot be null.", mechanismNames); + } + + /** + * Tests that createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler) + * does handle null mechanism name parameter correctly - does not allow. + * @throws HttpAuthenticationException + */ + @Test + public void testCreateAuthenticationMechanismMechanismNameNull() throws HttpAuthenticationException{ + try { + digestMechanismFactory.createAuthenticationMechanism(null,new HashMap(),dummyCallbackHandler); + Assert.fail("Mechanism name could not be null"); + }catch (IllegalArgumentException illegalArgumentException){ + // OK - expected exception state + } + } + + /** + * Tests that {createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler) + * does handle null properties parameter correctly - does not allow. + */ + @Test + public void testCreateAuthenticationMechanismPropertiesNull() throws HttpAuthenticationException{ + try { + digestMechanismFactory.createAuthenticationMechanism("DIGEST",null,dummyCallbackHandler); + Assert.fail("Properties could not be null"); + }catch (IllegalArgumentException illegalArgumentException){ + // OK - expected exception state + } + } + + /** + * Tests that createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler) + * does handle null callbackHandler parameter correctly - does not allow. + */ + @Test + public void testCreateAuthenticationMechanismCallbackHandlerNull() throws HttpAuthenticationException{ + try { + digestMechanismFactory.createAuthenticationMechanism("DIGEST",new HashMap(),null); + Assert.fail("CallbackHandler could not be null"); + }catch (IllegalArgumentException illegalArgumentException){ + // OK - expected exception state + } + } + + /** + * Tests that createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler) + * does handle wrong mechanism ("BASIC") - returns null. + */ + @Test + public void testCreateAuthenticationMechanismBasicMechanismName() throws HttpAuthenticationException{ + HttpServerAuthenticationMechanism httpServerAuthenticationMechanism = digestMechanismFactory.createAuthenticationMechanism("BASIC",new HashMap(),dummyCallbackHandler); + Assert.assertNull("Provided mechanism must be null.", httpServerAuthenticationMechanism); + } + + /** + * Tests that createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler) + * does handle all not null parameter correctly - does not return null. + */ + @Test + public void testCreateAuthenticationMechanismReturnNotNull() throws HttpAuthenticationException{ + HttpServerAuthenticationMechanism httpServerAuthenticationMechanism = digestMechanismFactory.createAuthenticationMechanism("DIGEST",new HashMap(),dummyCallbackHandler); + Assert.assertNotNull("HttpServerAuthenticationMechanism cannot be null.",httpServerAuthenticationMechanism); + } + + /** + * Tests that createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler) + * does handle wrong mechanism name correctly - returns null. + */ + @Test + public void testCreateAuthenticationMechanismWrongMechanismName() throws HttpAuthenticationException{ + HttpServerAuthenticationMechanism httpServerAuthenticationMechanism = digestMechanismFactory.createAuthenticationMechanism("MECHANISM_NAME_DOES_NOT_EXISTS",new HashMap(),dummyCallbackHandler); + Assert.assertNull("Provided mechanism must be null.", httpServerAuthenticationMechanism); + } +} diff --git a/http/external/pom.xml b/http/external/pom.xml index 8fabf72fb42..3951119cd99 100644 --- a/http/external/pom.xml +++ b/http/external/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/http/external/src/main/java/org/wildfly/security/http/external/ExternalAuthenticationMechanism.java b/http/external/src/main/java/org/wildfly/security/http/external/ExternalAuthenticationMechanism.java index 8dc6da9b21a..bd6021da8cc 100644 --- a/http/external/src/main/java/org/wildfly/security/http/external/ExternalAuthenticationMechanism.java +++ b/http/external/src/main/java/org/wildfly/security/http/external/ExternalAuthenticationMechanism.java @@ -59,19 +59,24 @@ public void evaluateRequest(HttpServerRequest request) throws HttpAuthentication String remoteUser = request.getRemoteUser(); if (remoteUser == null) { + httpExternal.trace("The remote-user was not obtained from the request"); request.noAuthenticationInProgress(); return; } if (authorize(remoteUser)) { + httpExternal.tracef("Authorization of user [%s] succeed", remoteUser); succeed(request); } else { + httpExternal.tracef("Authorization of user [%s] failed", remoteUser); fail(request); } } private boolean authorize(String username) throws HttpAuthenticationException { + httpExternal.tracef("Authorizing username: [%s]",username); + AuthorizeCallback authorizeCallback = new AuthorizeCallback(username, username); try { MechanismUtil.handleCallbacks(httpExternal, callbackHandler, authorizeCallback); @@ -88,6 +93,7 @@ private void succeed(HttpServerRequest request) throws HttpAuthenticationExcepti MechanismUtil.handleCallbacks(httpExternal, callbackHandler, AuthenticationCompleteCallback.SUCCEEDED); request.authenticationComplete(); } catch (AuthenticationMechanismException e) { + httpExternal.trace("Failed to complete successful authentication", e); throw e.toHttpAuthenticationException(); } catch (UnsupportedCallbackException e) { throw httpExternal.mechCallbackHandlerFailedForUnknownReason(e).toHttpAuthenticationException(); @@ -99,6 +105,7 @@ private void fail(HttpServerRequest request) throws HttpAuthenticationException MechanismUtil.handleCallbacks(httpExternal, callbackHandler, AuthenticationCompleteCallback.FAILED); request.authenticationFailed(httpExternal.authenticationFailed(), response -> response.setStatusCode(FORBIDDEN)); } catch (AuthenticationMechanismException e) { + httpExternal.trace("Failed authentication not completed", e); throw e.toHttpAuthenticationException(); } catch (UnsupportedCallbackException e) { throw httpExternal.mechCallbackHandlerFailedForUnknownReason(e).toHttpAuthenticationException(); diff --git a/http/form/pom.xml b/http/form/pom.xml index bc27ee4898d..2b72dc32603 100644 --- a/http/form/pom.xml +++ b/http/form/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/http/oidc/pom.xml b/http/oidc/pom.xml index fd1e3bba60b..f0a272321fb 100644 --- a/http/oidc/pom.xml +++ b/http/oidc/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml @@ -128,6 +128,11 @@ keycloak-admin-client test + + org.keycloak + keycloak-services + test + org.jboss.logmanager jboss-logmanager @@ -164,7 +169,7 @@ test - org.glassfish + org.eclipse.parsson jakarta.json test @@ -173,6 +178,17 @@ jmockit test + + org.wildfly.security + wildfly-elytron-credential-source-impl + test + + + org.wildfly.security + wildfly-elytron-tests-common + test-jar + test + diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java index 773b59d8bb3..e836cc3b468 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java @@ -18,10 +18,10 @@ package org.wildfly.security.http.oidc; +import static org.jboss.logging.annotations.Message.NONE; import static org.jboss.logging.Logger.Level.DEBUG; import static org.jboss.logging.Logger.Level.ERROR; import static org.jboss.logging.Logger.Level.WARN; -import static org.jboss.logging.annotations.Message.NONE; import java.io.IOException; @@ -88,11 +88,11 @@ interface ElytronMessages extends BasicLogger { @Message(id = 23011, value = "Refresh token failure") void refreshTokenFailure(@Cause Throwable cause); - @LogMessage(level = ERROR) + @LogMessage(level = DEBUG) @Message(id = 23012, value = "Refresh token failure status: %d %s") void refreshTokenFailureStatus(int status, String error); - @LogMessage(level = ERROR) + @LogMessage(level = DEBUG) @Message(id = 23013, value = "Failed verification of token: %s") void failedVerificationOfToken(String error); @@ -238,5 +238,45 @@ interface ElytronMessages extends BasicLogger { @Message(id = 23057, value = "principal-attribute '%s' claim does not exist, falling back to 'sub'") void principalAttributeClaimDoesNotExist(String principalAttributeClaim); + @Message(id = 23058, value = "Invalid keystore configuration for signing Request Objects.") + IOException invalidKeyStoreConfiguration(); + + @Message(id = 23059, value = "The signature algorithm specified is not supported by the OpenID Provider.") + IOException invalidRequestObjectSignatureAlgorithm(); + + @Message(id = 23060, value = "The encryption algorithm specified is not supported by the OpenID Provider.") + IOException invalidRequestObjectEncryptionAlgorithm(); + + @Message(id = 23061, value = "The content encryption algorithm (enc value) specified is not supported by the OpenID Provider.") + IOException invalidRequestObjectEncryptionEncValue(); + + @LogMessage(level = WARN) + @Message(id = 23062, value = "The OpenID provider does not support request parameters. Sending the request using OAuth2 format.") + void requestParameterNotSupported(); + + @Message(id = 23063, value = "Both request object encryption algorithm and request object content encryption algorithm must be configured to encrypt the request object.") + IllegalArgumentException invalidRequestObjectEncryptionAlgorithmConfiguration(); + + @Message(id = 23064, value = "Failed to create the authentication request using the request parameter.") + RuntimeException unableToCreateRequestWithRequestParameter(@Cause Exception cause); + + @Message(id = 23065, value = "Failed to create the authentication request using the request_uri parameter.") + RuntimeException unableToCreateRequestUriWithRequestParameter(@Cause Exception cause); + + @Message (id = 23066, value = "Failed to send a request to the OpenID provider's Pushed Authorization Request endpoint.") + RuntimeException failedToSendPushedAuthorizationRequest(@Cause Exception cause); + + @Message(id = 23067, value = "Cannot retrieve the request_uri as the pushed authorization request endpoint is not available for the OpenID provider.") + RuntimeException pushedAuthorizationRequestEndpointNotAvailable(); + + @LogMessage(level = WARN) + @Message(id = 23068, value = "The request object will be unsigned. This should not be used in a production environment. To sign the request object, for use in a production environment, please specify the request object signing algorithm.") + void unsignedRequestObjectIsUsed(); + + @Message(id = 23069, value = "The client secret has not been configured. Unable to sign the request object using the client secret.") + RuntimeException clientSecretNotConfigured(); + + @Message(id = 23070, value = "Authentication request format must be one of the following: oauth2, request, request_uri.") + RuntimeException invalidAuthenticationRequestFormat(); } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/IDToken.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/IDToken.java index b6445cc412e..d40be6bfce8 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/IDToken.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/IDToken.java @@ -20,10 +20,12 @@ import static org.wildfly.security.http.oidc.ElytronMessages.log; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import java.util.Map; +import java.util.HashMap; import org.jose4j.jwt.JwtClaims; @@ -163,7 +165,13 @@ public AddressClaimSet getAddress() { if (! (addressValueAsJson instanceof JsonObject)) { throw log.invalidTokenClaimValue(); } - return new AddressClaimSet((Map) addressValueAsJson); + HashMap result; + try { + result = new ObjectMapper().readValue(addressValueAsJson.toString(), HashMap.class); + } catch (JsonProcessingException e) { + throw log.invalidTokenClaimValue(); + } + return new AddressClaimSet(result); } /** diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWKEncPublicKeyLocator.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWKEncPublicKeyLocator.java new file mode 100644 index 00000000000..819e5950671 --- /dev/null +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWKEncPublicKeyLocator.java @@ -0,0 +1,113 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.http.oidc; + +import static org.apache.http.HttpHeaders.ACCEPT; +import static org.wildfly.security.http.oidc.ElytronMessages.log; +import static org.wildfly.security.http.oidc.Oidc.JSON_CONTENT_TYPE; + +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.Map; +import java.util.List; + +import org.apache.http.client.methods.HttpGet; +import org.wildfly.security.jose.jwk.JWK; +import org.wildfly.security.jose.jwk.JsonWebKeySet; +import org.wildfly.security.jose.jwk.JsonWebKeySetUtil; + +/** + * A public key locator that dynamically obtains the public key used for encryption + * from an OpenID provider by sending a request to the provider's {@code jwks_uri} + * when needed. + * + * @author Prarthona Paul + * */ +class JWKEncPublicKeyLocator implements PublicKeyLocator { + private List currentKeys = new ArrayList<>(); + + private volatile int lastRequestTime = 0; + + @Override + public PublicKey getPublicKey(String kid, OidcClientConfiguration config) { + int minTimeBetweenRequests = config.getMinTimeBetweenJwksRequests(); + int publicKeyCacheTtl = config.getPublicKeyCacheTtl(); + int currentTime = getCurrentTime(); + + PublicKey publicKey = lookupCachedKey(publicKeyCacheTtl, currentTime); + if (publicKey != null) { + return publicKey; + } + + synchronized (this) { + currentTime = getCurrentTime(); + if (currentTime > lastRequestTime + minTimeBetweenRequests) { + sendRequest(config); + lastRequestTime = currentTime; + } else { + log.debug("Won't send request to jwks url. Last request time was " + lastRequestTime); + } + return lookupCachedKey(publicKeyCacheTtl, currentTime); + } + + } + + @Override + public void reset(OidcClientConfiguration config) { + synchronized (this) { + sendRequest(config); + lastRequestTime = getCurrentTime(); + } + } + + private PublicKey lookupCachedKey(int publicKeyCacheTtl, int currentTime) { + if (lastRequestTime + publicKeyCacheTtl > currentTime) { + return currentKeys.get(0); // returns the first cached public key + } else { + return null; + } + } + + private static int getCurrentTime() { + return (int) (System.currentTimeMillis() / 1000); + } + + private void sendRequest(OidcClientConfiguration config) { + if (log.isTraceEnabled()) { + log.trace("Going to send request to retrieve new set of public keys to encrypt a JWT request for client " + config.getResourceName()); + } + + HttpGet request = new HttpGet(config.getJwksUrl()); + request.addHeader(ACCEPT, JSON_CONTENT_TYPE); + try { + JsonWebKeySet jwks = Oidc.sendJsonHttpRequest(config, request, JsonWebKeySet.class); + Map publicKeys = JsonWebKeySetUtil.getKeysForUse(jwks, JWK.Use.ENC); + + if (log.isDebugEnabled()) { + log.debug("Public keys successfully retrieved for client " + config.getResourceName() + ". New kids: " + publicKeys.keySet()); + } + + // update current keys + currentKeys.clear(); + currentKeys.addAll(publicKeys.values()); + } catch (OidcException e) { + log.error("Error when sending request to retrieve public keys", e); + } + } +} diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTClientCredentialsProvider.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTClientCredentialsProvider.java index 4da8d3a5384..13df213373b 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTClientCredentialsProvider.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTClientCredentialsProvider.java @@ -19,18 +19,13 @@ package org.wildfly.security.http.oidc; import static org.wildfly.security.http.oidc.ElytronMessages.log; +import static org.wildfly.security.http.oidc.JWTSigningUtils.loadKeyPairFromKeyStore; import static org.wildfly.security.http.oidc.Oidc.CLIENT_ASSERTION; import static org.wildfly.security.http.oidc.Oidc.CLIENT_ASSERTION_TYPE; import static org.wildfly.security.http.oidc.Oidc.CLIENT_ASSERTION_TYPE_JWT; -import static org.wildfly.security.http.oidc.Oidc.PROTOCOL_CLASSPATH; import static org.wildfly.security.http.oidc.Oidc.asInt; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; import java.security.KeyPair; -import java.security.KeyStore; -import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.RSAPublicKey; import java.util.Map; @@ -155,43 +150,4 @@ protected JwtClaims createRequestToken(String clientId, String tokenUrl) { jwtClaims.setExpirationTime(exp); return jwtClaims; } - - private static KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType) { - InputStream stream = findFile(keyStoreFile); - try { - KeyStore keyStore = KeyStore.getInstance(keyStoreType); - keyStore.load(stream, storePassword.toCharArray()); - PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyPassword.toCharArray()); - if (privateKey == null) { - log.unableToLoadKeyWithAlias(keyAlias); - } - PublicKey publicKey = keyStore.getCertificate(keyAlias).getPublicKey(); - return new KeyPair(publicKey, privateKey); - } catch (Exception e) { - throw log.unableToLoadPrivateKey(e); - } - } - - private static InputStream findFile(String keystoreFile) { - if (keystoreFile.startsWith(PROTOCOL_CLASSPATH)) { - String classPathLocation = keystoreFile.replace(PROTOCOL_CLASSPATH, ""); - // try current class classloader first - InputStream is = JWTClientCredentialsProvider.class.getClassLoader().getResourceAsStream(classPathLocation); - if (is == null) { - is = Thread.currentThread().getContextClassLoader().getResourceAsStream(classPathLocation); - } - if (is != null) { - return is; - } else { - throw log.unableToFindKeystoreFile(keystoreFile); - } - } else { - try { - // fallback to file - return new FileInputStream(keystoreFile); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - } - } } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTSigningUtils.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTSigningUtils.java new file mode 100644 index 00000000000..03546d8a23f --- /dev/null +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTSigningUtils.java @@ -0,0 +1,78 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.http.oidc; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; + +import static org.wildfly.security.http.oidc.ElytronMessages.log; +import static org.wildfly.security.http.oidc.Oidc.PROTOCOL_CLASSPATH; + +/** + * A utility class to obtain the KeyPair from a keystore file. + * + * @author Prarthona Paul + */ + +class JWTSigningUtils { + + public static KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType) { + InputStream stream = findFile(keyStoreFile); + try { + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(stream, storePassword.toCharArray()); + PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyPassword.toCharArray()); + if (privateKey == null) { + throw log.unableToLoadKeyWithAlias(keyAlias); + } + PublicKey publicKey = keyStore.getCertificate(keyAlias).getPublicKey(); + return new KeyPair(publicKey, privateKey); + } catch (Exception e) { + throw log.unableToLoadPrivateKey(e); + } + } + + public static InputStream findFile(String keystoreFile) { + if (keystoreFile.startsWith(PROTOCOL_CLASSPATH)) { + String classPathLocation = keystoreFile.replace(PROTOCOL_CLASSPATH, ""); + // try current class classloader first + InputStream is = JWTSigningUtils.class.getClassLoader().getResourceAsStream(classPathLocation); + if (is == null) { + is = Thread.currentThread().getContextClassLoader().getResourceAsStream(classPathLocation); + } + if (is != null) { + return is; + } else { + throw log.unableToFindKeystoreFile(keystoreFile); + } + } else { + try { + // fallback to file + return new FileInputStream(keystoreFile); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java index f42313b7f58..c6b38c9ef4d 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java @@ -45,6 +45,13 @@ public class Oidc { public static final String ACCEPT = "Accept"; + public static final String ADAPTER_STATE_COOKIE_PATH = "adapter-state-cookie-path"; + public static final String ALLOW_ANY_HOSTNAME = "allow-any-hostname"; + public static final String ALWAYS_REFRESH_TOKEN = "always-refresh-token"; + public static final String AUTH_SERVER_URL = "auth-server-url"; + public static final String AUTHENTICATION_REQUEST_FORMAT = "authentication-request-format"; + public static final String AUTODETECT_BEARER_ONLY = "autodetect-bearer-only"; + public static final String BEARER_ONLY = "bearer-only"; public static final String OIDC_NAME = "OIDC"; public static final String JSON_CONTENT_TYPE = "application/json"; public static final String HTML_CONTENT_TYPE = "text/html"; @@ -54,14 +61,27 @@ public class Oidc { public static final String KEYCLOAK_REALMS_PATH = "realms/"; public static final String JSON_CONFIG_CONTEXT_PARAM = "org.wildfly.security.http.oidc.json.config"; static final String ACCOUNT_PATH = "account"; + public static final String CORS_MAX_AGE = "cors-max-age"; + public static final String CORS_ALLOWED_HEADERS = "cors-allowed-headers"; + public static final String CORS_ALLOWED_METHODS = "cors-allowed-methods"; + public static final String CORS_EXPOSED_HEADERS = "cors-exposed-headers"; + public static final String CONNECTION_POOL_SIZE = "connection-pool-size"; public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "clients-managements/register-node"; public static final String CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH = "clients-managements/unregister-node"; + public static final String CREDENTIALS = "credentials"; + public static final String DISABLE_TRUST_MANAGER = "disable-trust-manager"; public static final String SLASH = "/"; public static final String OIDC_CLIENT_CONTEXT_KEY = OidcClientContext.class.getName(); public static final String CLIENT_ID = "client_id"; + public static final String CLIENT_ID_JSON_VALUE = "client-id"; + public static final String CLIENT_KEYSTORE = "client-keystore"; + public static final String CLIENT_KEYSTORE_PASSWORD = "client-keystore-password"; + public static final String CLIENT_KEY_PASSWORD = "client-key-password"; public static final String CODE = "code"; + public static final String ENABLE_CORS = "enable-cors"; public static final String ERROR = "error"; public static final String ERROR_DESCRIPTION = "error_description"; + public static final String EXPOSE_TOKEN = "expose-token"; public static final String FACES_REQUEST = "Faces-Request"; public static final String GRANT_TYPE = "grant_type"; public static final String INVALID_TOKEN = "invalid_token"; @@ -73,7 +93,17 @@ public class Oidc { public static final String OPTIONS = "OPTIONS"; public static final String PARTIAL = "partial/"; public static final String PASSWORD = "password"; + public static final String PRINCIPAL_ATTRIBUTE = "principal-attribute"; public static final String PROMPT = "prompt"; + public static final String PROXY_URL = "proxy-url"; + public static final String PUBLIC_CLIENT = "public-client"; + public static final String REALM = "realm"; + public static final String REALM_PUBLIC_KEY = "realm-public-key"; + public static final String REGISTER_NODE_AT_STARTUP = "register-node-at-startup"; + public static final String REGISTER_NODE_PERIOD = "register-node-period"; + public static final String REQUEST = "request"; + public static final String REQUEST_URI = "request_uri"; + public static final String RESOURCE = "resource"; public static final String SCOPE = "scope"; public static final String UI_LOCALES = "ui_locales"; public static final String USERNAME = "username"; @@ -83,6 +113,7 @@ public class Oidc { public static final String RESPONSE_TYPE = "response_type"; public static final String SESSION_STATE = "session_state"; public static final String SOAP_ACTION = "SOAPAction"; + public static final String SSL_REQUIRED = "ssl-required"; public static final String STALE_TOKEN = "Stale token"; public static final String STATE = "state"; public static final int INVALID_ISSUED_FOR_CLAIM = -1; @@ -115,8 +146,33 @@ public class Oidc { static final String DEFAULT_TOKEN_SIGNATURE_ALGORITHM = "RS256"; public static final String DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME = "wildfly.elytron.oidc.disable.typ.claim.validation"; public static final String ALLOW_QUERY_PARAMS_PROPERTY_NAME = "wildfly.elytron.oidc.allow.query.params"; + public static final String TOKEN_MINIMUM_TIME_TO_LIVE = "token-minimum-time-to-live"; + public static final String TOKEN_SIGNATURE_ALGORITHM = "token-signature-algorithm"; + public static final String TOKEN_STORE = "token-store"; + public static final String TRUSTSTORE = "truststore"; + public static final String TRUSTSTORE_PASSWORD = "truststore-password"; + public static final String TURN_OFF_CHANGE_SESSION_ID_ON_LOGIN = "turn-off-change-session-id-on-login"; + public static final String USE_RESOURCE_ROLE_MAPPINGS = "use-resource-role-mappings"; + public static final String USE_REALM_ROLE_MAPPINGS = "use-realm-role-mappings"; public static final String X_REQUESTED_WITH = "X-Requested-With"; public static final String XML_HTTP_REQUEST = "XMLHttpRequest"; + public static final String MIN_TIME_BETWEEN_JWKS_REQUESTS = "min-time-between-jwks-requests"; + public static final String PUBLIC_KEY_CACHE_TTL = "public-key-cache-ttl"; + public static final String IGNORE_OAUTH_QUERY_PARAMETER = "ignore-oauth-query-parameter"; + public static final String VERIFY_TOKEN_AUDIENCE = "verify-token-audience"; + public static final String REQUEST_OBJECT_SIGNING_ALGORITHM = "request-object-signing-algorithm"; + public static final String REQUEST_OBJECT_ENCRYPTION_ALG_VALUE = "request-object-encryption-alg-value"; + public static final String REQUEST_OBJECT_ENCRYPTION_ENC_VALUE = "request-object-encryption-enc-value"; + public static final String REQUEST_OBJECT_SIGNING_KEYSTORE_FILE = "request-object-signing-keystore-file"; + public static final String REQUEST_OBJECT_SIGNING_KEYSTORE_PASSWORD = "request-object-signing-keystore-password"; + public static final String REQUEST_OBJECT_SIGNING_KEY_PASSWORD = "request-object-signing-key-password"; + public static final String REQUEST_OBJECT_SIGNING_KEY_ALIAS = "request-object-signing-key-alias"; + public static final String REQUEST_OBJECT_SIGNING_KEYSTORE_TYPE = "request-object-signing-keystore-type"; + public static final String REDIRECT_REWRITE_RULES = "redirect-rewrite-rules"; + public static final String ENABLE_PKCE = "enable-pkce"; + public static final String CONFIDENTIAL_PORT = "confidential-port"; + public static final String ENABLE_BASIC_AUTH = "enable-basic-auth"; + public static final String PROVIDER_URL = "provider-url"; /** * Bearer token pattern. @@ -201,6 +257,27 @@ public enum TokenStore { COOKIE } + public enum AuthenticationRequestFormat { + OAUTH2("oauth2"), + REQUEST("request"), + REQUEST_URI("request_uri"); + + private final String value; + + AuthenticationRequestFormat(String value) { + this.value = value; + } + + /** + * Get the string value for this authentication format. + * + * @return the string value for this authentication format + */ + public String getValue() { + return value; + } + } + public enum ClientCredentialsProviderType { SECRET("secret"), JWT("jwt"), diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java index db872b30a89..ca56da28633 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java @@ -30,9 +30,11 @@ import static org.wildfly.security.http.oidc.Oidc.SLASH; import static org.wildfly.security.http.oidc.Oidc.SSLRequired; import static org.wildfly.security.http.oidc.Oidc.TokenStore; +import static org.wildfly.security.jose.util.JsonSerialization.readValue; import java.net.URI; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.Callable; @@ -41,7 +43,6 @@ import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.util.EntityUtils; -import org.wildfly.security.jose.util.JsonSerialization; /** * The OpenID Connect (OIDC) configuration for a client application. This class is based on @@ -81,6 +82,11 @@ public enum RelativeUrlsUsed { protected String jwksUrl; protected String issuerUrl; protected String principalAttribute = "sub"; + protected List requestObjectSigningAlgValuesSupported; + protected List requestObjectEncryptionEncValuesSupported; + protected List requestObjectEncryptionAlgValuesSupported; + protected boolean requestParameterSupported; + protected boolean requestUriParameterSupported; protected String resource; protected String clientId; @@ -126,6 +132,17 @@ public enum RelativeUrlsUsed { protected boolean verifyTokenAudience = false; protected String tokenSignatureAlgorithm = DEFAULT_TOKEN_SIGNATURE_ALGORITHM; + protected String authenticationRequestFormat; + protected String requestObjectSigningAlgorithm; + protected String requestObjectEncryptionAlgValue; + protected String requestObjectEncryptionEncValue; + protected String pushedAuthorizationRequestEndpoint; + protected String requestObjectSigningKeyStoreFile; + protected String requestObjectSigningKeyStorePassword; + protected String requestObjectSigningKeyPassword; + protected String requestObjectSigningKeyAlias; + protected String requestObjectSigningKeyStoreType; + protected JWKEncPublicKeyLocator encryptionPublicKeyLocator; public OidcClientConfiguration() { } @@ -223,6 +240,13 @@ protected void resolveUrls() { tokenUrl = config.getTokenEndpoint(); logoutUrl = config.getLogoutEndpoint(); jwksUrl = config.getJwksUri(); + requestParameterSupported = config.getRequestParameterSupported(); + requestObjectSigningAlgValuesSupported = config.getRequestObjectSigningAlgValuesSupported(); + requestObjectEncryptionEncValuesSupported = config.getRequestObjectEncryptionEncValuesSupported(); + requestObjectEncryptionAlgValuesSupported = config.getRequestObjectEncryptionAlgValuesSupported(); + requestUriParameterSupported = config.getRequestUriParameterSupported(); + pushedAuthorizationRequestEndpoint = config.getPushedAuthorizationRequestEndpoint(); + if (authServerBaseUrl != null) { // keycloak-specific properties accountUrl = getUrl(issuerUrl, ACCOUNT_PATH); @@ -246,7 +270,7 @@ protected OidcProviderMetadata getOidcProviderMetadata(String discoveryUrl) thro EntityUtils.consumeQuietly(response.getEntity()); throw new Exception(response.getStatusLine().getReasonPhrase()); } - return JsonSerialization.readValue(response.getEntity().getContent(), OidcProviderMetadata.class); + return readValue(response.getEntity().getContent(), OidcProviderMetadata.class); } finally { request.releaseConnection(); } @@ -329,6 +353,26 @@ public String getIssuerUrl() { return issuerUrl; } + public List getRequestObjectSigningAlgValuesSupported() { + return requestObjectSigningAlgValuesSupported; + } + + public List getRequestObjectEncryptionAlgValuesSupported() { + return requestObjectEncryptionAlgValuesSupported; + } + + public List getRequestObjectEncryptionEncValuesSupported() { + return requestObjectEncryptionEncValuesSupported; + } + + public boolean getRequestParameterSupported() { + return requestParameterSupported; + } + + public boolean getRequestUriParameterSupported() { + return requestUriParameterSupported; + } + public void setResource(String resource) { this.resource = resource; } @@ -419,10 +463,7 @@ public void setSSLRequired(SSLRequired sslRequired) { } public boolean isSSLEnabled() { - if (SSLRequired.NONE == sslRequired) { - return false; - } - return true; + return SSLRequired.NONE != sslRequired; } public int getConfidentialPort() { @@ -651,4 +692,91 @@ public String getTokenSignatureAlgorithm() { return tokenSignatureAlgorithm; } + public String getAuthenticationRequestFormat() { + return authenticationRequestFormat; + } + + public void setAuthenticationRequestFormat(String authenticationRequestFormat) { + this.authenticationRequestFormat = authenticationRequestFormat; + } + + public String getRequestObjectSigningAlgorithm() { + return requestObjectSigningAlgorithm; + } + + public void setRequestObjectSigningAlgorithm(String requestObjectSigningAlgorithm) { + this.requestObjectSigningAlgorithm = requestObjectSigningAlgorithm; + } + + public String getRequestObjectEncryptionAlgValue() { + return requestObjectEncryptionAlgValue; + } + + public void setRequestObjectEncryptionAlgValue(String requestObjectEncryptionAlgValue) { + this.requestObjectEncryptionAlgValue = requestObjectEncryptionAlgValue; + } + + public String getRequestObjectEncryptionEncValue() { + return requestObjectEncryptionEncValue; + } + + public void setRequestObjectEncryptionEncValue(String requestObjectEncryptionEncValue) { + this.requestObjectEncryptionEncValue = requestObjectEncryptionEncValue; + } + + public String getRequestObjectSigningKeyStoreFile() { + return requestObjectSigningKeyStoreFile; + } + + public void setRequestObjectSigningKeyStoreFile(String keyStoreFile) { + this.requestObjectSigningKeyStoreFile = keyStoreFile; + } + + public String getRequestObjectSigningKeyStorePassword() { + return requestObjectSigningKeyStorePassword; + } + + public void setRequestObjectSigningKeyStorePassword(String requestObjectSigningKeyStorePassword) { + this.requestObjectSigningKeyStorePassword = requestObjectSigningKeyStorePassword; + } + + public String getRequestObjectSigningKeyPassword() { + return requestObjectSigningKeyPassword; + } + + public void setRequestObjectSigningKeyPassword(String requestObjectSigningKeyPassword) { + this.requestObjectSigningKeyPassword = requestObjectSigningKeyPassword; + } + + public String getRequestObjectSigningKeyStoreType() { + return requestObjectSigningKeyStoreType; + } + + public void setRequestObjectSigningKeyStoreType(String requestObjectSigningKeyStoreType) { + this.requestObjectSigningKeyStoreType = requestObjectSigningKeyStoreType; + } + + public String getRequestObjectSigningKeyAlias() { + return requestObjectSigningKeyAlias; + } + + public void setRequestObjectSigningKeyAlias(String requestObjectSigningKeyAlias) { + this.requestObjectSigningKeyAlias = requestObjectSigningKeyAlias; + } + + public String getPushedAuthorizationRequestEndpoint() { + return pushedAuthorizationRequestEndpoint; + } + + public void setPushedAuthorizationRequestEndpoint(String pushedAuthorizationRequestEndpoint) { + this.pushedAuthorizationRequestEndpoint = pushedAuthorizationRequestEndpoint; + } + + public void setEncryptionPublicKeyLocator(JWKEncPublicKeyLocator publicKeySetExtractor) { + this.encryptionPublicKeyLocator = publicKeySetExtractor; + } + + public JWKEncPublicKeyLocator getEncryptionPublicKeyLocator() { + return this.encryptionPublicKeyLocator; + } } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java index 99f9b185a5d..43bebace9f6 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java @@ -18,7 +18,11 @@ package org.wildfly.security.http.oidc; +import static org.jose4j.jws.AlgorithmIdentifiers.NONE; import static org.wildfly.security.http.oidc.ElytronMessages.log; +import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.OAUTH2; +import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST; +import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST_URI; import static org.wildfly.security.http.oidc.Oidc.SSLRequired; import static org.wildfly.security.http.oidc.Oidc.TokenStore; @@ -100,6 +104,44 @@ protected OidcClientConfiguration internalBuild(final OidcJsonConfiguration oidc if (oidcJsonConfiguration.getTokenCookiePath() != null) { oidcClientConfiguration.setOidcStateCookiePath(oidcJsonConfiguration.getTokenCookiePath()); } + if (oidcJsonConfiguration.getScope() != null) { + oidcClientConfiguration.setScope(oidcJsonConfiguration.getScope()); + } + if (oidcJsonConfiguration.getAuthenticationRequestFormat() != null) { + if (!(oidcJsonConfiguration.getAuthenticationRequestFormat().equals(OAUTH2.getValue()) || + oidcJsonConfiguration.getAuthenticationRequestFormat().equals(REQUEST.getValue()) || + oidcJsonConfiguration.getAuthenticationRequestFormat().equals(REQUEST_URI.getValue()))) { + throw log.invalidAuthenticationRequestFormat(); + } + oidcClientConfiguration.setAuthenticationRequestFormat(oidcJsonConfiguration.getAuthenticationRequestFormat()); + } else { + oidcClientConfiguration.setAuthenticationRequestFormat(OAUTH2.getValue()); + } + if (oidcJsonConfiguration.getRequestObjectSigningAlgorithm() != null) { + oidcClientConfiguration.setRequestObjectSigningAlgorithm(oidcJsonConfiguration.getRequestObjectSigningAlgorithm()); + } else { + oidcClientConfiguration.setRequestObjectSigningAlgorithm(NONE); + } + if (oidcJsonConfiguration.getRequestObjectEncryptionAlgValue() != null && oidcJsonConfiguration.getRequestObjectEncryptionEncValue() != null) { //both are required to encrypt the request object + oidcClientConfiguration.setRequestObjectEncryptionAlgValue(oidcJsonConfiguration.getRequestObjectEncryptionAlgValue()); + oidcClientConfiguration.setRequestObjectEncryptionEncValue(oidcJsonConfiguration.getRequestObjectEncryptionEncValue()); + JWKEncPublicKeyLocator encryptionPublicKeyLocator = new JWKEncPublicKeyLocator(); + oidcClientConfiguration.setEncryptionPublicKeyLocator(encryptionPublicKeyLocator); + } else if (oidcJsonConfiguration.getRequestObjectEncryptionAlgValue() != null || oidcJsonConfiguration.getRequestObjectEncryptionEncValue() != null) { //if only one is specified, that is not correct + throw log.invalidRequestObjectEncryptionAlgorithmConfiguration(); + } + if (oidcJsonConfiguration.getRequestObjectSigningKeyStoreFile() != null + && oidcJsonConfiguration.getRequestObjectSigningKeyStorePassword() != null + && oidcJsonConfiguration.getRequestObjectSigningKeyPassword() != null + && oidcJsonConfiguration.getRequestObjectSigningKeyAlias() != null) { + oidcClientConfiguration.setRequestObjectSigningKeyStoreFile(oidcJsonConfiguration.getRequestObjectSigningKeyStoreFile()); + oidcClientConfiguration.setRequestObjectSigningKeyStorePassword(oidcJsonConfiguration.getRequestObjectSigningKeyStorePassword()); + oidcClientConfiguration.setRequestObjectSigningKeyPassword(oidcJsonConfiguration.getRequestObjectSigningKeyPassword()); + oidcClientConfiguration.setRequestObjectSigningKeyAlias(oidcJsonConfiguration.getRequestObjectSigningKeyAlias()); + if (oidcJsonConfiguration.getRequestObjectSigningKeyStoreType() != null) { + oidcClientConfiguration.setRequestObjectSigningKeyStoreType(oidcJsonConfiguration.getRequestObjectSigningKeyStoreType()); + } + } if (oidcJsonConfiguration.getPrincipalAttribute() != null) oidcClientConfiguration.setPrincipalAttribute(oidcJsonConfiguration.getPrincipalAttribute()); oidcClientConfiguration.setResourceCredentials(oidcJsonConfiguration.getCredentials()); @@ -190,8 +232,8 @@ public static OidcJsonConfiguration loadOidcJsonConfiguration(InputStream is) { return adapterConfig; } - public static OidcClientConfiguration build(OidcJsonConfiguration oidcJsonConfiguration) { return new OidcClientConfigurationBuilder().internalBuild(oidcJsonConfiguration); } + } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientContext.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientContext.java index 3c249bb846b..f5d930bd525 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientContext.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientContext.java @@ -525,6 +525,107 @@ public String getTokenSignatureAlgorithm() { public void setTokenSignatureAlgorithm(String tokenSignatureAlgorithm) { delegate.setTokenSignatureAlgorithm(tokenSignatureAlgorithm); } + + @Override + public String getAuthenticationRequestFormat() { + return delegate.getAuthenticationRequestFormat(); + } + + @Override + public void setAuthenticationRequestFormat(String authFormat) { + delegate.setAuthenticationRequestFormat(authFormat); + } + + @Override + public String getRequestObjectSigningAlgorithm() { + return delegate.getRequestObjectSigningAlgorithm(); + } + + @Override + public void setRequestObjectSigningAlgorithm(String requestSignature) { + delegate.setRequestObjectSigningAlgorithm(requestSignature); + } + + @Override + public String getRequestObjectEncryptionAlgValue() { + return delegate.getRequestObjectEncryptionAlgValue(); + } + + @Override + public void setRequestObjectEncryptionAlgValue(String requestObjectEncryptionAlgValue) { + delegate.setRequestObjectEncryptionAlgValue(requestObjectEncryptionAlgValue); + } + + @Override + public String getRequestObjectEncryptionEncValue() { + return delegate.requestObjectEncryptionEncValue; + } + + @Override + public void setRequestObjectEncryptionEncValue (String requestObjectEncryptionEncValue) { + delegate.requestObjectEncryptionEncValue = requestObjectEncryptionEncValue; + } + + @Override + public String getRequestObjectSigningKeyStoreFile() { + return delegate.requestObjectSigningKeyStoreFile; + } + + @Override + public void setRequestObjectSigningKeyStoreFile(String keyStoreFile) { + delegate.requestObjectSigningKeyStoreFile = keyStoreFile; + } + + @Override + public String getRequestObjectSigningKeyStorePassword() { + return delegate.requestObjectSigningKeyStorePassword; + } + + @Override + public void setRequestObjectSigningKeyStorePassword(String requestObjectSigningKeyStorePassword) { + delegate.requestObjectSigningKeyStorePassword = requestObjectSigningKeyStorePassword; + } + + @Override + public String getRequestObjectSigningKeyPassword() { + return delegate.requestObjectSigningKeyPassword; + } + + @Override + public void setRequestObjectSigningKeyPassword(String requestObjectSigningKeyPassword) { + delegate.requestObjectSigningKeyPassword = requestObjectSigningKeyPassword; + } + + @Override + public String getRequestObjectSigningKeyStoreType() { + return delegate.requestObjectSigningKeyStoreType; + } + + @Override + public void setRequestObjectSigningKeyStoreType(String type) { + delegate.requestObjectSigningKeyStoreType = type; + } + + @Override + public String getRequestObjectSigningKeyAlias() { + return delegate.requestObjectSigningKeyAlias; + } + + @Override + public void setRequestObjectSigningKeyAlias(String alias) { + delegate.requestObjectSigningKeyAlias = alias; + } + + @Override + public boolean getRequestParameterSupported() { + return delegate.requestParameterSupported; + } + + @Override + public boolean getRequestUriParameterSupported() { + return delegate.requestUriParameterSupported; + } + } protected String getAuthServerBaseUrl(OidcHttpFacade facade, String base) { diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcHttpFacade.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcHttpFacade.java index d27a3a9f200..ba5cb0fa3a9 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcHttpFacade.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcHttpFacade.java @@ -29,7 +29,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; @@ -121,8 +120,7 @@ public Principal getPrincipal() { SecurityIdentityCallback securityIdentityCallback = new SecurityIdentityCallback(); IdentityCredentialCallback credentialCallback = new IdentityCredentialCallback(new BearerTokenCredential(OidcPrincipal.class.cast(principal).getOidcSecurityContext().getTokenString()), true); callbackHandler.handle(new Callback[]{credentialCallback, AuthenticationCompleteCallback.SUCCEEDED, securityIdentityCallback}); - SecurityIdentity securityIdentity = securityIdentityCallback.getSecurityIdentity(); - return securityIdentity; + return securityIdentityCallback.getSecurityIdentity(); } } catch (UnsupportedCallbackException | IOException e) { throw new RuntimeException(e); @@ -205,11 +203,7 @@ public String getMethod() { @Override public String getURI() { - try { - return URLDecoder.decode(request.getRequestURI().toString(), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw log.failedToDecodeRequestUri(e); - } + return request.getRequestURI().toString(); } @Override @@ -230,7 +224,7 @@ public String getFirstParam(String param) { @Override public String getQueryParamValue(String param) { URI requestURI = request.getRequestURI(); - String query = requestURI.getQuery(); + String query = requestURI.getRawQuery(); if (query != null) { String[] parameters = query.split("&"); for (String parameter : parameters) { @@ -388,47 +382,7 @@ public void setCookie(final String name, final String value, final String path, } private void setCookie(final String name, final String value, final String path, final String domain, final int maxAge, final boolean secure, final boolean httpOnly, HttpServerResponse response) { - response.setResponseCookie(new HttpServerCookie() { - @Override - public String getName() { - return name; - } - - @Override - public String getValue() { - return value; - } - - @Override - public String getDomain() { - return domain; - } - - @Override - public int getMaxAge() { - return maxAge; - } - - @Override - public String getPath() { - return path; - } - - @Override - public boolean isSecure() { - return secure; - } - - @Override - public int getVersion() { - return 0; - } - - @Override - public boolean isHttpOnly() { - return httpOnly; - } - }); + response.setResponseCookie(HttpServerCookie.getInstance(name, value, domain, maxAge, path, secure, 0, httpOnly)); } @Override diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcJsonConfiguration.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcJsonConfiguration.java index 5e65d60fe06..4f107b79591 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcJsonConfiguration.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcJsonConfiguration.java @@ -19,6 +19,61 @@ package org.wildfly.security.http.oidc; import static org.wildfly.security.http.oidc.Oidc.DEFAULT_TOKEN_SIGNATURE_ALGORITHM; +import static org.wildfly.security.http.oidc.Oidc.ADAPTER_STATE_COOKIE_PATH; +import static org.wildfly.security.http.oidc.Oidc.ALLOW_ANY_HOSTNAME; +import static org.wildfly.security.http.oidc.Oidc.ALWAYS_REFRESH_TOKEN; +import static org.wildfly.security.http.oidc.Oidc.AUTH_SERVER_URL; +import static org.wildfly.security.http.oidc.Oidc.AUTHENTICATION_REQUEST_FORMAT; +import static org.wildfly.security.http.oidc.Oidc.AUTODETECT_BEARER_ONLY; +import static org.wildfly.security.http.oidc.Oidc.BEARER_ONLY; +import static org.wildfly.security.http.oidc.Oidc.CLIENT_ID_JSON_VALUE; +import static org.wildfly.security.http.oidc.Oidc.CLIENT_KEYSTORE; +import static org.wildfly.security.http.oidc.Oidc.CLIENT_KEYSTORE_PASSWORD; +import static org.wildfly.security.http.oidc.Oidc.CLIENT_KEY_PASSWORD; +import static org.wildfly.security.http.oidc.Oidc.CONFIDENTIAL_PORT; +import static org.wildfly.security.http.oidc.Oidc.CONNECTION_POOL_SIZE; +import static org.wildfly.security.http.oidc.Oidc.CORS_ALLOWED_HEADERS; +import static org.wildfly.security.http.oidc.Oidc.CORS_ALLOWED_METHODS; +import static org.wildfly.security.http.oidc.Oidc.CORS_EXPOSED_HEADERS; +import static org.wildfly.security.http.oidc.Oidc.CORS_MAX_AGE; +import static org.wildfly.security.http.oidc.Oidc.CREDENTIALS; +import static org.wildfly.security.http.oidc.Oidc.DISABLE_TRUST_MANAGER; +import static org.wildfly.security.http.oidc.Oidc.ENABLE_BASIC_AUTH; +import static org.wildfly.security.http.oidc.Oidc.ENABLE_CORS; +import static org.wildfly.security.http.oidc.Oidc.ENABLE_PKCE; +import static org.wildfly.security.http.oidc.Oidc.EXPOSE_TOKEN; +import static org.wildfly.security.http.oidc.Oidc.IGNORE_OAUTH_QUERY_PARAMETER; +import static org.wildfly.security.http.oidc.Oidc.MIN_TIME_BETWEEN_JWKS_REQUESTS; +import static org.wildfly.security.http.oidc.Oidc.PRINCIPAL_ATTRIBUTE; +import static org.wildfly.security.http.oidc.Oidc.PROVIDER_URL; +import static org.wildfly.security.http.oidc.Oidc.PROXY_URL; +import static org.wildfly.security.http.oidc.Oidc.PUBLIC_CLIENT; +import static org.wildfly.security.http.oidc.Oidc.PUBLIC_KEY_CACHE_TTL; +import static org.wildfly.security.http.oidc.Oidc.REDIRECT_REWRITE_RULES; +import static org.wildfly.security.http.oidc.Oidc.REGISTER_NODE_AT_STARTUP; +import static org.wildfly.security.http.oidc.Oidc.REGISTER_NODE_PERIOD; +import static org.wildfly.security.http.oidc.Oidc.REALM; +import static org.wildfly.security.http.oidc.Oidc.REALM_PUBLIC_KEY; +import static org.wildfly.security.http.oidc.Oidc.RESOURCE; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_ENCRYPTION_ALG_VALUE; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_ENCRYPTION_ENC_VALUE; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_ALGORITHM; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEY_ALIAS; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEY_PASSWORD; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEYSTORE_FILE; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEYSTORE_PASSWORD; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEYSTORE_TYPE; +import static org.wildfly.security.http.oidc.Oidc.SCOPE; +import static org.wildfly.security.http.oidc.Oidc.SSL_REQUIRED; +import static org.wildfly.security.http.oidc.Oidc.TOKEN_MINIMUM_TIME_TO_LIVE; +import static org.wildfly.security.http.oidc.Oidc.TOKEN_SIGNATURE_ALGORITHM; +import static org.wildfly.security.http.oidc.Oidc.TOKEN_STORE; +import static org.wildfly.security.http.oidc.Oidc.TRUSTSTORE; +import static org.wildfly.security.http.oidc.Oidc.TRUSTSTORE_PASSWORD; +import static org.wildfly.security.http.oidc.Oidc.TURN_OFF_CHANGE_SESSION_ID_ON_LOGIN; +import static org.wildfly.security.http.oidc.Oidc.USE_RESOURCE_ROLE_MAPPINGS; +import static org.wildfly.security.http.oidc.Oidc.USE_REALM_ROLE_MAPPINGS; +import static org.wildfly.security.http.oidc.Oidc.VERIFY_TOKEN_AUDIENCE; import java.util.Map; import java.util.TreeMap; @@ -34,116 +89,143 @@ * @author John D. Ament * @author Farah Juma */ -@JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required", - "resource", "public-client", "credentials", - "use-resource-role-mappings", "use-realm-role-mappings", - "enable-cors", "cors-max-age", "cors-allowed-methods", "cors-exposed-headers", - "expose-token", "bearer-only", "autodetect-bearer-only", - "connection-pool-size", - "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password", - "client-keystore", "client-keystore-password", "client-key-password", - "always-refresh-token", - "register-node-at-startup", "register-node-period", "token-store", "adapter-state-cookie-path", "principal-attribute", - "proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live", - "min-time-between-jwks-requests", "public-key-cache-ttl", - "ignore-oauth-query-parameter", "verify-token-audience", "token-signature-algorithm" +@JsonPropertyOrder({REALM, REALM_PUBLIC_KEY, AUTH_SERVER_URL, SSL_REQUIRED, + RESOURCE, PUBLIC_CLIENT, CREDENTIALS, + USE_RESOURCE_ROLE_MAPPINGS, USE_REALM_ROLE_MAPPINGS, + ENABLE_CORS, CORS_MAX_AGE, CORS_ALLOWED_METHODS, CORS_EXPOSED_HEADERS, + EXPOSE_TOKEN, BEARER_ONLY, AUTODETECT_BEARER_ONLY, CONNECTION_POOL_SIZE, + ALLOW_ANY_HOSTNAME, DISABLE_TRUST_MANAGER, TRUSTSTORE, TRUSTSTORE_PASSWORD, + CLIENT_KEYSTORE, CLIENT_KEYSTORE_PASSWORD, CLIENT_KEY_PASSWORD, + ALWAYS_REFRESH_TOKEN, + REGISTER_NODE_AT_STARTUP, REGISTER_NODE_PERIOD, TOKEN_STORE, ADAPTER_STATE_COOKIE_PATH, PRINCIPAL_ATTRIBUTE, + PROXY_URL, TURN_OFF_CHANGE_SESSION_ID_ON_LOGIN, TOKEN_MINIMUM_TIME_TO_LIVE, + MIN_TIME_BETWEEN_JWKS_REQUESTS, PUBLIC_KEY_CACHE_TTL, + IGNORE_OAUTH_QUERY_PARAMETER, VERIFY_TOKEN_AUDIENCE, TOKEN_SIGNATURE_ALGORITHM, SCOPE, + AUTHENTICATION_REQUEST_FORMAT, REQUEST_OBJECT_SIGNING_ALGORITHM, REQUEST_OBJECT_ENCRYPTION_ALG_VALUE, + REQUEST_OBJECT_ENCRYPTION_ENC_VALUE, REQUEST_OBJECT_SIGNING_KEYSTORE_FILE, + REQUEST_OBJECT_SIGNING_KEYSTORE_PASSWORD,REQUEST_OBJECT_SIGNING_KEY_PASSWORD, REQUEST_OBJECT_SIGNING_KEY_ALIAS, + REQUEST_OBJECT_SIGNING_KEYSTORE_TYPE }) public class OidcJsonConfiguration { - @JsonProperty("allow-any-hostname") + @JsonProperty(ALLOW_ANY_HOSTNAME) protected boolean allowAnyHostname; - @JsonProperty("disable-trust-manager") + @JsonProperty(DISABLE_TRUST_MANAGER) protected boolean disableTrustManager; - @JsonProperty("truststore") + @JsonProperty(TRUSTSTORE) protected String truststore; - @JsonProperty("truststore-password") + @JsonProperty(TRUSTSTORE_PASSWORD) protected String truststorePassword; - @JsonProperty("client-keystore") + @JsonProperty(CLIENT_KEYSTORE) protected String clientKeystore; - @JsonProperty("client-keystore-password") + @JsonProperty(CLIENT_KEYSTORE_PASSWORD) protected String clientKeystorePassword; - @JsonProperty("client-key-password") + @JsonProperty(CLIENT_KEY_PASSWORD) protected String clientKeyPassword; - @JsonProperty("connection-pool-size") + @JsonProperty(REQUEST_OBJECT_SIGNING_KEYSTORE_FILE) + protected String requestObjectSigningKeyStoreFile; + @JsonProperty(REQUEST_OBJECT_SIGNING_KEYSTORE_PASSWORD) + protected String requestObjectSigningKeyStorePassword; + @JsonProperty(REQUEST_OBJECT_SIGNING_KEY_PASSWORD) + protected String requestObjectSigningKeyPassword; + @JsonProperty(REQUEST_OBJECT_SIGNING_KEY_ALIAS) + protected String requestObjectSigningKeyAlias; + @JsonProperty(REQUEST_OBJECT_SIGNING_KEYSTORE_TYPE) + protected String requestObjectSigningKeyStoreType; + @JsonProperty(CONNECTION_POOL_SIZE) protected int connectionPoolSize = 20; - @JsonProperty("always-refresh-token") + @JsonProperty(ALWAYS_REFRESH_TOKEN) protected boolean alwaysRefreshToken = false; - @JsonProperty("register-node-at-startup") + @JsonProperty(REGISTER_NODE_AT_STARTUP) protected boolean registerNodeAtStartup = false; - @JsonProperty("register-node-period") + @JsonProperty(REGISTER_NODE_PERIOD) protected int registerNodePeriod = -1; - @JsonProperty("token-store") + @JsonProperty(TOKEN_STORE) protected String tokenStore; - @JsonProperty("adapter-state-cookie-path") + @JsonProperty(ADAPTER_STATE_COOKIE_PATH) protected String tokenCookiePath; - @JsonProperty("principal-attribute") + @JsonProperty(PRINCIPAL_ATTRIBUTE) protected String principalAttribute; - @JsonProperty("turn-off-change-session-id-on-login") + @JsonProperty(TURN_OFF_CHANGE_SESSION_ID_ON_LOGIN) protected Boolean turnOffChangeSessionIdOnLogin; - @JsonProperty("token-minimum-time-to-live") + @JsonProperty(TOKEN_MINIMUM_TIME_TO_LIVE) protected int tokenMinimumTimeToLive = 0; - @JsonProperty("min-time-between-jwks-requests") + @JsonProperty(MIN_TIME_BETWEEN_JWKS_REQUESTS) protected int minTimeBetweenJwksRequests = 10; - @JsonProperty("public-key-cache-ttl") + @JsonProperty(PUBLIC_KEY_CACHE_TTL) protected int publicKeyCacheTtl = 86400; // 1 day // https://tools.ietf.org/html/rfc7636 - @JsonProperty("enable-pkce") + @JsonProperty(ENABLE_PKCE) protected boolean pkce = false; - @JsonProperty("ignore-oauth-query-parameter") + @JsonProperty(IGNORE_OAUTH_QUERY_PARAMETER) protected boolean ignoreOAuthQueryParameter = false; - @JsonProperty("verify-token-audience") + @JsonProperty(VERIFY_TOKEN_AUDIENCE) protected boolean verifyTokenAudience = false; - @JsonProperty("confidential-port") + @JsonProperty(CONFIDENTIAL_PORT) protected int confidentialPort; - @JsonProperty("resource") + @JsonProperty(RESOURCE) protected String resource; - @JsonProperty("use-resource-role-mappings") + @JsonProperty(USE_RESOURCE_ROLE_MAPPINGS) protected boolean useResourceRoleMappings; - @JsonProperty("use-realm-role-mappings") + @JsonProperty(USE_REALM_ROLE_MAPPINGS) protected boolean useRealmRoleMappings = true; - @JsonProperty("enable-cors") + @JsonProperty(ENABLE_CORS) protected boolean cors; - @JsonProperty("cors-max-age") + @JsonProperty(CORS_MAX_AGE) protected int corsMaxAge = -1; - @JsonProperty("cors-allowed-headers") + @JsonProperty(CORS_ALLOWED_HEADERS) protected String corsAllowedHeaders; - @JsonProperty("cors-allowed-methods") + @JsonProperty(CORS_ALLOWED_METHODS) protected String corsAllowedMethods; - @JsonProperty("cors-exposed-headers") + @JsonProperty(CORS_EXPOSED_HEADERS) protected String corsExposedHeaders; - @JsonProperty("expose-token") + @JsonProperty(EXPOSE_TOKEN) protected boolean exposeToken; - @JsonProperty("bearer-only") + @JsonProperty(BEARER_ONLY) protected boolean bearerOnly; - @JsonProperty("autodetect-bearer-only") + @JsonProperty(AUTODETECT_BEARER_ONLY) protected boolean autodetectBearerOnly; - @JsonProperty("enable-basic-auth") + @JsonProperty(ENABLE_BASIC_AUTH) protected boolean enableBasicAuth; - @JsonProperty("public-client") + @JsonProperty(PUBLIC_CLIENT) protected boolean publicClient; - @JsonProperty("credentials") + @JsonProperty(CREDENTIALS) protected Map credentials = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - @JsonProperty("redirect-rewrite-rules") + @JsonProperty(REDIRECT_REWRITE_RULES) protected Map redirectRewriteRules; - @JsonProperty("realm") + @JsonProperty(REALM) protected String realm; - @JsonProperty("realm-public-key") + @JsonProperty(REALM_PUBLIC_KEY) protected String realmKey; - @JsonProperty("auth-server-url") + @JsonProperty(AUTH_SERVER_URL) protected String authServerUrl; - @JsonProperty("ssl-required") + @JsonProperty(SSL_REQUIRED) protected String sslRequired; - @JsonProperty("provider-url") + @JsonProperty(PROVIDER_URL) protected String providerUrl; - @JsonProperty("client-id") + @JsonProperty(CLIENT_ID_JSON_VALUE) protected String clientId; - @JsonProperty("token-signature-algorithm") + @JsonProperty(TOKEN_SIGNATURE_ALGORITHM) protected String tokenSignatureAlgorithm = DEFAULT_TOKEN_SIGNATURE_ALGORITHM; + @JsonProperty(SCOPE) + protected String scope; + @JsonProperty(AUTHENTICATION_REQUEST_FORMAT) + protected String authenticationRequestFormat; + + @JsonProperty(REQUEST_OBJECT_SIGNING_ALGORITHM) + protected String requestObjectSigningAlgorithm; + + @JsonProperty(REQUEST_OBJECT_ENCRYPTION_ALG_VALUE) + protected String requestObjectEncryptionAlgValue; + + @JsonProperty(REQUEST_OBJECT_ENCRYPTION_ENC_VALUE) + protected String requestObjectEncryptionEncValue; + /** * The Proxy url to use for requests to the auth-server, configurable via the adapter config property {@code proxy-url}. */ - @JsonProperty("proxy-url") + @JsonProperty(PROXY_URL) protected String proxyUrl; public boolean isAllowAnyHostname() { @@ -178,6 +260,13 @@ public void setTruststorePassword(String truststorePassword) { this.truststorePassword = truststorePassword; } + public String getRequestObjectSigningKeyStoreFile() { + return requestObjectSigningKeyStoreFile; + } + + public void setRequestObjectSigningKeyStoreFile(String requestObjectSigningKeyStoreFile) { + this.requestObjectSigningKeyStoreFile = requestObjectSigningKeyStoreFile; + } public String getClientKeystore() { return clientKeystore; } @@ -186,6 +275,22 @@ public void setClientKeystore(String clientKeystore) { this.clientKeystore = clientKeystore; } + public String getRequestObjectSigningKeyStoreType() { + return requestObjectSigningKeyStoreType; + } + + public void setRequestObjectSigningKeyStoreType(String requestObjectSigningKeyStoreType) { + this.requestObjectSigningKeyStoreType = requestObjectSigningKeyStoreType; + } + + public String getRequestObjectSigningKeyAlias() { + return requestObjectSigningKeyAlias; + } + + public void setRequestObjectSigningKeyAlias(String requestObjectSigningKeyAlias) { + this.requestObjectSigningKeyAlias = requestObjectSigningKeyAlias; + } + public String getClientKeystorePassword() { return clientKeystorePassword; } @@ -198,10 +303,26 @@ public String getClientKeyPassword() { return clientKeyPassword; } + public String getRequestObjectSigningKeyPassword() { + return requestObjectSigningKeyPassword; + } + + public String getRequestObjectSigningKeyStorePassword() { + return requestObjectSigningKeyStorePassword; + } + public void setClientKeyPassword(String clientKeyPassword) { this.clientKeyPassword = clientKeyPassword; } + public void setRequestObjectSigningKeyStorePassword(String requestObjectSigningKeyStorePassword) { + this.requestObjectSigningKeyStorePassword = requestObjectSigningKeyStorePassword; + } + + public void setRequestObjectSigningKeyPassword(String requestObjectSigningKeyPassword) { + this.requestObjectSigningKeyPassword = requestObjectSigningKeyPassword; + } + public int getConnectionPoolSize() { return connectionPoolSize; } @@ -511,5 +632,43 @@ public void setTokenSignatureAlgorithm(String tokenSignatureAlgorithm) { this.tokenSignatureAlgorithm = tokenSignatureAlgorithm; } + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + public String getAuthenticationRequestFormat() { + return authenticationRequestFormat; + } + + public void setAuthenticationRequestFormat(String authenticationRequestFormat) { + this.authenticationRequestFormat = authenticationRequestFormat; + } + + public String getRequestObjectSigningAlgorithm() { + return requestObjectSigningAlgorithm; + } + + public void setRequestObjectSigningAlgorithm(String requestObjectSigningAlgorithm) { + this.requestObjectSigningAlgorithm = requestObjectSigningAlgorithm; + } + + public String getRequestObjectEncryptionAlgValue() { + return requestObjectEncryptionAlgValue; + } + + public void setRequestObjectEncryptionAlgValue(String requestObjectEncryptionAlgValue) { + this.requestObjectEncryptionAlgValue = requestObjectEncryptionAlgValue; + } + + public String getRequestObjectEncryptionEncValue() { + return requestObjectEncryptionEncValue; + } + + public void setRequestObjectEncryptionEncValue (String requestObjectEncryptionEncValue) { + this.requestObjectEncryptionEncValue = requestObjectEncryptionEncValue; + } } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcPrincipal.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcPrincipal.java index dc26a5c49be..b506b6b7596 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcPrincipal.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcPrincipal.java @@ -50,9 +50,7 @@ public boolean equals(Object o) { OidcPrincipal that = (OidcPrincipal) o; - if (! name.equals(that.name)) return false; - - return true; + return name.equals(that.name); } @Override diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcProviderMetadata.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcProviderMetadata.java index 9984de7c023..7619a89b4b0 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcProviderMetadata.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcProviderMetadata.java @@ -114,6 +114,9 @@ public class OidcProviderMetadata { @JsonProperty("request_uri_parameter_supported") private Boolean requestUriParameterSupported; + @JsonProperty("pushed_authorization_request_endpoint") + private String pushedAuthorizationRequestEndpoint; + @JsonProperty("revocation_endpoint") private String revocationEndpoint; @@ -142,6 +145,12 @@ public class OidcProviderMetadata { @JsonProperty("tls_client_certificate_bound_access_tokens") private Boolean tlsClientCertificateBoundAccessTokens; + @JsonProperty("request_object_encryption_enc_values_supported") + private List requestObjectEncryptionEncValuesSupported; + + @JsonProperty("request_object_encryption_alg_values_supported") + private List requestObjectEncryptionAlgValuesSupported; + protected Map otherClaims = new HashMap(); public String getIssuer() { @@ -320,8 +329,8 @@ public void setClaimTypesSupported(List claimTypesSupported) { this.claimTypesSupported = claimTypesSupported; } - public Boolean getClaimsParameterSupported() { - return claimsParameterSupported; + public boolean getClaimsParameterSupported() { + return claimsParameterSupported == null ? false : claimsParameterSupported; } public void setClaimsParameterSupported(Boolean claimsParameterSupported) { @@ -336,16 +345,16 @@ public void setScopesSupported(List scopesSupported) { this.scopesSupported = scopesSupported; } - public Boolean getRequestParameterSupported() { - return requestParameterSupported; + public boolean getRequestParameterSupported() { + return requestParameterSupported == null ? false : requestParameterSupported; } public void setRequestParameterSupported(Boolean requestParameterSupported) { this.requestParameterSupported = requestParameterSupported; } - public Boolean getRequestUriParameterSupported() { - return requestUriParameterSupported; + public boolean getRequestUriParameterSupported() { + return requestUriParameterSupported == null ? false : requestUriParameterSupported; } public void setRequestUriParameterSupported(Boolean requestUriParameterSupported) { @@ -384,12 +393,12 @@ public void setRevocationEndpointAuthSigningAlgValuesSupported(List revo this.revocationEndpointAuthSigningAlgValuesSupported = revocationEndpointAuthSigningAlgValuesSupported; } - public Boolean getBackchannelLogoutSupported() { - return backchannelLogoutSupported; + public boolean getBackchannelLogoutSupported() { + return backchannelLogoutSupported == null ? false : backchannelLogoutSupported; } - public Boolean getBackchannelLogoutSessionSupported() { - return backchannelLogoutSessionSupported; + public boolean getBackchannelLogoutSessionSupported() { + return backchannelLogoutSessionSupported == null ? false : backchannelLogoutSessionSupported; } public void setBackchannelLogoutSessionSupported(Boolean backchannelLogoutSessionSupported) { @@ -407,8 +416,32 @@ public List getCodeChallengeMethodsSupported() { // KEYCLOAK-6771 Certificate Bound Token // https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.2 - public Boolean getTlsClientCertificateBoundAccessTokens() { - return tlsClientCertificateBoundAccessTokens; + public boolean getTlsClientCertificateBoundAccessTokens() { + return tlsClientCertificateBoundAccessTokens == null ? false : tlsClientCertificateBoundAccessTokens; + } + + public List getRequestObjectEncryptionAlgValuesSupported() { + return requestObjectEncryptionAlgValuesSupported; + } + + public void setRequestObjectEncryptionAlgValuesSupported(List requestObjectEncryptionAlgValuesSupported) { + this.requestObjectEncryptionAlgValuesSupported = requestObjectEncryptionAlgValuesSupported; + } + + public List getRequestObjectEncryptionEncValuesSupported() { + return requestObjectEncryptionEncValuesSupported; + } + + public void setRequestObjectEncryptionEncValuesSupported(List requestObjectEncryptionEncValuesSupported) { + this.requestObjectEncryptionEncValuesSupported = requestObjectEncryptionEncValuesSupported; + } + + public String getPushedAuthorizationRequestEndpoint() { + return pushedAuthorizationRequestEndpoint; + } + + public void setPushedAuthorizationRequestEndpoint(String url) { + this.pushedAuthorizationRequestEndpoint = url; } @JsonAnyGetter diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java index bdcc7168e8f..5ef5c26122e 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java @@ -18,6 +18,10 @@ package org.wildfly.security.http.oidc; +import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA256; +import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA384; +import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA512; +import static org.jose4j.jws.AlgorithmIdentifiers.NONE; import static org.wildfly.security.http.oidc.ElytronMessages.log; import static org.wildfly.security.http.oidc.Oidc.ALLOW_QUERY_PARAMS_PROPERTY_NAME; import static org.wildfly.security.http.oidc.Oidc.CLIENT_ID; @@ -32,13 +36,17 @@ import static org.wildfly.security.http.oidc.Oidc.PROMPT; import static org.wildfly.security.http.oidc.Oidc.REDIRECT_URI; import static org.wildfly.security.http.oidc.Oidc.RESPONSE_TYPE; +import static org.wildfly.security.http.oidc.Oidc.REQUEST; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_URI; import static org.wildfly.security.http.oidc.Oidc.SCOPE; import static org.wildfly.security.http.oidc.Oidc.SESSION_STATE; import static org.wildfly.security.http.oidc.Oidc.STATE; import static org.wildfly.security.http.oidc.Oidc.UI_LOCALES; +import static org.wildfly.security.http.oidc.Oidc.ClientCredentialsProviderType.SECRET; + +import static org.wildfly.security.http.oidc.Oidc.logToken; import static org.wildfly.security.http.oidc.Oidc.generateId; import static org.wildfly.security.http.oidc.Oidc.getQueryParamValue; -import static org.wildfly.security.http.oidc.Oidc.logToken; import static org.wildfly.security.http.oidc.Oidc.stripQueryParam; import java.io.IOException; @@ -47,15 +55,27 @@ import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.KeyPair; +import java.security.PublicKey; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; -import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; +import org.apache.http.HttpStatus; import org.apache.http.client.utils.URIBuilder; import org.apache.http.message.BasicNameValuePair; +import org.jose4j.jwa.AlgorithmConstraints; +import org.jose4j.jwe.JsonWebEncryption; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.keys.HmacKey; +import org.jose4j.lang.JoseException; import org.wildfly.security.http.HttpConstants; /** @@ -181,10 +201,13 @@ protected String getRedirectUri(String state) { List forwardableQueryParams = Arrays.asList(LOGIN_HINT, DOMAIN_HINT, KC_IDP_HINT, PROMPT, MAX_AGE, UI_LOCALES, SCOPE); List forwardedQueryParams = new ArrayList<>(forwardableQueryParams.size()); + Set allScopes = new HashSet<>(); + addScopes(deployment.getScope(), allScopes); + for (String paramName : forwardableQueryParams) { String paramValue = getQueryParamValue(facade, paramName); if (SCOPE.equals(paramName)) { - paramValue = addOidcScopeIfNeeded(paramValue); + paramValue = combineAndReorderScopes(allScopes, paramValue); } if (paramValue != null && !paramValue.isEmpty()) { forwardedQueryParams.add(new BasicNameValuePair(paramName, paramValue)); @@ -195,18 +218,74 @@ protected String getRedirectUri(String state) { if (deployment.getAuthUrl() == null) { return null; } - URIBuilder redirectUriBuilder = new URIBuilder(deployment.getAuthUrl()) - .addParameter(RESPONSE_TYPE, CODE) - .addParameter(CLIENT_ID, deployment.getResourceName()) - .addParameter(REDIRECT_URI, rewrittenRedirectUri(url)) - .addParameter(STATE, state); - redirectUriBuilder.addParameters(forwardedQueryParams); + + String redirectUri = rewrittenRedirectUri(url); + URIBuilder redirectUriBuilder = new URIBuilder(deployment.getAuthUrl()); + redirectUriBuilder.addParameter(RESPONSE_TYPE, CODE) + .addParameter(CLIENT_ID, deployment.getResourceName()); + + switch (deployment.getAuthenticationRequestFormat()) { + case REQUEST: + if (deployment.getRequestParameterSupported()) { + // add request objects into request parameter + try { + createRequestWithRequestParameter(REQUEST, redirectUriBuilder, redirectUri, state, forwardedQueryParams); + } catch (IOException | JoseException e) { + throw log.unableToCreateRequestWithRequestParameter(e); + } + } else { + // send request as usual + createOAuthRequest(redirectUriBuilder, redirectUri, state, forwardedQueryParams); + log.requestParameterNotSupported(); + } + break; + case REQUEST_URI: + if (deployment.getRequestUriParameterSupported()) { + try { + createRequestWithRequestParameter(REQUEST_URI, redirectUriBuilder, redirectUri, state, forwardedQueryParams); + } catch (IOException | JoseException e) { + throw log.unableToCreateRequestUriWithRequestParameter(e); + } + } else { + // send request as usual + createOAuthRequest(redirectUriBuilder, redirectUri, state, forwardedQueryParams); + log.requestParameterNotSupported(); + } + break; + default: + createOAuthRequest(redirectUriBuilder, redirectUri, state, forwardedQueryParams); + break; + } return redirectUriBuilder.build().toString(); } catch (URISyntaxException e) { throw log.unableToCreateRedirectResponse(e); } } + protected URIBuilder createOAuthRequest(URIBuilder redirectUriBuilder, String redirectUri, String state, List forwardedQueryParams) { + redirectUriBuilder.addParameter(REDIRECT_URI, redirectUri) + .addParameter(STATE, state) + .addParameters(forwardedQueryParams); + return redirectUriBuilder; + } + + protected URIBuilder createRequestWithRequestParameter(String requestFormat, URIBuilder redirectUriBuilder, String redirectUri, String state, List forwardedQueryParams) throws JoseException, IOException { + String request = convertToRequestParameter(redirectUriBuilder, redirectUri, state, forwardedQueryParams); + + switch (requestFormat) { + case REQUEST: + redirectUriBuilder.addParameter(REDIRECT_URI, redirectUri) + .addParameter(REQUEST, request); + break; + case REQUEST_URI: + String request_uri = ServerRequest.getRequestUri(request, deployment); + redirectUriBuilder.addParameter("request_uri", request_uri) + .addParameter(REDIRECT_URI, redirectUri); + break; + } + return redirectUriBuilder; + } + protected int getSSLRedirectPort() { return sslRedirectPort; } @@ -435,4 +514,112 @@ private static boolean hasScope(String scopeParam, String targetScope) { } return false; } + + private String combineAndReorderScopes(Set allScopes, String paramValue) { + StringBuilder combinedScopes = new StringBuilder(); + addScopes(paramValue, allScopes); + + //some OpenID providers require openid scope to be added in the beginning + combinedScopes.append(OIDC_SCOPE); + for (String scope : allScopes) { + if (!scope.equals(OIDC_SCOPE)) { + combinedScopes.append(" ").append(scope); + } + } + return combinedScopes.toString(); + } + + private void addScopes(String scopes, Set allScopes) { + if (scopes != null && !scopes.isEmpty()) { + allScopes.addAll(Arrays.asList(scopes.split("\\s+"))); + } + } + + private String convertToRequestParameter(URIBuilder redirectUriBuilder, String redirectUri, String state, List forwardedQueryParams) throws JoseException, IOException { + redirectUriBuilder.addParameter(SCOPE, OIDC_SCOPE); + + JwtClaims jwtClaims = new JwtClaims(); + jwtClaims.setIssuer(deployment.getResourceName()); + jwtClaims.setAudience(deployment.getIssuerUrl()); + + for ( NameValuePair parameter: forwardedQueryParams) { + jwtClaims.setClaim(parameter.getName(), parameter.getValue()); + } + jwtClaims.setClaim(STATE, state); + jwtClaims.setClaim(REDIRECT_URI, redirectUri); + jwtClaims.setClaim(RESPONSE_TYPE, CODE); + jwtClaims.setClaim(CLIENT_ID, deployment.getResourceName()); + + // sign JWT first before encrypting + JsonWebSignature signedRequest = signRequest(jwtClaims, deployment); + + // Encrypting optional + if (deployment.getRequestObjectEncryptionAlgValue() != null && !deployment.getRequestObjectEncryptionAlgValue().isEmpty() && + deployment.getRequestObjectEncryptionEncValue() != null && !deployment.getRequestObjectEncryptionEncValue().isEmpty()) { + return encryptRequest(signedRequest).getCompactSerialization(); + } else { + return signedRequest.getCompactSerialization(); + } + } + + private static KeyPair getkeyPair(OidcClientConfiguration deployment) throws IOException { + if (!deployment.getRequestObjectSigningAlgorithm().equals(NONE) && deployment.getRequestObjectSigningKeyStoreFile() == null){ + throw log.invalidKeyStoreConfiguration(); + } else { + return JWTSigningUtils.loadKeyPairFromKeyStore(deployment.getRequestObjectSigningKeyStoreFile(), + deployment.getRequestObjectSigningKeyStorePassword(), deployment.getRequestObjectSigningKeyPassword(), + deployment.getRequestObjectSigningKeyAlias(), deployment.getRequestObjectSigningKeyStoreType()); + } + } + + private static JsonWebSignature signRequest(JwtClaims jwtClaims, OidcClientConfiguration deployment) throws IOException, JoseException { + JsonWebSignature jsonWebSignature = new JsonWebSignature(); + jsonWebSignature.setPayload(jwtClaims.toJson()); + + if (!deployment.getRequestObjectSigningAlgValuesSupported().contains(deployment.getRequestObjectSigningAlgorithm())) { + throw log.invalidRequestObjectSignatureAlgorithm(); + } else { + if (deployment.getRequestObjectSigningAlgorithm().equals(NONE)) { //unsigned + jsonWebSignature.setAlgorithmConstraints(AlgorithmConstraints.NO_CONSTRAINTS); + jsonWebSignature.setAlgorithmHeaderValue(NONE); + } else if (deployment.getRequestObjectSigningAlgorithm().equals(HMAC_SHA256) + || deployment.getRequestObjectSigningAlgorithm().equals(HMAC_SHA384) + || deployment.getRequestObjectSigningAlgorithm().equals(HMAC_SHA512)) { //signed with symmetric key + jsonWebSignature.setAlgorithmHeaderValue(deployment.getRequestObjectSigningAlgorithm()); + String secretKey = (String) deployment.getResourceCredentials().get(SECRET.getValue()); + if (secretKey == null) { + throw log.clientSecretNotConfigured(); + } else { + Key key = new HmacKey(secretKey.getBytes(StandardCharsets.UTF_8)); //the client secret is a shared secret between the server and the client + jsonWebSignature.setKey(key); + } + } else { //signed with asymmetric key + KeyPair keyPair = getkeyPair(deployment); + jsonWebSignature.setKey(keyPair.getPrivate()); + jsonWebSignature.setAlgorithmHeaderValue(deployment.getRequestObjectSigningAlgorithm()); + } + if (!deployment.getRequestObjectSigningAlgorithm().equals(NONE)) + jsonWebSignature.sign(); + else + log.unsignedRequestObjectIsUsed(); + return jsonWebSignature; + } + } + + private JsonWebEncryption encryptRequest(JsonWebSignature signedRequest) throws JoseException, IOException { + if (!deployment.getRequestObjectEncryptionAlgValuesSupported().contains(deployment.getRequestObjectEncryptionAlgValue())) { + throw log.invalidRequestObjectEncryptionAlgorithm(); + } else if (!deployment.getRequestObjectEncryptionEncValuesSupported().contains(deployment.getRequestObjectEncryptionEncValue())) { + throw log.invalidRequestObjectEncryptionEncValue(); + } else { + JsonWebEncryption jsonEncryption = new JsonWebEncryption(); + jsonEncryption.setPayload(signedRequest.getCompactSerialization()); + jsonEncryption.setAlgorithmConstraints(new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.PERMIT, deployment.getRequestObjectEncryptionAlgValue(), deployment.getRequestObjectEncryptionEncValue())); + jsonEncryption.setAlgorithmHeaderValue(deployment.getRequestObjectEncryptionAlgValue()); + jsonEncryption.setEncryptionMethodHeaderParameter(deployment.getRequestObjectEncryptionEncValue()); + PublicKey encPublicKey = deployment.getEncryptionPublicKeyLocator().getPublicKey(null, deployment); + jsonEncryption.setKey(encPublicKey); + return jsonEncryption; + } + } } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcSecurityContext.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcSecurityContext.java index 5556f311967..c539a2e6224 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcSecurityContext.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcSecurityContext.java @@ -76,8 +76,8 @@ public String getRealm() { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); try { - token = new AccessToken(new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(tokenString)); - idToken = new IDToken(new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(idTokenString)); + token = tokenString == null ? null : new AccessToken(new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(tokenString)); + idToken = idTokenString == null ? null : new IDToken(new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(idTokenString)); } catch (InvalidJwtException e) { throw log.unableToParseToken(); } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/RequestAuthenticator.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/RequestAuthenticator.java index ca894423206..87b18e0abef 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/RequestAuthenticator.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/RequestAuthenticator.java @@ -54,10 +54,8 @@ public RequestAuthenticator(OidcHttpFacade facade, OidcClientConfiguration deplo public AuthOutcome authenticate() { AuthOutcome authenticate = doAuthenticate(); - if (AuthOutcome.AUTHENTICATED.equals(authenticate)) { - if (! facade.isAuthorized()) { - return AuthOutcome.FAILED; - } + if (AuthOutcome.AUTHENTICATED.equals(authenticate) && !facade.isAuthorized()) { + return AuthOutcome.FAILED; } return authenticate; } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/ServerRequest.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/ServerRequest.java index ad50d715c56..3a203541ee4 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/ServerRequest.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/ServerRequest.java @@ -25,13 +25,14 @@ import static org.wildfly.security.http.oidc.Oidc.KEYCLOAK_CLIENT_CLUSTER_HOST; import static org.wildfly.security.http.oidc.Oidc.PASSWORD; import static org.wildfly.security.http.oidc.Oidc.REDIRECT_URI; +import static org.wildfly.security.http.oidc.Oidc.REQUEST; import static org.wildfly.security.http.oidc.Oidc.USERNAME; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -46,6 +47,8 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.consumer.InvalidJwtException; import org.wildfly.security.jose.util.JsonSerialization; /** @@ -274,4 +277,34 @@ public static AccessAndIDTokenResponse getBearerToken(OidcClientConfiguration oi } return tokenResponse; } + + public static String getRequestUri(String request, OidcClientConfiguration deployment) throws OidcException { + if (deployment.getPushedAuthorizationRequestEndpoint() == null) { + throw log.pushedAuthorizationRequestEndpointNotAvailable(); + } + HttpPost parRequest = new HttpPost(deployment.getPushedAuthorizationRequestEndpoint()); + List formParams = new ArrayList(); + formParams.add(new BasicNameValuePair(REQUEST, request)); + ClientCredentialsProviderUtils.setClientCredentials(deployment, parRequest, formParams); + + UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, StandardCharsets.UTF_8); + parRequest.setEntity(form); + + HttpResponse response; + try { + response = deployment.getClient().execute(parRequest); + } catch (Exception e) { + throw log.failedToSendPushedAuthorizationRequest(e); + } + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) { + EntityUtils.consumeQuietly(response.getEntity()); + throw log.unexpectedResponseCodeFromOidcProvider(response.getStatusLine().getStatusCode()); + } + try (InputStream inputStream = response.getEntity().getContent()) { + JwtClaims jwt = JwtClaims.parse(readString(inputStream, StandardCharsets.UTF_8)); + return jwt.getClaimValueAsString("request_uri"); + } catch (IOException | InvalidJwtException e) { + throw log.failedToDecodeRequestUri(e); + } + } } diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/TokenValidator.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/TokenValidator.java index b1540bb1146..746318043f6 100644 --- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/TokenValidator.java +++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/TokenValidator.java @@ -274,6 +274,18 @@ public ErrorCodeValidator.Error validate(JwtContext jwtContext) throws Malformed } return null; } + + private static String getAccessTokenHash(String accessTokenString, String jwsAlgorithm) throws NoSuchAlgorithmException { + byte[] inputBytes = accessTokenString.getBytes(StandardCharsets.UTF_8); + String javaAlgName = getJavaAlgorithmForHash(jwsAlgorithm); + MessageDigest md = MessageDigest.getInstance(javaAlgName); + md.update(inputBytes); + byte[] hash = md.digest(); + int hashLength = hash.length / 2; + byte[] hashInput = Arrays.copyOf(hash, hashLength); // leftmost half of the hash + return ByteIterator.ofBytes(hashInput).base64Encode(BASE64_URL, false).drainToString(); + } + } private static class TypeValidator implements ErrorCodeValidator { @@ -297,17 +309,6 @@ public ErrorCodeValidator.Error validate(JwtContext jwtContext) throws Malformed } } - private static String getAccessTokenHash(String accessTokenString, String jwsAlgorithm) throws NoSuchAlgorithmException { - byte[] inputBytes = accessTokenString.getBytes(StandardCharsets.UTF_8); - String javaAlgName = getJavaAlgorithmForHash(jwsAlgorithm); - MessageDigest md = MessageDigest.getInstance(javaAlgName); - md.update(inputBytes); - byte[] hash = md.digest(); - int hashLength = hash.length / 2; - byte[] hashInput = Arrays.copyOf(hash, hashLength); // leftmost half of the hash - return ByteIterator.ofBytes(hashInput).base64Encode(BASE64_URL, false).drainToString(); - } - public static class VerifiedTokens { private final AccessToken accessToken; diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/BearerTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/BearerTest.java index 18c4b2f087d..275c9b181ac 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/BearerTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/BearerTest.java @@ -27,7 +27,10 @@ import static org.wildfly.security.http.oidc.Oidc.OIDC_NAME; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -36,6 +39,7 @@ import java.util.Map; import org.apache.http.HttpStatus; +import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -146,6 +150,35 @@ public static void generalCleanup() throws Exception { } } + @Test + public void testOIDCSecurityContextDeserialization() throws Exception { + String accessTokenString = KeycloakConfiguration.getAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl(), TEST_REALM, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, CLIENT_ID, CLIENT_SECRET); + AccessToken accessToken = new AccessToken(new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(accessTokenString)); + OidcSecurityContext oidcSecurityContext = new OidcSecurityContext(accessTokenString, accessToken, null, null); + OidcPrincipal oidcPrincipal = new OidcPrincipal("alice", oidcSecurityContext); + + // Serialize + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); + objectOutputStream.writeObject(oidcPrincipal); + objectOutputStream.close(); + + //deserialize + byte[] bytes = byteArrayOutputStream.toByteArray(); + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); + ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); + OidcPrincipal deserializedOidcPrincipal = (OidcPrincipal)objectInputStream.readObject(); + OidcSecurityContext deserializedOidcSecurityContext = deserializedOidcPrincipal.getOidcSecurityContext(); + AccessToken deserializedAccessToken = deserializedOidcSecurityContext.getToken(); + + assertEquals(accessTokenString, deserializedOidcSecurityContext.getTokenString()); + assertEquals(KeycloakConfiguration.ALICE, deserializedOidcPrincipal.getName()); + assertEquals(KeycloakConfiguration.ALICE, deserializedAccessToken.getPreferredUsername()); + assertEquals("alice@gmail.com", deserializedAccessToken.getEmail()); + assertEquals(TEST_REALM, deserializedOidcSecurityContext.getRealm()); + objectInputStream.close(); + } + @Test public void testSucessfulAuthenticationWithAuthServerUrl() throws Exception { performBearerAuthentication(getOidcConfigurationInputStream(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/IDTokenTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/IDTokenTest.java new file mode 100644 index 00000000000..3678d433247 --- /dev/null +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/IDTokenTest.java @@ -0,0 +1,56 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.http.oidc; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import org.jose4j.jwt.JwtClaims; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.wildfly.common.Assert.assertNotNull; + +/** + * Tests for ID Token. + */ +public class IDTokenTest { + + @Test + public void testIDTokenWithAddressClaim() { + JwtClaims jwtClaims = new JwtClaims(); + JsonObject jsonObject = Json.createObjectBuilder() + .add("address", Json.createObjectBuilder() + .add("region", "US") + .add("country", "New York") + .add("locality", "NY") + .add("postal_code", "10021")) + .build(); + jwtClaims.setClaim("given_name", "Alice"); + jwtClaims.setClaim("family_name", "Smith"); + jwtClaims.setClaim("address", jsonObject.get("address")); + IDToken idToken = new IDToken(jwtClaims); + assertNotNull(idToken); + assertEquals("NY", idToken.getAddress().getLocality()); + assertEquals("10021", idToken.getAddress().getPostalCode()); + assertEquals("US", idToken.getAddress().getRegion()); + assertEquals("New York", idToken.getAddress().getCountry()); + assertEquals("Alice", idToken.getGivenName()); + assertEquals("Smith", idToken.getFamilyName()); + } +} diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/KeycloakConfiguration.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/KeycloakConfiguration.java index bb3703dd682..8ebf4051bf2 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/KeycloakConfiguration.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/KeycloakConfiguration.java @@ -20,12 +20,23 @@ import static org.wildfly.security.http.oidc.OidcBaseTest.TENANT1_REALM; import static org.wildfly.security.http.oidc.OidcBaseTest.TENANT2_REALM; - +import static org.wildfly.security.http.oidc.Oidc.OIDC_SCOPE; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Collections; import java.util.List; +import java.util.Objects; +import javax.security.auth.x500.X500Principal; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; @@ -33,9 +44,10 @@ import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RolesRepresentation; import org.keycloak.representations.idm.UserRepresentation; - +import org.wildfly.security.ssl.test.util.CAGenerationTool; import io.restassured.RestAssured; + /** * Keycloak configuration for testing. * @@ -50,6 +62,25 @@ public class KeycloakConfiguration { private static final String BOB = "bob"; private static final String BOB_PASSWORD = "bob123+"; public static final String ALLOWED_ORIGIN = "http://somehost"; + public static final boolean EMAIL_VERIFIED = false; + public static final String RSA_KEYSTORE_FILE_NAME = "jwt.keystore"; + public static final String EC_KEYSTORE_FILE_NAME = "jwtEC.keystore"; + public static final String KEYSTORE_ALIAS = "jwtKeystore"; + public static final String KEYSTORE_PASS = "Elytron"; + public static final String PKCS12_KEYSTORE_TYPE = "PKCS12"; + public static String KEYSTORE_CLASSPATH; + + /* Accepted Request Object Encrypting Algorithms for KeyCloak*/ + public static final String RSA_OAEP = "RSA-OAEP"; + public static final String RSA_OAEP_256 = "RSA-OAEP-256"; + public static final String RSA1_5 = "RSA1_5"; + + /* Accepted Request Object Encryption Methods for KeyCloak*/ + public static final String A128CBC_HS256 = "A128CBC-HS256"; + public static final String A192CBC_HS384 = "A192CBC-HS384"; + public static final String A256CBC_HS512 = "A256CBC-HS512"; + public static CAGenerationTool caGenerationTool = null; + public X509Certificate caCertificate = null; // the users below are for multi-tenancy tests specifically public static final String TENANT1_USER = "tenant1_user"; @@ -73,20 +104,20 @@ public class KeycloakConfiguration { * */ public static RealmRepresentation getRealmRepresentation(final String realmName, String clientId, String clientSecret, - String clientHostName, int clientPort, String clientApp) { - return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp); + String clientHostName, int clientPort, String clientApp, boolean configureClientScopes) throws Exception { + return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, configureClientScopes); } public static RealmRepresentation getRealmRepresentation(final String realmName, String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp, int accessTokenLifespan, - int ssoSessionMaxLifespan, boolean multiTenancyApp) { - return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, accessTokenLifespan, ssoSessionMaxLifespan, multiTenancyApp); + int ssoSessionMaxLifespan, boolean configureClientScopes, boolean multiTenancyApp) throws Exception { + return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, accessTokenLifespan, ssoSessionMaxLifespan, configureClientScopes, multiTenancyApp); } public static RealmRepresentation getRealmRepresentation(final String realmName, String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp, boolean directAccessGrantEnabled, String bearerOnlyClientId, - String corsClientId) { + String corsClientId) throws Exception { return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, bearerOnlyClientId, corsClientId); } @@ -120,28 +151,36 @@ public static String getAccessToken(String authServerUrl, String realmName, Stri .as(AccessTokenResponse.class).getToken(); } + private static RealmRepresentation createRealm(final String realmName, String clientId, String clientSecret, + String clientHostName, int clientPort, String clientApp, + boolean directAccessGrantEnabled, String bearerOnlyClientId, + String corsClientId) throws Exception { + return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, bearerOnlyClientId, corsClientId, false); + } + private static RealmRepresentation createRealm(String name, String clientId, String clientSecret, - String clientHostName, int clientPort, String clientApp) { - return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, false, null, null); + String clientHostName, int clientPort, String clientApp, boolean configureClientScopes) throws Exception { + return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, false, null, null, configureClientScopes); } private static RealmRepresentation createRealm(String name, String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp, int accessTokenLifeSpan, int ssoSessionMaxLifespan, - boolean multiTenancyApp) { - return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, false, null, null, accessTokenLifeSpan, ssoSessionMaxLifespan, multiTenancyApp); + boolean configureClientScopes, boolean multiTenancyApp) throws Exception { + return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, false, null, null, accessTokenLifeSpan, ssoSessionMaxLifespan, configureClientScopes, multiTenancyApp); } private static RealmRepresentation createRealm(String name, String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp, boolean directAccessGrantEnabled, String bearerOnlyClientId, - String corsClientId) { - return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, bearerOnlyClientId, corsClientId, 3, 3, false); + String corsClientId, boolean configureClientScopes) throws Exception { + return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, bearerOnlyClientId, corsClientId, 3, 3, configureClientScopes, false); } private static RealmRepresentation createRealm(String name, String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp, boolean directAccessGrantEnabled, String bearerOnlyClientId, - String corsClientId, int accessTokenLifespan, int ssoSessionMaxLifespan, boolean multiTenancyApp) { + String corsClientId, int accessTokenLifespan, int ssoSessionMaxLifespan, + boolean configureClientScopes, boolean multiTenancyApp) throws Exception { RealmRepresentation realm = new RealmRepresentation(); realm.setRealm(name); realm.setEnabled(true); @@ -159,7 +198,12 @@ private static RealmRepresentation createRealm(String name, String clientId, Str realm.getRoles().getRealm().add(new RoleRepresentation("user", null, false)); realm.getRoles().getRealm().add(new RoleRepresentation("admin", null, false)); - realm.getClients().add(createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, multiTenancyApp)); + ClientRepresentation webAppClient = createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, multiTenancyApp); + if (configureClientScopes) { + webAppClient.setDefaultClientScopes(Collections.singletonList(OIDC_SCOPE)); + webAppClient.setOptionalClientScopes(Arrays.asList("phone", "email", "profile")); + } + realm.getClients().add(webAppClient); if (bearerOnlyClientId != null) { realm.getClients().add(createBearerOnlyClient(bearerOnlyClientId)); @@ -185,17 +229,12 @@ private static RealmRepresentation createRealm(String name, String clientId, Str } private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp, - boolean directAccessGrantEnabled, boolean multiTenancyApp) { + boolean directAccessGrantEnabled, boolean multiTenancyApp) throws Exception { return createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, null, multiTenancyApp); } private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort, - String clientApp, boolean directAccessGrantEnabled, String allowedOrigin) { - return createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, allowedOrigin, false); - } - - private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort, - String clientApp, boolean directAccessGrantEnabled, String allowedOrigin, boolean multiTenancyApp) { + String clientApp, boolean directAccessGrantEnabled, String allowedOrigin, boolean multiTenancyApp) throws Exception { ClientRepresentation client = new ClientRepresentation(); client.setClientId(clientId); client.setPublicClient(false); @@ -208,9 +247,29 @@ private static ClientRepresentation createWebAppClient(String clientId, String c } client.setEnabled(true); client.setDirectAccessGrantsEnabled(directAccessGrantEnabled); + if (allowedOrigin != null) { client.setWebOrigins(Collections.singletonList(allowedOrigin)); } + + OIDCAdvancedConfigWrapper oidcAdvancedConfigWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client); + oidcAdvancedConfigWrapper.setUseJwksUrl(false); + KEYSTORE_CLASSPATH = Objects.requireNonNull(KeycloakConfiguration.class.getClassLoader().getResource("")).getPath(); + File ksFile = new File(KEYSTORE_CLASSPATH + RSA_KEYSTORE_FILE_NAME); + if (ksFile.exists()) { + InputStream stream = findFile(KEYSTORE_CLASSPATH + RSA_KEYSTORE_FILE_NAME); + KeyStore keyStore = KeyStore.getInstance(PKCS12_KEYSTORE_TYPE); + keyStore.load(stream, KEYSTORE_PASS.toCharArray()); + client.getAttributes().put("jwt.credential.certificate", Base64.getEncoder().encodeToString(keyStore.getCertificate(KEYSTORE_ALIAS).getEncoded())); + } else { + caGenerationTool = CAGenerationTool.builder() + .setBaseDir(KEYSTORE_CLASSPATH) + .setRequestIdentities(CAGenerationTool.Identity.values()) // Create all identities. + .build(); + X500Principal principal = new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=OcspResponder"); + X509Certificate rsaCert = caGenerationTool.createIdentity(KEYSTORE_ALIAS, principal, RSA_KEYSTORE_FILE_NAME, CAGenerationTool.Identity.CA); + client.getAttributes().put("jwt.credential.certificate", Base64.getEncoder().encodeToString(rsaCert.getEncoded())); + } return client; } @@ -229,6 +288,7 @@ private static UserRepresentation createUser(String username, String password, L user.setCredentials(new ArrayList<>()); user.setRealmRoles(realmRoles); user.setEmail(username + "@gmail.com"); + user.setEmailVerified(EMAIL_VERIFIED); user.setFirstName("Alice"); user.setLastName("Smith"); @@ -240,4 +300,12 @@ private static UserRepresentation createUser(String username, String password, L return user; } + private static InputStream findFile(String keystoreFile) { + try { + return new FileInputStream(keystoreFile); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + } \ No newline at end of file diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/MockOidcClientConfiguration.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/MockOidcClientConfiguration.java new file mode 100644 index 00000000000..b59d75a192f --- /dev/null +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/MockOidcClientConfiguration.java @@ -0,0 +1,167 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.http.oidc; + +import com.gargoylesoftware.htmlunit.TextPage; +import io.restassured.RestAssured; +import mockit.Mock; +import mockit.MockUp; +import mockit.integration.junit4.JMockit; +import okhttp3.mockwebserver.MockWebServer; +import org.apache.http.HttpStatus; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.wildfly.security.http.HttpServerAuthenticationMechanism; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA256; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; +import static org.wildfly.security.http.oidc.KeycloakConfiguration.ALICE; +import static org.wildfly.security.http.oidc.KeycloakConfiguration.ALICE_PASSWORD; +import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST; +import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST_URI; +import static org.wildfly.security.http.oidc.Oidc.OIDC_NAME; +import static org.wildfly.security.http.oidc.Oidc.OIDC_SCOPE; + +/** + * Tests for cases where the OpenID provider does not support + * request parameters when sending the request object as a JWT. + * The OidcClientConfiguration class is mocked to return values + * indicating a lack of support for request parameters. + * + * @author Prarthona Paul + */ +@RunWith(JMockit.class) +public class MockOidcClientConfiguration extends OidcBaseTest { + + @BeforeClass + public static void startTestContainers() throws Exception { + assumeTrue("Docker isn't available, OIDC tests will be skipped", isDockerAvailable()); + KEYCLOAK_CONTAINER = new KeycloakContainer(); + KEYCLOAK_CONTAINER.start(); + sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, false)); + client = new MockWebServer(); + client.start(CLIENT_PORT); + } + + @AfterClass + public static void generalCleanup() throws Exception { + if (KEYCLOAK_CONTAINER != null) { + RestAssured + .given() + .auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl())) + .when() + .delete(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms/" + TEST_REALM).then().statusCode(204); + KEYCLOAK_CONTAINER.stop(); + } + if (client != null) { + client.shutdown(); + } + } + + @BeforeClass + public static void beforeClass() { + System.setProperty("oidc.provider.url", KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM); + } + + @AfterClass + public static void afterClass() { + System.clearProperty("oidc.provider.url"); + } + + @Test + public void testOidcWithRequestParameterUnsupported() throws Exception { + mockOidcClientConfig(); + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue()), REQUEST.getValue()); + } + + @Test + public void testOidcWithRequestUriParameterUnsupported() throws Exception { + mockOidcClientConfig(); + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_URI.getValue()), REQUEST_URI.getValue()); + } + + public void performAuthentication(InputStream oidcConfig, String requestFormat) throws Exception { + Map props = new HashMap<>(); + OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig); + assertEquals(OidcClientConfiguration.RelativeUrlsUsed.NEVER, oidcClientConfiguration.getRelativeUrls()); + OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration); + oidcFactory = new OidcMechanismFactory(oidcClientContext); + HttpServerAuthenticationMechanism mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler()); + + URI requestUri = new URI(getClientUrl()); + TestingHttpServerRequest request = new TestingHttpServerRequest(null, requestUri); + mechanism.evaluateRequest(request); + TestingHttpServerResponse response = request.getResponse(); + assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getStatusCode()); + assertEquals(Status.NO_AUTH, request.getResult()); + assertFalse(response.getFirstResponseHeaderValue("Location").contains(requestFormat + "=")); + assertTrue(response.getFirstResponseHeaderValue("Location").contains("scope=" + OIDC_SCOPE + "+phone+profile+email")); //ALL scopes should be added to the URL directly + + client.setDispatcher(createAppResponse(mechanism, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT)); + + TextPage page = loginToKeycloak(ALICE, ALICE_PASSWORD, requestUri, response.getLocation(), + response.getCookies()).click(); + assertTrue(page.getContent().contains(CLIENT_PAGE_TEXT)); + } + + + private void mockOidcClientConfig(){ + new MockUp(){ + // Used to indicate that the OpenID provider does not support request_uri parameter + @Mock + boolean getRequestUriParameterSupported(){ + return false; + } + + // Used to indicate that the OpenID provider does not support request parameter + @Mock + boolean getRequestParameterSupported(){ + return false; + } + }; + } + + private InputStream getOidcConfigurationInputStreamWithRequestParameter(String requestParameter){ + String oidcConfig = "{\n" + + " \"client-id\" : \"" + CLIENT_ID + "\",\n" + + " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "/" + "\",\n" + + " \"public-client\" : \"false\",\n" + + " \"ssl-required\" : \"EXTERNAL\",\n" + + " \"authentication-request-format\" : \"" + requestParameter + "\",\n" + + " \"request-object-signing-algorithm\" : \"" + HMAC_SHA256 + "\",\n" + + " \"scope\" : \"profile email phone\",\n" + + " \"credentials\" : {\n" + + " \"secret\" : \"" + CLIENT_SECRET + "\"\n" + + " }\n" + + "}"; + return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java index fb9d8345431..6eb698160a0 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java @@ -38,6 +38,9 @@ import javax.security.sasl.AuthorizeCallback; import org.apache.http.HttpStatus; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.consumer.InvalidJwtException; +import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.junit.AfterClass; import org.keycloak.representations.idm.RealmRepresentation; import org.testcontainers.DockerClientFactory; @@ -46,6 +49,8 @@ import org.wildfly.security.auth.callback.IdentityCredentialCallback; import org.wildfly.security.auth.callback.SecurityIdentityCallback; import org.wildfly.security.auth.server.SecurityDomain; +import org.wildfly.security.credential.BearerTokenCredential; +import org.wildfly.security.credential.Credential; import org.wildfly.security.evidence.Evidence; import org.wildfly.security.http.HttpServerAuthenticationMechanism; import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory; @@ -76,9 +81,10 @@ public class OidcBaseTest extends AbstractBaseHttpTest { public static final String CLIENT_ID = "test-webapp"; - public static final String CLIENT_SECRET = "secret"; + public static final String CLIENT_SECRET = "longerclientsecretthatisstleast256bitslong"; public static KeycloakContainer KEYCLOAK_CONTAINER; public static final String TEST_REALM = "WildFly"; + public static final String TEST_REALM_WITH_SCOPES = "WildFlyScopes"; public static final String TENANT1_REALM = "tenant1"; public static final String TENANT2_REALM = "tenant2"; public static final String KEYCLOAK_USERNAME = "username"; @@ -89,11 +95,18 @@ public class OidcBaseTest extends AbstractBaseHttpTest { public static final String CLIENT_PAGE_TEXT = "Welcome page!"; public static final String CLIENT_HOST_NAME = "localhost"; public static MockWebServer client; // to simulate the application being secured + public static final Boolean CONFIGURE_CLIENT_SCOPES = true; // to simulate the application being secured public static final String TENANT1_ENDPOINT = "tenant1"; public static final String TENANT2_ENDPOINT = "tenant2"; - protected HttpServerAuthenticationMechanismFactory oidcFactory; + public enum RequestObjectErrorType { + INVALID_ALGORITHM, + MISSING_CLIENT_SECRET, + INVALID_REQUEST_FORMAT, + MISSING_ENC_VALUE + } + @AfterClass public static void generalCleanup() throws Exception { if (KEYCLOAK_CONTAINER != null) { @@ -132,12 +145,19 @@ protected static boolean isDockerAvailable() { return false; } } - protected CallbackHandler getCallbackHandler() { - return getCallbackHandler(null); + return getCallbackHandler(false, null, null); } protected CallbackHandler getCallbackHandler(String expectedPrincipal) { + return getCallbackHandler(false, null, expectedPrincipal); + } + + protected CallbackHandler getCallbackHandler(boolean checkScope, String expectedScopes) { + return getCallbackHandler(checkScope, expectedScopes, null); + } + + protected CallbackHandler getCallbackHandler(boolean checkScope, String expectedScopes, String expectedPrincipal) { return callbacks -> { for(Callback callback : callbacks) { if (callback instanceof EvidenceVerifyCallback) { @@ -149,7 +169,13 @@ protected CallbackHandler getCallbackHandler(String expectedPrincipal) { } else if (callback instanceof AuthenticationCompleteCallback) { // NO-OP } else if (callback instanceof IdentityCredentialCallback) { - // NO-OP + if (checkScope) { + try { + checkForScopeClaims(callback, expectedScopes); + } catch (InvalidJwtException e) { + throw new RuntimeException(e); + } + } } else if (callback instanceof AuthorizeCallback) { ((AuthorizeCallback) callback).setAuthorized(true); } else if (callback instanceof SecurityIdentityCallback) { @@ -271,6 +297,7 @@ protected HtmlInput loginToKeycloak(String username, String password, URI reques webClient.addCookie(getCookieString(cookie), requestUri.toURL(), null); } } + HtmlPage keycloakLoginPage = webClient.getPage(location); HtmlForm loginForm = keycloakLoginPage.getForms().get(0); loginForm.getInputByName(KEYCLOAK_USERNAME).setValueAttribute(username); @@ -305,27 +332,43 @@ protected String getCookieString(HttpServerCookie cookie) { return header.toString(); } - protected void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak, - int expectedDispatcherStatusCode, String expectedLocation, String clientPageText) throws Exception { - performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, getClientUrl(), expectedLocation, clientPageText); + protected void checkForScopeClaims(Callback callback, String expectedScopes) throws InvalidJwtException { + Credential credential = ((IdentityCredentialCallback)callback).getCredential(); + String token = ((BearerTokenCredential) credential).getToken(); + JwtClaims jwtClaims = new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(token); + + if (expectedScopes != null) { + if (expectedScopes.contains("email")) { + assertTrue(jwtClaims.getClaimValueAsString("email_verified").contains(String.valueOf(KeycloakConfiguration.EMAIL_VERIFIED))); + } + if (expectedScopes.contains("profile")) { + assertTrue(jwtClaims.getClaimValueAsString("preferred_username").contains(KeycloakConfiguration.ALICE)); + } + } } + // Note: The tests will fail if `localhost` is not listed first in `/etc/hosts` file for the loopback addresses (IPv4 and IPv6). protected void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak, - int expectedDispatcherStatusCode, String expectedLocation, String clientPageText, - CallbackHandler callbackHandler) throws Exception { - performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, getClientUrl(), expectedLocation, clientPageText, - callbackHandler); + int expectedDispatcherStatusCode, String expectedLocation, String clientPageText) throws Exception { + performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, getClientUrl(), expectedLocation, + clientPageText, null, false); } protected void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak, int expectedDispatcherStatusCode, String clientUrl, String expectedLocation, String clientPageText) throws Exception { - performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, clientUrl, expectedLocation, clientPageText, - getCallbackHandler()); + performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, clientUrl, expectedLocation, + clientPageText, null, false); } - protected void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak, - int expectedDispatcherStatusCode, String clientUrl, String expectedLocation, String clientPageText, - CallbackHandler callbackHandler) throws Exception { + protected void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak, int expectedDispatcherStatusCode, + String expectedLocation, String clientPageText, String expectedScope, boolean checkInvalidScopeError) throws Exception { + performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, getClientUrl(), expectedLocation, clientPageText, + expectedScope, checkInvalidScopeError); + } + + private void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak, + int expectedDispatcherStatusCode, String clientUrl, String expectedLocation, String clientPageText, + String expectedScope, boolean checkInvalidScopeError) throws Exception { try { Map props = new HashMap<>(); OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig); @@ -333,7 +376,12 @@ protected void performAuthentication(InputStream oidcConfig, String username, St OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration); oidcFactory = new OidcMechanismFactory(oidcClientContext); - HttpServerAuthenticationMechanism mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, callbackHandler); + HttpServerAuthenticationMechanism mechanism; + if (expectedScope == null) { + mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler()); + } else { + mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler(true, expectedScope)); + } URI requestUri = new URI(clientUrl); TestingHttpServerRequest request = new TestingHttpServerRequest(null, requestUri); @@ -341,12 +389,22 @@ protected void performAuthentication(InputStream oidcConfig, String username, St TestingHttpServerResponse response = request.getResponse(); assertEquals(loginToKeycloak ? HttpStatus.SC_MOVED_TEMPORARILY : HttpStatus.SC_FORBIDDEN, response.getStatusCode()); assertEquals(Status.NO_AUTH, request.getResult()); + if (expectedScope != null) { + assertTrue(response.getFirstResponseHeaderValue("Location").contains("scope=" + expectedScope)); + } if (loginToKeycloak) { client.setDispatcher(createAppResponse(mechanism, expectedDispatcherStatusCode, expectedLocation, clientPageText)); - TextPage page = loginToKeycloak(username, password, requestUri, response.getLocation(), - response.getCookies()).click(); - assertTrue(page.getContent().contains(clientPageText)); + + if (checkInvalidScopeError) { + WebClient webClient = getWebClient(); + TextPage keycloakLoginPage = webClient.getPage(response.getLocation()); + assertTrue(keycloakLoginPage.getWebResponse().getWebRequest().toString().contains("error_description=Invalid+scopes")); + } else { + TextPage page = loginToKeycloak(username, password, requestUri, response.getLocation(), + response.getCookies()).click(); + assertTrue(page.getContent().contains(clientPageText)); + } } } finally { client.setDispatcher(new QueueDispatcher()); @@ -355,14 +413,14 @@ protected void performAuthentication(InputStream oidcConfig, String username, St protected InputStream getOidcConfigurationInputStreamWithProviderUrl() { String oidcConfig = "{\n" + - " \"resource\" : \"" + CLIENT_ID + "\",\n" + - " \"public-client\" : \"false\",\n" + - " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" + - " \"ssl-required\" : \"EXTERNAL\",\n" + - " \"credentials\" : {\n" + - " \"secret\" : \"" + CLIENT_SECRET + "\"\n" + + " \"" + Oidc.RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + Oidc.PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + Oidc.PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" + + " \"" + Oidc.SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + Oidc.CREDENTIALS + "\" : {\n" + + " \"" + Oidc.ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + " }\n" + "}"; return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); } -} \ No newline at end of file +} diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcProviderMetadataTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcProviderMetadataTest.java new file mode 100644 index 00000000000..3773451f682 --- /dev/null +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcProviderMetadataTest.java @@ -0,0 +1,378 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.http.oidc; + +import org.wildfly.security.jose.util.JsonSerialization; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * Test OIDC json config class to return of values. + */ +public class OidcProviderMetadataTest { + private static OidcProviderMetadata oidcProviderMetadata; + private static OidcProviderMetadata emptyOidcProviderMetadata; + private static OidcProviderMetadata withoutOptionalsOidcProviderMetadata; + + @BeforeClass + public static void setUp() throws IOException { + // load the control data + ByteArrayInputStream is = new ByteArrayInputStream(providerMetaData.getBytes()); + oidcProviderMetadata = JsonSerialization.readValue(is, OidcProviderMetadata.class); + is.close(); + + // control data to check variable inits in OidcProviderMetadata + is = new ByteArrayInputStream(emptyProviderMetaData.getBytes()); + emptyOidcProviderMetadata = JsonSerialization.readValue(is, OidcProviderMetadata.class); + is.close(); + + is = new ByteArrayInputStream(withoutOptionalsProviderMetaData.getBytes()); + withoutOptionalsOidcProviderMetadata = JsonSerialization.readValue(is, OidcProviderMetadata.class); + is.close(); + } + + @Test + public void testIssuer() throws Exception { + assertTrue("http://localhost:8080/realms/myrealm".equals(oidcProviderMetadata.getIssuer())); + assertTrue("http://localhost:8080/realms/myrealm".equals(withoutOptionalsOidcProviderMetadata.getIssuer())); + assertNull(emptyOidcProviderMetadata.getIssuer()); + } + + @Test + public void testAuthorizationEndpoint() throws Exception { + assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/auth".equals(oidcProviderMetadata.getAuthorizationEndpoint())); + assertTrue("http://localhost:8080/auth".equals(withoutOptionalsOidcProviderMetadata.getAuthorizationEndpoint())); + assertNull(emptyOidcProviderMetadata.getAuthorizationEndpoint()); + } + + @Test + public void testTokenEndpoint() throws Exception { + assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/token".equals(oidcProviderMetadata.getTokenEndpoint())); + assertTrue("http://localhost:8080/token".equals(withoutOptionalsOidcProviderMetadata.getTokenEndpoint())); + assertNull(emptyOidcProviderMetadata.getTokenEndpoint()); + } + + @Test + public void testIntrospectionEndpoint() throws Exception { + assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/token/introspect".equals(oidcProviderMetadata.getIntrospectionEndpoint())); + assertTrue("http://localhost:8080/introspect".equals(withoutOptionalsOidcProviderMetadata.getIntrospectionEndpoint())); + assertNull(emptyOidcProviderMetadata.getIntrospectionEndpoint()); + } + + @Test + public void testUserinfoEndpoint() throws Exception { + assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/userinfo".equals(oidcProviderMetadata.getUserinfoEndpoint())); + assertTrue("http://localhost:8080/userinfo".equals(withoutOptionalsOidcProviderMetadata.getUserinfoEndpoint())); + assertNull(emptyOidcProviderMetadata.getUserinfoEndpoint()); + } + + @Test + public void testLogoutEndpoint() throws Exception { + assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/logout".equals(oidcProviderMetadata.getLogoutEndpoint())); + assertTrue("http://localhost:8080/logout".equals(withoutOptionalsOidcProviderMetadata.getLogoutEndpoint())); + assertNull(emptyOidcProviderMetadata.getLogoutEndpoint()); + } + + @Test + public void testJwksUri() throws Exception { + assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/certs".equals(oidcProviderMetadata.getJwksUri())); + assertTrue("http://localhost:8080/certs".equals(withoutOptionalsOidcProviderMetadata.getJwksUri())); + assertNull(emptyOidcProviderMetadata.getJwksUri()); + } + + @Test + public void testCheckSessionIframe() throws Exception { + assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/login-status-iframe.html".equals(oidcProviderMetadata.getCheckSessionIframe())); + assertTrue("http://localhost:8080/login-status-iframe.html".equals(withoutOptionalsOidcProviderMetadata.getCheckSessionIframe())); + assertNull(emptyOidcProviderMetadata.getCheckSessionIframe()); + } + + @Test + public void testGrantTypesSupported() throws Exception { + List l = oidcProviderMetadata.getGrantTypesSupported(); + assertTrue(l.contains("refresh_token")); + assertNull(emptyOidcProviderMetadata.getGrantTypesSupported()); + } + + @Test + public void testResponseTypesSupported() throws Exception { + List l = oidcProviderMetadata.getResponseTypesSupported(); + assertTrue(l.contains("code id_token")); + assertNull(emptyOidcProviderMetadata.getResponseTypesSupported()); + } + + @Test + public void testSubjectTypesSupported() throws Exception { + List l = oidcProviderMetadata.getSubjectTypesSupported(); + assertTrue(l.contains("pairwise")); + assertNull(emptyOidcProviderMetadata.getSubjectTypesSupported()); + } + + @Test + public void testIdTokenSigningAlgValuesSupported() throws Exception { + List l = oidcProviderMetadata.getIdTokenSigningAlgValuesSupported(); + assertTrue(l.contains("HS256")); + assertNull(emptyOidcProviderMetadata.getIdTokenSigningAlgValuesSupported()); + } + + @Test + public void testIdTokenEncryptionAlgValuesSupported() throws Exception { + List l = oidcProviderMetadata.getIdTokenEncryptionAlgValuesSupported(); + assertTrue(l.contains("RSA1_5")); + assertNull(emptyOidcProviderMetadata.getIdTokenEncryptionAlgValuesSupported()); + } + + @Test + public void testIdTokenEncryptionEncValuesSupported() throws Exception { + List l = oidcProviderMetadata.getIdTokenEncryptionEncValuesSupported(); + assertTrue(l.contains("A128CBC-HS256")); + assertNull(emptyOidcProviderMetadata.getIdTokenEncryptionEncValuesSupported()); + } + + @Test + public void testUserInfoSigningAlgValuesSupported() throws Exception { + List l = oidcProviderMetadata.getUserInfoSigningAlgValuesSupported(); + assertTrue(l.contains("EdDSA")); + assertNull(emptyOidcProviderMetadata.getUserInfoSigningAlgValuesSupported()); + } + + @Test + public void testRequestObjectSigningAlgValuesSupported() throws Exception { + List l = oidcProviderMetadata.getRequestObjectSigningAlgValuesSupported(); + assertTrue(l.contains("RS384")); + assertNull(emptyOidcProviderMetadata.getRequestObjectSigningAlgValuesSupported()); + assertNull(withoutOptionalsOidcProviderMetadata.getRequestObjectSigningAlgValuesSupported()); + } + + @Test + public void testResponseModesSupported() throws Exception { + List l = oidcProviderMetadata.getResponseModesSupported(); + assertTrue(l.contains("query.jwt")); + assertNull(emptyOidcProviderMetadata.getResponseModesSupported()); + } + + @Test + public void testRegistrationEndpoint() throws Exception { + assertTrue("http://localhost:8080/realms/myrealm/clients-registrations/openid-connect".equals(oidcProviderMetadata.getRegistrationEndpoint())); + assertTrue("http://localhost:8080/openid-connect".equals(withoutOptionalsOidcProviderMetadata.getRegistrationEndpoint())); + assertNull(emptyOidcProviderMetadata.getRegistrationEndpoint()); + } + + @Test + public void testTokenEndpointAuthMethodsSupported() throws Exception { + List l = oidcProviderMetadata.getTokenEndpointAuthMethodsSupported(); + assertTrue(l.contains("client_secret_basic")); + assertNull(emptyOidcProviderMetadata.getTokenEndpointAuthMethodsSupported()); + } + + @Test + public void testTokenEndpointAuthSigningAlgValuesSupported() throws Exception { + List l = oidcProviderMetadata.getTokenEndpointAuthSigningAlgValuesSupported(); + assertTrue(l.contains("PS384")); + assertNull(emptyOidcProviderMetadata.getTokenEndpointAuthSigningAlgValuesSupported()); + } + + @Test + public void testClaimsSupported() throws Exception { + List l = oidcProviderMetadata.getClaimsSupported(); + assertTrue(l.contains("given_name")); + assertNull(emptyOidcProviderMetadata.getClaimsSupported()); + } + + @Test + public void testClaimTypesSupported() throws Exception { + List l = oidcProviderMetadata.getClaimTypesSupported(); + assertTrue(l.contains("normal")); + assertNull(emptyOidcProviderMetadata.getClaimTypesSupported()); + } + + @Test + public void testClaimsParameterSupported() throws Exception { + assertTrue(oidcProviderMetadata.getClaimsParameterSupported()); + assertFalse(withoutOptionalsOidcProviderMetadata.getClaimsParameterSupported()); + } + + @Test + public void testScopesSupported() throws Exception { + List l = oidcProviderMetadata.getScopesSupported(); + assertTrue(l.contains("offline_access")); + assertNull(emptyOidcProviderMetadata.getScopesSupported()); + } + + @Test + public void testRequestParameterSupported() throws Exception { + assertTrue(oidcProviderMetadata.getRequestParameterSupported()); + assertFalse(withoutOptionalsOidcProviderMetadata.getRequestParameterSupported()); + } + + @Test + public void testRequestUriParameterSupported() throws Exception { + assertTrue(oidcProviderMetadata.getRequestUriParameterSupported()); + assertFalse(withoutOptionalsOidcProviderMetadata.getRequestUriParameterSupported()); + } + + @Test + public void testPushedAuthorizationRequestEndpoint() throws Exception { + assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/ext/par/request".equals(oidcProviderMetadata.getPushedAuthorizationRequestEndpoint())); + assertNull(emptyOidcProviderMetadata.getPushedAuthorizationRequestEndpoint()); + assertNull(withoutOptionalsOidcProviderMetadata.getPushedAuthorizationRequestEndpoint()); + } + + @Test + public void testRevocationEndpoint() throws Exception { + assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/revoke".equals(oidcProviderMetadata.getRevocationEndpoint())); + assertTrue("http://localhost:8080/revoke".equals(withoutOptionalsOidcProviderMetadata.getRevocationEndpoint())); + assertNull(emptyOidcProviderMetadata.getRevocationEndpoint()); + } + + @Test + public void testRevocationEndpointAuthMethodsSupported() throws Exception { + List l = oidcProviderMetadata.getRevocationEndpointAuthMethodsSupported(); + assertTrue(l.contains("client_secret_basic")); + assertNull(emptyOidcProviderMetadata.getRevocationEndpointAuthMethodsSupported()); + } + + @Test + public void testRevocationEndpointAuthSigningAlgValuesSupported() throws Exception { + List l = oidcProviderMetadata.getRevocationEndpointAuthSigningAlgValuesSupported(); + assertTrue(l.contains("RS384")); + assertNull(emptyOidcProviderMetadata.getRevocationEndpointAuthSigningAlgValuesSupported()); + } + + @Test + public void testBackchannelLogoutSupported() throws Exception { + assertTrue(oidcProviderMetadata.getBackchannelLogoutSupported()); + assertFalse(withoutOptionalsOidcProviderMetadata.getBackchannelLogoutSupported()); + } + + @Test + public void testBackchannelLogoutSessionSupported() throws Exception { + assertTrue(oidcProviderMetadata.getBackchannelLogoutSessionSupported()); + assertFalse(withoutOptionalsOidcProviderMetadata.getBackchannelLogoutSessionSupported()); + } + + @Test + public void testCodeChallengeMethodsSupported() throws Exception { + List l = oidcProviderMetadata.getCodeChallengeMethodsSupported(); + assertTrue(l.contains("S256")); + assertNull(emptyOidcProviderMetadata.getCodeChallengeMethodsSupported()); + } + + @Test + public void testTlsClientCertificateBoundAccessTokens() throws Exception { + assertTrue(oidcProviderMetadata.getTlsClientCertificateBoundAccessTokens()); + assertFalse(withoutOptionalsOidcProviderMetadata.getTlsClientCertificateBoundAccessTokens()); + } + + @Test + public void testRequestObjectEncryptionEncValuesSupported() throws Exception { + List l = oidcProviderMetadata.getRequestObjectEncryptionEncValuesSupported(); + assertTrue(l.contains("A192GCM")); + assertNull(emptyOidcProviderMetadata.getRequestObjectEncryptionEncValuesSupported()); + assertNull(withoutOptionalsOidcProviderMetadata.getRequestObjectEncryptionEncValuesSupported()); + } + + @Test + public void testRequestObjectEncryptionAlgValuesSupported() throws Exception { + List l = oidcProviderMetadata.getRequestObjectEncryptionAlgValuesSupported(); + assertTrue(l.contains("RSA1_5")); + assertNull(emptyOidcProviderMetadata.getRequestObjectEncryptionAlgValuesSupported()); + assertNull(withoutOptionalsOidcProviderMetadata.getRequestObjectEncryptionAlgValuesSupported()); + } + + // Control data taken from keycloak + private static final String providerMetaData = "{\n" + + "\"issuer\":\"http://localhost:8080/realms/myrealm\"\n" + + ",\"authorization_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/auth\"\n" + + ",\"token_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/token\"\n" + + ",\"introspection_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/token/introspect\"\n" + + ",\"userinfo_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/userinfo\"\n" + + ",\"end_session_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/logout\"\n" + + ",\"jwks_uri\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/certs\"\n" + + ",\"check_session_iframe\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/login-status-iframe.html\"\n" + + ",\"grant_types_supported\":[\"authorization_code\",\"implicit\",\"refresh_token\",\"password\",\"client_credentials\",\"urn:openid:params:grant-type:ciba\",\"urn:ietf:params:oauth:grant-type:device_code\"]\n" + + ",\"response_types_supported\":[\"code\",\"none\",\"id_token\",\"token\",\"id_token token\",\"code id_token\",\"code token\",\"code id_token token\"]\n" + + ",\"subject_types_supported\":[\"public\",\"pairwise\"]\n" + + ",\"id_token_signing_alg_values_supported\":[\"PS384\",\"RS384\",\"EdDSA\",\"ES384\",\"HS256\",\"HS512\",\"ES256\",\"RS256\",\"HS384\",\"ES512\",\"PS256\",\"PS512\",\"RS512\"]\n" + + ",\"id_token_encryption_alg_values_supported\":[\"RSA-OAEP\",\"RSA-OAEP-256\",\"RSA1_5\"]\n" + + ",\"id_token_encryption_enc_values_supported\":[\"A256GCM\",\"A192GCM\",\"A128GCM\",\"A128CBC-HS256\",\"A192CBC-HS384\",\"A256CBC-HS512\"]\n" + + ",\"userinfo_signing_alg_values_supported\":[\"PS384\",\"RS384\",\"EdDSA\",\"ES384\",\"HS256\",\"HS512\",\"ES256\",\"RS256\",\"HS384\",\"ES512\",\"PS256\",\"PS512\",\"RS512\",\"none\"]\n" + + ",\"request_object_signing_alg_values_supported\":[\"PS384\",\"RS384\",\"EdDSA\",\"ES384\",\"HS256\",\"HS512\",\"ES256\",\"RS256\",\"HS384\",\"ES512\",\"PS256\",\"PS512\",\"RS512\",\"none\"]\n" + + ",\"response_modes_supported\":[\"query\",\"fragment\",\"form_post\",\"query.jwt\",\"fragment.jwt\",\"form_post.jwt\",\"jwt\"]\n" + + ",\"registration_endpoint\":\"http://localhost:8080/realms/myrealm/clients-registrations/openid-connect\"\n" + + ",\"token_endpoint_auth_methods_supported\":[\"private_key_jwt\",\"client_secret_basic\",\"client_secret_post\",\"tls_client_auth\",\"client_secret_jwt\"]\n" + + ",\"token_endpoint_auth_signing_alg_values_supported\":[\"PS384\",\"RS384\",\"EdDSA\",\"ES384\",\"HS256\",\"HS512\",\"ES256\",\"RS256\",\"HS384\",\"ES512\",\"PS256\",\"PS512\",\"RS512\"]\n" + + ",\"claims_supported\":[\"aud\",\"sub\",\"iss\",\"auth_time\",\"name\",\"given_name\",\"family_name\",\"preferred_username\",\"email\",\"acr\"]\n" + + ",\"claim_types_supported\":[\"normal\"]\n" + + ",\"claims_parameter_supported\":true\n" + + ",\"scopes_supported\":[\"openid\",\"address\",\"profile\",\"offline_access\",\"microprofile-jwt\",\"acr\",\"web-origins\",\"basic\",\"email\",\"roles\",\"phone\"]\n" + + ",\"request_parameter_supported\":true\n" + + ",\"request_uri_parameter_supported\":true\n" + + ",\"pushed_authorization_request_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/ext/par/request\"\n" + + ",\"revocation_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/revoke\"\n" + + ",\"revocation_endpoint_auth_methods_supported\":[\"private_key_jwt\",\"client_secret_basic\",\"client_secret_post\",\"tls_client_auth\",\"client_secret_jwt\"]\n" + + ",\"revocation_endpoint_auth_signing_alg_values_supported\":[\"PS384\",\"RS384\",\"EdDSA\",\"ES384\",\"HS256\",\"HS512\",\"ES256\",\"RS256\",\"HS384\",\"ES512\",\"PS256\",\"PS512\",\"RS512\"]\n" + + ",\"backchannel_logout_supported\":true\n" + + ",\"backchannel_logout_session_supported\":true\n" + + ",\"code_challenge_methods_supported\":[\"plain\",\"S256\"]\n" + + ",\"tls_client_certificate_bound_access_tokens\":true\n" + + ",\"request_object_encryption_enc_values_supported\":[\"A256GCM\",\"A192GCM\",\"A128GCM\",\"A128CBC-HS256\",\"A192CBC-HS384\",\"A256CBC-HS512\"]\n" + + ",\"request_object_encryption_alg_values_supported\":[\"RSA-OAEP\",\"RSA-OAEP-256\",\"RSA1_5\"]\n" + + "}"; + + private static final String emptyProviderMetaData = "{}"; + + private static final String withoutOptionalsProviderMetaData = "{\n" + + "\"issuer\":\"http://localhost:8080/realms/myrealm\"\n" + + ",\"authorization_endpoint\":\"http://localhost:8080/auth\"\n" + + ",\"token_endpoint\":\"http://localhost:8080/token\"\n" + + ",\"introspection_endpoint\":\"http://localhost:8080/introspect\"\n" + + ",\"userinfo_endpoint\":\"http://localhost:8080/userinfo\"\n" + + ",\"end_session_endpoint\":\"http://localhost:8080/logout\"\n" + + ",\"jwks_uri\":\"http://localhost:8080/certs\"\n" + + ",\"check_session_iframe\":\"http://localhost:8080/login-status-iframe.html\"\n" + + ",\"grant_types_supported\":[\"authorization_code\",\"implicit\"]\n" + + ",\"response_types_supported\":[\"code\",\"none\",\"id_token\",\"token\"]\n" + + ",\"subject_types_supported\":[\"public\",\"pairwise\"]\n" + + ",\"id_token_signing_alg_values_supported\":[\"PS384\"]\n" + + ",\"id_token_encryption_alg_values_supported\":[\"RSA-OAEP\",\"RSA-OAEP-256\",\"RSA1_5\"]\n" + + ",\"id_token_encryption_enc_values_supported\":[\"A256GCM\"]\n" + + ",\"userinfo_signing_alg_values_supported\":[\"PS384\",\"none\"]\n" + + ",\"response_modes_supported\":[\"query\",\"fragment\",\"form_post.jwt\",\"jwt\"]\n" + + ",\"registration_endpoint\":\"http://localhost:8080/openid-connect\"\n" + + ",\"token_endpoint_auth_methods_supported\":[\"private_key_jwt\",\"client_secret_basic\"]\n" + + ",\"token_endpoint_auth_signing_alg_values_supported\":[\"PS384\",\"RS384\"]\n" + + ",\"claims_supported\":[\"aud\",\"sub\"]\n" + + ",\"claim_types_supported\":[\"normal\"]\n" + + ",\"scopes_supported\":[\"openid\",\"address\",\"profile\"]\n" + + ",\"revocation_endpoint\":\"http://localhost:8080/revoke\"\n" + + ",\"revocation_endpoint_auth_methods_supported\":[\"private_key_jwt\"]\n" + + ",\"revocation_endpoint_auth_signing_alg_values_supported\":[\"PS384\",\"RS384\",\"EdDSA\"]\n" + + ",\"code_challenge_methods_supported\":[\"plain\",\"S256\"]\n" + + "}"; +} diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcTest.java index a9b13687551..4dede8b5ed6 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcTest.java @@ -18,6 +18,20 @@ package org.wildfly.security.http.oidc; +import static org.jose4j.jws.AlgorithmIdentifiers.NONE; +import static org.jose4j.jws.AlgorithmIdentifiers.RSA_USING_SHA256; +import static org.jose4j.jws.AlgorithmIdentifiers.RSA_USING_SHA512; +import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA256; +import static org.jose4j.jws.AlgorithmIdentifiers.RSA_PSS_USING_SHA256; +import static org.wildfly.security.http.oidc.KeycloakConfiguration.KEYSTORE_CLASSPATH; +import static org.wildfly.security.http.oidc.KeycloakConfiguration.KEYSTORE_PASS; +import static org.wildfly.security.http.oidc.KeycloakConfiguration.PKCS12_KEYSTORE_TYPE; +import static org.wildfly.security.http.oidc.KeycloakConfiguration.RSA1_5; +import static org.wildfly.security.http.oidc.KeycloakConfiguration.RSA_OAEP; +import static org.wildfly.security.http.oidc.KeycloakConfiguration.RSA_OAEP_256; +import static org.wildfly.security.http.oidc.KeycloakConfiguration.A128CBC_HS256; +import static org.wildfly.security.http.oidc.KeycloakConfiguration.A192CBC_HS384; +import static org.wildfly.security.http.oidc.KeycloakConfiguration.A256CBC_HS512; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -30,7 +44,31 @@ import static org.wildfly.security.http.oidc.KeycloakConfiguration.TENANT1_USER; import static org.wildfly.security.http.oidc.KeycloakConfiguration.TENANT2_PASSWORD; import static org.wildfly.security.http.oidc.KeycloakConfiguration.TENANT2_USER; +import static org.wildfly.security.http.oidc.Oidc.AUTH_SERVER_URL; +import static org.wildfly.security.http.oidc.Oidc.AUTHENTICATION_REQUEST_FORMAT; +import static org.wildfly.security.http.oidc.Oidc.CREDENTIALS; +import static org.wildfly.security.http.oidc.Oidc.ClientCredentialsProviderType; +import static org.wildfly.security.http.oidc.Oidc.PROVIDER_URL; import static org.wildfly.security.http.oidc.Oidc.OIDC_NAME; +import static org.wildfly.security.http.oidc.Oidc.OIDC_SCOPE; +import static org.wildfly.security.http.oidc.Oidc.PUBLIC_CLIENT; +import static org.wildfly.security.http.oidc.Oidc.PRINCIPAL_ATTRIBUTE; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_ALGORITHM; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEYSTORE_FILE; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEYSTORE_PASSWORD; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEYSTORE_TYPE; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEY_PASSWORD; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEY_ALIAS; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_ENCRYPTION_ALG_VALUE; +import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_ENCRYPTION_ENC_VALUE; +import static org.wildfly.security.http.oidc.Oidc.RESOURCE; +import static org.wildfly.security.http.oidc.Oidc.REALM; +import static org.wildfly.security.http.oidc.Oidc.SCOPE; +import static org.wildfly.security.http.oidc.Oidc.SSL_REQUIRED; +import static org.wildfly.security.http.oidc.Oidc.TOKEN_SIGNATURE_ALGORITHM; +import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.OAUTH2; +import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST; +import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST_URI; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -39,19 +77,20 @@ import java.util.HashMap; import java.util.Map; -import org.apache.http.HttpStatus; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.wildfly.security.http.HttpServerAuthenticationMechanism; +import javax.security.auth.callback.CallbackHandler; +import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.TextPage; import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.html.HtmlPage; - import io.restassured.RestAssured; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.QueueDispatcher; +import org.apache.http.HttpStatus; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.wildfly.security.http.HttpServerAuthenticationMechanism; /** * Tests for the OpenID Connect authentication mechanism. @@ -69,9 +108,10 @@ public static void startTestContainers() throws Exception { assumeTrue("Docker isn't available, OIDC tests will be skipped", isDockerAvailable()); KEYCLOAK_CONTAINER = new KeycloakContainer(); KEYCLOAK_CONTAINER.start(); - sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP)); - sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TENANT1_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, ACCESS_TOKEN_LIFESPAN, SESSION_MAX_LIFESPAN, true)); - sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TENANT2_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, ACCESS_TOKEN_LIFESPAN, SESSION_MAX_LIFESPAN, true)); + sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, false)); + sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM_WITH_SCOPES, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, CONFIGURE_CLIENT_SCOPES)); + sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TENANT1_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, ACCESS_TOKEN_LIFESPAN, SESSION_MAX_LIFESPAN, false, true)); + sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TENANT2_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, ACCESS_TOKEN_LIFESPAN, SESSION_MAX_LIFESPAN, false, true)); client = new MockWebServer(); client.start(CLIENT_PORT); } @@ -84,6 +124,11 @@ public static void generalCleanup() throws Exception { .auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl())) .when() .delete(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms/" + TEST_REALM).then().statusCode(204); + RestAssured + .given() + .auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl())) + .when() + .delete(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms/" + TEST_REALM_WITH_SCOPES).then().statusCode(204); RestAssured .given() .auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl())) @@ -187,23 +232,139 @@ public void testTokenSignatureAlgorithm() throws Exception { performAuthentication(getOidcConfigurationInputStreamWithTokenSignatureAlgorithm(), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT); } + @Test + public void testInvalidScope() throws Exception { + String expectedScope = OIDC_SCOPE + "+INVALID_SCOPE"; + performAuthentication(getOidcConfigurationInputStreamWithScope("INVALID_SCOPE"), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), "error=invalid_scope", expectedScope, true); + } @Test - public void testPrincipalAttribute() throws Exception { - // custom principal-attribute - performAuthentication(getOidcConfigurationInputStreamWithPrincipalAttribute("aud"), KeycloakConfiguration.ALICE, - KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, - getCallbackHandler("test-webapp")); + public void testEmptyScope() throws Exception { + performAuthentication(getOidcConfigurationInputStreamWithScope(""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, OIDC_SCOPE, false); + } + + @Test + public void testSingleScopeValue() throws Exception { + String expectedScope = OIDC_SCOPE + "+profile"; + performAuthentication(getOidcConfigurationInputStreamWithScope("profile"), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, expectedScope, false); + } + + @Test + public void testMultipleScopeValue() throws Exception { + String expectedScope = OIDC_SCOPE + "+phone+profile+email"; + performAuthentication(getOidcConfigurationInputStreamWithScope("email phone profile"), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, expectedScope, false); + } - // standard principal-attribute - performAuthentication(getOidcConfigurationInputStreamWithPrincipalAttribute("given_name"), KeycloakConfiguration.ALICE, - KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, - getCallbackHandler("Alice")); + @Test + public void testOpenIDScopeValue() throws Exception { + String expectedScope = OIDC_SCOPE; + performAuthentication(getOidcConfigurationInputStreamWithScope(OIDC_SCOPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, expectedScope, false); + } - // invalid principal-attribute, logging in should still succeed - performAuthentication(getOidcConfigurationInputStreamWithPrincipalAttribute("invalid_claim"), KeycloakConfiguration.ALICE, - KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, - getCallbackHandler()); + @Test + public void testOpenIDWithMultipleScopeValue() throws Exception { + String expectedScope = OIDC_SCOPE + "+phone+profile+email";//order gets changed when combining with query parameters + performAuthentication(getOidcConfigurationInputStreamWithScope("email phone profile " + OIDC_SCOPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, expectedScope, false); + } + + // Note: The tests will fail if `localhost` is not listed first in `/etc/hosts` file for the loopback addresses (IPv4 and IPv6). + @Test + public void testSuccessfulOauth2Request() throws Exception { + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(OAUTH2.getValue(), "", "", ""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT); + } + + @Test + public void testSuccessfulPlaintextRequest() throws Exception { + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), NONE, "", ""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT); + } + + @Test + public void testSuccessfulPlaintextEncryptedRequest() throws Exception { + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), NONE, RSA_OAEP, A128CBC_HS256), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT); + } + + @Test + public void testSuccessfulRsaSignedAndEncryptedRequest() throws Exception { + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), RSA_USING_SHA512, RSA_OAEP, A192CBC_HS384, KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT); + } + + @Test + public void testSuccessfulPsSignedAndRsaEncryptedRequest() throws Exception { + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), RSA_PSS_USING_SHA256, RSA_OAEP_256, A256CBC_HS512, KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT); + } + + @Test + public void testInvalidSigningAlgorithm() throws Exception { + //ES256K is a valid signature algorithm, but not one of the ones supported by keycloak + testRequestObjectInvalidConfiguration(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), "ES256K", RSA_OAEP_256, A256CBC_HS512, KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), RequestObjectErrorType.INVALID_ALGORITHM); + } + + @Test + public void testSuccessfulRsaSignedRequest() throws Exception { + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), RSA_USING_SHA256, "", "", KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT); + } + + @Test + public void testSuccessfulPsSignedRequest() throws Exception { + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), RSA_PSS_USING_SHA256, "", "", KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT); + } + @Test + public void testInvalidRequestEncryptionAlgorithm() throws Exception { + // None is not a valid algorithm for encrypting jwt's and RSA-OAEP is not a valid algorithm for signing + testRequestObjectInvalidConfiguration(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), RSA1_5, NONE, NONE, KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), RequestObjectErrorType.INVALID_ALGORITHM); + } + + @Test + public void testSuccessfulPlaintextRequestUri() throws Exception { + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_URI.getValue(), NONE, "", ""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT); + } + + @Test + public void testSuccessfulHmacSignedRequestUri() throws Exception { + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), HMAC_SHA256, "", ""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT); + } + + @Test + public void testSuccessfulHmacSignedAndEncryptedRequestUri() throws Exception { + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), HMAC_SHA256, RSA_OAEP, A128CBC_HS256), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT); + } + + @Test + public void testSuccessfulSignedAndEncryptedRequestUri() throws Exception { + performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_URI.getValue(), RSA_USING_SHA256, RSA_OAEP_256, A256CBC_HS512, KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, + true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT); + } + + @Test + public void testSuccessfulHmacSignedRequestObjectWithoutSecret() throws Exception { + // this is supposed to fail since for symmetric algorithms we sign the request object with the client secret + testRequestObjectInvalidConfiguration(getOidcConfigurationInputStreamWithRequestObjectPublicClient(REQUEST.getValue(), HMAC_SHA256), RequestObjectErrorType.MISSING_CLIENT_SECRET); + } + + @Test + public void testIncorrectAuthenticationFormat() throws Exception { + testRequestObjectInvalidConfiguration(getOidcConfigurationInputStreamWithRequestObjectPublicClient("INVALID_REQUEST_PARAMETER", HMAC_SHA256), RequestObjectErrorType.INVALID_REQUEST_FORMAT); + } + + @Test + public void testRequestObjectConfigMissingENCValue() throws Exception { + testRequestObjectInvalidConfiguration(getOidcConfigurationInputStreamWithoutEncValue(REQUEST.getValue(), RSA_OAEP), RequestObjectErrorType.MISSING_ENC_VALUE); } /***************************************************************************************************************************************** @@ -381,6 +542,37 @@ private void testNonExistingUser(String username, String password, String tenant assertTrue(page.getBody().asText().contains("Invalid username or password")); } + private void loginToAppMultiTenancy(InputStream oidcConfig, String username, String password, boolean loginToKeycloak, + int expectedDispatcherStatusCode, String expectedLocation, String clientPageText, + CallbackHandler callbackHandler) throws Exception { + try { + Map props = new HashMap<>(); + OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig); + assertEquals(OidcClientConfiguration.RelativeUrlsUsed.NEVER, oidcClientConfiguration.getRelativeUrls()); + + OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration); + oidcFactory = new OidcMechanismFactory(oidcClientContext); + + HttpServerAuthenticationMechanism mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, callbackHandler); + + URI requestUri = new URI(getClientUrl()); + TestingHttpServerRequest request = new TestingHttpServerRequest(null, requestUri); + mechanism.evaluateRequest(request); + TestingHttpServerResponse response = request.getResponse(); + assertEquals(loginToKeycloak ? HttpStatus.SC_MOVED_TEMPORARILY : HttpStatus.SC_FORBIDDEN, response.getStatusCode()); + assertEquals(Status.NO_AUTH, request.getResult()); + + if (loginToKeycloak) { + client.setDispatcher(createAppResponse(mechanism, expectedDispatcherStatusCode, expectedLocation, clientPageText)); + TextPage page = loginToKeycloak(username, password, requestUri, response.getLocation(), + response.getCookies()).click(); + assertTrue(page.getContent().contains(clientPageText)); + } + } finally { + client.setDispatcher(new QueueDispatcher()); + } + } + private void performTenantRequestWithAuthServerUrl(String username, String password, String tenant, String otherTenant) throws Exception { performTenantRequest(username, password, tenant, otherTenant, true); } @@ -434,6 +626,54 @@ private void performTenantRequest(String username, String password, String tenan } } + private void testRequestObjectInvalidConfiguration(InputStream oidcConfig, RequestObjectErrorType requestObjectErrorType) throws Exception { + try { + Map props = new HashMap<>(); + try { + OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig); + if (requestObjectErrorType == RequestObjectErrorType.MISSING_ENC_VALUE || requestObjectErrorType == RequestObjectErrorType.INVALID_REQUEST_FORMAT) { + Assert.fail("No error was thrown while attempting to build the client configuration."); + } + assertEquals(OidcClientConfiguration.RelativeUrlsUsed.NEVER, oidcClientConfiguration.getRelativeUrls()); + + OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration); + oidcFactory = new OidcMechanismFactory(oidcClientContext); + HttpServerAuthenticationMechanism mechanism; + + if (oidcClientConfiguration.getAuthenticationRequestFormat().contains(REQUEST.getValue())) { + mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler(true, "+phone+profile+email")); + } else { + mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler()); + } + + URI requestUri = new URI(getClientUrl()); + TestingHttpServerRequest request = new TestingHttpServerRequest(null, requestUri); + try { + mechanism.evaluateRequest(request); + Assert.fail("No error was thrown while attempting to evaluate the request"); + } catch (Exception e) { + + if (requestObjectErrorType == RequestObjectErrorType.INVALID_ALGORITHM) { + assertTrue(e.getMessage().contains("Failed to create the authentication request")); + } else if (requestObjectErrorType == RequestObjectErrorType.MISSING_CLIENT_SECRET) { + assertTrue(e.getMessage().contains("The client secret has not been configured.")); + } else { + throw e; + } + } + } catch (Exception e) { + if (requestObjectErrorType == RequestObjectErrorType.INVALID_REQUEST_FORMAT) { + assertTrue(e.getMessage().contains("Authentication request format must be one of the following: oauth2, request, request_uri.")); + } else if (requestObjectErrorType == RequestObjectErrorType.MISSING_ENC_VALUE) { + assertTrue(e.getMessage().contains("Both request object encryption algorithm and request object content encryption algorithm must be configured to encrypt the request object.")); + } + } + } finally { + client.setDispatcher(new QueueDispatcher()); + } + } + + private InputStream getOidcConfigurationInputStream() { return getOidcConfigurationInputStream(CLIENT_SECRET); } @@ -444,13 +684,13 @@ private InputStream getOidcConfigurationInputStream(String clientSecret) { private InputStream getOidcConfigurationInputStream(String clientSecret, String authServerUrl) { String oidcConfig = "{\n" + - " \"realm\" : \"" + TEST_REALM + "\",\n" + - " \"resource\" : \"" + CLIENT_ID + "\",\n" + - " \"public-client\" : \"false\",\n" + - " \"auth-server-url\" : \"" + authServerUrl + "\",\n" + - " \"ssl-required\" : \"EXTERNAL\",\n" + - " \"credentials\" : {\n" + - " \"secret\" : \"" + clientSecret + "\"\n" + + " \"" + REALM + "\" : \"" + TEST_REALM + "\",\n" + + " \"" + RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + AUTH_SERVER_URL + "\" : \"" + authServerUrl + "\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + clientSecret + "\"\n" + " }\n" + "}"; return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); @@ -458,12 +698,12 @@ private InputStream getOidcConfigurationInputStream(String clientSecret, String private InputStream getOidcConfigurationInputStreamWithEnvironmentVariableExpression() { String oidcConfig = "{\n" + - " \"resource\" : \"" + CLIENT_ID + "\",\n" + - " \"public-client\" : \"false\",\n" + - " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "${oidc.provider-url-env}\",\n" + - " \"ssl-required\" : \"EXTERNAL\",\n" + - " \"credentials\" : {\n" + - " \"secret\" : \"" + CLIENT_SECRET + "\"\n" + + " \"" + RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "${oidc.provider-url-env}\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + " }\n" + "}"; return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); @@ -471,12 +711,12 @@ private InputStream getOidcConfigurationInputStreamWithEnvironmentVariableExpres private InputStream getOidcConfigurationInputStreamWithSystemPropertyExpression() { String oidcConfig = "{\n" + - " \"resource\" : \"" + CLIENT_ID + "\",\n" + - " \"public-client\" : \"false\",\n" + - " \"provider-url\" : \"${oidc.provider.url}\",\n" + - " \"ssl-required\" : \"EXTERNAL\",\n" + - " \"credentials\" : {\n" + - " \"secret\" : \"" + CLIENT_SECRET + "\"\n" + + " \"" + RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + PROVIDER_URL + "\" : \"${oidc.provider.url}\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + " }\n" + "}"; return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); @@ -484,12 +724,12 @@ private InputStream getOidcConfigurationInputStreamWithSystemPropertyExpression( private InputStream getOidcConfigurationInputStreamWithProviderUrlTrailingSlash() { String oidcConfig = "{\n" + - " \"resource\" : \"" + CLIENT_ID + "\",\n" + - " \"public-client\" : \"false\",\n" + - " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "/" + "\",\n" + - " \"ssl-required\" : \"EXTERNAL\",\n" + - " \"credentials\" : {\n" + - " \"secret\" : \"" + CLIENT_SECRET + "\"\n" + + " \"" + RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "/" + "\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + " }\n" + "}"; return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); @@ -497,11 +737,11 @@ private InputStream getOidcConfigurationInputStreamWithProviderUrlTrailingSlash( private InputStream getOidcConfigurationMissingRequiredOption() { String oidcConfig = "{\n" + - " \"public-client\" : \"false\",\n" + - " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" + - " \"ssl-required\" : \"EXTERNAL\",\n" + - " \"credentials\" : {\n" + - " \"secret\" : \"" + CLIENT_SECRET + "\"\n" + + " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + " }\n" + "}"; return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); @@ -509,27 +749,109 @@ private InputStream getOidcConfigurationMissingRequiredOption() { private InputStream getOidcConfigurationInputStreamWithTokenSignatureAlgorithm() { String oidcConfig = "{\n" + - " \"token-signature-algorithm\" : \"RS256\",\n" + - " \"resource\" : \"" + CLIENT_ID + "\",\n" + - " \"public-client\" : \"false\",\n" + - " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" + - " \"ssl-required\" : \"EXTERNAL\",\n" + - " \"credentials\" : {\n" + - " \"secret\" : \"" + CLIENT_SECRET + "\"\n" + + " \"" + TOKEN_SIGNATURE_ALGORITHM + "\" : \"RS256\",\n" + + " \"" + RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + + " }\n" + + "}"; + return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); + } + private InputStream getOidcConfigurationInputStreamWithScope(String scopeValue){ + String oidcConfig = "{\n" + + " \"" + Oidc.CLIENT_ID_JSON_VALUE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM_WITH_SCOPES + "/" + "\",\n" + + " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + SCOPE + "\" : \"" + scopeValue + "\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + + " }\n" + + "}"; + return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); + } + private InputStream getOidcConfigurationInputStreamWithRequestParameter(String requestParameter, String signingAlgorithm, String encryptionAlgorithm, String encMethod){ + String oidcConfig = "{\n" + + " \"" + Oidc.CLIENT_ID_JSON_VALUE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM_WITH_SCOPES + "/" + "\",\n" + + " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + AUTHENTICATION_REQUEST_FORMAT + "\" : \"" + requestParameter + "\",\n" + + " \"" + REQUEST_OBJECT_SIGNING_ALGORITHM + "\" : \"" + signingAlgorithm + "\",\n" + + " \"" + REQUEST_OBJECT_ENCRYPTION_ALG_VALUE + "\" : \"" + encryptionAlgorithm + "\",\n" + + " \"" + REQUEST_OBJECT_ENCRYPTION_ENC_VALUE + "\" : \"" + encMethod + "\",\n" + + " \"" + SCOPE + "\" : \"profile email phone\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + " }\n" + "}"; return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); } + private InputStream getOidcConfigurationInputStreamWithoutEncValue(String requestParameter, String encryptionAlgorithm){ + String oidcConfig = "{\n" + + " \"" + Oidc.CLIENT_ID_JSON_VALUE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM_WITH_SCOPES + "/" + "\",\n" + + " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + AUTHENTICATION_REQUEST_FORMAT + "\" : \"" + requestParameter + "\",\n" + + " \"" + REQUEST_OBJECT_ENCRYPTION_ALG_VALUE + "\" : \"" + encryptionAlgorithm + "\",\n" + + " \"" + SCOPE + "\" : \"profile email phone\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + + " }\n" + + "}"; + return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); + } + + private InputStream getOidcConfigurationInputStreamWithRequestParameter(String requestParameter, String signingAlgorithm, String encryptionAlgorithm, String encMethod, String keyStorePath, String alias, String keyStoreType){ + String oidcConfig = "{\n" + + " \"" + Oidc.CLIENT_ID_JSON_VALUE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM_WITH_SCOPES + "/" + "\",\n" + + " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + AUTHENTICATION_REQUEST_FORMAT + "\" : \"" + requestParameter + "\",\n" + + " \"" + REQUEST_OBJECT_SIGNING_ALGORITHM + "\" : \"" + signingAlgorithm + "\",\n" + + " \"" + REQUEST_OBJECT_ENCRYPTION_ALG_VALUE + "\" : \"" + encryptionAlgorithm + "\",\n" + + " \"" + REQUEST_OBJECT_ENCRYPTION_ENC_VALUE + "\" : \"" + encMethod + "\",\n" + + " \"" + REQUEST_OBJECT_SIGNING_KEYSTORE_FILE + "\" : \"" + keyStorePath + "\",\n" + + " \"" + REQUEST_OBJECT_SIGNING_KEYSTORE_TYPE + "\" : \"" + keyStoreType + "\",\n" + + " \"" + REQUEST_OBJECT_SIGNING_KEYSTORE_PASSWORD + "\" : \"" + KEYSTORE_PASS + "\",\n" + + " \"" + REQUEST_OBJECT_SIGNING_KEY_PASSWORD + "\" : \"" + KEYSTORE_PASS + "\",\n" + + " \"" + REQUEST_OBJECT_SIGNING_KEY_ALIAS + "\" : \"" + alias + "\",\n" + + " \"" + SCOPE + "\" : \"email phone profile\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + + " }\n" + + "}"; + return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); + } + + private InputStream getOidcConfigurationInputStreamWithRequestObjectPublicClient(String requestParameter, String signingAlgorithm){ + String oidcConfig = "{\n" + + " \"" + Oidc.CLIENT_ID_JSON_VALUE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM_WITH_SCOPES + "/" + "\",\n" + + " \"" + PUBLIC_CLIENT + "\" : \"true\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + AUTHENTICATION_REQUEST_FORMAT + "\" : \"" + requestParameter + "\",\n" + + " \"" + REQUEST_OBJECT_SIGNING_ALGORITHM + "\" : \"" + signingAlgorithm + "\",\n" + + " \"" + SCOPE + "\" : \"email phone profile\"\n" + + "}"; + return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); + } + private InputStream getOidcConfigurationInputStreamWithPrincipalAttribute(String principalAttributeValue) { String oidcConfig = "{\n" + - " \"principal-attribute\" : \"" + principalAttributeValue + "\",\n" + - " \"resource\" : \"" + CLIENT_ID + "\",\n" + - " \"public-client\" : \"false\",\n" + - " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" + - " \"ssl-required\" : \"EXTERNAL\",\n" + - " \"credentials\" : {\n" + - " \"secret\" : \"" + CLIENT_SECRET + "\"\n" + + " \"" + PRINCIPAL_ATTRIBUTE + "\" : \"" + principalAttributeValue + "\",\n" + + " \"" + RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + " }\n" + "}"; return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); @@ -537,13 +859,13 @@ private InputStream getOidcConfigurationInputStreamWithPrincipalAttribute(String static InputStream getTenantConfigWithAuthServerUrl(String tenant) { String oidcConfig = "{\n" + - " \"realm\" : \"" + tenant + "\",\n" + - " \"resource\" : \"" + CLIENT_ID + "\",\n" + - " \"public-client\" : \"false\",\n" + - " \"auth-server-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "\",\n" + - " \"ssl-required\" : \"EXTERNAL\",\n" + - " \"credentials\" : {\n" + - " \"secret\" : \"" + CLIENT_SECRET + "\"\n" + + " \"" + REALM + "\" : \"" + tenant + "\",\n" + + " \""+ RESOURCE +"\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PUBLIC_CLIENT +"\" : \"false\",\n" + + " \"" + AUTH_SERVER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + " }\n" + "}"; return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); @@ -551,12 +873,12 @@ static InputStream getTenantConfigWithAuthServerUrl(String tenant) { static InputStream getTenantConfigWithProviderUrl(String tenant) { String oidcConfig = "{\n" + - " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + tenant + "\",\n" + - " \"client-id\" : \"" + CLIENT_ID + "\",\n" + - " \"public-client\" : \"false\",\n" + - " \"ssl-required\" : \"EXTERNAL\",\n" + - " \"credentials\" : {\n" + - " \"secret\" : \"" + CLIENT_SECRET + "\"\n" + + " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + tenant + "\",\n" + + " \"" + Oidc.CLIENT_ID_JSON_VALUE + "\" : \"" + CLIENT_ID + "\",\n" + + " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" + + " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" + + " \"" + CREDENTIALS + "\" : {\n" + + " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" + " }\n" + "}"; return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8)); @@ -566,3 +888,4 @@ private static final String getClientPageTestForTenant(String tenant) { return tenant.equals(TENANT1_ENDPOINT) ? TENANT1_ENDPOINT : TENANT2_ENDPOINT + ":" + CLIENT_PAGE_TEXT; } } + diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsBaseTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsBaseTest.java index a5d7a527193..e6bb2762ed5 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsBaseTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsBaseTest.java @@ -38,7 +38,7 @@ public static void startTestContainers() throws Exception { assumeTrue("Docker isn't available, OIDC tests will be skipped", isDockerAvailable()); KEYCLOAK_CONTAINER = new KeycloakContainer(); KEYCLOAK_CONTAINER.start(); - sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, 3, 3, true)); + sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, 3, 3, false, true)); client = new MockWebServer(); client.start(CLIENT_PORT); } diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsDisabledTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsDisabledTest.java index e9d36b66bc6..f32771d3812 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsDisabledTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsDisabledTest.java @@ -72,4 +72,3 @@ public void testSuccessfulAuthenticationWithQueryParamsWithSystemPropertyDisable } } - diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsEnabledTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsEnabledTest.java index ff320a28e02..3f9c5515fa1 100644 --- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsEnabledTest.java +++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsEnabledTest.java @@ -79,7 +79,13 @@ public void testSuccessfulAuthenticationWithQueryParamsWithSystemPropertyEnabled performAuthentication(getOidcConfigurationInputStreamWithProviderUrl(), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, originalUrl, expectedUrlAfterRedirect, CLIENT_PAGE_TEXT); + + queryParams = "?url=http%3A%2F%2Flocalhost%2F%3Fone%3Dabc%26two%3Ddef&three=ghi"; + originalUrl = getClientUrl() + queryParams; + expectedUrlAfterRedirect = originalUrl; + performAuthentication(getOidcConfigurationInputStreamWithProviderUrl(), KeycloakConfiguration.ALICE, + KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, originalUrl, + expectedUrlAfterRedirect, CLIENT_PAGE_TEXT); } } - diff --git a/http/spnego/pom.xml b/http/spnego/pom.xml index 652384b9a15..f44833a344d 100644 --- a/http/spnego/pom.xml +++ b/http/spnego/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/http/sso/pom.xml b/http/sso/pom.xml index 1e6e3a56441..3db2fc06dc4 100644 --- a/http/sso/pom.xml +++ b/http/sso/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/http/sso/src/main/java/org/wildfly/security/http/util/sso/SingleSignOnServerMechanismFactory.java b/http/sso/src/main/java/org/wildfly/security/http/util/sso/SingleSignOnServerMechanismFactory.java index 867560e90eb..17dc3309357 100644 --- a/http/sso/src/main/java/org/wildfly/security/http/util/sso/SingleSignOnServerMechanismFactory.java +++ b/http/sso/src/main/java/org/wildfly/security/http/util/sso/SingleSignOnServerMechanismFactory.java @@ -46,6 +46,7 @@ *

The single sign-one capabilities provided by this factory is based on a HTTP Cookie to track SSO sessions and also an {@link IdentityCache} providing * a storage (eg.: using a shared or distributable cache/map) for these sessions and related data. * + * @deprecated Only inner class SingleSignOnConfiguration is deprecated. * @author Pedro Igor * @author Paul Ferraro */ diff --git a/http/stateful-basic/pom.xml b/http/stateful-basic/pom.xml index 7e240807980..ff8fd8a810b 100644 --- a/http/stateful-basic/pom.xml +++ b/http/stateful-basic/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/http/stateful-basic/src/main/java/org/wildfly/security/http/sfbasic/BasicAuthenticationMechanism.java b/http/stateful-basic/src/main/java/org/wildfly/security/http/sfbasic/BasicAuthenticationMechanism.java index 103bddbaedf..eccff5eb0e5 100644 --- a/http/stateful-basic/src/main/java/org/wildfly/security/http/sfbasic/BasicAuthenticationMechanism.java +++ b/http/stateful-basic/src/main/java/org/wildfly/security/http/sfbasic/BasicAuthenticationMechanism.java @@ -329,48 +329,7 @@ public CachedIdentity remove() { } private static HttpServerCookie createCookie(final String name, final String value) { - return new HttpServerCookie() { - - @Override - public boolean isSecure() { - return false; - } - - @Override - public boolean isHttpOnly() { - return false; - } - - @Override - public int getVersion() { - return 0; - } - - @Override - public String getValue() { - return value; - } - - @Override - public String getPath() { - return "/"; - } - - @Override - public String getName() { - return name; - } - - @Override - public int getMaxAge() { - return -1; - } - - @Override - public String getDomain() { - return null; - } - }; + return HttpServerCookie.getInstance(name, value, null, -1, "/", false, 0, false); } } diff --git a/http/util/pom.xml b/http/util/pom.xml index 41b5708936c..3643831549e 100644 --- a/http/util/pom.xml +++ b/http/util/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/jose/jwk/pom.xml b/jose/jwk/pom.xml index 286fcbc9d2f..4a82a6a3894 100644 --- a/jose/jwk/pom.xml +++ b/jose/jwk/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/jose/util/pom.xml b/jose/util/pom.xml index 0b5931947eb..ce6101c1ffb 100644 --- a/jose/util/pom.xml +++ b/jose/util/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/json-util/pom.xml b/json-util/pom.xml index 52c14cae984..6ecab3a776c 100644 --- a/json-util/pom.xml +++ b/json-util/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT 4.0.0 diff --git a/keystore/pom.xml b/keystore/pom.xml index 7d719a494c6..4ab364a873b 100644 --- a/keystore/pom.xml +++ b/keystore/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT 4.0.0 diff --git a/keystore/src/test/java/org/wildfly/security/keystore/KeyStoreUtilTest.java b/keystore/src/test/java/org/wildfly/security/keystore/KeyStoreUtilTest.java index 1ff8b92dbaa..07bc7814a81 100644 --- a/keystore/src/test/java/org/wildfly/security/keystore/KeyStoreUtilTest.java +++ b/keystore/src/test/java/org/wildfly/security/keystore/KeyStoreUtilTest.java @@ -102,122 +102,32 @@ public void afterTest() { @Test public void testJKS() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { - System.out.println("Testing JKS..."); - Certificate jkscert = generateCertificate(); - String filename = "testks.jks"; - String alias = "alias"; - char[] password = "password".toCharArray(); - - generateKeyStoreWithKey(filename, "jks", alias, password, jkscert); - - KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password); - Assert.assertNotNull(loadedStore); - Certificate loadedCert = loadedStore.getCertificate(alias); - - Assert.assertEquals(jkscert, loadedCert); + testKeyStore("testks.jks", "jks", false); } @Test public void testJCEKS() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { - System.out.println("Testing JCEKS..."); - Certificate jkscert = generateCertificate(); - String filename = "testks.pkcs12"; - String alias = "alias"; - char[] password = "password".toCharArray(); - - generateKeyStoreWithKey(filename, "jceks", alias, password, jkscert); - - KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password); - Assert.assertNotNull(loadedStore); - Certificate loadedCert = loadedStore.getCertificate(alias); - - Assert.assertEquals(jkscert, loadedCert); + testKeyStore("testks.pkcs12", "jceks", false); } @Test public void testPKCS12() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException { - System.out.println("Testing PKCS12..."); - Certificate jkscert = generateCertificate(); - String filename = "testks.asdf"; - String alias = "alias"; - char[] password = "password".toCharArray(); - - generateKeyStoreWithKey(filename, "pkcs12", alias, password, jkscert); - - KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password); - Assert.assertNotNull(loadedStore); - Certificate loadedCert = loadedStore.getCertificate(alias); - - Assert.assertEquals(jkscert, loadedCert); + testKeyStore("testks.asdf", "pkcs12", false); } @Test - public void testBKS() throws CertificateException, KeyStoreException, IOException { - System.out.println("Testing BKS..."); - Certificate jkscert = generateCertificate(); - String filename = "testks.bks"; - String alias = "alias"; - char[] password = "password".toCharArray(); - boolean bcfailed = false; - try { - generateKeyStoreWithKey(filename, "bks", alias, password, jkscert); - } catch (Exception e) { - bcfailed = true; - } - - Assume.assumeFalse("BC elytronProvider not found, skipping BC keystore recognition", bcfailed); - - KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password); - Assert.assertNotNull(loadedStore); - Certificate loadedCert = loadedStore.getCertificate(alias); - - Assert.assertEquals(jkscert, loadedCert); + public void testBKS() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException { + testKeyStore("testks.asdf", "bks", true); } @Test - public void testUBER() throws CertificateException, KeyStoreException, IOException { - System.out.println("Testing UBER..."); - Certificate jkscert = generateCertificate(); - String filename = "testks.ubr"; - String alias = "alias"; - char[] password = "password".toCharArray(); - boolean bcfailed = false; - try { - generateKeyStoreWithKey(filename, "uber", alias, password, jkscert); - } catch (Exception e) { - bcfailed = true; - } - - Assume.assumeFalse("BC elytronProvider not found, skipping BC keystore recognition", bcfailed); - - KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password); - Assert.assertNotNull(loadedStore); - Certificate loadedCert = loadedStore.getCertificate(alias); - - Assert.assertEquals(jkscert, loadedCert); + public void testUBER() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException { + testKeyStore("testks.asdf", "uber", true); } @Test - public void testBCFKS() throws CertificateException, KeyStoreException, IOException { - System.out.println("Testing BCFKS..."); - Certificate jkscert = generateCertificate(); - String filename = "testks.bcfks"; - String alias = "alias"; - char[] password = "password".toCharArray(); - boolean bcfailed = false; - try { - generateKeyStoreWithKey(filename, "bcfks", alias, password, jkscert); - } catch (Exception e) { - bcfailed = true; - } - - Assume.assumeFalse("BC elytronProvider not found, skipping BC keystore recognition", bcfailed); - - KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password); - Assert.assertNotNull(loadedStore); - Certificate loadedCert = loadedStore.getCertificate(alias); - - Assert.assertEquals(jkscert, loadedCert); + public void testBCFKS() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException { + testKeyStore("testks.asdf", "bcfks", true); } @Test @@ -302,4 +212,29 @@ private static File getWorkingDir() { } return workingDir; } + + private void testKeyStore(String filename, String keystoreType, boolean testBCKeyStore) throws CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException { + System.out.println("Testing " + keystoreType.toUpperCase() + "..."); + Certificate jkscert = generateCertificate(); + String alias = "alias"; + char[] password = "password".toCharArray(); + + if (testBCKeyStore) { + boolean bcfailed = false; + try { + generateKeyStoreWithKey(filename, keystoreType, alias, password, jkscert); + } catch (Exception e) { + bcfailed = true; + } + Assume.assumeFalse("BC elytronProvider not found, skipping BC keystore recognition", bcfailed); + } else { + generateKeyStoreWithKey(filename, keystoreType, alias, password, jkscert); + } + + KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password); + Assert.assertNotNull(loadedStore); + Certificate loadedCert = loadedStore.getCertificate(alias); + + Assert.assertEquals(jkscert, loadedCert); + } } diff --git a/manager/action/pom.xml b/manager/action/pom.xml index bc2e0010dea..b0b5cc6e8bd 100644 --- a/manager/action/pom.xml +++ b/manager/action/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/manager/base/pom.xml b/manager/base/pom.xml index 5b38909c483..c43c374f491 100644 --- a/manager/base/pom.xml +++ b/manager/base/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/manager/base/src/test/java/org/wildfly/security/manager/AlternateSecurityManagerTest.java b/manager/base/src/test/java/org/wildfly/security/manager/AlternateSecurityManagerTest.java index c12f0d51329..c873a635f72 100644 --- a/manager/base/src/test/java/org/wildfly/security/manager/AlternateSecurityManagerTest.java +++ b/manager/base/src/test/java/org/wildfly/security/manager/AlternateSecurityManagerTest.java @@ -32,6 +32,7 @@ import java.util.Stack; import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.wildfly.security.ParametricPrivilegedAction; @@ -84,6 +85,8 @@ public class AlternateSecurityManagerTest { @Before public void before() { + Assume.assumeTrue("Skipping AlternateSecurityManagerTest suite, tests are not being run on JDK 17 or lower.", + Integer.parseInt(System.getProperty("java.specification.version")) <= 17); AccessControlContext current = AccessController.getContext(); ProtectionDomain[] domains = getProtectionDomainStack(current); diff --git a/mechanism/base/pom.xml b/mechanism/base/pom.xml index 107e155f66b..e8b21d9cb63 100644 --- a/mechanism/base/pom.xml +++ b/mechanism/base/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/AuthenticationMechanismException.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/AuthenticationMechanismException.java index 525fd9fdba3..e5a82cb2d03 100644 --- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/AuthenticationMechanismException.java +++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/AuthenticationMechanismException.java @@ -102,6 +102,14 @@ public static AuthenticationMechanismException fromException(final Exception sou return copyContents(source, new AuthenticationMechanismException(source.getMessage(), source.getCause())); } + /** + * Copies the stack trace and suppressed exceptions from a source exception to a specified throwable. + * + * @param source the source exception from which the stack trace and suppressed exceptions should be copied. + * @param throwable the throwable to which the contents should be copied. + * @param the type of throwable to which the contents should be copied. + * @return the throwable that was passed in as a parameter, with the contents copied from the source exception. + */ private static T copyContents(final Exception source, final T throwable) { throwable.setStackTrace(source.getStackTrace()); final Throwable[] suppressed = source.getSuppressed(); diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/MechanismUtil.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/MechanismUtil.java index 622a53c1f1b..28243d661b5 100644 --- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/MechanismUtil.java +++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/MechanismUtil.java @@ -37,7 +37,7 @@ * * @author David M. Lloyd * - * @deprecated Should not be part of public API. Moved into internal {@link org.wildfly.security.mechanism._private.MechanismUtil}. + * @deprecated Should not be part of public API. Moved into internal {@link org.wildfly.security.mechanism._private.MechanismUtil org.wildfly.security.mechanism._private.MechanismUtil}. */ @Deprecated public final class MechanismUtil { @@ -57,6 +57,7 @@ private MechanismUtil() {} * @param providers the security providers to use with the {@link PasswordFactory} * @param the password type * @return the password + * @throws AuthenticationMechanismException if there is an error retrieving the password */ @Deprecated public static S getPasswordCredential(String userName, CallbackHandler callbackHandler, Class passwordType, String passwordAlgorithm, AlgorithmParameterSpec matchParameters, AlgorithmParameterSpec generateParameters, Supplier providers) throws AuthenticationMechanismException { @@ -78,6 +79,7 @@ public static S getPasswordCredential(String userName, Call * @param the password type * @param log mechanism specific logger * @return the password + * @throws AuthenticationMechanismException if there is an error retrieving the password */ @Deprecated public static S getPasswordCredential(String userName, CallbackHandler callbackHandler, Class passwordType, String passwordAlgorithm, AlgorithmParameterSpec matchParameters, AlgorithmParameterSpec generateParameters, Supplier providers, ElytronMessages log) throws AuthenticationMechanismException { diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerErrorCode.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerErrorCode.java index f1659f2dcef..111a4c2a340 100644 --- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerErrorCode.java +++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerErrorCode.java @@ -45,6 +45,9 @@ public enum ScramServerErrorCode { private final String text; private final byte[] messageBytes; + /** + * Creates an error code instance with a String representation and a byte array for error message. + */ ScramServerErrorCode() { text = name().replace('_', '-').toLowerCase(Locale.US); final int length = text.length(); @@ -54,14 +57,29 @@ public enum ScramServerErrorCode { messageBytes = msg; } + /** + * Returns the String representation of the error code. + * + * @return String representation of the error code. + */ public String getText() { return text; } + /** + * Returns the copy of the byte array representing the error message. + * + * @return copy of the byte array representing the error message. + */ public byte[] getMessageBytes() { return messageBytes.clone(); } + /** + * Returns the byte array representing the error message. + * + * @return the byte array representing the error message. + */ byte[] getRawMessageBytes() { return messageBytes; } diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerException.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerException.java index a0594f69c42..7e7057ad1d2 100644 --- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerException.java +++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerException.java @@ -83,6 +83,11 @@ public String getMessage() { return super.getMessage() + ": " + error.getText(); } + /** + * Returns the error code of the exception. + * + * @return the error code of the exception. + */ public ScramServerErrorCode getError() { return error; } diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/_private/MechanismUtil.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/_private/MechanismUtil.java index 74e7b99e85c..32192d27f85 100644 --- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/_private/MechanismUtil.java +++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/_private/MechanismUtil.java @@ -67,6 +67,7 @@ private MechanismUtil() {} * @param the password type * @param log mechanism specific logger * @return the password + * @throws AuthenticationMechanismException if there is an error retrieving the password */ public static S getPasswordCredential(String userName, CallbackHandler callbackHandler, Class passwordType, String passwordAlgorithm, AlgorithmParameterSpec matchParameters, AlgorithmParameterSpec generateParameters, Supplier providers, ElytronMessages log) throws AuthenticationMechanismException { Assert.checkNotNullParam("userName", userName); @@ -168,6 +169,7 @@ public static void handleCallbacks(ElytronMessages log, CallbackHandler callback * @param scope the HTTP scope to store computed value (must not be {@code null}) * @param key the key to retrieve (must not be {@code null}) * @param mappingFunction the function to apply to acquire the value (must not be {@code null}) + * @param the type of returned value * @return the stored or new value (not {@code null}) */ public static R computeIfAbsent(HttpScope scope, String key, Function mappingFunction) { diff --git a/mechanism/digest/pom.xml b/mechanism/digest/pom.xml index 0b85d633b40..c9989e73abb 100644 --- a/mechanism/digest/pom.xml +++ b/mechanism/digest/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestQuote.java b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestQuote.java index 0f6cc400572..da81adc1fb7 100644 --- a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestQuote.java +++ b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestQuote.java @@ -21,7 +21,7 @@ import org.wildfly.common.bytes.ByteStringBuilder; /** - * Utility class used to convert string to quoted strings + * Utility class used to convert string to quoted strings. * * @author Peter Skopek * @@ -33,6 +33,12 @@ public class DigestQuote { private DigestQuote() { } + /** + * Checks if a given character needs to be quoted. + * + * @param ch the character to check. + * @return {@code true} if the character needs to be quoted, {@code false} otherwise. + */ private static boolean quoteNeeded(char ch) { return ch == '"' || // escape char @@ -46,8 +52,8 @@ private static boolean quoteNeeded(char ch) { /** * Creates new String quoted by SASL rules. * - * @param inputStr String to be quoted - * @return + * @param inputStr String to be quoted. + * @return new String with quoted characters. */ public static String quote(String inputStr) { int len = inputStr.length(); @@ -64,6 +70,12 @@ public static String quote(String inputStr) { return sb.toString(); } + /** + * Creates new Array quoted by SASL rules. + * + * @param input Byte array to be quoted. + * @return new byte array with quoted bytes. + */ public static byte[] quote(byte[] input) { ByteStringBuilder bsb = new ByteStringBuilder(); for (int i = 0; i < input.length; i++) { diff --git a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestUtil.java b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestUtil.java index fc4e4399638..c277abd51d1 100644 --- a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestUtil.java +++ b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestUtil.java @@ -30,8 +30,6 @@ import java.util.HashMap; import java.util.function.Supplier; -import javax.security.sasl.SaslException; - import org.wildfly.common.bytes.ByteStringBuilder; import org.wildfly.security.mechanism._private.ElytronMessages; import org.wildfly.security.mechanism.AuthenticationMechanismException; @@ -52,9 +50,12 @@ public class DigestUtil { /** * Client side method to parse challenge sent by server. * - * @param challenge - * @return - * @throws AuthenticationMechanismException + * @param challenge the byte array representing the authentication challenge to be parsed. + * @param charset the charset to decide which whitespace separator is used. + * @param multiRealm {@code true} if there are multiple realms in the challenge, {@code false} otherwise + * @param log the logger to use. + * @return A new HashMap representing response for the parsed challenge + * @throws AuthenticationMechanismException if there is an error parsing the challenge */ public static HashMap parseResponse(byte [] challenge, Charset charset, boolean multiRealm, ElytronMessages log) throws AuthenticationMechanismException { @@ -170,6 +171,15 @@ else if (expectSeparator) { return response; } + /** + * Adds a key-value pair to a HashMap representing a parsed challenge. + * + * @param response the HashMap to add the key-value pair to. + * @param keyBuilder the StringBuilder containing the key. + * @param valueBuilder the ByteStringBuilder containing the value. + * @param realmNumber the current number of realms in the parsed challenge. + * @return the updated number of realms in the parsed challenge. + */ private static int addToParsedChallenge(HashMap response, StringBuilder keyBuilder, ByteStringBuilder valueBuilder, int realmNumber) { String k = keyBuilder.toString(); byte[] v = valueBuilder.toArray(); @@ -183,6 +193,13 @@ private static int addToParsedChallenge(HashMap response, String return realmNumber; } + /** + * Finds the next non-whitespace character in a byte array. + * + * @param buffer the byte array to search in. + * @param startPoint the starting point in the buffer to begin the search. + * @return the index of the next non-whitespace character. + */ private static int skipWhiteSpace(byte[] buffer, int startPoint) { int i = startPoint; while (i < buffer.length && isWhiteSpace(buffer[i])) { @@ -191,6 +208,12 @@ private static int skipWhiteSpace(byte[] buffer, int startPoint) { return i; } + /** + * Checks if a given byte is a whitespace character. + * + * @param b the byte to check. + * @return {@code true} if the byte is a whitespace character, {@code false} otherwise. + */ private static boolean isWhiteSpace(byte b) { if (b == 13) // CR return true; @@ -204,6 +227,15 @@ else if (b == 32) // SPACE return false; } + /** + * Digests the concatenated username, realm and password. + * + * @param messageDigest the message digest algorithm to use when computing the digest. + * @param username the username to use when concatenating. + * @param realm the realm to use when concatenating. + * @param password the password in the form of a char array to use when concatenating. + * @return byte array of the digested password. + */ public static byte[] userRealmPasswordDigest(MessageDigest messageDigest, String username, String realm, char[] password) { CharsetEncoder latin1Encoder = StandardCharsets.ISO_8859_1.newEncoder(); latin1Encoder.reset(); @@ -232,10 +264,13 @@ public static byte[] userRealmPasswordDigest(MessageDigest messageDigest, String } /** - * Get array of password chars from TwoWayPassword + * Get array of password chars from TwoWayPassword. * - * @return - * @throws SaslException + * @param password the TwoWayPassword that needs to be processed. + * @param providers the supplier for the providers to be used for processing. + * @param log the logger to use. + * @throws AuthenticationMechanismException if there is an error retrieving the encoded password. + * @return encoded password in the form of a char array. */ public static char[] getTwoWayPasswordChars(TwoWayPassword password, Supplier providers, ElytronMessages log) throws AuthenticationMechanismException { if (password == null) { diff --git a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/PasswordDigestObtainer.java b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/PasswordDigestObtainer.java index b876af7ad2c..7af037bd6ba 100644 --- a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/PasswordDigestObtainer.java +++ b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/PasswordDigestObtainer.java @@ -45,7 +45,7 @@ import static org.wildfly.security.mechanism.digest.DigestUtil.userRealmPasswordDigest; /** - * Utility class used to obtain username+realm+password using SASL/HTTP mechanism callbacks + * Utility class used to obtain username+realm+password using SASL/HTTP mechanism callbacks. * * @author Jan Kalina */ @@ -67,6 +67,20 @@ public class PasswordDigestObtainer { private RealmCallback realmCallback; private NameCallback nameCallback; + /** + * Constructs a new {@code PasswordDigestObtainer} instance. + * + * @param callbackHandler the callbackHandler to handle the callbacks required to obtain the username and password. + * @param defaultUsername the default username to use if a callback is not provided. + * @param defaultRealm the default realm to use if a callback is not provided. + * @param log the logger to use. + * @param credentialAlgorithm the name of the algorithm for obtaining the credential. + * @param messageDigest the {@link MessageDigest} used for digesting the password. + * @param passwordFactoryProviders the supplier of the providers to use when creating a {@code PasswordFactory} instance. + * @param realms the realms to check for a user and password. + * @param readOnlyRealmUsername {@code true} if the username passed in the callback can be modified, {@code false} otherwise. + * @param skipRealmCallbacks {@code true} if realm callbacks should be skipped, {@code false} otherwise. + */ public PasswordDigestObtainer(CallbackHandler callbackHandler, String defaultUsername, String defaultRealm, ElytronMessages log, String credentialAlgorithm, MessageDigest messageDigest, Supplier passwordFactoryProviders, String[] realms, @@ -83,14 +97,30 @@ public PasswordDigestObtainer(CallbackHandler callbackHandler, String defaultUse this.skipRealmCallbacks = skipRealmCallbacks; } + /** + * Returns the username obtained from callback or the default one. + * + * @return the username obtained from callback or the default one. + */ public String getUsername() { return username; } + /** + * Returns the realm obtained from callback or the default one. + * + * @return the realm obtained from callback or the default one. + */ public String getRealm() { return realm; } + /** + * Handles callbacks for user and password information. + * + * @return the salted password. + * @throws AuthenticationMechanismException if the callback handler does not support credential acquisition. + */ public byte[] handleUserRealmPasswordCallbacks() throws AuthenticationMechanismException { realmChoiceCallBack = skipRealmCallbacks || realms == null || realms.length <= 1 ? null : @@ -115,6 +145,12 @@ public byte[] handleUserRealmPasswordCallbacks() throws AuthenticationMechanismE throw log.mechCallbackHandlerDoesNotSupportCredentialAcquisition(null); } + /** + * Obtains the pre-digested salted password for the {@code username} in the {@code realm}. + * + * @return the pre-digested salted password if obtained, {@code null} otherwise. + * @throws AuthenticationMechanismException if an exception occurs while handling the callbacks. + */ private byte[] getPredigestedSaltedPassword() throws AuthenticationMechanismException { if (realmChoiceCallBack != null) { try { @@ -180,6 +216,12 @@ private byte[] getPredigestedSaltedPassword() throws AuthenticationMechanismExce return null; } + /** + * Obtains the salted password from a two-way callback. + * + * @return the byte array of the salted password if obtained, {@code null} otherwise. + * @throws AuthenticationMechanismException if an error occurs during the process of handling callbacks or obtaining the password. + */ private byte[] getSaltedPasswordFromTwoWay() throws AuthenticationMechanismException { if (realmChoiceCallBack != null) { try { @@ -253,6 +295,12 @@ private byte[] getSaltedPasswordFromTwoWay() throws AuthenticationMechanismExcep return null; } + /** + * Obtains the salted password from a password callback. + * + * @return the byte array of the salted password. + * @throws AuthenticationMechanismException if an error occurs during the process of handling callbacks or obtaining the password. + */ private byte[] getSaltedPasswordFromPasswordCallback() throws AuthenticationMechanismException { PasswordCallback passwordCallback = new PasswordCallback("User password: ", false); diff --git a/mechanism/gssapi/pom.xml b/mechanism/gssapi/pom.xml index 055553ef208..adccd6cda1e 100644 --- a/mechanism/gssapi/pom.xml +++ b/mechanism/gssapi/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/mechanism/gssapi/src/main/java/org/wildfly/security/mechanism/gssapi/GSSCredentialSecurityFactory.java b/mechanism/gssapi/src/main/java/org/wildfly/security/mechanism/gssapi/GSSCredentialSecurityFactory.java index 03566163be3..2322b9ede25 100644 --- a/mechanism/gssapi/src/main/java/org/wildfly/security/mechanism/gssapi/GSSCredentialSecurityFactory.java +++ b/mechanism/gssapi/src/main/java/org/wildfly/security/mechanism/gssapi/GSSCredentialSecurityFactory.java @@ -87,12 +87,25 @@ public final class GSSCredentialSecurityFactory implements SecurityFactory credentialOperator; + /** + * Constructs a new {@code GSSCredentialSecurityFactory} instance. + * + * @param minimumRemainingLifetime the minimum remaining lifetime for a {@link GSSCredential} in seconds. + * @param rawSupplier the supplier of raw credentials. + */ GSSCredentialSecurityFactory(final int minimumRemainingLifetime, final ExceptionSupplier rawSupplier) { this.minimumRemainingLifetime = minimumRemainingLifetime; this.rawSupplier = rawSupplier; credentialOperator = this::update; } + /** + * Updates the {@link GSSKerberosCredential}. If the original is not valid, it gets a new {@code GSSKerberosCredential} + * from the {@code rawSupplier}, otherwise returns the original. + * + * @param original the original {@code GSSKerberosCredential} to be updated. + * @return the original if still valid, new {@code GSSKerberosCredential} otherwise. + */ private GSSKerberosCredential update(GSSKerberosCredential original) { GSSKerberosCredential result = null; try { @@ -116,6 +129,13 @@ private GSSKerberosCredential update(GSSKerberosCredential original) { return result; } + /** + * Checks if the GSSCredential is still valid. + * + * @param gssCredential the GSSCredential to check. + * @return {@code true} if the GSSCredential is valid, {@code false} otherwise. + * @throws GeneralSecurityException if an error occurs during the validation. + */ private boolean testIsValid(GSSCredential gssCredential) throws GeneralSecurityException { checkNotNullParam("gssCredential", gssCredential); boolean stillValid; @@ -131,6 +151,12 @@ private boolean testIsValid(GSSCredential gssCredential) throws GeneralSecurityE return stillValid; } + /** + * Checks if the Kerberos ticket is still valid. If not, attempts to refresh it. + * + * @param ticket the Kerberos ticket to be checked. + * @return {@code true} if the ticket is valid, {@code false} otherwise. + */ private boolean testIsValid(KerberosTicket ticket) { if (ticket == null) { log.trace("No cached KerberosTicket"); @@ -231,9 +257,9 @@ public Builder setIsServer(final boolean isServer) { } /** - * Set if the KerberosTicket should also be obtained and associated with the Credential/ + * Set if the KerberosTicket should also be obtained and associated with the Credential. * - * @param obtainKerberosTicket if the KerberosTicket should also be obtained and associated with the Credential/ + * @param obtainKerberosTicket if the KerberosTicket should also be obtained and associated with the Credential. * @return {@code this} to allow chaining. */ public Builder setObtainKerberosTicket(final boolean obtainKerberosTicket) { @@ -297,7 +323,7 @@ public Builder setPrincipal(final String principal) { } /** - * Set if debug logging should be enabled for the JAAS authentication portion of obtaining the {@link GSSCredential} + * Set if debug logging should be enabled for the JAAS authentication portion of obtaining the {@link GSSCredential}. * * @param debug if debug logging should be enabled for the JAAS authentication portion of obtaining the {@link GSSCredential} * @return {@code this} to allow chaining. @@ -336,7 +362,7 @@ public Builder setCheckKeyTab(final boolean value) { } /** - * Set other configuration options for {@code Krb5LoginModule} + * Set other configuration options for {@code Krb5LoginModule}. * * @param options the configuration options which will be appended to options passed into {@code Krb5LoginModule} * @return {@code this} to allow chaining. @@ -380,6 +406,14 @@ public SecurityFactory build() throws IOException { return new GSSCredentialSecurityFactory(minimumRemainingLifetime > 0 ? minimumRemainingLifetime : 0, () -> createGSSCredential(configuration)); } + /** + * Creates an instance of the {@link GSSKerberosCredential} class, which represents a Kerberos credential + * that can be used for authentication using the GSS-API. + * + * @param configuration the configuration used for creating the {@link LoginContext}. + * @return the {@code GSSKerberosCredential} - the GSSCredential object and Kerberos Ticket (if {@code obtainKerberosTicket} is {@code true}. + * @throws GeneralSecurityException if an error occurs during the creation of {@code GSSKerberosCredential}. + */ private GSSKerberosCredential createGSSCredential(Configuration configuration) throws GeneralSecurityException { if (failCache != 0 && System.currentTimeMillis() - lastFailTime < failCache * 1000) { throw log.initialLoginSkipped(failCache); @@ -445,10 +479,24 @@ private GSSKerberosCredential createGSSCredential(Configuration configuration) t } } + /** + * Performs a privileged action. If a security manager is set, the action will be executed via + * {@link AccessController#doPrivileged(PrivilegedAction)}. If no security manager is set, + * the action will be executed directly. + * + * @param action the action do be executed. + * @param the type of the action. + * @return the result of the executed action. + */ private static T doPrivileged(final PrivilegedAction action) { return System.getSecurityManager() != null ? AccessController.doPrivileged(action) : action.run(); } + /** + * Checks if the keytab exists and if it contains any keys for the specified principal. + * + * @throws IOException if the keytab does not exist or if it does not contain any keys for the specified principal. + */ private void checkKeyTab() throws IOException { KeyTab kt = KeyTab.getInstance(keyTab); if (!kt.exists()) { @@ -459,6 +507,12 @@ private void checkKeyTab() throws IOException { } } + /** + * Creates a {@link Configuration} that is used to initiate a {@link LoginContext}. + * + * @return a {@code Configuration} for initiating a {@code LoginContext}. + * @throws IOException if the keyTab does not exist or there are no keys for the principal in the keyTab. + */ private Configuration createConfiguration() throws IOException { Map options = new HashMap<>(); if (debug) { @@ -491,6 +545,9 @@ public AppConfigurationEntry[] getAppConfigurationEntry(String name) { }; } + /** + * Asserts that the builder has not yet been built. + */ private void assertNotBuilt() { if (built) { throw log.builderAlreadyBuilt(); @@ -499,6 +556,12 @@ private void assertNotBuilt() { } + /** + * Wraps the given {@link GSSCredential} and prevents it from being disposed. + * + * @param credential the {@code GSSCredential} to be wrapped. + * @return the wrapped {@code GSSCredential}. + */ private static GSSCredential wrapCredential(final GSSCredential credential) { return new GSSCredential() { diff --git a/mechanism/http/pom.xml b/mechanism/http/pom.xml index 452ff36091d..394a1de641d 100644 --- a/mechanism/http/pom.xml +++ b/mechanism/http/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/mechanism/http/src/main/java/org/wildfly/security/mechanism/http/UsernamePasswordAuthenticationMechanism.java b/mechanism/http/src/main/java/org/wildfly/security/mechanism/http/UsernamePasswordAuthenticationMechanism.java index 328ef1cd200..413848192c5 100644 --- a/mechanism/http/src/main/java/org/wildfly/security/mechanism/http/UsernamePasswordAuthenticationMechanism.java +++ b/mechanism/http/src/main/java/org/wildfly/security/mechanism/http/UsernamePasswordAuthenticationMechanism.java @@ -49,13 +49,24 @@ public abstract class UsernamePasswordAuthenticationMechanism implements HttpSer protected final CallbackHandler callbackHandler; /** - * @param callbackHandler + * Constructs a new {@code UsernamePasswordAuthenticationMechanism} instance. + * + * @param callbackHandler the CallbackHandler used for authentication. */ protected UsernamePasswordAuthenticationMechanism(CallbackHandler callbackHandler) { super(); this.callbackHandler = callbackHandler; } + /** + * Authenticates the user for provided realm using their username and password. + * + * @param realmName the realm for which the user is authenticating. + * @param username the username of the authenticating user. + * @param password the password of the authenticating user. + * @return {@code true} if the user is authenticated for the realm, {@code false} otherwise. + * @throws HttpAuthenticationException if there was an IOException caused by the CallbackHandler. + */ protected boolean authenticate(String realmName, String username, char[] password) throws HttpAuthenticationException { RealmCallback realmCallback = realmName != null ? new RealmCallback("User realm", realmName) : null; NameCallback nameCallback = new NameCallback("Remote Authentication Name", username); @@ -94,6 +105,13 @@ protected boolean authenticate(String realmName, String username, char[] passwor } } + /** + * Checks if the user is authorized. + * + * @param username the username to authorize. + * @return {@code true} if the user is authorized, {@code false} otherwise. + * @throws HttpAuthenticationException if there was an IOException caused by the CallbackHandler. + */ protected boolean authorize(String username) throws HttpAuthenticationException { httpUserPass.debugf("Username authorization. Username: [%s].", username); @@ -111,10 +129,22 @@ protected boolean authorize(String username) throws HttpAuthenticationException } } + /** + * Sends the information to the callbackHandler that the authorization succeeded. + * + * @throws IOException if an input or output error occurs. + * @throws UnsupportedCallbackException if the implementation of callbackHandler does not support the specified Callback type. + */ protected void succeed() throws IOException, UnsupportedCallbackException { callbackHandler.handle(new Callback[] { AuthenticationCompleteCallback.SUCCEEDED }); } + /** + * Sends the information to the callbackHandler that the authorization failed. + * + * @throws IOException if an input or output error occurs. + * @throws UnsupportedCallbackException if the implementation of callbackHandler does not support the specified Callback type. + */ protected void fail() throws IOException, UnsupportedCallbackException { callbackHandler.handle(new Callback[] { AuthenticationCompleteCallback.FAILED }); } diff --git a/mechanism/oauth2/pom.xml b/mechanism/oauth2/pom.xml index 15efe4ee541..e57683a7787 100644 --- a/mechanism/oauth2/pom.xml +++ b/mechanism/oauth2/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Client.java b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Client.java index 315b9c1ee8e..ec78a73eeaa 100644 --- a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Client.java +++ b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Client.java @@ -33,6 +33,8 @@ import static org.wildfly.common.Assert.assertTrue; /** + * Implementation of the client side of the OAuth2 SASL mechanism. + * * @author Pedro Igor */ public class OAuth2Client { @@ -43,12 +45,26 @@ public class OAuth2Client { private final String authorizationId; private ElytronMessages log; + /** + * Constructs a new {@code OAuth2Client} instance. + * + * @param authorizationId the ID of the user to be authorized. + * @param callbackHandler the callback handler for verifying the Bearer token. + * @param log the logger to use. + */ public OAuth2Client(String authorizationId, CallbackHandler callbackHandler, ElytronMessages log) { this.authorizationId = authorizationId; this.callbackHandler = callbackHandler; this.log = log; } + /** + * Gets the initial response message from the client that will be sent to the server. + * It retrieves the Bearer token from a callback and constructs an encoded message that includes the token. + * + * @return encoded message that includes the Bearer token. + * @throws AuthenticationMechanismException if an error occurs during the callback or the token is {@code null}. + */ public OAuth2InitialClientMessage getInitialResponse() throws AuthenticationMechanismException { final CredentialCallback credentialCallback = new CredentialCallback(BearerTokenCredential.class); @@ -80,13 +96,19 @@ public OAuth2InitialClientMessage getInitialResponse() throws AuthenticationMech return new OAuth2InitialClientMessage(null, null, encoded.toArray()); } + /** + * Handles the server's response to the initial client message. + * + * @param serverMessage the byte array containing the server's response. + * @return {@code null} if the response was successful, aborting the authentication otherwise. + */ public byte[] handleServerResponse(byte[] serverMessage) { // got a successful response if (serverMessage.length == 0) { return null; } - // otherwise, server responded with a error message + // otherwise, server responded with an error message try { String errorMessage = ByteIterator.ofBytes(serverMessage).asUtf8String().base64Decode().asUtf8String().drainToString(); log.debugf("Got error message from server [%s].", errorMessage); diff --git a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2InitialClientMessage.java b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2InitialClientMessage.java index f5b6d6014a4..c244b043213 100644 --- a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2InitialClientMessage.java +++ b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2InitialClientMessage.java @@ -19,6 +19,8 @@ package org.wildfly.security.mechanism.oauth2; /** + * Represents the initial client message for OAuth2 protocol. + * * @author Pedro Igor */ public class OAuth2InitialClientMessage { @@ -27,24 +29,51 @@ public class OAuth2InitialClientMessage { private final byte[] messageBytes; private final String authorizationId; + /** + * Constructs a new {@code OAuth2InitialClientMessage} instance. + * + * @param authorizationId the ID of the user to be authorized. + * @param auth the authorization information in form of a String. + * @param messageBytes the byte array containing the message. + */ public OAuth2InitialClientMessage(String authorizationId, String auth, byte[] messageBytes) { this.authorizationId = authorizationId; this.auth = auth; this.messageBytes = messageBytes; } + /** + * Returns the ID of the user to be authorized. + * + * @return the ID of the user to be authorized. + */ public String getAuthorizationId() { return this.authorizationId; } + /** + * Returns the byte array containing the message. + * + * @return the byte array containing the message. + */ public byte[] getMessage() { return this.messageBytes; } + /** + * Returns the authorization information in form of a String. + * + * @return the authorization information in form of a String. + */ public String getAuth() { return auth; } + /** + * Returns whether the client provides a Bearer token. + * + * @return {@code True} if the authorization information contains "Bearer", {@code false} otherwise. + */ public boolean isBearerToken() { return this.auth.startsWith("Bearer"); } diff --git a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Server.java b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Server.java index c1c3e52324f..306771e6fc8 100644 --- a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Server.java +++ b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Server.java @@ -40,7 +40,7 @@ import org.wildfly.security.mechanism.AuthenticationMechanismException; /** - * An OAuth2 Sasl Server based on RFC-7628. + * An OAuth2 Server based on RFC-7628. * * @author Pedro Igor */ @@ -53,12 +53,26 @@ public class OAuth2Server { private final Map serverConfig; private ElytronMessages log; + /** + * Constructs a new {@code OAuth2Server} instance. + * + * @param callbackHandler the callback handler for verifying the Bearer token. + * @param serverConfig the server configuration. + * @param log the logger to use. + */ public OAuth2Server(CallbackHandler callbackHandler, Map serverConfig, ElytronMessages log) { this.callbackHandler = callbackHandler; this.serverConfig = serverConfig; this.log = log; } + /** + * Parses the initial client's message in OAuth2 protocol. + * + * @param fromBytes the initial client's message. + * @return parsed client's message. + * @throws AuthenticationMechanismException if an error occurs during the parsing or the message is invalid. + */ public OAuth2InitialClientMessage parseInitialClientMessage(byte[] fromBytes) throws AuthenticationMechanismException { byte[] messageBytes = fromBytes.clone(); ByteIterator byteIterator = ByteIterator.ofBytes(fromBytes.clone()); @@ -98,6 +112,13 @@ public OAuth2InitialClientMessage parseInitialClientMessage(byte[] fromBytes) th } } + /** + * Returns the value associated with a key from an OAuth2 message. + * + * @param key the key for which the value is extracted. + * @param keyValuesPart the String containing key-value pairs in form of OAuth2 message. + * @return the value of the key-value pair, {@code null} if the key is not found. + */ private String getValue(String key, String keyValuesPart) { for (String current : keyValuesPart.split(KV_DELIMITER)) { String[] keyValue = current.split("="); @@ -110,6 +131,14 @@ private String getValue(String key, String keyValuesPart) { return null; } + /** + * Evaluates the initial response sent by the client and verifies if the Bearer token is valid. + * If so, authorizes the user. + * + * @param initialClientMessage the initial client's message containing the Bearer token. + * @return an empty byte array if the token was authorized, error message otherwise. + * @throws AuthenticationMechanismException if an error occurs during the evaluation or the message doesn't contain the Bearer token. + */ public byte[] evaluateInitialResponse(OAuth2InitialClientMessage initialClientMessage) throws AuthenticationMechanismException { if (initialClientMessage.isBearerToken()) { String auth = initialClientMessage.getAuth(); @@ -153,6 +182,12 @@ public byte[] evaluateInitialResponse(OAuth2InitialClientMessage initialClientMe throw log.mechInvalidClientMessage(); } + /** + * Creates an error message in the format of a json object. + * + * @return The error message containing a "status" field with the value "invalid_token" + * and an optional field "openid-configuration" with {@code CONFIG_OPENID_CONFIGURATION_URL} value. + */ private byte[] createErrorMessage() { JsonObjectBuilder objectBuilder = Json.createObjectBuilder(); diff --git a/mechanism/scram/pom.xml b/mechanism/scram/pom.xml index e771289f141..bee2afec31a 100644 --- a/mechanism/scram/pom.xml +++ b/mechanism/scram/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramClient.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramClient.java index 0531242c362..c6cb67e86b9 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramClient.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramClient.java @@ -48,6 +48,8 @@ import org.wildfly.security.sasl.util.StringPrep; /** + * A client-side implementation for the SCRAM authentication. + * * @author David M. Lloyd */ public final class ScramClient { @@ -61,6 +63,19 @@ public final class ScramClient { private final int minimumIterationCount; private final int maximumIterationCount; + /** + * Constructs a new {@code ScramClient} instance. + * + * @param mechanism the SCRAM mechanism used for the authentication. + * @param authorizationId the ID of the user to be authorized. + * @param callbackHandler the callbackHandler used for the authentication. + * @param secureRandom an optional secure RNG to use. + * @param bindingData the binding data for the "PLUS" channel binding option. + * @param bindingType the binding type for the "PLUS" channel binding option. + * @param minimumIterationCount the minimum number of iterations for password hashing. + * @param maximumIterationCount the maximum number of iterations for password hashing. + * @param providers the security providers. + */ ScramClient(final ScramMechanism mechanism, final String authorizationId, final CallbackHandler callbackHandler, final SecureRandom secureRandom, final byte[] bindingData, final String bindingType, final int minimumIterationCount, final int maximumIterationCount, final Supplier providers) { this.mechanism = mechanism; this.authorizationId = authorizationId; @@ -73,26 +88,56 @@ public final class ScramClient { this.providers = providers; } + /** + * Returns the secure RNG used for the authentication. + * + * @return the secure RNG used for the authentication. + */ Random getRandom() { return secureRandom != null ? secureRandom : ThreadLocalRandom.current(); } + /** + * Returns the SCRAM mechanism used for the authentication. + * + * @return the SCRAM mechanism used for the authentication. + */ public ScramMechanism getMechanism() { return mechanism; } + /** + * Returns the ID of the user to be authorized. + * + * @return the ID of the user to be authorized. + */ public String getAuthorizationId() { return authorizationId; } + /** + * Returns the binding type for the "PLUS" channel binding option. + * + * @return the binding type for the "PLUS" channel binding option. + */ public String getBindingType() { return bindingType; } + /** + * Returns the binding data for the "PLUS" channel binding option. + * + * @return the binding data for the "PLUS" channel binding option. + */ byte[] getRawBindingData() { return bindingData; } + /** + * Returns a copy of the binding data for the "PLUS" channel binding option. + * + * @return a copy of the binding data for the "PLUS" channel binding option. + */ public byte[] getBindingData() { final byte[] bindingData = this.bindingData; return bindingData == null ? null : bindingData.clone(); @@ -146,6 +191,15 @@ public ScramInitialClientMessage getInitialResponse() throws AuthenticationMecha return new ScramInitialClientMessage(this, name, binding, nonce, initialPartIndex, encoded.toArray()); } + /** + * Parses the initial server message and creates {@link ScramInitialServerMessage} from parsed information. + * Also checks if the message have all necessary properties. + * + * @param initialResponse the initial client response for the server. + * @param bytes the byte array containing the initial server message to parse. + * @return the initial server message. + * @throws AuthenticationMechanismException if an error occurs during the parsing. + */ public ScramInitialServerMessage parseInitialServerMessage(final ScramInitialClientMessage initialResponse, final byte[] bytes) throws AuthenticationMechanismException { final byte[] challenge = bytes.clone(); final ByteIterator bi = ByteIterator.ofBytes(challenge); @@ -190,6 +244,18 @@ public ScramInitialServerMessage parseInitialServerMessage(final ScramInitialCli return new ScramInitialServerMessage(initialResponse, serverNonce, salt, iterationCount, challenge); } + /** + * Handles the initial challenge from the server and create a response from the client. + * The method uses a password credential obtained from the callback handler to derive a salted password, + * which is then used to generate a client key, stored key, and client proof. + * + * @param initialResponse the initial client message. + * @param initialChallenge the initial server message. + * @return the final client message. + * @throws AuthenticationMechanismException if an error occurs while obtaining the password, + * creating the {@link ScramFinalClientMessage} or the mechanism in the initial response or challenge message + * does not match the mechanism expected by the server + */ public ScramFinalClientMessage handleInitialChallenge(ScramInitialClientMessage initialResponse, ScramInitialServerMessage initialChallenge) throws AuthenticationMechanismException { boolean trace = saslScram.isTraceEnabled(); @@ -288,6 +354,14 @@ public ScramFinalClientMessage handleInitialChallenge(ScramInitialClientMessage } } + /** + * Parses the final server message and creates {@link ScramFinalServerMessage} from parsed information. + * Also checks if the message have all necessary properties. + * + * @param messageBytes the byte array of the final server message. + * @return the final server message. + * @throws AuthenticationMechanismException if an error occurs during the parsing or the server rejected the authentication request. + */ public ScramFinalServerMessage parseFinalServerMessage(final byte[] messageBytes) throws AuthenticationMechanismException { final ByteIterator bi = ByteIterator.ofBytes(messageBytes); final byte[] sig; @@ -312,6 +386,13 @@ public ScramFinalServerMessage parseFinalServerMessage(final byte[] messageBytes return new ScramFinalServerMessage(sig, messageBytes); } + /** + * Verifies the final challenge received from the server. + * + * @param finalResponse the final client message. + * @param finalChallenge the final server message. + * @throws AuthenticationMechanismException if an error occurs during the verification or the server signature is invalid. + */ public void verifyFinalChallenge(final ScramFinalClientMessage finalResponse, final ScramFinalServerMessage finalChallenge) throws AuthenticationMechanismException { boolean trace = saslScram.isTraceEnabled(); diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalClientMessage.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalClientMessage.java index ed77a508121..066f1ff8bde 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalClientMessage.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalClientMessage.java @@ -21,6 +21,8 @@ import org.wildfly.security.password.interfaces.ScramDigestPassword; /** + * Final client message for the SCRAM authentication. + * * @author David M. Lloyd */ public final class ScramFinalClientMessage { @@ -32,6 +34,16 @@ public final class ScramFinalClientMessage { private final byte[] messageBytes; private final int proofOffset; + /** + * Constructs a new {@code ScramFinalClientMessage} instance. + * + * @param initialResponse the initial client message. + * @param initialChallenge the initial server message. + * @param password the password used for authentication. + * @param clientProof the client proof sent to the server. + * @param messageBytes the byte array of the message. + * @param proofOffset the proof location in the {@code messageBytes}. + */ ScramFinalClientMessage(final ScramInitialClientMessage initialResponse, final ScramInitialServerMessage initialChallenge, final ScramDigestPassword password, final byte[] clientProof, final byte[] messageBytes, final int proofOffset) { this.initialResponse = initialResponse; this.initialChallenge = initialChallenge; @@ -41,38 +53,83 @@ public final class ScramFinalClientMessage { this.proofOffset = proofOffset; } + /** + * Returns the initial client message. + * + * @return the initial client message. + */ public ScramInitialClientMessage getInitialResponse() { return initialResponse; } + /** + * Returns the initial server message. + * + * @return the initial server message. + */ public ScramInitialServerMessage getInitialChallenge() { return initialChallenge; } + /** + * Returns the password used for authentication. + * + * @return the password used for authentication. + */ public ScramDigestPassword getPassword() { return password; } + /** + * Returns the client proof sent to the server. + * + * @return the client proof sent to the server. + */ byte[] getRawClientProof() { return clientProof; } + /** + * Returns the byte array of the message. + * + * @return the byte array of the message. + */ byte[] getRawMessageBytes() { return messageBytes; } + /** + * Returns a copy of the client proof sent to the server. + * + * @return a copy of the client proof sent to the server. + */ public byte[] getClientProof() { return clientProof.clone(); } + /** + * Returns a copy of the byte array of the message. + * + * @return a copy of the byte array of the message. + */ public byte[] getMessageBytes() { return messageBytes.clone(); } + /** + * Returns the SCRAM mechanism in the initial client message. + * + * @return the SCRAM mechanism in the initial client message. + */ public ScramMechanism getMechanism() { return initialResponse.getMechanism(); } + /** + * Returns the proof location in the message. + * + * @return the proof location in the message. + */ int getProofOffset() { return proofOffset; } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalServerMessage.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalServerMessage.java index c8b77f2c922..1a4470ac7a7 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalServerMessage.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalServerMessage.java @@ -19,29 +19,57 @@ package org.wildfly.security.mechanism.scram; /** + * Final server message for the SCRAM authentication. + * * @author David M. Lloyd */ public final class ScramFinalServerMessage { private final byte[] serverSignature; private final byte[] messageBytes; + /** + * Constructs a new {@code ScramFinalServerMessage} instance. + * + * @param serverSignature the server signature sent to the client in form of the byte array. + * @param messageBytes the final server message in form of byte array. + */ ScramFinalServerMessage(final byte[] serverSignature, final byte[] messageBytes) { this.serverSignature = serverSignature; this.messageBytes = messageBytes; } + /** + * Returns the server signature sent to the client in form of the byte array. + * + * @return the server signature sent to the client in form of the byte array. + */ byte[] getRawServerSignature() { return serverSignature; } + /** + * Returns the final server message in form of byte array. + * + * @return the final server message in form of byte array. + */ byte[] getRawMessageBytes() { return messageBytes; } + /** + * Returns a copy of the server signature sent to the client in form of the byte array. + * + * @return a copy of the server signature sent to the client in form of the byte array. + */ public byte[] getServerSignature() { return serverSignature.clone(); } + /** + * Returns a copy of the final server message in form of byte array. + * + * @return a copy of the final server message in form of byte array. + */ public byte[] getMessageBytes() { return messageBytes.clone(); } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialClientMessage.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialClientMessage.java index 62c6eb212b0..e5e9fa0de15 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialClientMessage.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialClientMessage.java @@ -21,6 +21,8 @@ import java.util.Arrays; /** + * Initial client message for the SCRAM authentication. + * * @author David M. Lloyd */ public final class ScramInitialClientMessage { @@ -34,6 +36,16 @@ public final class ScramInitialClientMessage { private final int initialPartIndex; private final byte[] messageBytes; + /** + * Constructs a new {@code ScramInitialClientMessage} instance using data from the {@code scramClient}. + * + * @param scramClient the SCRAM client providing binding type and data, SCRAM mechanism and authorization ID. + * @param authenticationName the name of the user that is authenticated. + * @param binding whether the client supports channel binding. + * @param nonce a unique value generated by the client to the server. + * @param initialPartIndex index of the initial part of the message. + * @param messageBytes the byte array of the message. + */ ScramInitialClientMessage(final ScramClient scramClient, final String authenticationName, final boolean binding, final byte[] nonce, final int initialPartIndex, final byte[] messageBytes) { this.binding = binding; this.initialPartIndex = initialPartIndex; @@ -46,6 +58,19 @@ public final class ScramInitialClientMessage { this.messageBytes = messageBytes; } + /** + * Constructs a new {@code ScramInitialClientMessage} instance. + * + * @param mechanism the SCRAM mechanism used for the authentication. + * @param authorizationId the ID of the user to be authorized. + * @param authenticationName the name of the user that is authenticated. + * @param binding whether the client supports channel binding. + * @param bindingType the binding type for the "PLUS" channel binding option. + * @param bindingData the binding data for the "PLUS" channel binding option. + * @param nonce a unique value generated by the client to the server. + * @param initialPartIndex index of the initial part of the message. + * @param messageBytes the byte array of the message. + */ ScramInitialClientMessage(final ScramMechanism mechanism, final String authorizationId, final String authenticationName, final boolean binding, final String bindingType, final byte[] bindingData, final byte[] nonce, final int initialPartIndex, final byte[] messageBytes) { this.mechanism = mechanism; this.authorizationId = authorizationId; @@ -58,54 +83,119 @@ public final class ScramInitialClientMessage { this.messageBytes = messageBytes; } + /** + * Returns the SCRAM mechanism used for the authentication. + * + * @return the SCRAM mechanism used for the authentication. + */ public ScramMechanism getMechanism() { return mechanism; } + /** + * Returns the name of the user that is authenticated. + * + * @return the name of the user that is authenticated. + */ public String getAuthenticationName() { return authenticationName; } + /** + * Returns a copy of a unique value generated by the client to the server. + * + * @return a copy of a unique value generated by the client to the server. + */ public byte[] getNonce() { return nonce.clone(); } + /** + * Returns a unique value generated by the client to the server. + * + * @return a unique value generated by the client to the server. + */ byte[] getRawNonce() { return nonce; } + /** + * Returns the initial part of the message. + * + * @return the initial part of the message up to the length of {@code initialPartIndex}. + */ public byte[] getInitialPart() { return Arrays.copyOfRange(messageBytes, 0, initialPartIndex); } + /** + * Returns a copy of the byte array of the message. + * + * @return a copy of the byte array of the message. + */ public byte[] getMessageBytes() { return messageBytes.clone(); } + /** + * Returns the ID of the user to be authorized. + * + * @return the ID of the user to be authorized. + */ public String getAuthorizationId() { return authorizationId; } + /** + * Returns whether the client supports channel binding. + * + * @return {@code true} if the client supports channel binding, {@code false} otherwise. + */ public boolean isBinding() { return binding; } + /** + * Returns the binding type for the "PLUS" channel binding option. + * + * @return the binding type for the "PLUS" channel binding option. + */ public String getBindingType() { return bindingType; } + /** + * Returns a copy of the binding data for the "PLUS" channel binding option. + * + * @return a copy of the binding data for the "PLUS" channel binding option. + */ public byte[] getBindingData() { return bindingData == null ? null : bindingData.clone(); } + /** + * Returns the binding data for the "PLUS" channel binding option. + * + * @return the binding data for the "PLUS" channel binding option. + */ byte[] getRawBindingData() { return bindingData; } + /** + * Returns index of the initial part of the message. + * + * @return index of the initial part of the message. + */ int getInitialPartIndex() { return initialPartIndex; } + /** + * Returns the byte array of the message. + * + * @return the byte array of the message. + */ byte[] getRawMessageBytes() { return messageBytes; } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerMessage.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerMessage.java index fd0b0d9306b..8e5f24a78bf 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerMessage.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerMessage.java @@ -19,6 +19,8 @@ package org.wildfly.security.mechanism.scram; /** + * Initial server message for the SCRAM authentication. + * * @author David M. Lloyd */ public final class ScramInitialServerMessage { @@ -28,6 +30,15 @@ public final class ScramInitialServerMessage { private final int iterationCount; private final byte[] messageBytes; + /** + * Constructs a new {@code ScramInitialServerMessage} instance. + * + * @param initialResponse the initial client message that this initial server message is responding to. + * @param serverNonce the server generated nonce. + * @param salt the salt used for generating salted password. + * @param iterationCount the iteration count used for generating salted password. + * @param messageBytes the message in form of byte array. + */ ScramInitialServerMessage(final ScramInitialClientMessage initialResponse, final byte[] serverNonce, final byte[] salt, final int iterationCount, final byte[] messageBytes) { this.initialResponse = initialResponse; this.serverNonce = serverNonce; @@ -36,38 +47,83 @@ public final class ScramInitialServerMessage { this.messageBytes = messageBytes; } + /** + * Returns the SCRAM mechanism in the initial client message. + * + * @return the SCRAM mechanism in the initial client message. + */ public ScramMechanism getMechanism() { return initialResponse.getMechanism(); } + /** + * Returns the initial client message. + * + * @return the initial client message. + */ public ScramInitialClientMessage getInitialResponse() { return initialResponse; } + /** + * Returns a copy of the server nonce. + * + * @return a copy of the server nonce. + */ public byte[] getServerNonce() { return serverNonce.clone(); } + /** + * Returns the server nonce. + * + * @return the server nonce. + */ byte[] getRawServerNonce() { return serverNonce; } + /** + * Returns the iteration count used for generating salted password. + * + * @return the iteration count used for generating salted password. + */ public int getIterationCount() { return iterationCount; } + /** + * Returns the salt used for generating salted password. + * + * @return the salt used for generating salted password. + */ byte[] getRawSalt() { return salt; } + /** + * Returns the initial server message in form of byte array. + * + * @return the initial server message in form of byte array. + */ byte[] getRawMessageBytes() { return messageBytes; } + /** + * Returns a copy of the salt used for generating salted password. + * + * @return a copy of the salt used for generating salted password. + */ public byte[] getSalt() { return salt.clone(); } + /** + * Returns a copy of the message in form of byte array. + * + * @return a copy of the message in form of byte array. + */ public byte[] getMessageBytes() { return messageBytes.clone(); } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerResult.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerResult.java index 0c0df7332c2..07ff6d4f93c 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerResult.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerResult.java @@ -21,21 +21,39 @@ import org.wildfly.security.password.interfaces.ScramDigestPassword; /** + * A class for encapsulation of the initial SCRAM challenge and the digest password. + * * @author David M. Lloyd */ public final class ScramInitialServerResult { private final ScramInitialServerMessage scramInitialChallenge; private final ScramDigestPassword scramDigestPassword; + /** + * Constructs a new {@code ScramInitialServerResult}. + * + * @param scramInitialChallenge the SCRAM challenge message. + * @param scramDigestPassword the digest password for the SCRAM authentication. + */ ScramInitialServerResult(final ScramInitialServerMessage scramInitialChallenge, final ScramDigestPassword scramDigestPassword) { this.scramInitialChallenge = scramInitialChallenge; this.scramDigestPassword = scramDigestPassword; } + /** + * Returns the SCRAM challenge message. + * + * @return ScramInitialServerMessage + */ public ScramInitialServerMessage getScramInitialChallenge() { return scramInitialChallenge; } + /** + * Returns the digest password for the SCRAM authentication. + * + * @return ScramDigestPassword + */ public ScramDigestPassword getScramDigestPassword() { return scramDigestPassword; } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java index 7f5ad535398..80ae5e41a93 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java @@ -30,10 +30,12 @@ import org.wildfly.security.sasl.WildFlySasl; /** + * Implementation of the SCRAM authentication mechanism. + * * @author David M. Lloyd */ public final class ScramMechanism { - /** Hash size; may be less than the output size of the MD/MAC */ + // Hash size; may be less than the output size of the MD/MAC private final int hashSize; private final String messageDigestName; private final String hmacName; @@ -41,6 +43,15 @@ public final class ScramMechanism { private final String passwordAlgorithm; private final String toString; + /** + * Constructs a new {@code ScramMechanism}. + * + * @param hashSize the size of the hash of the SCRAM mechanism. + * @param messageDigestName the name of the message digest algorithm. + * @param hmacName the name of the HMAC algorithm. + * @param plus {@code true} to use the PLUS channel binding, {@code false} otherwise. + * @param passwordAlgorithm the name of the password algorithm in {@link ScramDigestPassword}. + */ private ScramMechanism(final int hashSize, final String messageDigestName, final String hmacName, final boolean plus, final String passwordAlgorithm) { this.hashSize = hashSize; this.messageDigestName = messageDigestName; @@ -75,6 +86,7 @@ private ScramMechanism(final int hashSize, final String messageDigestName, final * @param bindingCallback the optional channel binding callback result (may be {@code null}) * @param minimumIterationCount the minimum iteration count to allow * @param maximumIterationCount the maximum iteration count to allow + * @param providers the security providers. * @return the SCRAM client, or {@code null} if the client cannot be created from this mechanism variant * @throws AuthenticationMechanismException if the mechanism fails for some reason * @see WildFlySasl#SCRAM_MIN_ITERATION_COUNT @@ -94,6 +106,18 @@ public ScramClient createClient(final String authorizationId, final CallbackHand return new ScramClient(this, authorizationId, callbackHandler, secureRandom, bindingData, bindingType, minimumIterationCount, maximumIterationCount, providers); } + /** + * Create a SCRAM server for this mechanism. + * + * @param callbackHandler the callback handler (may not be {@code null}). + * @param random an optional secure random implementation to use (may be {@code null}). + * @param bindingCallback the optional channel binding callback result (may be {@code null}). + * @param minimumIterationCount the minimum iteration count to allow. + * @param maximumIterationCount the maximum iteration count to allow. + * @param providers the security providers. + * @return the SCRAM server, or {@code null} if the server cannot be created from this mechanism variant. + * @throws AuthenticationMechanismException if the mechanism fails for some reason. + */ public ScramServer createServer(final CallbackHandler callbackHandler, final SecureRandom random, final ChannelBindingCallback bindingCallback, final int minimumIterationCount, final int maximumIterationCount, final Supplier providers) throws AuthenticationMechanismException { final byte[] bindingData; final String bindingType; @@ -108,26 +132,57 @@ public ScramServer createServer(final CallbackHandler callbackHandler, final Sec return new ScramServer(this, callbackHandler, random, bindingData, bindingType, minimumIterationCount, maximumIterationCount, providers); } + /** + * Returns the size of the hash of the SCRAM mechanism. + * + * @return the size of the hash of the SCRAM mechanism. + */ public int getHashSize() { return hashSize; } + /** + * Returns the name of the message digest algorithm. + * + * @return the name of the message digest algorithm. + */ public String getMessageDigestName() { return messageDigestName; } + /** + * Returns the name of the HMAC algorithm. + * + * @return the name of the HMAC algorithm. + */ public String getHmacName() { return hmacName; } + /** + * Returns whether the SCRAM mechanism uses the PLUS channel binding. + * + * @return {@code true} to use the PLUS channel binding, {@code false} otherwise. + */ public boolean isPlus() { return plus; } + /** + * Returns the name of the password algorithm from {@code ScramDigestPassword}. + * + * @return the name of the password algorithm. + */ public String getPasswordAlgorithm() { return passwordAlgorithm; } + /** + * Returns a String representation of the SCRAM mechanism. + * Contains the Digest name, PLUS channel binding and hash size. + * + * @return a String representation of the SCRAM mechanism. + */ public String toString() { return toString; } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramServer.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramServer.java index ab7360b9b50..2189a52bdb8 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramServer.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramServer.java @@ -55,6 +55,8 @@ import org.wildfly.security.sasl.util.StringPrep; /** + * A server-side implementation for the SCRAM authentication. + * * @author David M. Lloyd */ public final class ScramServer { @@ -67,6 +69,18 @@ public final class ScramServer { private final int minimumIterationCount; private final int maximumIterationCount; + /** + * Constructs a new {@code ScramServer}. + * + * @param mechanism the SCRAM mechanism used for the authentication. + * @param callbackHandler the callback handler for the authentication. + * @param random an optional secure RNG to use. + * @param bindingData the binding data for the "PLUS" channel binding option. + * @param bindingType the binding type for the "PLUS" channel binding option. + * @param minimumIterationCount the minimum number of iterations for password hashing. + * @param maximumIterationCount the maximum number of iterations for password hashing. + * @param providers the security providers. + */ ScramServer(final ScramMechanism mechanism, final CallbackHandler callbackHandler, final SecureRandom random, final byte[] bindingData, final String bindingType, final int minimumIterationCount, final int maximumIterationCount, final Supplier providers) { this.mechanism = mechanism; this.callbackHandler = callbackHandler; @@ -185,6 +199,14 @@ public ScramInitialClientMessage parseInitialClientMessage(ChannelBindingCallbac } } + /** + * Evaluates the initial client response message in SCRAM authentication. + * Generates a server nonce and salted password. + * + * @param clientMessage the initial client response message. + * @return the initial server result, containing the initial server message and the digest password. + * @throws AuthenticationMechanismException if an error occurs during the evaluation. + */ public ScramInitialServerResult evaluateInitialResponse(final ScramInitialClientMessage clientMessage) throws AuthenticationMechanismException { final boolean trace = saslScram.isTraceEnabled(); @@ -240,6 +262,16 @@ public ScramInitialServerResult evaluateInitialResponse(final ScramInitialClient return new ScramInitialServerResult(new ScramInitialServerMessage(clientMessage, serverNonce, salt, iterationCount, messageBytes), password); } + /** + * Parses the final client message and constructs the {@link ScramFinalClientMessage} from this parsed information. + * Also checks if the message has all necessary properties. + * + * @param initialResponse the initial client response message provided by {@link ScramServer#parseInitialClientMessage(ChannelBindingCallback, byte[])}. + * @param initialResult the initial server result provided by {@link ScramServer#evaluateInitialResponse(ScramInitialClientMessage)}. + * @param bytes the byte array representation of the client response. + * @return the final client message. + * @throws AuthenticationMechanismException if an error occurs during the parsing. + */ public ScramFinalClientMessage parseFinalClientMessage(final ScramInitialClientMessage initialResponse, final ScramInitialServerResult initialResult, final byte[] bytes) throws AuthenticationMechanismException { final ScramInitialServerMessage initialChallenge = initialResult.getScramInitialChallenge(); Assert.checkNotNullParam("initialResponse", initialResponse); @@ -352,6 +384,14 @@ public ScramFinalClientMessage parseFinalClientMessage(final ScramInitialClientM } } + /** + * Evaluates a SCRAM final client message and authorizes the user. + * + * @param initialResult the result of the initial server message evaluation provided by {@link ScramServer#evaluateInitialResponse(ScramInitialClientMessage)}. + * @param clientMessage the final client message provided by {@link ScramServer#parseFinalClientMessage(ScramInitialClientMessage, ScramInitialServerResult, byte[])}. + * @return the final server message providing the server signature and response. + * @throws AuthenticationMechanismException if an error occurs during the evaluation. + */ public ScramFinalServerMessage evaluateFinalClientMessage(final ScramInitialServerResult initialResult, final ScramFinalClientMessage clientMessage) throws AuthenticationMechanismException { final boolean trace = saslScram.isTraceEnabled(); @@ -464,26 +504,56 @@ public ScramFinalServerMessage evaluateFinalClientMessage(final ScramInitialServ } } + /** + * Returns the SCRAM mechanism used for the authentication. + * + * @return the SCRAM mechanism used for the authentication. + */ public ScramMechanism getMechanism() { return mechanism; } + /** + * Returns the callback handler for the authentication. + * + * @return the callback handler for the authentication. + */ public CallbackHandler getCallbackHandler() { return callbackHandler; } + /** + * Returns the RNG used for the authentication. + * + * @return the RNG used for the authentication. + */ Random getRandom() { return random != null ? random : ThreadLocalRandom.current(); } + /** + * Returns the copy of the binding data for the "PLUS" channel binding option. + * + * @return the copy of the binding data for the "PLUS" channel binding option. + */ public byte[] getBindingData() { return bindingData == null ? null : bindingData.clone(); } + /** + * Returns the binding data for the "PLUS" channel binding option. + * + * @return the binding data for the "PLUS" channel binding option. + */ byte[] getRawBindingData() { return bindingData; } + /** + * Returns the binding type for the "PLUS" channel binding option. + * + * @return the binding type for the "PLUS" channel binding option. + */ public String getBindingType() { return bindingType; } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramUtil.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramUtil.java index dacdf27579a..6180f70e28f 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramUtil.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramUtil.java @@ -26,6 +26,8 @@ import org.wildfly.common.iteration.ByteIterator; /** + * Common utility functions used by SCRAM authentication mechanism. + * * @author David M. Lloyd */ class ScramUtil { @@ -47,6 +49,13 @@ class ScramUtil { randomCharDictionary = dict; } + /** + * Generates nonce of specified length. + * + * @param length the length of the nonce. + * @param random the RNG used for creating the nonce. + * @return a byte array containing the nonce. + */ public static byte[] generateNonce(int length, Random random) { final byte[] chars = new byte[length]; for (int i = 0; i < length; i ++) { @@ -55,6 +64,13 @@ public static byte[] generateNonce(int length, Random random) { return chars; } + /** + * Parses positive integer from provided ByteIterator. + * + * @param i the ByteIterator to parse the positive integer from. + * @return the parsed integer. + * @throws NumberFormatException if the ByteIterator doesn't contain number or the number is too big for an integer + */ public static int parsePosInt(final ByteIterator i) { int a, c; if (! i.hasNext()) { @@ -80,6 +96,13 @@ public static int parsePosInt(final ByteIterator i) { return a; } + /** + * Bitwise XOR operation between two byte arrays of the same length. + * XOR operation returns 1 if only one of two corresponding bits is 1. For example: 0101 and 0011 gives 0110. + * + * @param hash the first byte array for the XOR operation. This byte array is modified by the method in place + * @param input the second byte array for the XOR operation. + */ static void xor(final byte[] hash, final byte[] input) { assert hash.length == input.length; for (int i = 0; i < hash.length; i++) { diff --git a/password/impl/pom.xml b/password/impl/pom.xml index 3730706429b..2e71afafcf7 100644 --- a/password/impl/pom.xml +++ b/password/impl/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/password/impl/src/main/java/org/wildfly/security/password/impl/BSDUnixDESCryptPasswordImpl.java b/password/impl/src/main/java/org/wildfly/security/password/impl/BSDUnixDESCryptPasswordImpl.java index cb918d2ebe6..c7fb6a44b5b 100644 --- a/password/impl/src/main/java/org/wildfly/security/password/impl/BSDUnixDESCryptPasswordImpl.java +++ b/password/impl/src/main/java/org/wildfly/security/password/impl/BSDUnixDESCryptPasswordImpl.java @@ -30,7 +30,6 @@ import java.security.spec.InvalidParameterSpecException; import java.security.spec.KeySpec; import java.util.Arrays; -import java.util.concurrent.ThreadLocalRandom; import org.wildfly.security.password.interfaces.BSDUnixDESCryptPassword; import org.wildfly.security.password.spec.ClearPasswordSpec; @@ -72,11 +71,11 @@ class BSDUnixDESCryptPasswordImpl extends AbstractPasswordImpl implements BSDUni } BSDUnixDESCryptPasswordImpl(final ClearPasswordSpec passwordSpec) throws InvalidKeySpecException { - this(passwordSpec.getEncodedPassword(), ThreadLocalRandom.current().nextInt() & 0xffffff, DEFAULT_ITERATION_COUNT); + this(passwordSpec.getEncodedPassword(), PasswordUtil.generateRandomSaltInt() & 0xffffff, DEFAULT_ITERATION_COUNT); } BSDUnixDESCryptPasswordImpl(final char[] password, final Charset hashCharset) throws InvalidKeySpecException, InvalidParameterSpecException { - this(password, ThreadLocalRandom.current().nextInt() & 0xffffff, DEFAULT_ITERATION_COUNT, hashCharset); + this(password, PasswordUtil.generateRandomSaltInt() & 0xffffff, DEFAULT_ITERATION_COUNT, hashCharset); } BSDUnixDESCryptPasswordImpl(final char[] password, final IteratedSaltedPasswordAlgorithmSpec spec, final Charset hashCharset) throws InvalidKeySpecException, InvalidParameterSpecException { @@ -84,7 +83,7 @@ class BSDUnixDESCryptPasswordImpl extends AbstractPasswordImpl implements BSDUni } BSDUnixDESCryptPasswordImpl(final char[] password, final IteratedPasswordAlgorithmSpec spec, final Charset hashCharset) throws InvalidKeySpecException, InvalidParameterSpecException { - this(password, ThreadLocalRandom.current().nextInt() & 0xffffff, spec.getIterationCount(), hashCharset); + this(password, PasswordUtil.generateRandomSaltInt() & 0xffffff, spec.getIterationCount(), hashCharset); } BSDUnixDESCryptPasswordImpl(final char[] password, final SaltedPasswordAlgorithmSpec spec, final Charset hashCharset) throws InvalidKeySpecException, InvalidParameterSpecException { diff --git a/password/impl/src/main/java/org/wildfly/security/password/impl/PasswordUtil.java b/password/impl/src/main/java/org/wildfly/security/password/impl/PasswordUtil.java index 21e1f8a58f1..9327f4b5ac0 100644 --- a/password/impl/src/main/java/org/wildfly/security/password/impl/PasswordUtil.java +++ b/password/impl/src/main/java/org/wildfly/security/password/impl/PasswordUtil.java @@ -17,7 +17,7 @@ */ package org.wildfly.security.password.impl; -import java.util.concurrent.ThreadLocalRandom; +import org.wildfly.common.Assert; /** * Helper utility methods for operations on passwords. @@ -27,6 +27,8 @@ */ final class PasswordUtil { + private static final ThreadLocalSecureRandom THREAD_LOCAL_SECURE_RANDOM = new ThreadLocalSecureRandom(); + /** * Generate a random salt as byte array. * @@ -35,7 +37,22 @@ final class PasswordUtil { */ public static byte[] generateRandomSalt(int saltSize) { byte[] randomSalt = new byte[saltSize]; - ThreadLocalRandom.current().nextBytes(randomSalt); + THREAD_LOCAL_SECURE_RANDOM.get().nextBytes(randomSalt); return randomSalt; } + + /** + * Generate a random salt as int. + * + * @return a byte array representing the random salt + */ + static int generateRandomSaltInt() { + byte[] saltBytes = generateRandomSalt(4); + return convertBytesToInt(saltBytes); + } + + static int convertBytesToInt(byte[] saltBytes) { + Assert.assertTrue(saltBytes.length == 4); + return (saltBytes[0] & 0xff) << 24 | (saltBytes[1] & 0xff) << 16 | (saltBytes[2] & 0xff) << 8 | saltBytes[3] & 0xff; + } } diff --git a/password/impl/src/main/java/org/wildfly/security/password/impl/ThreadLocalSecureRandom.java b/password/impl/src/main/java/org/wildfly/security/password/impl/ThreadLocalSecureRandom.java new file mode 100644 index 00000000000..5a99da754e8 --- /dev/null +++ b/password/impl/src/main/java/org/wildfly/security/password/impl/ThreadLocalSecureRandom.java @@ -0,0 +1,32 @@ +/* + * JBoss, Home of Professional Open Source + * + * Copyright 2024 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.password.impl; + +import java.security.SecureRandom; +import java.util.function.Supplier; + +class ThreadLocalSecureRandom implements Supplier { + final ThreadLocal localInstance = new ThreadLocal<>(); + + public SecureRandom get() { + if (localInstance.get() == null) { + localInstance.set(new SecureRandom()); + } + return localInstance.get(); + } +} diff --git a/password/impl/src/main/java/org/wildfly/security/password/impl/UnixDESCryptPasswordImpl.java b/password/impl/src/main/java/org/wildfly/security/password/impl/UnixDESCryptPasswordImpl.java index 00ad94110da..267dfd70200 100644 --- a/password/impl/src/main/java/org/wildfly/security/password/impl/UnixDESCryptPasswordImpl.java +++ b/password/impl/src/main/java/org/wildfly/security/password/impl/UnixDESCryptPasswordImpl.java @@ -31,7 +31,6 @@ import java.security.spec.InvalidParameterSpecException; import java.security.spec.KeySpec; import java.util.Arrays; -import java.util.concurrent.ThreadLocalRandom; import org.wildfly.security.password.interfaces.UnixDESCryptPassword; import org.wildfly.security.password.spec.ClearPasswordSpec; @@ -68,11 +67,11 @@ class UnixDESCryptPasswordImpl extends AbstractPasswordImpl implements UnixDESCr } UnixDESCryptPasswordImpl(final ClearPasswordSpec spec) throws InvalidKeySpecException, InvalidKeyException { - this((short) (ThreadLocalRandom.current().nextInt() & 0xfff), spec.getEncodedPassword()); + this((short) (PasswordUtil.generateRandomSaltInt() & 0xfff), spec.getEncodedPassword()); } UnixDESCryptPasswordImpl(final char[] passwordChars, final Charset hashCharset) throws InvalidKeyException { - this((short) (ThreadLocalRandom.current().nextInt() & 0xfff), passwordChars, hashCharset); + this((short) (PasswordUtil.generateRandomSaltInt() & 0xfff), passwordChars, hashCharset); } UnixDESCryptPasswordImpl(final char[] passwordChars, SaltedPasswordAlgorithmSpec algorithmSpec, final Charset hashCharset) throws InvalidParameterSpecException, InvalidKeyException { diff --git a/password/impl/src/test/java/org/wildfly/security/password/impl/PasswordUtilTest.java b/password/impl/src/test/java/org/wildfly/security/password/impl/PasswordUtilTest.java new file mode 100644 index 00000000000..ace17e04677 --- /dev/null +++ b/password/impl/src/test/java/org/wildfly/security/password/impl/PasswordUtilTest.java @@ -0,0 +1,32 @@ +/* + * JBoss, Home of Professional Open Source + * + * Copyright 2024 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.password.impl; + +import org.junit.Assert; +import org.junit.Test; + +public class PasswordUtilTest { + + @Test + public void testConvertBytesToInt() { + Assert.assertEquals(0, PasswordUtil.convertBytesToInt(new byte[] {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00})); + Assert.assertEquals(Integer.MAX_VALUE, PasswordUtil.convertBytesToInt(new byte[] {(byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff})); + Assert.assertEquals(Integer.MIN_VALUE, PasswordUtil.convertBytesToInt(new byte[] {(byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00})); + Assert.assertEquals(-1, PasswordUtil.convertBytesToInt(new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff})); + } +} diff --git a/password/impl/src/test/java/org/wildfly/security/password/impl/SunUnixMD5CryptTest.java b/password/impl/src/test/java/org/wildfly/security/password/impl/SunUnixMD5CryptTest.java index 53306441384..b7a306f85e0 100644 --- a/password/impl/src/test/java/org/wildfly/security/password/impl/SunUnixMD5CryptTest.java +++ b/password/impl/src/test/java/org/wildfly/security/password/impl/SunUnixMD5CryptTest.java @@ -24,7 +24,6 @@ import static org.junit.Assert.assertTrue; import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import org.junit.Test; @@ -43,42 +42,31 @@ public class SunUnixMD5CryptTest { @Test - public void testParseCryptStringWithoutRounds() throws NoSuchAlgorithmException, InvalidKeySpecException { - String cryptString = "$md5$zrdhpMlZ$$wBvMOEqbSjU.hu5T2VEP01"; - - // Get the spec by parsing the crypt string - SunUnixMD5CryptPassword password = (SunUnixMD5CryptPassword) ModularCrypt.decode(cryptString); - assertEquals(0, password.getIterationCount()); - - // Use the spec to build a new crypt string and compare it to the original - assertEquals(cryptString, ModularCrypt.encodeAsString(password)); + public void testParseCryptStringWithoutRounds() throws InvalidKeySpecException { + testParseCryptString("$md5$zrdhpMlZ$$wBvMOEqbSjU.hu5T2VEP01", 0); } @Test - public void testParseCryptStringWithRounds() throws NoSuchAlgorithmException, InvalidKeySpecException { - String cryptString = "$md5,rounds=1000$saltstring$$1wGsmnKgDGdu03LxKu0VI1"; - - // Get the spec by parsing the crypt string - SunUnixMD5CryptPassword password = (SunUnixMD5CryptPassword) ModularCrypt.decode(cryptString); - assertEquals(1_000, password.getIterationCount()); - - // Use the spec to build a new crypt string and compare it to the original - assertEquals(cryptString, ModularCrypt.encodeAsString(password)); + public void testParseCryptStringWithRounds() throws InvalidKeySpecException { + testParseCryptString("$md5,rounds=1000$saltstring$$1wGsmnKgDGdu03LxKu0VI1", 1_000); } @Test - public void testParseCryptStringWithBareSalt() throws NoSuchAlgorithmException, InvalidKeySpecException { - String cryptString = "$md5,rounds=1500$saltstring$F9DNxgHVXWaeLS9zUaWXd."; + public void testParseCryptStringWithBareSalt() throws InvalidKeySpecException { + testParseCryptString("$md5,rounds=1500$saltstring$F9DNxgHVXWaeLS9zUaWXd.", 1_500); + } - // Get the spec by parsing the crypt string + private static void testParseCryptString(String cryptString, int iterCount) throws InvalidKeySpecException { SunUnixMD5CryptPassword password = (SunUnixMD5CryptPassword) ModularCrypt.decode(cryptString); - assertEquals(1_500, password.getIterationCount()); + assertEquals(iterCount, password.getIterationCount()); // Use the spec to build a new crypt string and compare it to the original assertEquals(cryptString, ModularCrypt.encodeAsString(password)); + } - private void generateAndVerify(String cryptString, String correctPassword) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + + private static void generateAndVerify(String cryptString, String correctPassword) throws InvalidKeyException, InvalidKeySpecException { final PasswordFactorySpiImpl spi = new PasswordFactorySpiImpl(); SunUnixMD5CryptPassword password = (SunUnixMD5CryptPassword) ModularCrypt.decode(cryptString); final String algorithm = password.getAlgorithm(); @@ -100,49 +88,49 @@ private void generateAndVerify(String cryptString, String correctPassword) throw } @Test - public void testHashEmptyPassword() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + public void testHashEmptyPassword() throws InvalidKeyException, InvalidKeySpecException { String password = ""; String cryptString = "$md5,rounds=10000$saltstring$$uwcsteApj7mCi4AIwYIT5."; generateAndVerify(cryptString, password); } @Test - public void testHashEmptyPasswordWithBareSalt() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + public void testHashEmptyPasswordWithBareSalt() throws InvalidKeyException, InvalidKeySpecException { String password = ""; String cryptString = "$md5,rounds=10000$saltstring$gWOS3RRZtQ5TiYRg.vBx40"; generateAndVerify(cryptString, password); } @Test - public void testHashShortPassword() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + public void testHashShortPassword() throws InvalidKeyException, InvalidKeySpecException { String password = "Hello world!"; String cryptString = "$md5$saltstringsalt$$MsEJKkfiaflU4ioBHkqWe0"; generateAndVerify(cryptString, password); } @Test - public void testHashShortPasswordWithBareSalt() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + public void testHashShortPasswordWithBareSalt() throws InvalidKeyException, InvalidKeySpecException { String password = "Hello world!"; String cryptString = "$md5$saltstringsalt$uOXM5LLS7ZtN3eYYS54sM/"; generateAndVerify(cryptString, password); } @Test - public void testHashLongPassword() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + public void testHashLongPassword() throws InvalidKeyException, InvalidKeySpecException { String password = "This is a very very very long password! This is the 2nd sentence in THE password. This is a test.@$%"; String cryptString = "$md5,rounds=10000$saltstringsaltstring$$Occfaf7BttKIkRRUARiWU0"; generateAndVerify(cryptString, password); } @Test - public void testHashLongPasswordWithBareSalt() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + public void testHashLongPasswordWithBareSalt() throws InvalidKeyException, InvalidKeySpecException { String password = "This is a very very very long password! This is the 2nd sentence in THE password. This is a test.@$%"; String cryptString = "$md5,rounds=10000$saltstringsaltstring$0xbVBdJfPIual8oRvkU/f."; generateAndVerify(cryptString, password); } @Test - public void testKnownCryptStrings() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + public void testKnownCryptStrings() throws InvalidKeyException, InvalidKeySpecException { // Crypt string with bare salt generateAndVerify("$md5$RPgLF6IJ$WTvAlUJ7MqH5xak2FMEwS/", "passwd"); diff --git a/permission/pom.xml b/permission/pom.xml index d089d44f12d..de8707d9c43 100644 --- a/permission/pom.xml +++ b/permission/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT 4.0.0 diff --git a/permission/src/main/java/org/wildfly/security/permission/IntNameSetPermissionCollection.java b/permission/src/main/java/org/wildfly/security/permission/IntNameSetPermissionCollection.java index 81653a35ae2..1174e427149 100644 --- a/permission/src/main/java/org/wildfly/security/permission/IntNameSetPermissionCollection.java +++ b/permission/src/main/java/org/wildfly/security/permission/IntNameSetPermissionCollection.java @@ -33,10 +33,6 @@ final class IntNameSetPermissionCollection extends NameSetPermissionCollection { super(sourcePermission, nameEnumeration); } - private Permission permissionFor(int id) { - return ((AbstractNamedPermission)getSourcePermission()).withName(getNameEnumeration().nameOf(id)); - } - protected void doAdd(final AbstractPermission permission) { int setBits= getBitsForName(permission); final AtomicInteger bitSet = this.bitSet; @@ -113,6 +109,10 @@ public boolean hasNext() { public Permission next() { return nextElement(); } + + private Permission permissionFor(int id) { + return ((AbstractNamedPermission)getSourcePermission()).withName(getNameEnumeration().nameOf(id)); + } } } diff --git a/permission/src/main/java/org/wildfly/security/permission/LongNameSetPermissionCollection.java b/permission/src/main/java/org/wildfly/security/permission/LongNameSetPermissionCollection.java index 3ef54a65549..4ebc3469911 100644 --- a/permission/src/main/java/org/wildfly/security/permission/LongNameSetPermissionCollection.java +++ b/permission/src/main/java/org/wildfly/security/permission/LongNameSetPermissionCollection.java @@ -33,10 +33,6 @@ final class LongNameSetPermissionCollection extends NameSetPermissionCollection super(sourcePermission, nameEnumeration); } - private Permission permissionFor(int id) { - return ((AbstractNamedPermission)getSourcePermission()).withName(getNameEnumeration().nameOf(id)); - } - protected void doAdd(final AbstractPermission permission) { long setBits = getBitsForName(permission); final AtomicLong bitSet = this.bitSet; @@ -113,6 +109,10 @@ public boolean hasNext() { public Permission next() { return nextElement(); } + + private Permission permissionFor(int id) { + return ((AbstractNamedPermission)getSourcePermission()).withName(getNameEnumeration().nameOf(id)); + } } } diff --git a/permission/src/main/java/org/wildfly/security/permission/PermissionActions.java b/permission/src/main/java/org/wildfly/security/permission/PermissionActions.java index a45c5fdad88..f4aa390da7c 100644 --- a/permission/src/main/java/org/wildfly/security/permission/PermissionActions.java +++ b/permission/src/main/java/org/wildfly/security/permission/PermissionActions.java @@ -38,7 +38,7 @@ * * @author David M. Lloyd * - * @deprecated Use one of the abstract permission classes like {@link AbstractActionSetPermission} instead. + * @deprecated Use one of the abstract permission classes like {@link org.wildfly.security.permission.AbstractActionSetPermission org.wildfly.security.permission.AbstractActionSetPermission} instead. */ @Deprecated public final class PermissionActions { diff --git a/pom.xml b/pom.xml index 2978619fac8..1c3d9b88295 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT pom WildFly Elytron Parent @@ -56,48 +56,53 @@ 11 - 2.15.2 + 3.7.0 + 2.17.0 ${version.com.fasterxml.jackson} - 1.4 - 2.0.2 - 3.8.1 - 2.0.0-M24 - 1.0.0 + 1.6.0 + 4.0.1 + 3.15.0 + 2.0.0.AM27 + 2.1.6 + 2.0.3 2.0.0-M3 1.0.0-M8 1.67 - 2.9.2 - 4.5.13 - 4.4.15 - 3.4.3.Final - 2.1.18.Final + 2.12.0 + 4.5.14 + 4.4.16 + 1.1.5 + 3.5.3.Final + 2.1.19.Final 1.1.6.Final 2.2.1.Final - 1.9.2.Final - 1.0.4.GA - 2.0.0 + 1.12.2.Final + 1.2.0.Final + 2.1.2 5.0.0 - 2.4.0.Final - 1.7 + 3.5.1.Final + 1.11 4.13.1 1.34 - 2.4.0 - 2.0.0 + 2.7.1 2.4.9 - 8.2.1 + 9.37.3 3.8.1 1.0.8.Final 1.0.1.Final - 1.5.4.Final + 1.6.0.Final 2.7 5.4.1 3.0.0 - 0.9.3 + 0.9.6 1.15.3 - 18.0.2 - 4.3.3 + 25.0.2 + 5.5.0 2.40.0 2.3.0 + 23.0.7 + 2.7 + 3.8.16.Final INFO @@ -253,11 +258,119 @@ maven-javadoc-plugin + ${version-javadoc-plugin} + + + org.wildfly.common + wildfly-common + ${version.org.wildfly.common} + + + org.jboss.logging + jboss-logging + ${version.org.jboss.logging} + + + org.jboss.logging + jboss-logging-annotations + ${version.org.jboss.logging.tools} + + + org.jboss.logmanager + jboss-logmanager + ${version.org.jboss.logmanager} + + + org.wildfly.security + wildfly-elytron-credential-source-impl + ${project.version} + + + org.wildfly.security + wildfly-elytron-ssh-util + ${project.version} + + + org.wildfly.client + wildfly-client-config + ${version.org.wildfly.client.config} + + + org.wildfly.security + wildfly-elytron-provider-util + ${project.version} + + + org.kohsuke.metainf-services + metainf-services + ${version.org.kohsuke.metainf-services.metainf-services} + + + org.apache.httpcomponents + httpcore + ${version.org.apache.httpcomponents.httpcore} + + + org.apache.httpcomponents + httpclient + ${version.org.apache.httpcomponents.httpclient} + + + org.bitbucket.b_c + jose4j + ${version.org.bitbucket.b_c.jose4j} + + + com.fasterxml.jackson.core + jackson-databind + ${version.com.fasterxml.jackson.databind} + + + com.fasterxml.jackson.core + jackson-annotations + ${version.com.fasterxml.jackson} + + + com.fasterxml.jackson.core + jackson-core + ${version.com.fasterxml.jackson} + + + jakarta.servlet + jakarta.servlet-api + ${version.jakarta.servlet.jakarta-servlet-api} + + + jakarta.json + jakarta.json-api + ${version.jakarta.json.jakarta-json-api} + + + commons-cli + commons-cli + ${version.commons-cli} + + + org.apache.commons + commons-lang3 + ${version.org.apache.commons} + + + org.apache.sshd + sshd-common + ${version.org.apache.sshd.common} + + + org.jboss.modules + jboss-modules + ${version.org.jboss.modules} + + true none protected - 8 + 11 ${project.basedir}/asn1/src/main/java/; ${project.basedir}/audit/src/main/java/; @@ -521,6 +634,11 @@ wildfly-elytron-digest ${project.version} + + org.wildfly.security + wildfly-elytron-dynamic-ssl + ${project.version} + org.wildfly.security wildfly-elytron-encryption @@ -994,6 +1112,11 @@ jose4j ${version.org.bitbucket.b_c.jose4j} + + org.aesh + aesh + ${version.org.aesh} + @@ -1156,9 +1285,9 @@ - org.glassfish + org.eclipse.parsson jakarta.json - ${version.org.glassfish.jakarta.json} + ${version.org.eclipse.parsson.jakarta.json} test @@ -1381,6 +1510,7 @@ credential/source/impl digest encryption + dynamic-ssl http/base http/basic http/bearer diff --git a/provider/util/pom.xml b/provider/util/pom.xml index dba50f1d05e..75d25211809 100644 --- a/provider/util/pom.xml +++ b/provider/util/pom.xml @@ -23,7 +23,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/anonymous/pom.xml b/sasl/anonymous/pom.xml index 81d019db7f7..187cd392425 100644 --- a/sasl/anonymous/pom.xml +++ b/sasl/anonymous/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/auth/util/pom.xml b/sasl/auth/util/pom.xml index 1c76594a76c..b3464240776 100644 --- a/sasl/auth/util/pom.xml +++ b/sasl/auth/util/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml diff --git a/sasl/base/pom.xml b/sasl/base/pom.xml index d54737a9f17..c69df220d9f 100644 --- a/sasl/base/pom.xml +++ b/sasl/base/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/base/src/main/java/org/wildfly/security/sasl/util/SaslMechanismInformation.java b/sasl/base/src/main/java/org/wildfly/security/sasl/util/SaslMechanismInformation.java index 7eb7361b639..62a8d01aad2 100644 --- a/sasl/base/src/main/java/org/wildfly/security/sasl/util/SaslMechanismInformation.java +++ b/sasl/base/src/main/java/org/wildfly/security/sasl/util/SaslMechanismInformation.java @@ -55,6 +55,9 @@ */ public final class SaslMechanismInformation { + /** + * The class providing Sasl Mechanism Names. + */ public static final class Names { public static final String CRAM_MD5 = "CRAM-MD5"; public static final String DIGEST_MD5 = "DIGEST-MD5"; diff --git a/sasl/base/src/main/java/org/wildfly/security/sasl/util/UsernamePasswordHashUtil.java b/sasl/base/src/main/java/org/wildfly/security/sasl/util/UsernamePasswordHashUtil.java index aa1a8e43403..d118dfbfb1e 100644 --- a/sasl/base/src/main/java/org/wildfly/security/sasl/util/UsernamePasswordHashUtil.java +++ b/sasl/base/src/main/java/org/wildfly/security/sasl/util/UsernamePasswordHashUtil.java @@ -34,7 +34,7 @@ * * @author Darran Lofthouse * - * @deprecated Use {@link org.wildfly.security.password.PasswordFactory} instead. + * @deprecated Use {@link org.wildfly.security.password.PasswordFactory org.wildfly.security.password.PasswordFactory} instead. */ @Deprecated public class UsernamePasswordHashUtil { diff --git a/sasl/deprecated/pom.xml b/sasl/deprecated/pom.xml index 9b8d3ecb715..670e9617d71 100644 --- a/sasl/deprecated/pom.xml +++ b/sasl/deprecated/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClient.java b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClient.java index b7f54b4501f..6c98492af3f 100644 --- a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClient.java +++ b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClient.java @@ -28,7 +28,7 @@ * of the authentication process. * * @author David M. Lloyd - * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslClient} instead. + * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslClient org.wildfly.security.sasl.auth.util.AuthenticationContextSaslClient} instead. */ @Deprecated public final class AuthenticationContextSaslClient extends AbstractDelegatingSaslClient { diff --git a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClientFactory.java b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClientFactory.java index 9afa9d31cfb..4b4a4980a2b 100644 --- a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClientFactory.java +++ b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClientFactory.java @@ -33,7 +33,7 @@ * of the authentication process. * * @author David M. Lloyd - * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslClientFactory} instead. + * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslClientFactory org.wildfly.security.sasl.auth.util.AuthenticationContextSaslClientFactory} instead. */ @Deprecated public final class AuthenticationContextSaslClientFactory extends AbstractDelegatingSaslClientFactory { diff --git a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServer.java b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServer.java index 0ad16526b80..9daf5422ac2 100644 --- a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServer.java +++ b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServer.java @@ -29,7 +29,7 @@ * of the authentication process. * * @author David M. Lloyd - * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslServer} instead. + * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslServer org.wildfly.security.sasl.auth.util.AuthenticationContextSaslServer} instead. */ @Deprecated public final class AuthenticationContextSaslServer extends AbstractDelegatingSaslServer { diff --git a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServerFactory.java b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServerFactory.java index 4c567577b51..e3eb37f4972 100644 --- a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServerFactory.java +++ b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServerFactory.java @@ -32,7 +32,7 @@ * of the authentication process. * * @author David M. Lloyd - * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslServerFactory} instead. + * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslServerFactory org.wildfly.security.sasl.auth.util.AuthenticationContextSaslServerFactory} instead. */ @Deprecated public final class AuthenticationContextSaslServerFactory extends AbstractDelegatingSaslServerFactory { diff --git a/sasl/digest/pom.xml b/sasl/digest/pom.xml index 7dcb935ce6d..9b8ab397d77 100644 --- a/sasl/digest/pom.xml +++ b/sasl/digest/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/digest/src/main/java/org/wildfly/security/sasl/digest/AbstractDigestMechanism.java b/sasl/digest/src/main/java/org/wildfly/security/sasl/digest/AbstractDigestMechanism.java index 7a128580188..61b05cf0aa6 100644 --- a/sasl/digest/src/main/java/org/wildfly/security/sasl/digest/AbstractDigestMechanism.java +++ b/sasl/digest/src/main/java/org/wildfly/security/sasl/digest/AbstractDigestMechanism.java @@ -73,6 +73,8 @@ public enum FORMAT {CLIENT, SERVER} public static final int DEFAULT_MAXBUF = 65536; public static final char DELIMITER = ','; public static final String[] CIPHER_OPTS = {"des", "3des", "rc4", "rc4-40", "rc4-56"}; + private static final String CLIENT_MAGIC_INTEGRITY = "Digest session key to client-to-server signing key magic constant"; + private static final String SERVER_MAGIC_INTEGRITY = "Digest session key to server-to-client signing key magic constant"; private FORMAT format; protected final String digestURI; @@ -217,9 +219,9 @@ protected DigestWrapper(boolean confidential) { @Override public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException { if (confidential) { - return AbstractDigestMechanism.this.wrapConfidentialityProtectedMessage(outgoing, offset, len); + return wrapConfidentialityProtectedMessage(outgoing, offset, len); } else { - return AbstractDigestMechanism.this.wrapIntegrityProtectedMessage(outgoing, offset, len); + return wrapIntegrityProtectedMessage(outgoing, offset, len); } } @@ -229,152 +231,148 @@ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException { @Override public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException { if (confidential) { - return AbstractDigestMechanism.this.unwrapConfidentialityProtectedMessage(incoming, offset, len); + return unwrapConfidentialityProtectedMessage(incoming, offset, len); } else { - return AbstractDigestMechanism.this.unwrapIntegrityProtectedMessage(incoming, offset, len); + return unwrapIntegrityProtectedMessage(incoming, offset, len); } } + private byte[] wrapIntegrityProtectedMessage(byte[] message, int offset, int len) throws SaslException { - } - - private static final String CLIENT_MAGIC_INTEGRITY = "Digest session key to client-to-server signing key magic constant"; - private static final String SERVER_MAGIC_INTEGRITY = "Digest session key to server-to-client signing key magic constant"; + byte[] messageMac = computeHMAC(wrapHmacKeyIntegrity, wrapSeqNum, hmacMD5, message, offset, len); - private byte[] wrapIntegrityProtectedMessage(byte[] message, int offset, int len) throws SaslException { - - byte[] messageMac = computeHMAC(wrapHmacKeyIntegrity, wrapSeqNum, hmacMD5, message, offset, len); + byte[] result = new byte[len + 16]; + System.arraycopy(message, offset, result, 0, len); + System.arraycopy(messageMac, 0, result, len, 10); + integerByteOrdered(1, result, len + 10, 2); // 2-byte message type number in network byte order with value 1 + integerByteOrdered(wrapSeqNum, result, len + 12, 4); // 4-byte sequence number in network byte order + wrapSeqNum++; + return result; + } - byte[] result = new byte[len + 16]; - System.arraycopy(message, offset, result, 0, len); - System.arraycopy(messageMac, 0, result, len, 10); - integerByteOrdered(1, result, len + 10, 2); // 2-byte message type number in network byte order with value 1 - integerByteOrdered(wrapSeqNum, result, len + 12, 4); // 4-byte sequence number in network byte order - wrapSeqNum++; - return result; - } + private byte[] unwrapIntegrityProtectedMessage(byte[] message, int offset, int len) throws SaslException { - private byte[] unwrapIntegrityProtectedMessage(byte[] message, int offset, int len) throws SaslException { + int messageType = decodeByteOrderedInteger(message, offset + len - 6, 2); + int extractedSeqNum = decodeByteOrderedInteger(message, offset + len - 4, 4); - int messageType = decodeByteOrderedInteger(message, offset + len - 6, 2); - int extractedSeqNum = decodeByteOrderedInteger(message, offset + len - 4, 4); + if (messageType != 1) { + throw saslDigest.mechMessageTypeMustEqual(1, messageType).toSaslException(); + } - if (messageType != 1) { - throw saslDigest.mechMessageTypeMustEqual(1, messageType).toSaslException(); - } + if (extractedSeqNum != unwrapSeqNum) { + throw saslDigest.mechBadSequenceNumberWhileUnwrapping(unwrapSeqNum, extractedSeqNum).toSaslException(); + } - if (extractedSeqNum != unwrapSeqNum) { - throw saslDigest.mechBadSequenceNumberWhileUnwrapping(unwrapSeqNum, extractedSeqNum).toSaslException(); - } + byte[] extractedMessageMac = new byte[10]; + byte[] extractedMessage = new byte[len - 16]; + System.arraycopy(message, offset, extractedMessage, 0, len - 16); + System.arraycopy(message, offset + len - 16, extractedMessageMac, 0, 10); - byte[] extractedMessageMac = new byte[10]; - byte[] extractedMessage = new byte[len - 16]; - System.arraycopy(message, offset, extractedMessage, 0, len - 16); - System.arraycopy(message, offset + len - 16, extractedMessageMac, 0, 10); + byte[] expectedHmac = computeHMAC(unwrapHmacKeyIntegrity, extractedSeqNum, hmacMD5, extractedMessage, 0, extractedMessage.length); - byte[] expectedHmac = computeHMAC(unwrapHmacKeyIntegrity, extractedSeqNum, hmacMD5, extractedMessage, 0, extractedMessage.length); + // validate MAC block + if (Arrays2.equals(expectedHmac, 0, extractedMessageMac, 0, 10) == false) { + return NO_BYTES; + } - // validate MAC block - if (Arrays2.equals(expectedHmac, 0, extractedMessageMac, 0, 10) == false) { - return NO_BYTES; + unwrapSeqNum++; // increment only if MAC is valid + return extractedMessage; } - unwrapSeqNum++; // increment only if MAC is valid - return extractedMessage; - } - - private byte[] wrapConfidentialityProtectedMessage(byte[] message, int offset, int len) throws SaslException { + private byte[] wrapConfidentialityProtectedMessage(byte[] message, int offset, int len) throws SaslException { - byte[] messageMac = computeHMAC(wrapHmacKeyIntegrity, wrapSeqNum, hmacMD5, message, offset, len); + byte[] messageMac = computeHMAC(wrapHmacKeyIntegrity, wrapSeqNum, hmacMD5, message, offset, len); - int paddingLength = 0; - byte[] pad = null; - int blockSize = wrapCipher.getBlockSize(); - if (blockSize > 0) { - paddingLength = blockSize - ((len + 10) % blockSize); - pad = new byte[paddingLength]; - Arrays.fill(pad, (byte)paddingLength); - } + int paddingLength = 0; + byte[] pad = null; + int blockSize = wrapCipher.getBlockSize(); + if (blockSize > 0) { + paddingLength = blockSize - ((len + 10) % blockSize); + pad = new byte[paddingLength]; + Arrays.fill(pad, (byte)paddingLength); + } - byte[] toCipher = new byte[len + paddingLength + 10]; - System.arraycopy(message, offset, toCipher, 0, len); - if (paddingLength > 0) { - System.arraycopy(pad, 0, toCipher, len, paddingLength); - } - System.arraycopy(messageMac, 0, toCipher, len + paddingLength, 10); + byte[] toCipher = new byte[len + paddingLength + 10]; + System.arraycopy(message, offset, toCipher, 0, len); + if (paddingLength > 0) { + System.arraycopy(pad, 0, toCipher, len, paddingLength); + } + System.arraycopy(messageMac, 0, toCipher, len + paddingLength, 10); - byte[] cipheredPart = null; - try { - cipheredPart = wrapCipher.update(toCipher); - } catch (Exception e) { - throw saslDigest.mechProblemDuringCrypt(e).toSaslException(); - } - if (cipheredPart == null){ - throw saslDigest.mechProblemDuringCryptResultIsNull().toSaslException(); - } + byte[] cipheredPart = null; + try { + cipheredPart = wrapCipher.update(toCipher); + } catch (Exception e) { + throw saslDigest.mechProblemDuringCrypt(e).toSaslException(); + } + if (cipheredPart == null){ + throw saslDigest.mechProblemDuringCryptResultIsNull().toSaslException(); + } - byte[] result = new byte[cipheredPart.length + 6]; - System.arraycopy(cipheredPart, 0, result, 0, cipheredPart.length); - integerByteOrdered(1, result, cipheredPart.length, 2); // 2-byte message type number in network byte order with value 1 - integerByteOrdered(wrapSeqNum, result, cipheredPart.length + 2, 4); // 4-byte sequence number in network byte order + byte[] result = new byte[cipheredPart.length + 6]; + System.arraycopy(cipheredPart, 0, result, 0, cipheredPart.length); + integerByteOrdered(1, result, cipheredPart.length, 2); // 2-byte message type number in network byte order with value 1 + integerByteOrdered(wrapSeqNum, result, cipheredPart.length + 2, 4); // 4-byte sequence number in network byte order - wrapSeqNum++; - return result; - } + wrapSeqNum++; + return result; + } - private byte[] unwrapConfidentialityProtectedMessage(byte[] message, int offset, int len) throws SaslException { + private byte[] unwrapConfidentialityProtectedMessage(byte[] message, int offset, int len) throws SaslException { - int messageType = decodeByteOrderedInteger(message, offset + len - 6, 2); - int extractedSeqNum = decodeByteOrderedInteger(message, offset + len - 4, 4); + int messageType = decodeByteOrderedInteger(message, offset + len - 6, 2); + int extractedSeqNum = decodeByteOrderedInteger(message, offset + len - 4, 4); - if (messageType != 1) { - throw saslDigest.mechMessageTypeMustEqual(1, messageType).toSaslException(); - } + if (messageType != 1) { + throw saslDigest.mechMessageTypeMustEqual(1, messageType).toSaslException(); + } - if (extractedSeqNum != unwrapSeqNum) { - throw saslDigest.mechBadSequenceNumberWhileUnwrapping(unwrapSeqNum, extractedSeqNum).toSaslException(); - } + if (extractedSeqNum != unwrapSeqNum) { + throw saslDigest.mechBadSequenceNumberWhileUnwrapping(unwrapSeqNum, extractedSeqNum).toSaslException(); + } - byte[] clearText = null; - try { - clearText = unwrapCipher.update(message, offset, len - 6); - } catch (Exception e) { - throw saslDigest.mechProblemDuringDecrypt(e).toSaslException(); - } - if (clearText == null){ - throw saslDigest.mechProblemDuringDecryptResultIsNull().toSaslException(); - } + byte[] clearText = null; + try { + clearText = unwrapCipher.update(message, offset, len - 6); + } catch (Exception e) { + throw saslDigest.mechProblemDuringDecrypt(e).toSaslException(); + } + if (clearText == null){ + throw saslDigest.mechProblemDuringDecryptResultIsNull().toSaslException(); + } - byte[] hmac = new byte[10]; - System.arraycopy(clearText, clearText.length - 10, hmac, 0, 10); - - byte[] decryptedMessage = null; - // strip potential padding - if (unwrapCipher.getBlockSize() > 0) { - int padSize = clearText[clearText.length - 10 - 1]; - int decryptedMessageSize = clearText.length - 10; - if (padSize < 8) { - int i = clearText.length - 10 - 1; - while (clearText[i] == padSize) { - i--; + byte[] hmac = new byte[10]; + System.arraycopy(clearText, clearText.length - 10, hmac, 0, 10); + + byte[] decryptedMessage = null; + // strip potential padding + if (unwrapCipher.getBlockSize() > 0) { + int padSize = clearText[clearText.length - 10 - 1]; + int decryptedMessageSize = clearText.length - 10; + if (padSize < 8) { + int i = clearText.length - 10 - 1; + while (clearText[i] == padSize) { + i--; + } + decryptedMessageSize = i + 1; } - decryptedMessageSize = i + 1; + decryptedMessage = new byte[decryptedMessageSize]; + System.arraycopy(clearText, 0, decryptedMessage, 0, decryptedMessageSize); + } else { + decryptedMessage = new byte[clearText.length - 10]; + System.arraycopy(clearText, 0, decryptedMessage, 0, clearText.length - 10); } - decryptedMessage = new byte[decryptedMessageSize]; - System.arraycopy(clearText, 0, decryptedMessage, 0, decryptedMessageSize); - } else { - decryptedMessage = new byte[clearText.length - 10]; - System.arraycopy(clearText, 0, decryptedMessage, 0, clearText.length - 10); - } - byte[] expectedHmac = computeHMAC(unwrapHmacKeyIntegrity, extractedSeqNum, hmacMD5, decryptedMessage, 0, decryptedMessage.length); + byte[] expectedHmac = computeHMAC(unwrapHmacKeyIntegrity, extractedSeqNum, hmacMD5, decryptedMessage, 0, decryptedMessage.length); + + // check hmac-s + if (Arrays2.equals(expectedHmac, 0, hmac, 0, 10) == false) { + return NO_BYTES; + } - // check hmac-s - if (Arrays2.equals(expectedHmac, 0, hmac, 0, 10) == false) { - return NO_BYTES; + unwrapSeqNum++; // increment only if MAC is valid + return decryptedMessage; } - unwrapSeqNum++; // increment only if MAC is valid - return decryptedMessage; } protected void createCiphersAndKeys() throws SaslException { diff --git a/sasl/entity/pom.xml b/sasl/entity/pom.xml index c54dcf9ef1b..641fc593cb2 100644 --- a/sasl/entity/pom.xml +++ b/sasl/entity/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/external/pom.xml b/sasl/external/pom.xml index ff509270cc2..519e17253d6 100644 --- a/sasl/external/pom.xml +++ b/sasl/external/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslClientTest.java b/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslClientTest.java index 252114d8791..5d099418c40 100644 --- a/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslClientTest.java +++ b/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslClientTest.java @@ -40,6 +40,7 @@ import javax.security.sasl.SaslException; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.wildfly.security.sasl.WildFlySasl; @@ -213,13 +214,19 @@ public void testServerChallengeEmptyAuthzId() throws Exception { /** * Test failing (as we only authenticate "admin") authn for unsupported data "test" from client. */ - @Test(expected = SaslException.class) - public void testWrongServerChallenge() throws Exception { + @Test + public void testWrongServerChallenge() { final SaslClientFactory factory = obtainSaslClientFactory(ExternalSaslClientFactory.class); - final SaslClient saslClient = factory.createSaslClient(MECHANISMS_EXTERNAL_ONLY, ADMIN, "test", "localhost", setProps(), - null); - assertFalse(saslClient.isComplete()); - saslClient.evaluateChallenge("test".getBytes(StandardCharsets.UTF_8)); + try { + final SaslClient saslClient = factory.createSaslClient(MECHANISMS_EXTERNAL_ONLY, ADMIN, "test", "localhost", setProps(), + null); + assertFalse(saslClient.isComplete()); + Assert.assertThrows(SaslException.class,()->{ + saslClient.evaluateChallenge("test".getBytes(StandardCharsets.UTF_8)); + }); + }catch(SaslException saslException){ + fail("Failed to create SaslClient Instance"); + } } @Test diff --git a/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslServerTest.java b/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslServerTest.java index 9a0e4e73c81..06b1a4af1d4 100644 --- a/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslServerTest.java +++ b/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslServerTest.java @@ -44,6 +44,7 @@ import javax.security.sasl.SaslServerFactory; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.wildfly.security.auth.callback.AuthenticationConfigurationCallback; @@ -183,25 +184,36 @@ public void testAuthnClientData() throws Exception { /** * Test failing (as we only authenticate "admin") authn for unsupported data "test" from client. */ - @Test(expected = SaslException.class) - public void testFailedAuthn() throws Exception { - SaslServer saslServer = obtainSaslServerFactory(ExternalSaslServerFactory.class).createSaslServer(EXTERNAL, "test", - "localhost", setProps(), CALLBACK_HANDLER_AUTHZ_ADMIN); - assertFalse(saslServer.isComplete()); - saslServer.evaluateResponse("test".getBytes(StandardCharsets.UTF_8)); + @Test + public void testFailedAuthn() { + try { + SaslServer saslServer = obtainSaslServerFactory(ExternalSaslServerFactory.class).createSaslServer(EXTERNAL, "test", + "localhost", setProps(), CALLBACK_HANDLER_AUTHZ_ADMIN); + assertFalse(saslServer.isComplete()); + Assert.assertThrows(SaslException.class,()->{ + saslServer.evaluateResponse("test".getBytes(StandardCharsets.UTF_8)); + }); + }catch (SaslException saslException){ + fail("Failed to create SaslServer Instance"); + } } /** * Test failing authn (as we only authenticate "admin") for empty data received from client. */ - @Test(expected = SaslException.class) - public void testAuthnEmptyData() throws Exception { - SaslServer saslServer = obtainSaslServerFactory(ExternalSaslServerFactory.class).createSaslServer(EXTERNAL, "test", - "localhost", setProps(), CALLBACK_HANDLER_AUTHZ_ADMIN); - - assertFalse(saslServer.isComplete()); - - saslServer.evaluateResponse(AbstractSaslParticipant.NO_BYTES); + @Test + public void testAuthnEmptyData() { + try{ + SaslServer saslServer = obtainSaslServerFactory(ExternalSaslServerFactory.class).createSaslServer(EXTERNAL, "test", + "localhost", setProps(), CALLBACK_HANDLER_AUTHZ_ADMIN); + + assertFalse(saslServer.isComplete()); + Assert.assertThrows(SaslException.class,()->{ + saslServer.evaluateResponse(AbstractSaslParticipant.NO_BYTES); + }); + }catch (SaslException saslException){ + fail("Failed to create SaslServer Instance"); + } } @Test diff --git a/sasl/gs2/pom.xml b/sasl/gs2/pom.xml index e9b525e3157..185464c86c0 100644 --- a/sasl/gs2/pom.xml +++ b/sasl/gs2/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/gssapi/pom.xml b/sasl/gssapi/pom.xml index 0a204def8d3..9e8c46f409e 100644 --- a/sasl/gssapi/pom.xml +++ b/sasl/gssapi/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/localuser/pom.xml b/sasl/localuser/pom.xml index aa8cfebf241..4d2dcb70815 100644 --- a/sasl/localuser/pom.xml +++ b/sasl/localuser/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/oauth2/pom.xml b/sasl/oauth2/pom.xml index 9981c7aa310..10585dc67e8 100644 --- a/sasl/oauth2/pom.xml +++ b/sasl/oauth2/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/otp/pom.xml b/sasl/otp/pom.xml index dc749ed6030..2ebcf826d96 100644 --- a/sasl/otp/pom.xml +++ b/sasl/otp/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/plain/pom.xml b/sasl/plain/pom.xml index da68c0f9155..ce0d479311a 100644 --- a/sasl/plain/pom.xml +++ b/sasl/plain/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/sasl/scram/pom.xml b/sasl/scram/pom.xml index c1699f121cb..6be4f3ec0aa 100644 --- a/sasl/scram/pom.xml +++ b/sasl/scram/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/ssh/util/pom.xml b/ssh/util/pom.xml index f606b2a8925..cfeab877b40 100644 --- a/ssh/util/pom.xml +++ b/ssh/util/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/ssl/pom.xml b/ssl/pom.xml index add987adf0d..bf3c3e3ebe1 100644 --- a/ssl/pom.xml +++ b/ssl/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT 4.0.0 diff --git a/ssl/src/main/java/org/wildfly/security/ssl/SNIContextMatcher.java b/ssl/src/main/java/org/wildfly/security/ssl/SNIContextMatcher.java index 7790d2827ae..ff7aeceae5e 100644 --- a/ssl/src/main/java/org/wildfly/security/ssl/SNIContextMatcher.java +++ b/ssl/src/main/java/org/wildfly/security/ssl/SNIContextMatcher.java @@ -26,6 +26,9 @@ import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLContext; +/** + * A class which returns a matching SSL context based on the SNI server list provided. + */ public class SNIContextMatcher { private final SSLContext defaultContext; @@ -38,6 +41,10 @@ public class SNIContextMatcher { this.exacts = exacts; } + /** + * Used for finding the matching servers from the server list. + * Firstly, the exacts are matched, if not found, wildcards are tried. + */ public SSLContext getContext(List servers) { for (Map.Entry entry : exacts.entrySet()) { for (SNIServerName server : servers) { @@ -60,6 +67,9 @@ public SSLContext getDefaultContext() { return defaultContext; } + /** + * A class which allows building and configuration SNIContextMatcher. The builder, at minimum requres a default SSLContext. + */ public static class Builder { private SSLContext defaultContext; @@ -91,4 +101,4 @@ public Builder addMatch(String name, SSLContext context) { return this; } } -} +} \ No newline at end of file diff --git a/ssl/src/main/java/org/wildfly/security/ssl/SNISSLContext.java b/ssl/src/main/java/org/wildfly/security/ssl/SNISSLContext.java index dbea0b91882..6ab0f43f1c5 100644 --- a/ssl/src/main/java/org/wildfly/security/ssl/SNISSLContext.java +++ b/ssl/src/main/java/org/wildfly/security/ssl/SNISSLContext.java @@ -17,9 +17,12 @@ import javax.net.ssl.SSLContext; +/** + * A class that uses the provided SNIContextMatcher to determine the SSLContext to be used for the connection. + */ public class SNISSLContext extends SSLContext { public SNISSLContext(SNIContextMatcher matcher) { super(new SNISSLContextSpi(matcher), matcher.getDefaultContext().getProvider(), matcher.getDefaultContext().getProtocol()); } -} +} \ No newline at end of file diff --git a/ssl/src/main/java/org/wildfly/security/ssl/SSLUtils.java b/ssl/src/main/java/org/wildfly/security/ssl/SSLUtils.java index 4d1285a6c9a..4377fed8663 100644 --- a/ssl/src/main/java/org/wildfly/security/ssl/SSLUtils.java +++ b/ssl/src/main/java/org/wildfly/security/ssl/SSLUtils.java @@ -128,10 +128,8 @@ public static SecurityFactory createSslContextFactory(ProtocolSelect return () -> { for (String protocol : supportedProtocols) { List providerList = preferredProviderByAlgorithm.getOrDefault(protocol.toUpperCase(Locale.ENGLISH), Collections.emptyList()); - if (log.isTraceEnabled()) { - if (providerList.isEmpty()) { - log.tracef("No providers are available for protocol %s", protocol); - } + if (log.isTraceEnabled() && providerList.isEmpty()) { + log.tracef("No providers are available for protocol %s", protocol); } for (Provider provider : providerList) { try { diff --git a/ssl/src/main/java/org/wildfly/security/ssl/X509CRLExtendedTrustManager.java b/ssl/src/main/java/org/wildfly/security/ssl/X509CRLExtendedTrustManager.java index 822d19282cd..53042f1305c 100644 --- a/ssl/src/main/java/org/wildfly/security/ssl/X509CRLExtendedTrustManager.java +++ b/ssl/src/main/java/org/wildfly/security/ssl/X509CRLExtendedTrustManager.java @@ -52,7 +52,7 @@ * Extension to the {@link X509TrustManager} interface to support CRL verification. * * @author Pedro Igor - * @deprecated use {@link X509RevocationTrustManager} instead + * @deprecated use {@link org.wildfly.security.ssl.X509RevocationTrustManager org.wildfly.security.ssl.X509RevocationTrustManager} instead */ @Deprecated public final class X509CRLExtendedTrustManager extends X509ExtendedTrustManager { 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 aef9cf3d35e..8e14fa93089 100644 --- a/ssl/src/main/java/org/wildfly/security/ssl/X509RevocationTrustManager.java +++ b/ssl/src/main/java/org/wildfly/security/ssl/X509RevocationTrustManager.java @@ -394,11 +394,9 @@ private void checkCertPathLength(X509Certificate currCert) throws CertPathValida X500Principal issuer = currCert.getIssuerX500Principal(); int pathLenConstraint = -1; - if (currCert.getVersion() < 3) { // version 1 or version 2 - if (i == 1) { - if (subject.equals(issuer)) { - pathLenConstraint = Integer.MAX_VALUE; - } + if (currCert.getVersion() < 3) { // version 1 or version 2 + if (i == 1 && subject.equals(issuer)) { + pathLenConstraint = Integer.MAX_VALUE; } } else { pathLenConstraint = currCert.getBasicConstraints(); @@ -408,15 +406,14 @@ private void checkCertPathLength(X509Certificate currCert) throws CertPathValida pathLenConstraint = maxPathLength; } - if (!subject.equals(issuer)) { - if (pathLenConstraint < i) { - throw new CertPathValidatorException - ("check failed: pathLenConstraint violated - " - + "this cert must be the last cert in the " - + "certification path", null, null, -1, - PKIXReason.PATH_TOO_LONG); - } + if (!subject.equals(issuer) && pathLenConstraint < i) { + throw new CertPathValidatorException + ("check failed: pathLenConstraint violated - " + + "this cert must be the last cert in the " + + "certification path", null, null, -1, + PKIXReason.PATH_TOO_LONG); } + if (pathLenConstraint < maxPathLength) maxPathLength = pathLenConstraint; } diff --git a/tests/base/pom.xml b/tests/base/pom.xml index e960945fa6f..10306ce7191 100644 --- a/tests/base/pom.xml +++ b/tests/base/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml @@ -54,6 +54,7 @@ 1.0.2 + 2.4.0-b180830.0438 @@ -385,7 +386,10 @@ org.wildfly.security wildfly-elytron-digest - + + org.wildfly.security + wildfly-elytron-http-cert + org.wildfly.security wildfly-elytron-http @@ -402,6 +406,10 @@ org.wildfly.security wildfly-elytron-http-digest + + org.wildfly.security + wildfly-elytron-http-bearer + org.wildfly.security wildfly-elytron-http-external @@ -638,11 +646,6 @@ apacheds-kerberos-codec test - - org.apache.directory.server - apacheds-protocol-kerberos - test - org.apache.directory.server apacheds-protocol-ldap @@ -654,6 +657,11 @@ + + org.apache.kerby + kerb-simplekdc + test + org.apache.directory.mavibot mavibot @@ -709,7 +717,7 @@ - org.glassfish + org.eclipse.parsson jakarta.json test @@ -766,27 +774,21 @@ test - com.sun.xml.bind - jaxb-core - 2.3.0 - test - - - com.sun.xml.bind - jaxb-impl - 2.3.0 + org.glassfish.jaxb + jaxb-runtime + ${version.org.glassfish.jaxb.jaxb-runtime} test org.hsqldb hsqldb - 2.3.1 + 2.7.1 test org.wildfly.security - wildfly-elytron-credential + wildfly-elytron-dynamic-ssl test @@ -800,7 +802,7 @@ - --add-modules java.sql --illegal-access=permit + --add-modules java.sql --illegal-access=permit --add-exports=jdk.security.jgss/com.sun.security.sasl.gsskerb=ALL-UNNAMED -Djdk.attach.allowAttachSelf=true @@ -822,7 +824,7 @@ com.github.siom79.japicmp japicmp-maven-plugin - 0.13.0 + 0.20.0 diff --git a/tests/base/src/test/java/org/wildfly/security/apacheds/LdapService.java b/tests/base/src/test/java/org/wildfly/security/apacheds/LdapService.java index 66405961cbd..6952fa783d2 100644 --- a/tests/base/src/test/java/org/wildfly/security/apacheds/LdapService.java +++ b/tests/base/src/test/java/org/wildfly/security/apacheds/LdapService.java @@ -143,7 +143,6 @@ public Builder addPartition(final String id, final String partitionName, final i for (String current : indexes) { partitionFactory.addIndex(partition, current, indexSize); } - partition.setCacheService(directoryService.getCacheService()); partition.initialize(); directoryService.addPartition(partition); diff --git a/tests/base/src/test/java/org/wildfly/security/auth/client/MaskedPasswordSSLAuthenticationTest.java b/tests/base/src/test/java/org/wildfly/security/auth/client/MaskedPasswordSSLAuthenticationTest.java index 119548f4c4b..0ba8e2713a1 100644 --- a/tests/base/src/test/java/org/wildfly/security/auth/client/MaskedPasswordSSLAuthenticationTest.java +++ b/tests/base/src/test/java/org/wildfly/security/auth/client/MaskedPasswordSSLAuthenticationTest.java @@ -25,12 +25,10 @@ import java.io.Closeable; import java.io.IOException; -import java.io.InputStream; import java.net.InetAddress; import java.net.URI; import java.security.AccessController; import java.security.KeyStore; -import java.security.KeyStoreException; import java.security.PrivilegedAction; import java.security.Security; import java.util.Locale; @@ -38,17 +36,11 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedKeyManager; -import javax.net.ssl.X509TrustManager; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -63,6 +55,8 @@ import org.wildfly.security.ssl.SSLUtils; import org.wildfly.security.ssl.test.util.CAGenerationTool; import org.wildfly.security.ssl.test.util.CAGenerationTool.Identity; +import org.wildfly.security.ssl.test.util.DefinedCAIdentity; +import org.wildfly.security.ssl.test.util.DefinedIdentity; import org.wildfly.security.x500.principal.X500AttributePrincipalDecoder; /** @@ -72,64 +66,13 @@ */ public class MaskedPasswordSSLAuthenticationTest { - private static final char[] PASSWORD = "Elytron".toCharArray(); + private static final String JKS_LOCATION = "./target/test-classes/jks"; private static CAGenerationTool caGenerationTool; - /** - * Get the key manager backed by the specified key store. - * - * @param keystorePath the path to the keystore with X509 private key - * @return the initialised key manager. - */ - private static X509ExtendedKeyManager getKeyManager(final String keystorePath) throws Exception { - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); - keyManagerFactory.init(createKeyStore(keystorePath), PASSWORD); - - for (KeyManager current : keyManagerFactory.getKeyManagers()) { - if (current instanceof X509ExtendedKeyManager) { - return (X509ExtendedKeyManager) current; - } - } - - throw new IllegalStateException("Unable to obtain X509ExtendedKeyManager."); - } - - private static TrustManagerFactory getTrustManagerFactory() throws Exception { - return TrustManagerFactory.getInstance("PKIX"); - } - - /** - * Get the trust manager that trusts all certificates signed by the certificate authority. - * - * @return the trust manager that trusts all certificates signed by the certificate authority. - * @throws KeyStoreException - */ - private static X509TrustManager getCATrustManager() throws Exception { - TrustManagerFactory trustManagerFactory = getTrustManagerFactory(); - trustManagerFactory.init(createKeyStore("/jks/ca.truststore")); - - for (TrustManager current : trustManagerFactory.getTrustManagers()) { - if (current instanceof X509TrustManager) { - return (X509TrustManager) current; - } - } - - throw new IllegalStateException("Unable to obtain X509TrustManager."); - } - - private static KeyStore createKeyStore(final String path) throws Exception { - KeyStore keyStore = KeyStore.getInstance("jks"); - try (InputStream caTrustStoreFile = MaskedPasswordSSLAuthenticationTest.class.getResourceAsStream(path)) { - keyStore.load(caTrustStoreFile, PASSWORD); - } - - return keyStore; - } - - private static SecurityDomain getKeyStoreBackedSecurityDomain(String keyStorePath) throws Exception { - SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(createKeyStore(keyStorePath)); + private static SecurityDomain getKeyStoreBackedSecurityDomain(KeyStore keyStore) throws Exception { + SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(keyStore); return SecurityDomain.builder() .addRealm("KeystoreRealm", securityRealm) @@ -156,10 +99,13 @@ public static void afterTest() throws IOException { @Test public void testTwoWay() throws Exception { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); + SSLContext serverContext = new SSLContextBuilder() - .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore")) - .setKeyManager(getKeyManager("/jks/scarab.keystore")) - .setTrustManager(getCATrustManager()) + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(scarab.createKeyManager()) + .setTrustManager(ca.createTrustManager()) .setNeedClientAuth(true) .build().create(); @@ -191,11 +137,11 @@ private SecurityIdentity performConnectionTest(SSLContext serverContext, SSLCont SSLSocket sslSocket = (SSLSocket) clientContext.getSocketFactory().createSocket(InetAddress.getLoopbackAddress(), 1111); sslSocket.getSession(); + System.out.println("Client connected"); return sslSocket; } catch (Exception e) { + System.out.println("Client Connection Failed"); throw new RuntimeException(e); - } finally { - System.out.println("Client connected"); } }); diff --git a/tests/base/src/test/java/org/wildfly/security/auth/realm/DistributedSecurityRealmTest.java b/tests/base/src/test/java/org/wildfly/security/auth/realm/DistributedSecurityRealmTest.java index 1e0001c20bc..1a1abbe686b 100644 --- a/tests/base/src/test/java/org/wildfly/security/auth/realm/DistributedSecurityRealmTest.java +++ b/tests/base/src/test/java/org/wildfly/security/auth/realm/DistributedSecurityRealmTest.java @@ -133,6 +133,16 @@ public void testExistingIdentity3() throws Exception { identity.dispose(); } + @Test + public void testVerifyEvidence() throws Exception { + + RealmIdentity identity = realm.getRealmIdentity(new NamePrincipal("user1")); + Assert.assertTrue(identity.verifyEvidence(new PasswordGuessEvidence(pass1))); + Assert.assertFalse(identity.verifyEvidence(new PasswordGuessEvidence(pass2))); + Assert.assertFalse(identity.verifyEvidence(new PasswordGuessEvidence(pass3))); + identity.dispose(); + } + @Test public void testEvidence() throws Exception { RealmIdentity identity = realm.getRealmIdentity(new SimpleEvidence("evidenceUser", true)); diff --git a/tests/base/src/test/java/org/wildfly/security/auth/realm/token/JwtSecurityRealmTest.java b/tests/base/src/test/java/org/wildfly/security/auth/realm/token/JwtSecurityRealmTest.java index 250e0f4e272..917fb30cc47 100644 --- a/tests/base/src/test/java/org/wildfly/security/auth/realm/token/JwtSecurityRealmTest.java +++ b/tests/base/src/test/java/org/wildfly/security/auth/realm/token/JwtSecurityRealmTest.java @@ -337,6 +337,7 @@ public void testMultipleTokenTypes() throws Exception { @Test public void testUnsecuredJkuEndpoint() throws Exception { + checkIdentityDoesNotExist("1", 50832); BearerTokenEvidence evidence = new BearerTokenEvidence(createJwt(keyPair1, 60, -1, "1", new URI("https://localhost:50832"))); X509TrustManager tm = getTrustManager(); @@ -435,6 +436,7 @@ public void testJkuMultipleKeys() throws Exception { @Test public void testInvalidJku() throws Exception { + checkIdentityDoesNotExist("1", 80); BearerTokenEvidence evidence = new BearerTokenEvidence(createJwt(keyPair1, 60, -1, "1", new URI("https://localhost:80"))); X509TrustManager tm = getTrustManager(); @@ -455,6 +457,7 @@ public void testInvalidJku() throws Exception { @Test public void testInvalidKid() throws Exception { + checkIdentityDoesNotExist("badkid", 50831); BearerTokenEvidence evidence = new BearerTokenEvidence(createJwt(keyPair1, 60, -1, "badkid", new URI("https://localhost:50831"))); X509TrustManager tm = getTrustManager(); @@ -833,4 +836,20 @@ public MockResponse dispatch(RecordedRequest recordedRequest) { }; } + private void checkIdentityDoesNotExist(String kid, int port) throws Exception { + BearerTokenEvidence evidence = new BearerTokenEvidence(createJwt(keyPair1, 60, -1, kid, new URI("https://localhost:" + port))); + + X509TrustManager tm = getTrustManager(); + SSLContext sslContext = new SSLContextBuilder().setTrustManager(tm).setClientMode(true).setSessionTimeout(10).build().create(); + + TokenSecurityRealm securityRealm = TokenSecurityRealm.builder() + .principalClaimName("sub") + .validator(JwtValidator.builder() + .issuer("elytron-oauth2-realm") + .audience("my-app-valid") + .useSslContext(sslContext).useSslHostnameVerifier((a,b) -> true).build()) + .build(); + + assertIdentityNotExist(securityRealm, evidence); + } } diff --git a/tests/base/src/test/java/org/wildfly/security/auth/server/CustomNameRewriterTest.java b/tests/base/src/test/java/org/wildfly/security/auth/server/CustomNameRewriterTest.java new file mode 100644 index 00000000000..c3a8f59d7ab --- /dev/null +++ b/tests/base/src/test/java/org/wildfly/security/auth/server/CustomNameRewriterTest.java @@ -0,0 +1,92 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.auth.server; + +import org.junit.Test; +import org.wildfly.security.auth.permission.LoginPermission; +import org.wildfly.security.auth.realm.FileSystemSecurityRealm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CustomNameRewriterTest { + + private static final String BEFORE_USER_NAME = "Bob"; + private static final String AFTER_USER_NAME = "Robert"; + + @Test + public void testCustomNameRewriter() { + CustomNameRewriter rewriter = new CustomNameRewriter(); + String adjustedName = rewriter.rewriteName(BEFORE_USER_NAME); + assertEquals(AFTER_USER_NAME, adjustedName); + } + + @Test + public void testCustomNameRewriterAuthentication() throws Exception { + FileSystemSecurityRealm fileSystemSecurityRealm = createSecurityRealm(); + CustomNameRewriter rewriter = new CustomNameRewriter(); + SecurityDomain domainWithRewriter = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", fileSystemSecurityRealm).build() + .setPermissionMapper(((permissionMappable, roles) -> LoginPermission.getInstance())) + .setPreRealmRewriter(rewriter) + .build(); + ServerAuthenticationContext sac1 = domainWithRewriter.createNewAuthenticationContext(); + sac1.setAuthenticationName(BEFORE_USER_NAME); // security domain contains the user "Robert" + assertTrue(sac1.authorize()); + + SecurityDomain domainWithoutRewriter = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", fileSystemSecurityRealm).build() + .setPermissionMapper(((permissionMappable, roles) -> LoginPermission.getInstance())) + .build(); + ServerAuthenticationContext sac2 = domainWithoutRewriter.createNewAuthenticationContext(); + sac2.setAuthenticationName(BEFORE_USER_NAME); // should fail if rewriter not configured + assertFalse(sac2.authorize()); + } + + @Test + public void testCustomNameRewriterNonExistingUser() throws Exception{ + FileSystemSecurityRealm fileSystemSecurityRealm = createSecurityRealm(); + CustomNameRewriter rewriter = new CustomNameRewriter(); + SecurityDomain securityDomain = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", fileSystemSecurityRealm).build() + .setPermissionMapper(((permissionMappable, roles) -> LoginPermission.getInstance())) + .setPreRealmRewriter(rewriter) + .build(); + ServerAuthenticationContext sac = securityDomain.createNewAuthenticationContext(); + sac.setAuthenticationName("John"); + assertFalse(sac.authorize()); + } + + private FileSystemSecurityRealm createSecurityRealm() throws Exception { + FileSystemSecurityRealm realm = new FileSystemSecurityRealm(ServerUtils.getRootPath(true, getClass())); + ServerUtils.addUser(realm, AFTER_USER_NAME); + return realm; + } + + private final class CustomNameRewriter implements NameRewriter { + + @Override + public String rewriteName(String original) { + if (original == null) { + return null; + } else if (original.equals(BEFORE_USER_NAME)) { + return AFTER_USER_NAME; + } else { + return original; + } + } + } +} diff --git a/tests/base/src/test/java/org/wildfly/security/credential/source/impl/CommandCredentialSourceTest.java b/tests/base/src/test/java/org/wildfly/security/credential/source/impl/CommandCredentialSourceTest.java index 7f7454c71f6..c41d01bbccd 100644 --- a/tests/base/src/test/java/org/wildfly/security/credential/source/impl/CommandCredentialSourceTest.java +++ b/tests/base/src/test/java/org/wildfly/security/credential/source/impl/CommandCredentialSourceTest.java @@ -18,7 +18,8 @@ package org.wildfly.security.credential.source.impl; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertEquals; import java.io.File; import java.io.IOException; @@ -43,7 +44,7 @@ public void testCommand() throws GeneralSecurityException, IOException { assertNotNull(password); final ClearPassword clearPassword = password.castAs(ClearPassword.class, ClearPassword.ALGORITHM_CLEAR); assertNotNull(clearPassword); - assertEquals(new String(clearPassword.getPassword()), "secret_key_THREE"); + assertEquals("secret_key_THREE", new String(clearPassword.getPassword())); } private static CommandCredentialSource.Builder getBuilder() { diff --git a/tests/base/src/test/java/org/wildfly/security/credential/store/KeystorePasswordStoreTest.java b/tests/base/src/test/java/org/wildfly/security/credential/store/KeystorePasswordStoreTest.java index e040f146f1f..b0505bd12ab 100644 --- a/tests/base/src/test/java/org/wildfly/security/credential/store/KeystorePasswordStoreTest.java +++ b/tests/base/src/test/java/org/wildfly/security/credential/store/KeystorePasswordStoreTest.java @@ -34,7 +34,7 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.function.Supplier; -import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; diff --git a/tests/base/src/test/java/org/wildfly/security/http/HttpAuthenticatorTest.java b/tests/base/src/test/java/org/wildfly/security/http/HttpAuthenticatorTest.java index e20540e3765..f1bc2e557c6 100644 --- a/tests/base/src/test/java/org/wildfly/security/http/HttpAuthenticatorTest.java +++ b/tests/base/src/test/java/org/wildfly/security/http/HttpAuthenticatorTest.java @@ -64,7 +64,7 @@ public class HttpAuthenticatorTest extends AbstractBaseHttpTest { " qop=auth,\n" + " response=\"8ca523f5e9506fed4657c9700eebdbec\",\n" + " opaque=\"FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS\""; - private final String digestSha256Header = "Digest username=\"Mufasa\",\n" + private final String digestSha256Header = "Digest username=\"Mufasa\",\n" + " realm=\"http-auth@example.org\",\n" + " uri=\"/dir/index.html\",\n" + " algorithm=SHA-256,\n" @@ -180,7 +180,7 @@ public List prepareBasicSilentMechanisms() th return mechanisms; } - public void prepareSilentBasicWithDigestMechanisms() throws Exception{ + public void prepareSilentBasicWithDigestMechanisms() throws Exception { List mechanisms = prepareBasicSilentMechanisms(); Map digestProps = new HashMap<>(); digestProps.put(CONFIG_REALM, "http-auth@example.org"); @@ -219,7 +219,7 @@ public void testBasicSilent() throws Exception { } @Test - public void testBasicSilentWithDigest() throws Exception{ + public void testBasicSilentWithDigest() throws Exception { // authenticate using only DIGEST mechanism prepareSilentBasicWithDigestMechanisms(); authenticateWithDigestMD5(); @@ -255,5 +255,4 @@ public void testUsingSecurityProviderServerMechanismWithDigestMD5() throws Excep authenticateWithDigestMD5(); } - } diff --git a/tests/base/src/test/java/org/wildfly/security/http/bearer/BearerAuthenticationMechanismTest.java b/tests/base/src/test/java/org/wildfly/security/http/bearer/BearerAuthenticationMechanismTest.java new file mode 100644 index 00000000000..a3087b37ad4 --- /dev/null +++ b/tests/base/src/test/java/org/wildfly/security/http/bearer/BearerAuthenticationMechanismTest.java @@ -0,0 +1,58 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2023 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.http.bearer; + +import org.junit.Assert; +import org.junit.Test; +import org.wildfly.security.http.HttpServerAuthenticationMechanism; +import org.wildfly.security.http.impl.AbstractBaseHttpTest; + +import static org.wildfly.security.http.HttpConstants.BEARER_TOKEN; +import static org.wildfly.security.http.HttpConstants.UNAUTHORIZED; + +import java.util.Collections; + +/** + * Test of server side of the Bearer HTTP mechanism. + * + * @author Keshav Kumar + */ +public class BearerAuthenticationMechanismTest extends AbstractBaseHttpTest { + + @Test + public void testBearerAuthenticationMechanism() throws Exception { + HttpServerAuthenticationMechanism mechanism = bearerFactory.createAuthenticationMechanism(BEARER_TOKEN, Collections.emptyMap(), getCallbackHandler(null, "testrealm@host.com", null, "random")); + + //Test no authentication in progress + TestingHttpServerRequest request1 = new TestingHttpServerRequest(new String[]{}); + mechanism.evaluateRequest(request1); + Assert.assertEquals(Status.NO_AUTH, request1.getResult()); + + //Test unsuccessful authentication + TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[]{"Bearer test"}); + mechanism.evaluateRequest(request2); + Assert.assertEquals(Status.FAILED, request2.getResult()); + Assert.assertEquals(UNAUTHORIZED, request2.getResponse().getStatusCode()); + + //Test successful Authentication + TestingHttpServerRequest request3 = new TestingHttpServerRequest(new String[]{"Bearer random"}); + mechanism.evaluateRequest(request3); + Assert.assertEquals(Status.COMPLETE, request3.getResult()); + } +} diff --git a/tests/base/src/test/java/org/wildfly/security/http/cert/ClientCertAuthenticationMechanismTest.java b/tests/base/src/test/java/org/wildfly/security/http/cert/ClientCertAuthenticationMechanismTest.java new file mode 100644 index 00000000000..6180ab1ed44 --- /dev/null +++ b/tests/base/src/test/java/org/wildfly/security/http/cert/ClientCertAuthenticationMechanismTest.java @@ -0,0 +1,85 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.http.cert; + +import mockit.Tested; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.wildfly.security.auth.realm.SimpleMapBackedSecurityRealm; +import org.wildfly.security.auth.server.SecurityDomain; +import org.wildfly.security.cache.IdentityCache; +import org.wildfly.security.http.HttpAuthenticationException; +import org.wildfly.security.http.HttpServerAuthenticationMechanism; +import org.wildfly.security.http.impl.AbstractBaseHttpTest; + +import javax.security.auth.x500.X500Principal; +import java.security.Provider; +import java.security.Security; +import java.util.HashMap; +import java.util.Map; +import static org.wildfly.security.http.HttpConstants.*; + +public class ClientCertAuthenticationMechanismTest extends AbstractBaseHttpTest { + private static final Provider provider = WildFlyElytronHttpClientCertProvider.getInstance(); + + @Tested + private IdentityCache identityCache; + + @BeforeClass + public static void registerCertProvider() { + Security.insertProviderAt(provider, 1); + SecurityDomain securityDomain = SecurityDomain.builder().addRealm("Simple", new SimpleMapBackedSecurityRealm()).build().setDefaultRealmName("Simple").build(); + } + + @AfterClass + public static void removeCertProvider() { + Security.removeProvider(provider.getName()); + } + + private HttpServerAuthenticationMechanism createMechanism() throws HttpAuthenticationException { + Map props = new HashMap<>(); + return certFactory.createAuthenticationMechanism(CLIENT_CERT_NAME, props, getCallbackHandler("Duk3")); + } + + //Test request with no certs + @Test + public void testNoCert() throws Exception { + TestingHttpServerRequest request = new TestingHttpServerRequest(new String[]{}); + createMechanism().evaluateRequest(request); + Assert.assertEquals(Status.NO_AUTH, request.getResult()); + } + + //Test request with invalid/unknown cert + @Test + public void testUnknownCert() throws Exception { + TestingHttpServerRequest request = new TestingHttpServerRequest(new String[]{"Cert random"}, new X500Principal("CN=Duke, OU=Test, O=Wonderland, C=US")); + createMechanism().evaluateRequest(request); + Assert.assertEquals(Status.FAILED, request.getResult()); + } + + //Test request with known cert + @Test + public void testKnownCert() throws Exception { + TestingHttpServerRequest request = new TestingHttpServerRequest(new String[]{"Cert test"}, new X500Principal("CN=Duk3, OU=T3st, O=W0nd3rl4nd, C=US")); + createMechanism().evaluateRequest(request); + Assert.assertEquals(Status.COMPLETE, request.getResult()); + } +} diff --git a/tests/base/src/test/java/org/wildfly/security/http/digest/DigestAuthenticationMechanismTest.java b/tests/base/src/test/java/org/wildfly/security/http/digest/DigestAuthenticationMechanismTest.java index 1da396d9a58..a38274fe6a5 100644 --- a/tests/base/src/test/java/org/wildfly/security/http/digest/DigestAuthenticationMechanismTest.java +++ b/tests/base/src/test/java/org/wildfly/security/http/digest/DigestAuthenticationMechanismTest.java @@ -63,6 +63,18 @@ public static void removePasswordProvider() { Security.removeProvider(provider.getName()); } + public void evaluateRequest(String[] authorization, HttpServerAuthenticationMechanism mechanism) throws Exception{ + TestingHttpServerRequest request = new TestingHttpServerRequest(authorization); + mechanism.evaluateRequest(request); + Assert.assertEquals(Status.COMPLETE, request.getResult()); + } + + public void evaluateRequest(String[] authorization, HttpServerAuthenticationMechanism mechanism, String uri) throws Exception{ + TestingHttpServerRequest request = new TestingHttpServerRequest(authorization, new URI(uri)); + mechanism.evaluateRequest(request); + Assert.assertEquals(Status.COMPLETE, request.getResult()); + } + @Test public void testRfc2617() throws Exception { mockDigestNonce("AAAAAQABsxiWa25/kpFxsPCrpDCFsjkTzs/Xr7RPsi/VVN6faYp21Hia3h4="); @@ -78,7 +90,7 @@ public void testRfc2617() throws Exception { Assert.assertEquals(UNAUTHORIZED, response.getStatusCode()); Assert.assertEquals("Digest realm=\"testrealm@host.com\", nonce=\"AAAAAQABsxiWa25/kpFxsPCrpDCFsjkTzs/Xr7RPsi/VVN6faYp21Hia3h4=\", opaque=\"00000000000000000000000000000000\", algorithm=MD5, qop=auth", response.getAuthenticateHeader()); - TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] { + evaluateRequest(new String[] { "Digest username=\"Mufasa\",\n" + " realm=\"testrealm@host.com\",\n" + " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\",\n" + @@ -89,9 +101,21 @@ public void testRfc2617() throws Exception { " response=\"" + computeDigest("/dir/index.html", "dcd98b7102dd2f0e8b11d0f600bfb0c093", "0a4f113b", "00000001", "Mufasa", "Circle Of Life", "MD5", "testrealm@host.com", "auth", "GET") + "\",\n" + " opaque=\"00000000000000000000000000000000\",\n" + " algorithm=MD5" - }); - mechanism.evaluateRequest(request2); - Assert.assertEquals(Status.COMPLETE, request2.getResult()); + },mechanism); + + // test case insensitive + evaluateRequest(new String[] { + "DiGeSt username=\"Mufasa\",\n" + + " realm=\"testrealm@host.com\",\n" + + " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\",\n" + + " uri=\"/dir/index.html\",\n" + + " qop=auth,\n" + + " nc=00000001,\n" + + " cnonce=\"0a4f113b\",\n" + + " response=\"" + computeDigest("/dir/index.html", "dcd98b7102dd2f0e8b11d0f600bfb0c093", "0a4f113b", "00000001", "Mufasa", "Circle Of Life", "MD5", "testrealm@host.com", "auth", "GET") + "\",\n" + + " opaque=\"00000000000000000000000000000000\",\n" + + " algorithm=MD5" + },mechanism); } @Test @@ -104,7 +128,8 @@ public void testRfc2617EncodedQuery() throws Exception { String path = "/dir/index.html?foo=b%2Fr"; String uri = "http://localhost" + path; - TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] { + + evaluateRequest(new String[]{ "Digest username=\"Mufasa\",\n" + " realm=\"testrealm@host.com\",\n" + " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\",\n" + @@ -115,9 +140,7 @@ public void testRfc2617EncodedQuery() throws Exception { " response=\"" + computeDigest("http://localhost/dir/index.html?foo=b%2Fr", "dcd98b7102dd2f0e8b11d0f600bfb0c093", "0a4f113b", "00000001", "Mufasa", "Circle Of Life", "MD5", "testrealm@host.com", "auth", "GET") + "\",\n" + " opaque=\"00000000000000000000000000000000\",\n" + " algorithm=MD5" - }, new URI(uri)); - mechanism.evaluateRequest(request2); - Assert.assertEquals(Status.COMPLETE, request2.getResult()); + },mechanism,uri); } @Test @@ -130,7 +153,8 @@ public void testRfc2617EncodedPath() throws Exception { String path = "/dir/foo%2Fr/index.html?foo=b%2Fr"; String uri = "http://localhost" + path; - TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] { + + evaluateRequest(new String[] { "Digest username=\"Mufasa\",\n" + " realm=\"testrealm@host.com\",\n" + " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\",\n" + @@ -141,9 +165,7 @@ public void testRfc2617EncodedPath() throws Exception { " response=\"" + computeDigest("http://localhost/dir/foo%2Fr/index.html?foo=b%2Fr", "dcd98b7102dd2f0e8b11d0f600bfb0c093", "0a4f113b", "00000001", "Mufasa", "Circle Of Life", "MD5", "testrealm@host.com", "auth", "GET") + "\",\n" + " opaque=\"00000000000000000000000000000000\",\n" + " algorithm=MD5" - }, new URI(uri)); - mechanism.evaluateRequest(request2); - Assert.assertEquals(Status.COMPLETE, request2.getResult()); + },mechanism, uri); } @Test @@ -161,7 +183,7 @@ public void testRfc7616sha256() throws Exception { Assert.assertEquals(UNAUTHORIZED, response.getStatusCode()); Assert.assertEquals("Digest realm=\"http-auth@example.org\", nonce=\"7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v\", opaque=\"00000000000000000000000000000000\", algorithm=SHA-256, qop=auth", response.getAuthenticateHeader()); - TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] { + evaluateRequest(new String[] { "Digest username=\"Mufasa\",\n" + " realm=\"http-auth@example.org\",\n" + " uri=\"/dir/index.html\",\n" + @@ -172,9 +194,7 @@ public void testRfc7616sha256() throws Exception { " qop=auth,\n" + " response=\"" + computeDigest("/dir/index.html", "7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", "f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", "00000001", "Mufasa", "Circle of Life", "SHA-256", "http-auth@example.org", "auth", "GET") + "\",\n" + " opaque=\"FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS\"" - }); - mechanism.evaluateRequest(request2); - Assert.assertEquals(Status.COMPLETE, request2.getResult()); + },mechanism); } @Test @@ -192,7 +212,7 @@ public void testSha512_256() throws Exception { Assert.assertEquals(UNAUTHORIZED, response.getStatusCode()); Assert.assertEquals("Digest realm=\"api@example.org\", nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", opaque=\"00000000000000000000000000000000\", algorithm=SHA-512-256, qop=auth", response.getAuthenticateHeader()); - TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] { + evaluateRequest(new String[] { "Digest username*=UTF-8''J%C3%A4s%C3%B8n%20Doe,\n" + " realm=\"api@example.org\",\n" + " uri=\"/doe.json\",\n" + @@ -204,6 +224,68 @@ public void testSha512_256() throws Exception { " response=\"" + computeDigest("/doe.json", "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", "NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v", "00000001", "J\u00E4s\u00F8n Doe", "Secret, or not?", "SHA-512-256", "api@example.org", "auth", "GET") + "\",\n" + " opaque=\"00000000000000000000000000000000\",\n" + " userhash=false" + },mechanism); + } + + @Test + public void testSha256WithDigestPassword() throws Exception { + mockDigestNonce("5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK"); + Map props = new HashMap<>(); + props.put(CONFIG_REALM, "api@example.org"); + props.put("org.wildfly.security.http.validate-digest-uri", "false"); + HttpServerAuthenticationMechanism mechanism = digestFactory.createAuthenticationMechanism(DIGEST_NAME + "-" + SHA256, props, getCallbackHandler("J\u00E4s\u00F8n Doe", "api@example.org", "Secret, or not?", true)); + + TestingHttpServerRequest request1 = new TestingHttpServerRequest(null); + mechanism.evaluateRequest(request1); + Assert.assertEquals(Status.NO_AUTH, request1.getResult()); + TestingHttpServerResponse response = request1.getResponse(); + Assert.assertEquals(UNAUTHORIZED, response.getStatusCode()); + Assert.assertEquals("Digest realm=\"api@example.org\", nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", opaque=\"00000000000000000000000000000000\", algorithm=SHA-256, qop=auth", response.getAuthenticateHeader()); + + TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] { + "Digest username*=UTF-8''J%C3%A4s%C3%B8n%20Doe,\n" + + " realm=\"api@example.org\",\n" + + " uri=\"/doe.json\",\n" + + " algorithm=SHA-256,\n" + + " nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\",\n" + + " nc=00000001,\n" + + " cnonce=\"NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v\",\n" + + " qop=auth,\n" + + " response=\"" + computeDigest("/doe.json", "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", "NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v", "00000001", "J\u00E4s\u00F8n Doe", "Secret, or not?", "SHA-256", "api@example.org", "auth", "GET") + "\",\n" + + " opaque=\"00000000000000000000000000000000\",\n" + + " userhash=false" + }); + mechanism.evaluateRequest(request2); + Assert.assertEquals(Status.COMPLETE, request2.getResult()); + } + + @Test + public void testDigestMD5Password() throws Exception { + mockDigestNonce("5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK"); + Map props = new HashMap<>(); + props.put(CONFIG_REALM, "api@example.org"); + props.put("org.wildfly.security.http.validate-digest-uri", "false"); + HttpServerAuthenticationMechanism mechanism = digestFactory.createAuthenticationMechanism(DIGEST_NAME, props, getCallbackHandler("J\u00E4s\u00F8n Doe", "api@example.org", "Secret, or not?", true)); + + TestingHttpServerRequest request1 = new TestingHttpServerRequest(null); + mechanism.evaluateRequest(request1); + Assert.assertEquals(Status.NO_AUTH, request1.getResult()); + TestingHttpServerResponse response = request1.getResponse(); + Assert.assertEquals(UNAUTHORIZED, response.getStatusCode()); + Assert.assertEquals("Digest realm=\"api@example.org\", nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", opaque=\"00000000000000000000000000000000\", algorithm=MD5, qop=auth", response.getAuthenticateHeader()); + + TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] { + "Digest username*=UTF-8''J%C3%A4s%C3%B8n%20Doe,\n" + + " realm=\"api@example.org\",\n" + + " uri=\"/doe.json\",\n" + + " algorithm=MD5,\n" + + " nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\",\n" + + " nc=00000001,\n" + + " cnonce=\"NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v\",\n" + + " qop=auth,\n" + + " response=\"" + computeDigest("/doe.json", "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", "NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v", "00000001", "J\u00E4s\u00F8n Doe", "Secret, or not?", "MD5", "api@example.org", "auth", "GET") + "\",\n" + + " opaque=\"00000000000000000000000000000000\",\n" + + " userhash=false" }); mechanism.evaluateRequest(request2); Assert.assertEquals(Status.COMPLETE, request2.getResult()); @@ -238,4 +320,4 @@ private String encode(String src, MessageDigest md) { } return res.toString(); } -} +} \ No newline at end of file diff --git a/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java b/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java index 5ffc16fa88b..fd4513e18b1 100644 --- a/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java +++ b/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java @@ -31,6 +31,7 @@ import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.Arrays; @@ -40,31 +41,34 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; - import javax.net.ssl.SSLSession; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.x500.X500Principal; import javax.security.sasl.AuthorizeCallback; import javax.security.sasl.RealmCallback; - import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.Assert; - import org.wildfly.security.auth.callback.AuthenticationCompleteCallback; import org.wildfly.security.auth.callback.AvailableRealmsCallback; import org.wildfly.security.auth.callback.CachedIdentityAuthorizeCallback; import org.wildfly.security.auth.callback.CredentialCallback; import org.wildfly.security.auth.callback.EvidenceVerifyCallback; import org.wildfly.security.auth.callback.IdentityCredentialCallback; +import org.wildfly.security.auth.callback.PrincipalAuthorizeCallback; import org.wildfly.security.auth.server.SecurityIdentity; import org.wildfly.security.authz.Roles; +import org.wildfly.security.credential.BearerTokenCredential; import org.wildfly.security.credential.Credential; import org.wildfly.security.credential.PasswordCredential; +import org.wildfly.security.evidence.BearerTokenEvidence; import org.wildfly.security.evidence.PasswordGuessEvidence; +import org.wildfly.security.evidence.X509PeerCertificateChainEvidence; import org.wildfly.security.http.HttpAuthenticationException; import org.wildfly.security.http.HttpExchangeSpi; import org.wildfly.security.http.HttpScope; @@ -75,31 +79,40 @@ import org.wildfly.security.http.HttpServerResponse; import org.wildfly.security.http.Scope; import org.wildfly.security.http.basic.BasicMechanismFactory; +import org.wildfly.security.http.bearer.BearerMechanismFactory; +import org.wildfly.security.http.cert.ClientCertMechanismFactory; import org.wildfly.security.http.digest.DigestMechanismFactory; import org.wildfly.security.http.digest.NonceManager; import org.wildfly.security.http.external.ExternalMechanismFactory; import org.wildfly.security.password.Password; import org.wildfly.security.password.PasswordFactory; import org.wildfly.security.password.interfaces.ClearPassword; +import org.wildfly.security.password.interfaces.DigestPassword; import org.wildfly.security.password.spec.ClearPasswordSpec; +import org.wildfly.security.password.spec.DigestPasswordAlgorithmSpec; +import org.wildfly.security.password.spec.EncryptablePasswordSpec; +import org.wildfly.security.x500.cert.SelfSignedX509CertificateAndSigningKey; import mockit.Mock; import mockit.MockUp; -// has dependency on wildfly-elytron-sasl, wildfly-elytron-http-basic and wildfly-elytron-digest +// has dependency on wildfly-elytron-sasl, wildfly-elytron-http-cert, wildfly-elytron-http-basic and wildfly-elytron-digest public class AbstractBaseHttpTest { protected HttpServerAuthenticationMechanismFactory basicFactory = new BasicMechanismFactory(ELYTRON_PASSWORD_PROVIDERS.get()); protected HttpServerAuthenticationMechanismFactory digestFactory = new DigestMechanismFactory(ELYTRON_PASSWORD_PROVIDERS.get()); protected final HttpServerAuthenticationMechanismFactory externalFactory = new ExternalMechanismFactory(ELYTRON_PASSWORD_PROVIDERS.get()); + protected final HttpServerAuthenticationMechanismFactory bearerFactory = new BearerMechanismFactory(ELYTRON_PASSWORD_PROVIDERS.get()); + protected HttpServerAuthenticationMechanismFactory certFactory = new ClientCertMechanismFactory(ELYTRON_PASSWORD_PROVIDERS.get()[0]); protected HttpServerAuthenticationMechanismFactory statefulBasicFactory = new org.wildfly.security.http.sfbasic.BasicMechanismFactory(ELYTRON_PASSWORD_PROVIDERS.get()); - protected void mockDigestNonce(final String nonce){ - new MockUp(){ + protected void mockDigestNonce(final String nonce) { + new MockUp() { @Mock String generateNonce(byte[] salt) { return nonce; } + @Mock boolean useNonce(final String nonce, byte[] salt, int nonceCount) { return true; @@ -113,6 +126,7 @@ protected SecurityIdentity mockSecurityIdentity(Principal p) { public Principal getPrincipal() { return p; } + @Mock public Roles getRoles() { return Roles.NONE; @@ -137,6 +151,7 @@ protected static class TestingHttpServerRequest implements HttpServerRequest { private List cookies; private String requestMethod = "GET"; private Map> requestHeaders = new HashMap<>(); + private X500Principal testPrincipal = null; private Map sessionScopeAttachments = new HashMap<>(); public TestingHttpServerRequest(String[] authorization) { @@ -147,6 +162,15 @@ public TestingHttpServerRequest(String[] authorization) { this.cookies = new ArrayList<>(); } + public TestingHttpServerRequest(String[] authorization, X500Principal principal) { + if (authorization != null) { + requestHeaders.put(AUTHORIZATION, Arrays.asList(authorization)); + } + this.remoteUser = null; + this.cookies = new ArrayList<>(); + this.testPrincipal = principal; + } + public TestingHttpServerRequest(String[] authorization, URI requestURI) { if (authorization != null) { requestHeaders.put(AUTHORIZATION, Arrays.asList(authorization)); @@ -193,51 +217,10 @@ public TestingHttpServerRequest(String[] authorization, URI requestURI, String c if (cookie != null) { final String cookieName = cookie.substring(0, cookie.indexOf('=')); final String cookieValue = cookie.substring(cookie.indexOf('=') + 1); - cookies.add(new HttpServerCookie() { - @Override - public String getName() { - return cookieName; - } - - @Override - public String getValue() { - return cookieValue; - } - - @Override - public String getDomain() { - return null; - } - - @Override - public int getMaxAge() { - return -1; - } - - @Override - public String getPath() { - return "/"; - } - - @Override - public boolean isSecure() { - return false; - } - - @Override - public int getVersion() { - return 0; - } - - @Override - public boolean isHttpOnly() { - return true; - } - }); + cookies.add(HttpServerCookie.getInstance(cookieName, cookieValue, null, -1, "/", false, 0, true)); } } - public Status getResult() { return result; } @@ -257,12 +240,18 @@ public String getFirstRequestHeaderValue(String headerName) { return headerValues != null ? headerValues.get(0) : null; } + public SSLSession getSSLSession() { - throw new IllegalStateException(); + return null; } public Certificate[] getPeerCertificates() { - throw new IllegalStateException(); + if (testPrincipal != null) { + X509Certificate cert1 = SelfSignedX509CertificateAndSigningKey.builder().setDn(testPrincipal).build().getSelfSignedCertificate(); + return new Certificate[]{ cert1 }; + } + + return null; } public void noAuthenticationInProgress(HttpServerMechanismsResponder responder) { @@ -343,45 +332,48 @@ public boolean resumeRequest() { } public HttpScope getScope(Scope scope) { - return new HttpScope() { - - @Override - public boolean exists() { - return true; - } + if (scope.equals(Scope.SSL_SESSION)) { + return null; + } else { + return new HttpScope() { - @Override - public boolean create() { - return false; - } + @Override + public boolean exists() { + return true; + } - @Override - public boolean supportsAttachments() { - return true; - } + @Override + public boolean create() { + return false; + } - @Override - public boolean supportsInvalidation() { - return false; - } + @Override + public boolean supportsAttachments() { + return true; + } - @Override - public void setAttachment(String key, Object value) { - if (scope.equals(Scope.SESSION)) { - sessionScopeAttachments.put(key, value); + @Override + public boolean supportsInvalidation() { + return false; } - } - @Override - public Object getAttachment(String key) { - if (scope.equals(Scope.SESSION)) { - return sessionScopeAttachments.get(key); - } else { - return null; + @Override + public void setAttachment(String key, Object value) { + if (scope.equals(Scope.SESSION)) { + sessionScopeAttachments.put(key, value); + } } - } - }; + @Override + public Object getAttachment(String key) { + if (scope.equals(Scope.SESSION)) { + return sessionScopeAttachments.get(key); + } else { + return null; + } + } + }; + } } public Collection getScopeIds(Scope scope) { @@ -392,7 +384,7 @@ public HttpScope getScope(Scope scope, String id) { throw new IllegalStateException(); } - public void setRemoteUser (String remoteUser) { + public void setRemoteUser(String remoteUser) { this.remoteUser = remoteUser; } @@ -459,9 +451,25 @@ public boolean forward(String path) { } } + protected CallbackHandler getCallbackHandler(String realm) { + return getCallbackHandler(null, realm, null); + }; + protected CallbackHandler getCallbackHandler(String username, String realm, String password) { + return getCallbackHandler(username, realm, password, null, false); + } + + protected CallbackHandler getCallbackHandler(String username, String realm, String password, String token) { + return getCallbackHandler(username, realm, password, token, false); + } + + protected CallbackHandler getCallbackHandler(String username, String realm, String password, boolean useDigestPassword) { + return getCallbackHandler(username, realm, password, null, useDigestPassword); + } + + protected CallbackHandler getCallbackHandler(String username, String realm, String password, String token, boolean useDigestPassword) { return callbacks -> { - for(Callback callback : callbacks) { + for (Callback callback : callbacks) { if (callback instanceof AvailableRealmsCallback) { ((AvailableRealmsCallback) callback).setRealmNames(realm); } else if (callback instanceof RealmCallback) { @@ -469,38 +477,75 @@ protected CallbackHandler getCallbackHandler(String username, String realm, Stri } else if (callback instanceof NameCallback) { Assert.assertEquals(username, ((NameCallback) callback).getDefaultName()); } else if (callback instanceof CredentialCallback) { - if (!ClearPassword.ALGORITHM_CLEAR.equals(((CredentialCallback) callback).getAlgorithm())) { - throw new UnsupportedCallbackException(callback); - } - try { - PasswordFactory factory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR, ELYTRON_PASSWORD_PROVIDERS); - Password pass = factory.generatePassword(new ClearPasswordSpec(password.toCharArray())); - ((CredentialCallback) callback).setCredential(new PasswordCredential(pass)); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { - throw new IllegalStateException(e); + if (useDigestPassword) { + String credentialAlgorithm = ((CredentialCallback) callback).getAlgorithm(); + if (! DigestPassword.ALGORITHM_DIGEST_SHA_256.equals(credentialAlgorithm) && + ! DigestPassword.ALGORITHM_DIGEST_MD5.equals(credentialAlgorithm)) { + throw new UnsupportedCallbackException(callback); + } + try { + PasswordFactory factory = PasswordFactory.getInstance(credentialAlgorithm, ELYTRON_PASSWORD_PROVIDERS); + DigestPasswordAlgorithmSpec algorithmSpec = new DigestPasswordAlgorithmSpec(username, realm); + EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(password.toCharArray(), algorithmSpec); + DigestPassword digestPassword = (DigestPassword) factory.generatePassword(encryptableSpec); + ((CredentialCallback) callback).setCredential(new PasswordCredential(digestPassword)); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new IllegalStateException(e); + } + } else { + if (!ClearPassword.ALGORITHM_CLEAR.equals(((CredentialCallback) callback).getAlgorithm())) { + throw new UnsupportedCallbackException(callback); + } + try { + PasswordFactory factory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR, ELYTRON_PASSWORD_PROVIDERS); + Password pass = factory.generatePassword(new ClearPasswordSpec(password.toCharArray())); + ((CredentialCallback) callback).setCredential(new PasswordCredential(pass)); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new IllegalStateException(e); + } } } else if (callback instanceof EvidenceVerifyCallback) { - PasswordGuessEvidence evidence = (PasswordGuessEvidence) ((EvidenceVerifyCallback) callback).getEvidence(); - ((EvidenceVerifyCallback) callback).setVerified(Arrays.equals(evidence.getGuess(), password.toCharArray())); - evidence.destroy(); + if (((EvidenceVerifyCallback) callback).getEvidence() instanceof PasswordGuessEvidence) { + PasswordGuessEvidence evidence = (PasswordGuessEvidence) ((EvidenceVerifyCallback) callback).getEvidence(); + ((EvidenceVerifyCallback) callback).setVerified(Arrays.equals(evidence.getGuess(), password.toCharArray())); + evidence.destroy(); + } else if (((EvidenceVerifyCallback) callback).getEvidence() instanceof BearerTokenEvidence) { + BearerTokenEvidence evidence = (BearerTokenEvidence) ((EvidenceVerifyCallback) callback).getEvidence(); + ((EvidenceVerifyCallback) callback).setVerified(Objects.equals(token, evidence.getToken())); + } else if (((EvidenceVerifyCallback) callback).getEvidence() instanceof X509PeerCertificateChainEvidence) { + X509PeerCertificateChainEvidence evidence = (X509PeerCertificateChainEvidence) ((EvidenceVerifyCallback) callback).getEvidence(); + evidence.setDecodedPrincipal(evidence.getFirstCertificate().getIssuerX500Principal()); + ((EvidenceVerifyCallback) callback).setVerified("CN=Duk3,OU=T3st,O=W0nd3rl4nd,C=US".equals(evidence.getFirstCertificate().getIssuerX500Principal().getName())); + } } else if (callback instanceof AuthenticationCompleteCallback) { // NO-OP } else if (callback instanceof IdentityCredentialCallback) { Credential credential = ((IdentityCredentialCallback) callback).getCredential(); - MatcherAssert.assertThat(credential, CoreMatchers.instanceOf(PasswordCredential.class)); - ClearPassword clearPwdCredential = ((PasswordCredential) credential).getPassword().castAs(ClearPassword.class); - Assert.assertNotNull(clearPwdCredential); - Assert.assertArrayEquals(password.toCharArray(), clearPwdCredential.getPassword()); + if (token != null) { + MatcherAssert.assertThat(credential, CoreMatchers.instanceOf(BearerTokenCredential.class)); + String obtainedToken = ((BearerTokenCredential) credential).getToken(); + Assert.assertNotNull(obtainedToken); + Assert.assertEquals(obtainedToken, token); + } else { + MatcherAssert.assertThat(credential, CoreMatchers.instanceOf(PasswordCredential.class)); + ClearPassword clearPwdCredential = ((PasswordCredential) credential).getPassword().castAs(ClearPassword.class); + Assert.assertNotNull(clearPwdCredential); + Assert.assertArrayEquals(password.toCharArray(), clearPwdCredential.getPassword()); + } } else if (callback instanceof AuthorizeCallback) { - if(username.equals(((AuthorizeCallback) callback).getAuthenticationID()) && - username.equals(((AuthorizeCallback) callback).getAuthorizationID())) { + if (token != null) { ((AuthorizeCallback) callback).setAuthorized(true); } else { - ((AuthorizeCallback) callback).setAuthorized(false); + if (username.equals(((AuthorizeCallback) callback).getAuthenticationID()) && + username.equals(((AuthorizeCallback) callback).getAuthorizationID())) { + ((AuthorizeCallback) callback).setAuthorized(true); + } else { + ((AuthorizeCallback) callback).setAuthorized(false); + } } } else if (callback instanceof CachedIdentityAuthorizeCallback) { CachedIdentityAuthorizeCallback ciac = (CachedIdentityAuthorizeCallback) callback; - if(ciac.getAuthorizationPrincipal() != null && + if (ciac.getAuthorizationPrincipal() != null && username.equals(ciac.getAuthorizationPrincipal().getName())) { ciac.setAuthorized(mockSecurityIdentity(ciac.getAuthorizationPrincipal())); } else if (ciac.getIdentity() != null && username.equals(ciac.getIdentity().getPrincipal().getName())) { @@ -508,6 +553,9 @@ protected CallbackHandler getCallbackHandler(String username, String realm, Stri } else { ciac.setAuthorized(null); } + } else if (callback instanceof PrincipalAuthorizeCallback){ + PrincipalAuthorizeCallback pac = (PrincipalAuthorizeCallback) callback; + pac.setAuthorized(true); } else { throw new UnsupportedCallbackException(callback); } diff --git a/tests/base/src/test/java/org/wildfly/security/ldap/DirContextFactoryRule.java b/tests/base/src/test/java/org/wildfly/security/ldap/DirContextFactoryRule.java index 8917941078b..d1dac5e6bca 100644 --- a/tests/base/src/test/java/org/wildfly/security/ldap/DirContextFactoryRule.java +++ b/tests/base/src/test/java/org/wildfly/security/ldap/DirContextFactoryRule.java @@ -94,7 +94,6 @@ private static void createStores(KeyStore localhostKeyStore, KeyStore scarabKeyS .addExtension(false, "BasicConstraints", "CA:true,pathlen:2147483647") .build(); X509Certificate issuerCertificate = issuerSelfSignedX509CertificateAndSigningKey.getSelfSignedCertificate(); - localhostKeyStore.setCertificateEntry("ca", issuerCertificate); trustStore.setCertificateEntry("mykey", issuerCertificate); // Generates certificate and keystore for Localhost diff --git a/tests/base/src/test/java/org/wildfly/security/ldap/ModifiabilitySuiteChild.java b/tests/base/src/test/java/org/wildfly/security/ldap/ModifiabilitySuiteChild.java index a42a8b3074e..0b29b35b41d 100644 --- a/tests/base/src/test/java/org/wildfly/security/ldap/ModifiabilitySuiteChild.java +++ b/tests/base/src/test/java/org/wildfly/security/ldap/ModifiabilitySuiteChild.java @@ -72,7 +72,7 @@ public static void createRealm() throws InvalidNameException { AttributeMapping.fromIdentity().from("sn").to("lastName").build(), AttributeMapping.fromIdentity().from("description").to("description").build(), AttributeMapping.fromIdentity().from("telephoneNumber").to("phones").build(), - AttributeMapping.fromFilter("(&(objectClass=groupOfNames)(member={0}))").searchDn("ou=Finance,dc=elytron,dc=wildfly,dc=org").extractRdn("OU").to("businessArea").build()) + AttributeMapping.fromFilter("(&(objectClass=groupOfNames)(member=uid={0}))").searchDn("ou=Finance,dc=elytron,dc=wildfly,dc=org").extractRdn("OU").to("businessArea").build()) .setNewIdentityParent(new LdapName("dc=elytron,dc=wildfly,dc=org")) .setNewIdentityAttributes(attributes) .setIteratorFilter("(uid=*)") diff --git a/tests/base/src/test/java/org/wildfly/security/ldap/PrincipalMappingSuiteChild.java b/tests/base/src/test/java/org/wildfly/security/ldap/PrincipalMappingSuiteChild.java index 433ad165696..5a4bfa775fb 100644 --- a/tests/base/src/test/java/org/wildfly/security/ldap/PrincipalMappingSuiteChild.java +++ b/tests/base/src/test/java/org/wildfly/security/ldap/PrincipalMappingSuiteChild.java @@ -36,7 +36,7 @@ public class PrincipalMappingSuiteChild { @Test - public void testSimpleToDn() throws RealmUnavailableException { + public void testLdapRealmPrincipalMapping() throws RealmUnavailableException { SecurityRealm realm = LdapSecurityRealmBuilder.builder() .setDirContextSupplier(LdapTestSuite.dirContextFactory.create()) .identityMapping() @@ -68,40 +68,6 @@ public void testDnToSimple() throws RealmUnavailableException { assertFalse("Exists", identity.exists()); } - @Test - public void testSimpleToSimpleValidate() throws RealmUnavailableException { - SecurityRealm realm = LdapSecurityRealmBuilder.builder() - .setDirContextSupplier(LdapTestSuite.dirContextFactory.create()) - .identityMapping() - .setSearchDn("dc=elytron,dc=wildfly,dc=org") - .setRdnIdentifier("uid") - .build() - .build(); - - RealmIdentity identity = realm.getRealmIdentity(new NamePrincipal("PlainUser")); - assertTrue("Exists", identity.exists()); - - identity = realm.getRealmIdentity(new NamePrincipal("nobody")); - assertFalse("Exists", identity.exists()); - } - - @Test - public void testSimpleToSimpleReload() throws RealmUnavailableException { - SecurityRealm realm = LdapSecurityRealmBuilder.builder() - .setDirContextSupplier(LdapTestSuite.dirContextFactory.create()) - .identityMapping() - .setSearchDn("dc=elytron,dc=wildfly,dc=org") - .setRdnIdentifier("uid") - .build() - .build(); - - RealmIdentity identity = realm.getRealmIdentity(new NamePrincipal("PlainUser")); - assertTrue("Exists", identity.exists()); - - identity = realm.getRealmIdentity(new NamePrincipal("nobody")); - assertFalse("Exists", identity.exists()); - } - @Test public void testDnToDnNoLookup() throws RealmUnavailableException { SecurityRealm realm = LdapSecurityRealmBuilder.builder() diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/digest/CompatibilityServerTest.java b/tests/base/src/test/java/org/wildfly/security/sasl/digest/CompatibilityServerTest.java index 3258ded2825..42de9f5290c 100644 --- a/tests/base/src/test/java/org/wildfly/security/sasl/digest/CompatibilityServerTest.java +++ b/tests/base/src/test/java/org/wildfly/security/sasl/digest/CompatibilityServerTest.java @@ -136,12 +136,7 @@ public void testRfc2831example2() throws Exception { assertEquals("chris", server.getAuthorizationID()); } - - /** - * Test with authorization ID (authzid) of other user - */ - @Test - public void testUnauthorizedAuthorizationId() throws Exception { + private SaslServer testCommonInitSaslServer() throws Exception { mockNonce("OA9BSXrbuRhWay"); Map serverProps = new HashMap(); @@ -159,6 +154,18 @@ public void testUnauthorizedAuthorizationId() throws Exception { assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8")); assertFalse(server.isComplete()); + return server; + } + + + //********************************************** */ + /** + * Test with authorization ID (authzid) of other user + */ + @Test + public void testUnauthorizedAuthorizationId() throws Exception { + SaslServer server = testCommonInitSaslServer(); + byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",maxbuf=65536,response=0d071450228e395e2c0999e02b6aa665,qop=auth,authzid=\"george\"".getBytes(StandardCharsets.UTF_8); try { @@ -560,22 +567,7 @@ public void testQopAuthConfRc440() throws Exception { */ @Test public void testReplayAttack() throws Exception { - mockNonce("OA9BSXrbuRhWay"); - - Map serverProps = new HashMap(); - serverProps.put(REALM_PROPERTY, "elwood.innosoft.com"); - SaslServer server = - new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5) - .setUserName("chris") - .setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray())) - .setProtocol("acap").setServerName("elwood.innosoft.com") - .setProperties(serverProps) - .build(); - assertFalse(server.isComplete()); - - byte[] message1 = server.evaluateResponse(new byte[0]); - assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8")); - assertFalse(server.isComplete()); + SaslServer server = testCommonInitSaslServer(); byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",nc=00000001,cnonce=\"OA6MHXh6VqTrRk\",digest-uri=\"imap/elwood.innosoft.com\",response=d388dad90d4bbd760a152321f2143af7,qop=auth".getBytes(StandardCharsets.UTF_8); try{ @@ -591,22 +583,7 @@ public void testReplayAttack() throws Exception { */ @Test public void testBadResponse() throws Exception { - mockNonce("OA9BSXrbuRhWay"); - - Map serverProps = new HashMap(); - serverProps.put(REALM_PROPERTY, "elwood.innosoft.com"); - SaslServer server = - new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5) - .setUserName("chris") - .setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray())) - .setProtocol("acap").setServerName("elwood.innosoft.com") - .setProperties(serverProps) - .build(); - assertFalse(server.isComplete()); - - byte[] message1 = server.evaluateResponse(new byte[0]); - assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8")); - assertFalse(server.isComplete()); + SaslServer server = testCommonInitSaslServer(); byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",response=d388dad90d4bbd760a152321f2143af7,qop=auth".getBytes(StandardCharsets.UTF_8); try{ @@ -743,4 +720,28 @@ public void testMoreRealmsWithEscapedDelimiters() throws Exception { assertEquals("chris", server.getAuthorizationID()); } + @Test + public void testIncorrectResponseField() throws Exception { + mockNonce("OA6MG9tEQGm2hh"); + + SaslServer server = + new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5) + .setUserName("chris") + .setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray())) + .setProtocol("imap").setServerName("elwood.innosoft.com") + .addMechanismRealm("elwood.innosoft.com") + .build(); + assertFalse(server.isComplete()); + + byte[] message = server.evaluateResponse(new byte[0]); + assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",charset=utf-8,algorithm=md5-sess", new String(message, "UTF-8")); + assertFalse(server.isComplete()); + + byte[] invalidMessage = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",nc=00000001,cnonce=\"OA6MHXh6VqTrRk\",digest-uri=\"imap/elwood.innosoft.com\",response=incorrectResponse,qop=auth".getBytes(StandardCharsets.UTF_8); + try { + server.evaluateResponse(invalidMessage); + } catch (SaslException e) { + assertTrue(e.getMessage().contains("invalid proof")); + } + } } diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/digest/DigestTest.java b/tests/base/src/test/java/org/wildfly/security/sasl/digest/DigestTest.java index e8e62fa8d51..4ec12c09e1f 100644 --- a/tests/base/src/test/java/org/wildfly/security/sasl/digest/DigestTest.java +++ b/tests/base/src/test/java/org/wildfly/security/sasl/digest/DigestTest.java @@ -42,6 +42,7 @@ import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.sasl.Sasl; import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; import javax.security.sasl.SaslServerFactory; @@ -146,14 +147,7 @@ public void testSuccessfulExchange() throws Exception { CallbackHandler clientCallback = createClearPwdClientCallbackHandler("George", "gpwd", "TestRealm"); SaslClient client = Sasl.createSaslClient(new String[]{ DIGEST }, "George", "TestProtocol", "TestServer", Collections.emptyMap(), clientCallback); - assertFalse(client.hasInitialResponse()); - byte[] message = server.evaluateResponse(new byte[0]); - log.debug("Challenge:"+ new String(message, StandardCharsets.ISO_8859_1)); - message = client.evaluateChallenge(message); - log.debug("Client response:"+ new String(message, StandardCharsets.ISO_8859_1)); - server.evaluateResponse(message); - assertTrue(server.isComplete()); - assertEquals("George", server.getAuthorizationID()); + assertExchange(server, client); } /** * Test a successful exchange using the DIGEST mechanism but the default realm. @@ -171,15 +165,7 @@ public void testSuccessfulExchange_DefaultRealm() throws Exception { CallbackHandler clientCallback = createClearPwdClientCallbackHandler("George", "gpwd", null); SaslClient client = Sasl.createSaslClient(new String[]{DIGEST}, "George", "TestProtocol", "TestServer", Collections.emptyMap(), clientCallback); - assertFalse(client.hasInitialResponse()); - byte[] message = server.evaluateResponse(new byte[0]); - log.debug("Challenge:"+ new String(message, StandardCharsets.ISO_8859_1)); - message = client.evaluateChallenge(message); - log.debug("Client response:"+ new String(message, StandardCharsets.ISO_8859_1)); - - server.evaluateResponse(message); - assertTrue(server.isComplete()); - assertEquals("George", server.getAuthorizationID()); + assertExchange(server, client); } /** @@ -201,15 +187,7 @@ public void testSuccessfulExchange_AlternativeProtocol() throws Exception { CallbackHandler clientCallback = createClearPwdClientCallbackHandler("George", "gpwd", null); SaslClient client = Sasl.createSaslClient(new String[]{DIGEST}, "George", "OtherProtocol", "TestServer", Collections.emptyMap(), clientCallback); - assertFalse(client.hasInitialResponse()); - byte[] message = server.evaluateResponse(new byte[0]); - log.debug("Challenge:"+ new String(message, StandardCharsets.UTF_8)); - message = client.evaluateChallenge(message); - log.debug("Client response:"+ new String(message, StandardCharsets.UTF_8)); - - server.evaluateResponse(message); - assertTrue(server.isComplete()); - assertEquals("George", server.getAuthorizationID()); + assertExchange(server, client); } /** @@ -318,14 +296,7 @@ public void testRealmSelection() throws Exception { CallbackHandler clientCallback = createClearPwdClientCallbackHandler("George", "gpwd", "last\\ "); SaslClient client = Sasl.createSaslClient(new String[]{DIGEST}, "George", "TestProtocol", "TestServer", Collections.emptyMap(), clientCallback); - assertFalse(client.hasInitialResponse()); - byte[] message = server.evaluateResponse(new byte[0]); - log.debug("Challenge:"+ new String(message, StandardCharsets.ISO_8859_1)); - message = client.evaluateChallenge(message); - log.debug("Client response:" + new String(message, StandardCharsets.ISO_8859_1)); - server.evaluateResponse(message); - assertTrue(server.isComplete()); - assertEquals("George", server.getAuthorizationID()); + assertExchange(server, client); } /* @@ -515,13 +486,7 @@ public void testSuccessfulExchange_PreHashedClient() throws Exception { SaslClient client = Sasl.createSaslClient(new String[]{DIGEST}, "George", "TestProtocol", "TestServer", clientProps, clientCallback); - assertFalse(client.hasInitialResponse()); - byte[] message = server.evaluateResponse(new byte[0]); - - message = client.evaluateChallenge(message); - server.evaluateResponse(message); - assertTrue(server.isComplete()); - assertEquals("George", server.getAuthorizationID()); + assertExchange(server,client); } /** @@ -544,13 +509,7 @@ public void testSuccessfulExchange_DefaultRealm_PreHashedClient() throws Excepti clientProps.put(PRE_DIGESTED_PROPERTY, "true"); SaslClient client = Sasl.createSaslClient(new String[]{DIGEST}, "George", "TestProtocol", "TestServer", clientProps, clientCallback); - assertFalse(client.hasInitialResponse()); - byte[] message = server.evaluateResponse(new byte[0]); - message = client.evaluateChallenge(message); - - server.evaluateResponse(message); - assertTrue(server.isComplete()); - assertEquals("George", server.getAuthorizationID()); + assertExchange(server,client); } /** @@ -753,14 +712,7 @@ public void testSuccessfulExchangeNullAuthorizationId() throws Exception { CallbackHandler clientCallback = createClearPwdClientCallbackHandler("George", "gpwd", "TestRealm"); SaslClient client = Sasl.createSaslClient(new String[]{ DIGEST }, null, "TestProtocol", "TestServer", Collections.emptyMap(), clientCallback); - assertFalse(client.hasInitialResponse()); - byte[] message = server.evaluateResponse(new byte[0]); - log.debug("Challenge:"+ new String(message, StandardCharsets.ISO_8859_1)); - message = client.evaluateChallenge(message); - log.debug("Client response:"+ new String(message, StandardCharsets.ISO_8859_1)); - server.evaluateResponse(message); - assertTrue(server.isComplete()); - assertEquals("George", server.getAuthorizationID()); + assertExchange(server, client); } /** @@ -779,14 +731,7 @@ public void testSuccessfulExchangeEmptyAuthorizationId() throws Exception { CallbackHandler clientCallback = createClearPwdClientCallbackHandler("George", "gpwd", "TestRealm"); SaslClient client = Sasl.createSaslClient(new String[]{ DIGEST }, "", "TestProtocol", "TestServer", Collections.emptyMap(), clientCallback); - assertFalse(client.hasInitialResponse()); - byte[] message = server.evaluateResponse(new byte[0]); - log.debug("Challenge:"+ new String(message, StandardCharsets.ISO_8859_1)); - message = client.evaluateChallenge(message); - log.debug("Client response:"+ new String(message, StandardCharsets.ISO_8859_1)); - server.evaluateResponse(message); - assertTrue(server.isComplete()); - assertEquals("George", server.getAuthorizationID()); + assertExchange(server, client); } private KeySpec getDigestKeySpec(String username, String password, String realm) throws NoSuchAlgorithmException { @@ -826,4 +771,14 @@ public void testUnboundServerName() throws Exception { assertEquals("TestServer5", server.getNegotiatedProperty(Sasl.BOUND_SERVER_NAME)); } + private void assertExchange(SaslServer server, SaslClient client) throws SaslException { + assertFalse(client.hasInitialResponse()); + byte[] message = server.evaluateResponse(new byte[0]); + log.debug("Challenge:"+ new String(message, StandardCharsets.ISO_8859_1)); + message = client.evaluateChallenge(message); + log.debug("Client response:"+ new String(message, StandardCharsets.ISO_8859_1)); + server.evaluateResponse(message); + assertTrue(server.isComplete()); + assertEquals("George", server.getAuthorizationID()); + } } diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/gssapi/TestKDC.java b/tests/base/src/test/java/org/wildfly/security/sasl/gssapi/TestKDC.java index 88789b58e58..23fd54723d0 100644 --- a/tests/base/src/test/java/org/wildfly/security/sasl/gssapi/TestKDC.java +++ b/tests/base/src/test/java/org/wildfly/security/sasl/gssapi/TestKDC.java @@ -26,7 +26,6 @@ import java.util.Map; import org.apache.directory.api.ldap.model.entry.DefaultEntry; -import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; import org.apache.directory.api.ldap.model.ldif.LdifEntry; import org.apache.directory.api.ldap.model.ldif.LdifReader; import org.apache.directory.api.ldap.model.schema.SchemaManager; @@ -37,21 +36,21 @@ import org.apache.directory.server.core.factory.DirectoryServiceFactory; import org.apache.directory.server.core.factory.PartitionFactory; import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor; -import org.apache.directory.server.kerberos.KerberosConfig; -import org.apache.directory.server.kerberos.kdc.KdcServer; import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory; -import org.apache.directory.server.kerberos.shared.keytab.Keytab; -import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry; import org.apache.directory.server.ldap.LdapServer; import org.apache.directory.server.protocol.shared.transport.TcpTransport; import org.apache.directory.server.protocol.shared.transport.Transport; -import org.apache.directory.server.protocol.shared.transport.UdpTransport; -import org.apache.directory.shared.kerberos.KerberosTime; import org.apache.directory.shared.kerberos.codec.types.EncryptionType; import org.apache.directory.shared.kerberos.components.EncryptionKey; +import org.apache.kerby.kerberos.kerb.KrbException; +import org.apache.kerby.kerberos.kerb.keytab.Keytab; +import org.apache.kerby.kerberos.kerb.keytab.KeytabEntry; +import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer; +import org.apache.kerby.kerberos.kerb.server.impl.DefaultInternalKdcServerImpl; +import org.apache.kerby.kerberos.kerb.type.KerberosTime; +import org.apache.kerby.kerberos.kerb.type.base.PrincipalName; import org.jboss.logging.Logger; -import javax.security.auth.kerberos.KerberosPrincipal; /** * Utility class to wrap starting and stopping of the directory server and the KDC. @@ -63,7 +62,7 @@ public class TestKDC { private static Logger log = Logger.getLogger(TestKDC.class); private File workingDir; private DirectoryService directoryService; - private KdcServer kdcServer; + private SimpleKdcServer kdcServer; private String originalConfig; private boolean exposeLdapServer; private LdapServer ldapServer; @@ -117,7 +116,6 @@ private static void createPartition(final DirectoryServiceFactory dsf, final Sch for (String current : indexAttributes) { pf.addIndex(p, current, 10); } - p.setCacheService(directoryService.getCacheService()); p.initialize(); directoryService.addPartition(p); } @@ -156,27 +154,24 @@ public void startKDC() { File configPath = new File(TestKDC.class.getResource("/krb5.conf").getFile()); originalConfig = System.setProperty("java.security.krb5.conf", configPath.getAbsolutePath()); - KdcServer kdcServer = new KdcServer(); - kdcServer.setServiceName("TestKDCServer"); - kdcServer.setSearchBaseDn("dc=wildfly,dc=org"); - KerberosConfig config = kdcServer.getConfig(); - config.setServicePrincipal("krbtgt/WILDFLY.ORG@WILDFLY.ORG"); - config.setPrimaryRealm("WILDFLY.ORG"); - config.setMaximumTicketLifetime(60000 * 1440); - config.setMaximumRenewableLifetime(60000 * 10080); - - config.setPaEncTimestampRequired(false); + try { + SimpleKdcServer kdcServer = new SimpleKdcServer(); + kdcServer.setKdcRealm("WILDFLY.ORG"); + kdcServer.setKdcHost("localhost"); + kdcServer.setInnerKdcImpl(new DefaultInternalKdcServerImpl(kdcServer.getKdcSetting())); + kdcServer.setAllowUdp(true); + kdcServer.setKdcUdpPort(6088); - UdpTransport udp = new UdpTransport("localhost", 6088); - kdcServer.addTransports(udp); + kdcServer.init(); - kdcServer.setDirectoryService(directoryService); + kdcServer.createPrincipal("sasl/test_server_1@WILDFLY.ORG", "servicepwd"); + kdcServer.createPrincipal("sasl/test_server_2@WILDFLY.ORG", "servicepwd"); + kdcServer.createPrincipal("jduke@WILDFLY.ORG", "theduke"); - // Launch the server - try { + // Launch the server kdcServer.start(); this.kdcServer = kdcServer; - } catch (IOException | LdapInvalidDnException e) { + } catch (KrbException e) { throw new IllegalStateException("Unable to start KDC", e); } } @@ -186,7 +181,11 @@ private void stopKDC() { return; } - kdcServer.stop(); + try { + kdcServer.stop(); + } catch (KrbException e) { + throw new IllegalStateException("Unable to stop KDC", e); + } kdcServer = null; if (originalConfig != null) { @@ -232,7 +231,7 @@ public void stopAll() { public String generateKeyTab(String keyTabFileName, String... credentials) { log.debug("Generating keytab: " + keyTabFileName); List entries = new ArrayList<>(); - KerberosTime ktm = new KerberosTime(); + KerberosTime ktm = KerberosTime.now(); for (int i = 0; i < credentials.length;) { String principal = credentials[i++]; @@ -242,15 +241,16 @@ public String generateKeyTab(String keyTabFileName, String... credentials) { .entrySet()) { EncryptionKey key = keyEntry.getValue(); log.debug("Adding key=" + key + " for principal=" + principal); - entries.add(new KeytabEntry(principal, KerberosPrincipal.KRB_NT_PRINCIPAL, ktm, (byte) key.getKeyVersion(), key)); + entries.add(new KeytabEntry(new PrincipalName(principal), ktm, key.getKeyVersion(), + new org.apache.kerby.kerberos.kerb.type.base.EncryptionKey(key.getKeyType().getValue(), key.getKeyValue(), key.getKeyVersion()))); } } - Keytab keyTab = Keytab.getInstance(); - keyTab.setEntries(entries); + Keytab keyTab = new Keytab(); + keyTab.addKeytabEntries(entries); try { File keyTabFile = new File(workingDir, keyTabFileName); - keyTab.write(keyTabFile); + keyTab.store(keyTabFile); return keyTabFile.getAbsolutePath(); } catch (IOException e) { throw new IllegalStateException("Cannot create keytab: " + keyTabFileName, e); diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV10Test.java b/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV10Test.java index 86edcb5fc06..f8370ce9944 100644 --- a/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV10Test.java +++ b/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV10Test.java @@ -101,54 +101,12 @@ public static void onAfter() throws Exception { @Test public void testWithResourceOwnerCredentialsUsingConfiguration() throws Exception { - URI serverUri = URI.create("protocol://test1.org"); - SaslClient saslClient = createSaslClientFromConfiguration(serverUri); - - assertNotNull("OAuth2SaslClient is null", saslClient); - - SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) - .setServerName("resourceserver.comn") - .setProtocol("imap") - .addRealm("oauth-realm", createSecurityRealmMock()) - .setDefaultRealmName("oauth-realm") - .build(); - - byte[] message = AbstractSaslParticipant.NO_BYTES; - - do { - message = saslClient.evaluateChallenge(message); - if (message == null) break; - message = saslServer.evaluateResponse(message); - } while (message != null); - - assertTrue(saslServer.isComplete()); - assertTrue(saslClient.isComplete()); + testWithSaslClientAndServer("protocol://test1.org"); } @Test public void testWithClientCredentialsUsingConfiguration() throws Exception { - URI serverUri = URI.create("protocol://test2.org"); - SaslClient saslClient = createSaslClientFromConfiguration(serverUri); - - assertNotNull("OAuth2SaslClient is null", saslClient); - - SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) - .setServerName("resourceserver.comn") - .setProtocol("imap") - .addRealm("oauth-realm", createSecurityRealmMock()) - .setDefaultRealmName("oauth-realm") - .build(); - - byte[] message = AbstractSaslParticipant.NO_BYTES; - - do { - message = saslClient.evaluateChallenge(message); - if (message == null) break; - message = saslServer.evaluateResponse(message); - } while (message != null); - - assertTrue(saslServer.isComplete()); - assertTrue(saslClient.isComplete()); + testWithSaslClientAndServer("protocol://test2.org"); } @Test @@ -204,28 +162,7 @@ public void failedInvalidClientCredentialsUsingConfiguration() throws Exception @Test public void testWithResourceOwnerCredentials() throws Exception { - URI serverUri = URI.create("protocol://test5.org"); - SaslClient saslClient = createSaslClientFromConfiguration(serverUri); - - assertNotNull("OAuth2SaslClient is null", saslClient); - - SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) - .setServerName("resourceserver.comn") - .setProtocol("imap") - .addRealm("oauth-realm", createSecurityRealmMock()) - .setDefaultRealmName("oauth-realm") - .build(); - - byte[] message = AbstractSaslParticipant.NO_BYTES; - - do { - message = saslClient.evaluateChallenge(message); - if (message == null) break; - message = saslServer.evaluateResponse(message); - } while (message != null); - - assertTrue(saslServer.isComplete()); - assertTrue(saslClient.isComplete()); + testWithSaslClientAndServer("protocol://test5.org"); } @Test @@ -496,4 +433,28 @@ private SaslClient createSaslClientFromConfiguration(URI serverUri) throws SaslE AuthenticationConfiguration authenticationConfiguration = contextConfigurationClient.getAuthenticationConfiguration(serverUri, context); return contextConfigurationClient.createSaslClient(serverUri, authenticationConfiguration, Arrays.asList(SaslMechanismInformation.Names.OAUTHBEARER)); } + + private void testWithSaslClientAndServer(String serverUri) throws Exception { + SaslClient saslClient = createSaslClientFromConfiguration(URI.create(serverUri)); + + assertNotNull("OAuth2SaslClient is null", saslClient); + + SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) + .setServerName("resourceserver.comn") + .setProtocol("imap") + .addRealm("oauth-realm", createSecurityRealmMock()) + .setDefaultRealmName("oauth-realm") + .build(); + + byte[] message = AbstractSaslParticipant.NO_BYTES; + + do { + message = saslClient.evaluateChallenge(message); + if (message == null) break; + message = saslServer.evaluateResponse(message); + } while (message != null); + + assertTrue(saslServer.isComplete()); + assertTrue(saslClient.isComplete()); + } } diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV11Test.java b/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV11Test.java index 86162b3530d..7921694da61 100644 --- a/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV11Test.java +++ b/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV11Test.java @@ -130,54 +130,12 @@ public static void tearDown() throws Exception { @Test public void testWithResourceOwnerCredentialsUsingConfiguration() throws Exception { - URI serverUri = URI.create("protocol://test1.org"); - SaslClient saslClient = createSaslClientFromConfiguration(serverUri); - - assertNotNull("OAuth2SaslClient is null", saslClient); - - SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) - .setServerName("resourceserver.comn") - .setProtocol("imap") - .addRealm("oauth-realm", createSecurityRealmMock()) - .setDefaultRealmName("oauth-realm") - .build(); - - byte[] message = AbstractSaslParticipant.NO_BYTES; - - do { - message = saslClient.evaluateChallenge(message); - if (message == null) break; - message = saslServer.evaluateResponse(message); - } while (message != null); - - assertTrue(saslServer.isComplete()); - assertTrue(saslClient.isComplete()); + testWithSaslClientAndServer("protocol://test1.org"); } @Test public void testWithClientCredentialsUsingConfiguration() throws Exception { - URI serverUri = URI.create("protocol://test2.org"); - SaslClient saslClient = createSaslClientFromConfiguration(serverUri); - - assertNotNull("OAuth2SaslClient is null", saslClient); - - SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) - .setServerName("resourceserver.comn") - .setProtocol("imap") - .addRealm("oauth-realm", createSecurityRealmMock()) - .setDefaultRealmName("oauth-realm") - .build(); - - byte[] message = AbstractSaslParticipant.NO_BYTES; - - do { - message = saslClient.evaluateChallenge(message); - if (message == null) break; - message = saslServer.evaluateResponse(message); - } while (message != null); - - assertTrue(saslServer.isComplete()); - assertTrue(saslClient.isComplete()); + testWithSaslClientAndServer("protocol://test2.org"); } @Test @@ -259,27 +217,7 @@ public void testWithResourceOwnerCredentials() throws Exception { @Test public void testWithBearerTokenFromConfiguration() throws Exception { - SaslClient saslClient = createSaslClientFromConfiguration(URI.create("protocol://test5.org")); - - assertNotNull("OAuth2SaslClient is null", saslClient); - - SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) - .setServerName("resourceserver.comn") - .setProtocol("imap") - .addRealm("oauth-realm", createSecurityRealmMock()) - .setDefaultRealmName("oauth-realm") - .build(); - - byte[] message = AbstractSaslParticipant.NO_BYTES; - - do { - message = saslClient.evaluateChallenge(message); - if (message == null) break; - message = saslServer.evaluateResponse(message); - } while (message != null); - - assertTrue(saslServer.isComplete()); - assertTrue(saslClient.isComplete()); + testWithSaslClientAndServer("protocol://test5.org"); } @Test @@ -473,34 +411,11 @@ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallback @Test public void testWithResourceOwnerCredentialsInCredentialStoreUsingConfiguration() throws Exception { - URI serverUri = URI.create("protocol://test8.org"); - SaslClient saslClient = createSaslClientFromConfiguration(serverUri); - - assertNotNull("OAuth2SaslClient is null", saslClient); - - SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) - .setServerName("resourceserver.comn") - .setProtocol("imap") - .addRealm("oauth-realm", createSecurityRealmMock()) - .setDefaultRealmName("oauth-realm") - .build(); - - byte[] message = AbstractSaslParticipant.NO_BYTES; - - do { - message = saslClient.evaluateChallenge(message); - if (message == null) break; - message = saslServer.evaluateResponse(message); - } while (message != null); - - assertTrue(saslServer.isComplete()); - assertTrue(saslClient.isComplete()); + testWithSaslClientAndServer("protocol://test8.org"); } - @Test - public void failedResourceOwnerCredentialsUsingConfiguration() throws Exception { - SaslClient saslClient = createSaslClientFromConfiguration(URI.create("protocol://test9.org")); - + public void testInvalidCredentialsUsingConfiguration(String serverURI) throws Exception { + SaslClient saslClient = createSaslClientFromConfiguration(URI.create(serverURI)); assertNotNull("OAuth2SaslClient is null", saslClient); SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) @@ -525,57 +440,18 @@ public void failedResourceOwnerCredentialsUsingConfiguration() throws Exception } @Test - public void failedResourceOwnerCredentialsFromCredentialStoreUsingConfiguration() throws Exception { - SaslClient saslClient = createSaslClientFromConfiguration(URI.create("protocol://test10.org")); - - assertNotNull("OAuth2SaslClient is null", saslClient); - - SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) - .setServerName("resourceserver.comn") - .setProtocol("imap") - .addRealm("oauth-realm", createSecurityRealmMock()) - .setDefaultRealmName("oauth-realm") - .build(); - - byte[] message = AbstractSaslParticipant.NO_BYTES; + public void failedResourceOwnerCredentialsUsingConfiguration() throws Exception { + testInvalidCredentialsUsingConfiguration("protocol://test9.org"); + } - try { - do { - message = saslClient.evaluateChallenge(message); - if (message == null) break; - message = saslServer.evaluateResponse(message); - } while (message != null); - fail("Expected bad response from server"); - } catch (Exception e) { - e.printStackTrace(); - } + @Test + public void failedResourceOwnerCredentialsFromCredentialStoreUsingConfiguration() throws Exception { + testInvalidCredentialsUsingConfiguration("protocol://test10.org"); } @Test public void failedClientCredentialsFromCredentialStoreUsingConfiguration() throws Exception { - SaslClient saslClient = createSaslClientFromConfiguration(URI.create("protocol://test11.org")); - - assertNotNull("OAuth2SaslClient is null", saslClient); - - SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) - .setServerName("resourceserver.comn") - .setProtocol("imap") - .addRealm("oauth-realm", createSecurityRealmMock()) - .setDefaultRealmName("oauth-realm") - .build(); - - byte[] message = AbstractSaslParticipant.NO_BYTES; - - try { - do { - message = saslClient.evaluateChallenge(message); - if (message == null) break; - message = saslServer.evaluateResponse(message); - } while (message != null); - fail("Expected bad response from server"); - } catch (Exception e) { - e.printStackTrace(); - } + testInvalidCredentialsUsingConfiguration("protocol://test11.org"); } private SecurityRealm createSecurityRealmMock() throws MalformedURLException { @@ -620,4 +496,28 @@ private SaslClient createSaslClientFromConfiguration(URI serverUri) throws SaslE AuthenticationConfiguration authenticationConfiguration = contextConfigurationClient.getAuthenticationConfiguration(serverUri, context); return contextConfigurationClient.createSaslClient(serverUri, authenticationConfiguration, Collections.singletonList(SaslMechanismInformation.Names.OAUTHBEARER)); } + + private void testWithSaslClientAndServer(String serverUri) throws Exception { + SaslClient saslClient = createSaslClientFromConfiguration(URI.create(serverUri)); + + assertNotNull("OAuth2SaslClient is null", saslClient); + + SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) + .setServerName("resourceserver.comn") + .setProtocol("imap") + .addRealm("oauth-realm", createSecurityRealmMock()) + .setDefaultRealmName("oauth-realm") + .build(); + + byte[] message = AbstractSaslParticipant.NO_BYTES; + + do { + message = saslClient.evaluateChallenge(message); + if (message == null) break; + message = saslServer.evaluateResponse(message); + } while (message != null); + + assertTrue(saslServer.isComplete()); + assertTrue(saslClient.isComplete()); + } } diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/otp/OTPTest.java b/tests/base/src/test/java/org/wildfly/security/sasl/otp/OTPTest.java index 69da63105c3..6ab00211f03 100644 --- a/tests/base/src/test/java/org/wildfly/security/sasl/otp/OTPTest.java +++ b/tests/base/src/test/java/org/wildfly/security/sasl/otp/OTPTest.java @@ -819,106 +819,18 @@ public void testAuthenticationWithInvalidPassPhrase() throws Exception { @Test public void testAuthenticationWithLongSeed() throws Exception { - final String algorithm = ALGORITHM_OTP_MD5; - final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); - assertNotNull(clientFactory); - - PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); - final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), - "thisSeedIsTooLong", 500)); - final SaslServerBuilder.BuilderReference securityDomainReference = new SaslServerBuilder.BuilderReference<>(); - final SaslServerBuilder.BuilderReference closeableReference = new SaslServerBuilder.BuilderReference<>(); - try { - final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); - - final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE); - final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com", - Collections.emptyMap(), cbh); - - byte[] message = saslClient.evaluateChallenge(new byte[0]); - try { - saslServer.evaluateResponse(message); - fail("Expected SaslException not thrown"); - } catch (SaslException expected) { - } - saslClient.dispose(); - saslServer.dispose(); - - // The password should remain unchanged - checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm); - } finally { - closeableReference.getReference().close(); - } + testPasswordRemainsUnchanged("thisSeedIsTooLong", 500); } @Test public void testAuthenticationWithNonAlphanumericSeed() throws Exception { - final String algorithm = ALGORITHM_OTP_MD5; - - final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); - assertNotNull(clientFactory); - - PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); - final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), - "A seed!", 500)); - final SaslServerBuilder.BuilderReference securityDomainReference = new SaslServerBuilder.BuilderReference<>(); - final SaslServerBuilder.BuilderReference closeableReference = new SaslServerBuilder.BuilderReference<>(); - try { - final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); - - final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE); - final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com", - Collections.emptyMap(), cbh); - - byte[] message = saslClient.evaluateChallenge(new byte[0]); - try { - saslServer.evaluateResponse(message); - fail("Expected SaslException not thrown"); - } catch (SaslException expected) { - } - saslClient.dispose(); - saslServer.dispose(); - - // The password should remain unchanged - checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm); - } finally { - closeableReference.getReference().close(); - } + testPasswordRemainsUnchanged("A seed!", 500); } @Test public void testAuthenticationWithInvalidSequenceNumber() throws Exception { - final String algorithm = ALGORITHM_OTP_MD5; - final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); - assertNotNull(clientFactory); - - PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); - final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), - "ke1234", 0)); - final SaslServerBuilder.BuilderReference securityDomainReference = new SaslServerBuilder.BuilderReference<>(); - final SaslServerBuilder.BuilderReference closeableReference = new SaslServerBuilder.BuilderReference<>(); - try { - final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); - - final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE); - final SaslClient saslClient = clientFactory.createSaslClient(new String[] { SaslMechanismInformation.Names.OTP }, null, "test", "testserver1.example.com", - Collections.emptyMap(), cbh); - - byte[] message = saslClient.evaluateChallenge(new byte[0]); - try { - saslServer.evaluateResponse(message); - fail("Expected SaslException not thrown"); - } catch (SaslException expected) { - } - saslClient.dispose(); - saslServer.dispose(); - - // The password should remain unchanged - checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm); - } finally { - closeableReference.getReference().close(); - } + testPasswordRemainsUnchanged("ke1234", 0); } @Test @@ -1082,6 +994,40 @@ private CallbackHandler createClientCallbackHandler(String username, String pass return ClientUtils.getCallbackHandler(new URI("remote://localhost"), context); } + private void testPasswordRemainsUnchanged(String seed, int sequenceNumber) throws Exception { + final String algorithm = ALGORITHM_OTP_MD5; + final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class); + assertNotNull(clientFactory); + + PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); + final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(), + seed, sequenceNumber)); + final SaslServerBuilder.BuilderReference securityDomainReference = new SaslServerBuilder.BuilderReference<>(); + final SaslServerBuilder.BuilderReference closeableReference = new SaslServerBuilder.BuilderReference<>(); + try { + final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference); + + final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE); + final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com", + Collections.emptyMap(), cbh); + + byte[] message = saslClient.evaluateChallenge(new byte[0]); + try { + saslServer.evaluateResponse(message); + fail("Expected SaslException not thrown"); + } catch (SaslException expected) { + } + saslClient.dispose(); + saslServer.dispose(); + + // The password should remain unchanged + checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm); + } finally { + closeableReference.getReference().close(); + } + + } + private static final String[] ALTERNATE_DICTIONARY = new String[] { "poel", "qewn", "xlob", "preg", "qome", "zarm", "sas", "oerk", "sct", "seb", "ilan", "wct", "bp", "sft", diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/scram/BasicScramSelfTest.java b/tests/base/src/test/java/org/wildfly/security/sasl/scram/BasicScramSelfTest.java index 0eeeb7a4d25..4c0933d6af0 100644 --- a/tests/base/src/test/java/org/wildfly/security/sasl/scram/BasicScramSelfTest.java +++ b/tests/base/src/test/java/org/wildfly/security/sasl/scram/BasicScramSelfTest.java @@ -92,46 +92,22 @@ public Void run() { @Test public void testAuthenticationSha1ClearPassword() throws Exception { - final SaslServer saslServer = - new SaslServerBuilder(ScramSaslServerFactory.class, SaslMechanismInformation.Names.SCRAM_SHA_1) - .setUserName("user") - .setPassword("pencil".toCharArray()) - .build(); - CallbackHandler clientHandler = createClientCallbackHandler("user", "pencil".toCharArray()); - testAuthentication(SaslMechanismInformation.Names.SCRAM_SHA_1, saslServer, clientHandler, "user", EMPTY); + performAuthenticationTest("user", "pencil", "user", "pencil"); } @Test(expected = SaslException.class) public void testAuthenticationSha1ClearPasswordBadUsername() throws Exception { - final SaslServer saslServer = - new SaslServerBuilder(ScramSaslServerFactory.class, SaslMechanismInformation.Names.SCRAM_SHA_1) - .setUserName("user") - .setPassword("pencil".toCharArray()) - .build(); - CallbackHandler clientHandler = createClientCallbackHandler("wrong", "pencil".toCharArray()); - testAuthentication(SaslMechanismInformation.Names.SCRAM_SHA_1, saslServer, clientHandler, "user", EMPTY); + performAuthenticationTest("user", "pencil", "wrong", "pencil"); } @Test(expected = SaslException.class) public void testAuthenticationSha1ClearPasswordBadPassword() throws Exception { - final SaslServer saslServer = - new SaslServerBuilder(ScramSaslServerFactory.class, SaslMechanismInformation.Names.SCRAM_SHA_1) - .setUserName("user") - .setPassword("pencil".toCharArray()) - .build(); - CallbackHandler clientHandler = createClientCallbackHandler("user", "wrong".toCharArray()); - testAuthentication(SaslMechanismInformation.Names.SCRAM_SHA_1, saslServer, clientHandler, "user", EMPTY); + performAuthenticationTest("user", "pencil", "user", "wrong"); } @Test public void testAuthenticationSha1ClearCredentialPassword() throws Exception { - final SaslServer saslServer = - new SaslServerBuilder(ScramSaslServerFactory.class, SaslMechanismInformation.Names.SCRAM_SHA_1) - .setUserName("user") - .setPassword("pencil".toCharArray()) - .build(); - CallbackHandler clientHandler = createClientCallbackHandler("user", "pencil".toCharArray()); - testAuthentication(SaslMechanismInformation.Names.SCRAM_SHA_1, saslServer, clientHandler, "user", EMPTY); + performAuthenticationTest("user", "pencil", "user", "pencil"); } @Test @@ -265,6 +241,17 @@ public void testPlusClientWithBindingWithNonPlusServer() throws Exception { } } + private void performAuthenticationTest(String username, String password, String clientUsername, String clientPassword) throws Exception { + final SaslServer saslServer = + new SaslServerBuilder(ScramSaslServerFactory.class, SaslMechanismInformation.Names.SCRAM_SHA_1) + .setUserName(username) + .setPassword(password.toCharArray()) + .build(); + CallbackHandler clientHandler = createClientCallbackHandler(clientUsername, clientPassword.toCharArray()); + + testAuthentication(SaslMechanismInformation.Names.SCRAM_SHA_1, saslServer, clientHandler, username, EMPTY); + } + private void testAuthentication(String mechanism, SaslServer saslServer, CallbackHandler clientHandler, String authorizationId, Map clientProps) throws Exception { final SaslClientFactory clientFactory = obtainSaslClientFactory(); assertNotNull(clientFactory); diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/scram/ScramServerCompatibilityTest.java b/tests/base/src/test/java/org/wildfly/security/sasl/scram/ScramServerCompatibilityTest.java index 7c8c7cbcb1b..fbbd40c747c 100644 --- a/tests/base/src/test/java/org/wildfly/security/sasl/scram/ScramServerCompatibilityTest.java +++ b/tests/base/src/test/java/org/wildfly/security/sasl/scram/ScramServerCompatibilityTest.java @@ -215,7 +215,7 @@ public void testAllowedAuthorizationId() throws Exception { assertEquals("v=xzTfS758LckdRoQKN/ZFY/Bauxo=", new String(message, StandardCharsets.UTF_8)); assertTrue(saslServer.isComplete()); - assertEquals(saslServer.getAuthorizationID(), "user"); + assertEquals("user", saslServer.getAuthorizationID()); } /** diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslAuthenticationTimeoutTest.java b/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslAuthenticationTimeoutTest.java index cc6dfc01146..aff240baf5c 100644 --- a/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslAuthenticationTimeoutTest.java +++ b/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslAuthenticationTimeoutTest.java @@ -19,6 +19,7 @@ package org.wildfly.security.sasl.test; import static java.security.AccessController.doPrivileged; +import static org.wildfly.security.sasl.WildFlySasl.AUTHENTICATION_TIMEOUT; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +27,7 @@ import java.security.Provider; import java.security.Security; import java.util.Collections; -import java.util.Map; +import java.util.HashMap; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; @@ -53,12 +54,8 @@ import org.wildfly.security.sasl.SaslMechanismSelector; import org.wildfly.security.sasl.digest.DigestServerFactory; import org.wildfly.security.sasl.digest.WildFlyElytronSaslDigestProvider; -import org.wildfly.security.sasl.util.AuthenticationTimeoutSaslServerFactory; import org.wildfly.security.sasl.util.SaslMechanismInformation; -import mockit.Mock; -import mockit.MockUp; - /** * Tests a successful authentication timeout for a custom executor service and the default executor service. * @@ -75,28 +72,8 @@ public class SaslAuthenticationTimeoutTest { WildFlyElytronPasswordProvider.getInstance() }; - /* - * Unable to set custom AUTHENTICATION_TIMEOUT using a property SaslServer factory (see ELY-1815), so using mock - * function to avoid using default timeout of 150 sec - */ - private static void mockGetTimeout() { - Class classToMock; - try { - classToMock = Class.forName("org.wildfly.security.sasl.util.AuthenticationTimeoutSaslServerFactory", true, AuthenticationTimeoutSaslServerFactory.class.getClassLoader()); - } catch (ClassNotFoundException e) { - throw new NoClassDefFoundError(e.getMessage()); - } - new MockUp(classToMock) { - @Mock - private long getTimeout(final Map props) { - return 3; - } - }; - } - @BeforeClass public static void registerPasswordProvider() { - mockGetTimeout(); for (Provider provider : providers) { Security.insertProviderAt(provider, 1); } @@ -121,12 +98,14 @@ public void testSuccessfulTimeout() throws Exception { INSTANCE.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); try { - + HashMap properties = new HashMap<>(); + properties.put(AUTHENTICATION_TIMEOUT, "3"); SaslServer server = new SaslServerBuilder(DigestServerFactory.class, DIGEST) .setUserName("George") .setPassword("gpwd".toCharArray()) .setProtocol("TestProtocol") .setServerName("TestServer") + .setProperties(properties) .setScheduledExecutorService(INSTANCE) .addMechanismRealm("TestRealm") .build(); @@ -153,12 +132,14 @@ public void testSuccessfulTimeout() throws Exception { public void testSuccessfulTimeout_DefaultExecuterService() throws Exception { try { - + HashMap properties = new HashMap<>(); + properties.put(AUTHENTICATION_TIMEOUT, "3"); SaslServer server = new SaslServerBuilder(DigestServerFactory.class, DIGEST) .setUserName("George") .setPassword("gpwd".toCharArray()) .setProtocol("TestProtocol") .setServerName("TestServer") + .setProperties(properties) .addMechanismRealm("TestRealm") .build(); diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslServerBuilder.java b/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslServerBuilder.java index edaef80f352..003b4a94328 100644 --- a/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslServerBuilder.java +++ b/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslServerBuilder.java @@ -77,7 +77,6 @@ import org.wildfly.security.sasl.util.ChannelBindingSaslServerFactory; import org.wildfly.security.sasl.util.CredentialSaslServerFactory; import org.wildfly.security.sasl.util.KeyManagerCredentialSaslServerFactory; -import org.wildfly.security.sasl.util.PropertiesSaslServerFactory; import org.wildfly.security.sasl.util.ProtocolSaslServerFactory; import org.wildfly.security.sasl.util.SecurityProviderSaslServerFactory; import org.wildfly.security.sasl.util.ServerNameSaslServerFactory; @@ -377,11 +376,8 @@ public SaslServer build() throws IOException { if (factory == null && providerSupplier != null) { factory = new SecurityProviderSaslServerFactory(providerSupplier); } - if (properties != null && properties.size() > 0) { - if (properties.containsKey(WildFlySasl.REALM_LIST)) { - factory = new AvailableRealmsSaslServerFactory(factory); - } - factory = new PropertiesSaslServerFactory(factory, properties); + if (properties != null && properties.size() > 0 && properties.containsKey(WildFlySasl.REALM_LIST)) { + factory = new AvailableRealmsSaslServerFactory(factory); } if (bindingTypeAndData != null) { factory = new ChannelBindingSaslServerFactory(factory, bindingTypeAndData.key, bindingTypeAndData.value); @@ -403,6 +399,9 @@ public SaslServer build() throws IOException { } final SaslAuthenticationFactory.Builder builder = SaslAuthenticationFactory.builder(); builder.setFactory(factory); + if (properties != null && properties.size() > 0) { + builder.setProperties(properties); + } builder.setSecurityDomain(securityDomain); if (scheduledExecutorService != null) { builder.setScheduledExecutorService(scheduledExecutorService); 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 56ed986bb11..b1005514045 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 @@ -19,10 +19,10 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; import static org.wildfly.security.ssl.test.util.CAGenerationTool.SIGNATURE_ALGORTHM; import static org.wildfly.security.x500.X500.OID_AD_OCSP; import static org.wildfly.security.x500.X500.OID_KP_OCSP_SIGNING; @@ -37,10 +37,9 @@ import java.net.ServerSocket; import java.net.SocketException; import java.net.URI; -import java.security.Principal; -import java.security.KeyStore; -import java.security.KeyStoreException; import java.security.AccessController; +import java.security.KeyStore; +import java.security.Principal; import java.security.PrivilegedAction; import java.security.Security; import java.security.cert.X509Certificate; @@ -55,17 +54,12 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; -import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedKeyManager; -import javax.net.ssl.X509TrustManager; import javax.security.auth.x500.X500Principal; import org.bouncycastle.asn1.x500.X500Name; @@ -79,21 +73,24 @@ import org.junit.BeforeClass; import org.junit.Test; import org.wildfly.common.Assert; -import org.wildfly.security.auth.server.SecurityIdentity; -import org.wildfly.security.password.WildFlyElytronPasswordProvider; import org.wildfly.security.auth.client.AuthenticationContext; import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient; import org.wildfly.security.auth.realm.KeyStoreBackedSecurityRealm; import org.wildfly.security.auth.server.SecurityDomain; +import org.wildfly.security.auth.server.SecurityIdentity; import org.wildfly.security.auth.server.SecurityRealm; +import org.wildfly.security.password.WildFlyElytronPasswordProvider; import org.wildfly.security.permission.PermissionVerifier; import org.wildfly.security.ssl.test.util.CAGenerationTool; import org.wildfly.security.ssl.test.util.CAGenerationTool.Identity; +import org.wildfly.security.ssl.test.util.CustomIdentity; +import org.wildfly.security.ssl.test.util.DefinedCAIdentity; +import org.wildfly.security.ssl.test.util.DefinedIdentity; import org.wildfly.security.x500.GeneralName; -import org.wildfly.security.x500.principal.X500AttributePrincipalDecoder; import org.wildfly.security.x500.cert.AccessDescription; import org.wildfly.security.x500.cert.AuthorityInformationAccessExtension; import org.wildfly.security.x500.cert.ExtendedKeyUsageExtension; +import org.wildfly.security.x500.principal.X500AttributePrincipalDecoder; /** * Simple test case to test authentication occurring during the establishment of an {@link SSLSession}. @@ -107,7 +104,7 @@ 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/jks"; + private static final String JKS_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); @@ -123,64 +120,20 @@ public class SSLAuthenticationTest { private static final File LADYBUG_REVOKED_PEM_CRL = new File(WORKING_DIR_CACRL, "ladybug-revoked.pem"); private static TestingOcspServer ocspServer = null; private static X509Certificate ocspResponderCertificate; - - /** - * Get the key manager backed by the specified key store. - * - * @param keystorePath the path to the keystore with X509 private key - * @return the initialised key manager. - */ - private static X509ExtendedKeyManager getKeyManager(final String keystorePath) throws Exception { - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); - keyManagerFactory.init(createKeyStore(keystorePath), PASSWORD); - - for (KeyManager current : keyManagerFactory.getKeyManagers()) { - if (current instanceof X509ExtendedKeyManager) { - return (X509ExtendedKeyManager) current; - } - } - - throw new IllegalStateException("Unable to obtain X509ExtendedKeyManager."); - } + private static KeyStore shortWingedKeyStore; + private static CustomIdentity goodIdentity; + private static CustomIdentity revokedIdentity; private static TrustManagerFactory getTrustManagerFactory() throws Exception { return TrustManagerFactory.getInstance("PKIX"); } - /** - * Get the trust manager that trusts all certificates signed by the certificate authority. - * - * @return the trust manager that trusts all certificates signed by the certificate authority. - * @throws KeyStoreException - */ - private static X509TrustManager getCATrustManager() throws Exception { - TrustManagerFactory trustManagerFactory = getTrustManagerFactory(); - trustManagerFactory.init(createKeyStore("/jks/ca.truststore")); - - for (TrustManager current : trustManagerFactory.getTrustManagers()) { - if (current instanceof X509TrustManager) { - return (X509TrustManager) current; - } - } - - throw new IllegalStateException("Unable to obtain X509TrustManager."); - } - private static KeyStore createKeyStore() throws Exception { - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(null,null); + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(null, null); return ks; } - private static KeyStore createKeyStore(final String path) throws Exception { - KeyStore keyStore = KeyStore.getInstance("jks"); - try (InputStream caTrustStoreFile = SSLAuthenticationTest.class.getResourceAsStream(path)) { - keyStore.load(caTrustStoreFile, PASSWORD); - } - - return keyStore; - } - private static void createTemporaryKeyStoreFile(KeyStore keyStore, File outputFile, char[] password) throws Exception { if (!outputFile.exists()) { outputFile.createNewFile(); @@ -190,12 +143,12 @@ private static void createTemporaryKeyStoreFile(KeyStore keyStore, File outputFi } } - private static SecurityDomain getKeyStoreBackedSecurityDomain(String keyStorePath) throws Exception { - return getKeyStoreBackedSecurityDomain(keyStorePath, true); + private static SecurityDomain getKeyStoreBackedSecurityDomain(KeyStore keyStore) throws Exception { + return getKeyStoreBackedSecurityDomain(keyStore, true); } - private static SecurityDomain getKeyStoreBackedSecurityDomain(String keyStorePath, boolean decoder) throws Exception { - SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(createKeyStore(keyStorePath)); + private static SecurityDomain getKeyStoreBackedSecurityDomain(KeyStore keyStore, boolean decoder) throws Exception { + SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(keyStore); SecurityDomain.Builder builder = SecurityDomain.builder() .addRealm("KeystoreRealm", securityRealm) @@ -222,34 +175,42 @@ public static void beforeTest() throws Exception { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); // Generates certificate and keystore for OCSP responder - ocspResponderCertificate = caGenerationTool.createIdentity("ocspResponder", - new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=OcspResponder"), - "ocsp-responder.keystore", Identity.CA, new ExtendedKeyUsageExtension(false, Collections.singletonList(OID_KP_OCSP_SIGNING))); + DefinedCAIdentity caIdentity = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedCAIdentity intermediateCAIdentity = caGenerationTool.getDefinedCAIdentity(Identity.INTERMEDIATE); + CustomIdentity responderIdentity = caIdentity.createIdentity("ocspResponder", + new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=OcspResponder"), + "ocsp-responder.keystore", new ExtendedKeyUsageExtension(false, Collections.singletonList(OID_KP_OCSP_SIGNING))); + ocspResponderCertificate = responderIdentity.getCertificate(); // Generates GOOD certificate referencing the OCSP responder - X509Certificate ocspCheckedGoodCertificate = caGenerationTool.createIdentity("checked", - new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedGood"), - "ocsp-checked-good.keystore", Identity.INTERMEDIATE, new AuthorityInformationAccessExtension(Collections.singletonList( - new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp")) - ))); + goodIdentity = intermediateCAIdentity.createIdentity("checked", + new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedGood"), + "ocsp-checked-good.keystore", new AuthorityInformationAccessExtension(Collections.singletonList( + new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp")) + ))); + X509Certificate ocspCheckedGoodCertificate = goodIdentity.getCertificate(); // Generates REVOKED certificate referencing the OCSP responder - X509Certificate ocspCheckedRevokedCertificate = caGenerationTool.createIdentity("checked", - new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedRevoked"), - "ocsp-checked-revoked.keystore", Identity.CA, (new AuthorityInformationAccessExtension(Collections.singletonList( - new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp")) - )))); + revokedIdentity = caIdentity.createIdentity("checked", + new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedRevoked"), + "ocsp-checked-revoked.keystore", (new AuthorityInformationAccessExtension(Collections.singletonList( + new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp")) + )))); + X509Certificate ocspCheckedRevokedCertificate = revokedIdentity.getCertificate(); // Generates UNKNOWN certificate referencing the OCSP responder - X509Certificate ocspCheckedUnknownCertificate = caGenerationTool.createIdentity("checked", - new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedUnknown"), - "ocsp-checked-unknown.keystore", Identity.CA, new AuthorityInformationAccessExtension(Collections.singletonList( - new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp")) - ))); - - X509Certificate greenJuneCertificate = caGenerationTool.getCertificate(Identity.GREENJUNE); - - KeyStore beetlesKeyStore = createKeyStore("/jks/beetles.keystore"); + CustomIdentity unknownIdentity = caIdentity.createIdentity("checked", + new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedUnknown"), + "ocsp-checked-unknown.keystore", new AuthorityInformationAccessExtension(Collections.singletonList( + new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp")) + ))); + X509Certificate ocspCheckedUnknownCertificate = unknownIdentity.getCertificate(); + + X509Certificate greenJuneCertificate = caGenerationTool + .getDefinedIdentity(Identity.GREENJUNE) + .getCertificate(); + + KeyStore beetlesKeyStore = caGenerationTool.getBeetlesKeyStore(); beetlesKeyStore.setCertificateEntry("ocspResponder", ocspResponderCertificate); beetlesKeyStore.setCertificateEntry("ocspCheckedGood", ocspCheckedGoodCertificate); beetlesKeyStore.setCertificateEntry("ocspCheckedRevoked", ocspCheckedRevokedCertificate); @@ -258,9 +219,9 @@ public static void beforeTest() throws Exception { createTemporaryKeyStoreFile(beetlesKeyStore, new File(JKS_LOCATION, "beetles.keystore"), PASSWORD); // Adds trusted cert for shortwinged - KeyStore shortwingedKeyStore = createKeyStore(); - shortwingedKeyStore.setCertificateEntry("rove", caGenerationTool.getCertificate(Identity.ROVE)); - createTemporaryKeyStoreFile(shortwingedKeyStore, SHORTWINGED_FILE, PASSWORD); + shortWingedKeyStore = createKeyStore(); + shortWingedKeyStore.setCertificateEntry("rove", caGenerationTool.getDefinedIdentity(Identity.ROVE).getCertificate()); + //createTemporaryKeyStoreFile(shortwingedKeyStore, SHORTWINGED_FILE, PASSWORD); // Used for all CRLs Calendar calendar = Calendar.getInstance(); @@ -273,52 +234,53 @@ public static void beforeTest() throws Exception { // Creates the CRL for ca/crl/blank.pem X509v2CRLBuilder caBlankCrlBuilder = new X509v2CRLBuilder( - convertSunStyleToBCStyle(caGenerationTool.getCertificate(Identity.CA).getSubjectDN()), + convertSunStyleToBCStyle(caIdentity.getCertificate().getSubjectDN()), currentDate ); X509CRLHolder caBlankCrlHolder = caBlankCrlBuilder.setNextUpdate(nextYear).build( new JcaContentSignerBuilder(SIGNATURE_ALGORTHM) .setProvider("BC") - .build(caGenerationTool.getPrivateKey(Identity.CA)) + .build(caIdentity.getPrivateKey()) ); // Creates the CRL for ica/crl/blank.pem X509v2CRLBuilder icaBlankCrlBuilder = new X509v2CRLBuilder( - convertSunStyleToBCStyle(caGenerationTool.getCertificate(Identity.INTERMEDIATE).getSubjectDN()), + convertSunStyleToBCStyle(intermediateCAIdentity.getCertificate().getSubjectDN()), currentDate ); X509CRLHolder icaBlankCrlHolder = icaBlankCrlBuilder.setNextUpdate(nextYear).build( new JcaContentSignerBuilder(SIGNATURE_ALGORTHM) .setProvider("BC") - .build(caGenerationTool.getPrivateKey(Identity.INTERMEDIATE)) + .build(intermediateCAIdentity.getPrivateKey()) ); // Creates the CRL for firefly-revoked.pem X509v2CRLBuilder fireflyRevokedCrlBuilder = new X509v2CRLBuilder( - convertSunStyleToBCStyle(caGenerationTool.getCertificate(Identity.CA).getSubjectDN()), + convertSunStyleToBCStyle(caIdentity.getCertificate().getSubjectDN()), currentDate ); fireflyRevokedCrlBuilder.addCRLEntry( - caGenerationTool.getCertificate(Identity.FIREFLY).getSerialNumber(), + caGenerationTool.getDefinedIdentity(Identity.FIREFLY).getCertificate().getSerialNumber(), revokeDate, CRLReason.unspecified ); X509CRLHolder fireflyRevokedCrlHolder = fireflyRevokedCrlBuilder.setNextUpdate(nextYear).build( new JcaContentSignerBuilder(SIGNATURE_ALGORTHM) .setProvider("BC") - .build(caGenerationTool.getPrivateKey(Identity.CA)) + .build(caIdentity.getPrivateKey()) ); + DefinedCAIdentity secondCAIdentity = caGenerationTool.getDefinedCAIdentity(Identity.SECOND_CA); // Creates the CRL for ladybug-revoked.pem X509v2CRLBuilder ladybugRevokedCrlBuilder = new X509v2CRLBuilder( - convertSunStyleToBCStyle(caGenerationTool.getCertificate(Identity.SECOND_CA).getSubjectDN()), + convertSunStyleToBCStyle(secondCAIdentity.getCertificate().getSubjectDN()), currentDate ); // revokes the certificate with serial number #2 ladybugRevokedCrlBuilder.addCRLEntry( - caGenerationTool.getCertificate(Identity.LADYBUG).getSerialNumber(), + caGenerationTool.getDefinedIdentity(Identity.LADYBUG).getCertificate().getSerialNumber(), revokeDate, CRLReason.unspecified ); @@ -326,35 +288,35 @@ public static void beforeTest() throws Exception { X509CRLHolder ladybugRevokedCrlHolder = ladybugRevokedCrlBuilder.setNextUpdate(nextYear).build( new JcaContentSignerBuilder(SIGNATURE_ALGORTHM) .setProvider("BC") - .build(caGenerationTool.getPrivateKey(Identity.SECOND_CA)) + .build(secondCAIdentity.getPrivateKey()) ); // Creates the CRL for ica-revoked.pem X509v2CRLBuilder icaRevokedCrlBuilder = new X509v2CRLBuilder( - convertSunStyleToBCStyle(caGenerationTool.getCertificate(Identity.CA).getSubjectDN()), + convertSunStyleToBCStyle(caIdentity.getCertificate().getSubjectDN()), currentDate ); icaRevokedCrlBuilder.addCRLEntry( - caGenerationTool.getCertificate(Identity.INTERMEDIATE).getSerialNumber(), + intermediateCAIdentity.getCertificate().getSerialNumber(), revokeDate, CRLReason.unspecified ); X509CRLHolder icaRevokedCrlHolder = icaRevokedCrlBuilder.setNextUpdate(nextYear).build( new JcaContentSignerBuilder(SIGNATURE_ALGORTHM) .setProvider("BC") - .build(caGenerationTool.getPrivateKey(Identity.CA)) + .build(caIdentity.getPrivateKey()) ); // Creates the CRL for rove-revoked.pem X509v2CRLBuilder roveRevokedCrlBuilder = new X509v2CRLBuilder( - convertSunStyleToBCStyle(caGenerationTool.getCertificate(Identity.INTERMEDIATE).getSubjectDN()), + convertSunStyleToBCStyle(intermediateCAIdentity.getCertificate().getSubjectDN()), currentDate ); X509CRLHolder roveRevokedCrlHolder = roveRevokedCrlBuilder.setNextUpdate(nextYear).build( new JcaContentSignerBuilder(SIGNATURE_ALGORTHM) .setProvider("BC") - .build(caGenerationTool.getPrivateKey(Identity.INTERMEDIATE)) + .build(intermediateCAIdentity.getPrivateKey()) ); PemWriter caBlankCrlOutput = new PemWriter(new OutputStreamWriter(new FileOutputStream(CA_BLANK_PEM_CRL))); @@ -385,9 +347,9 @@ public static void beforeTest() throws Exception { roveRevokedCrlOutput.close(); ocspServer = new TestingOcspServer(OCSP_PORT); - ocspServer.createIssuer(1, caGenerationTool.getCertificate(Identity.CA)); - ocspServer.createIssuer(2, caGenerationTool.getCertificate(Identity.INTERMEDIATE)); - ocspServer.createCertificate(1, 1, caGenerationTool.getCertificate(Identity.INTERMEDIATE)); + ocspServer.createIssuer(1, caIdentity.getCertificate()); + ocspServer.createIssuer(2, intermediateCAIdentity.getCertificate()); + ocspServer.createCertificate(1, 1, intermediateCAIdentity.getCertificate()); ocspServer.createCertificate(2, 2, ocspCheckedGoodCertificate); ocspServer.createCertificate(3, 1, ocspCheckedRevokedCertificate); ocspServer.revokeCertificate(3, 4); @@ -433,8 +395,9 @@ public static void afterTest() throws Exception { @Test public void testOneWay() throws Throwable { + DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY); SSLContext serverContext = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/firefly.keystore")) + .setKeyManager(firefly.createKeyManager()) .build().create(); performConnectionTest(serverContext, "protocol://test-one-way.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Firefly", null, true); @@ -442,8 +405,9 @@ public void testOneWay() throws Throwable { @Test public void testCrlBlank() throws Throwable { + DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY); SSLContext serverContext = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/firefly.keystore")) + .setKeyManager(firefly.createKeyManager()) .build().create(); performConnectionTest(serverContext, "protocol://test-one-way-crl.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Firefly", null, true); @@ -451,8 +415,9 @@ public void testCrlBlank() throws Throwable { @Test public void testServerRevoked() throws Throwable { + DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY); SSLContext serverContext = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/firefly.keystore")) + .setKeyManager(firefly.createKeyManager()) .build().create(); performConnectionTest(serverContext, "protocol://test-one-way-firefly-revoked.org", false, null, null, true); @@ -460,8 +425,9 @@ public void testServerRevoked() throws Throwable { @Test public void testServerIcaRevoked() throws Throwable { + DefinedIdentity rove = caGenerationTool.getDefinedIdentity(Identity.ROVE); SSLContext serverContext = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/rove.keystore")) + .setKeyManager(rove.createKeyManager()) .build().create(); performConnectionTest(serverContext, "protocol://test-one-way-ica-revoked.org", false, null, null, true); @@ -474,8 +440,9 @@ public void testServerIcaRevoked() throws Throwable { */ @Test public void testOneWayServerRejectedWithSingleCRL() throws Throwable { + DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY); SSLContext serverContext = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/firefly.keystore")) + .setKeyManager(firefly.createKeyManager()) .build().create(); performConnectionTest(serverContext, "protocol://test-one-way-one-crl.org", false, null, null, true); @@ -488,8 +455,9 @@ public void testOneWayServerRejectedWithSingleCRL() throws Throwable { */ @Test public void testOneWayServerRejectedWithMultipleCRL() throws Throwable { + DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY); SSLContext serverContext = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/firefly.keystore")) + .setKeyManager(firefly.createKeyManager()) .build().create(); performConnectionTest(serverContext, "protocol://test-one-way-multiple-crls-failure.org", false, @@ -503,8 +471,9 @@ public void testOneWayServerRejectedWithMultipleCRL() throws Throwable { */ @Test public void testOneWayServerAcceptedWithMultipleCRL() throws Throwable { + DefinedIdentity greenJune = caGenerationTool.getDefinedIdentity(Identity.GREENJUNE); SSLContext serverContext = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/greenjune.keystore")) + .setKeyManager(greenJune.createKeyManager()) .build().create(); performConnectionTest(serverContext, "protocol://test-one-way-multiple-crls-success.org", true, @@ -517,8 +486,9 @@ public void testOneWayServerAcceptedWithMultipleCRL() throws Throwable { */ @Test public void testCRLMaxCertPathSucceeds() throws Throwable { + DefinedIdentity rove = caGenerationTool.getDefinedIdentity(Identity.ROVE); SSLContext serverContext = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/rove.keystore")) + .setKeyManager(rove.createKeyManager()) .build().create(); performConnectionTest(serverContext, "protocol://test-one-way-max-cert-path.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Rove", null, true); @@ -531,8 +501,9 @@ public void testCRLMaxCertPathSucceeds() throws Throwable { */ @Test public void testCRLMaxCertPathFails() throws Throwable { + DefinedIdentity rove = caGenerationTool.getDefinedIdentity(Identity.ROVE); SSLContext serverContext = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/rove.keystore")) + .setKeyManager(rove.createKeyManager()) .build().create(); performConnectionTest(serverContext, "protocol://test-one-way-max-cert-path-failure.org", false, null, null, true); @@ -540,10 +511,12 @@ public void testCRLMaxCertPathFails() throws Throwable { @Test public void testTwoWay() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); SSLContext serverContext = new SSLContextBuilder() - .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore")) - .setKeyManager(getKeyManager("/jks/scarab.keystore")) - .setTrustManager(getCATrustManager()) + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(scarab.createKeyManager()) + .setTrustManager(ca.createTrustManager()) .setNeedClientAuth(true) .build().create(); @@ -553,10 +526,12 @@ public void testTwoWay() throws Throwable { @Test public void testTwoWayNoDecoder() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); SSLContext serverContext = new SSLContextBuilder() - .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore", false)) - .setKeyManager(getKeyManager("/jks/scarab.keystore")) - .setTrustManager(getCATrustManager()) + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore(), false)) + .setKeyManager(scarab.createKeyManager()) + .setTrustManager(ca.createTrustManager()) .setNeedClientAuth(true) .build().create(); @@ -566,10 +541,12 @@ public void testTwoWayNoDecoder() throws Throwable { @Test public void testTwoWayIca() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); SSLContext serverContext = new SSLContextBuilder() - .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/shortwinged.keystore")) - .setKeyManager(getKeyManager("/jks/scarab.keystore")) - .setTrustManager(getCATrustManager()) + .setSecurityDomain(getKeyStoreBackedSecurityDomain(shortWingedKeyStore)) + .setKeyManager(scarab.createKeyManager()) + .setTrustManager(ca.createTrustManager()) .setNeedClientAuth(true) .build().create(); @@ -585,9 +562,10 @@ public void testTwoWayIca() throws Throwable { public void testAcceptedIssuersConfiguredWithCRL() throws Throwable { InputStream crl = new FileInputStream("./target/test-classes/ica/crl/blank-blank.pem"); + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); X509RevocationTrustManager trustManager = X509RevocationTrustManager.builder() .setTrustManagerFactory(getTrustManagerFactory()) - .setTrustStore(createKeyStore("/jks/ca.truststore")) + .setTrustStore(ca.loadKeyStore()) .setCrlStream(crl) .setPreferCrls(true) .setNoFallback(true) @@ -608,11 +586,13 @@ public void testTwoWayClientRejectedWithSingleCRL() throws Throwable { // this CRL contains the certificate with the alias "ladybug" which is being sent by the client crlStreams.add(new FileInputStream("target/test-classes/ca/crl/ladybug-revoked.pem")); + DefinedCAIdentity secondCA = caGenerationTool.getDefinedCAIdentity(Identity.SECOND_CA); + DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY); SSLContext serverContext = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/firefly.keystore")) + .setKeyManager(firefly.createKeyManager()) .setTrustManager(X509RevocationTrustManager.builder() .setTrustManagerFactory(getTrustManagerFactory()) - .setTrustStore(createKeyStore("/jks/ca.truststore2")) + .setTrustStore(secondCA.loadKeyStore()) .setCrlStreams(crlStreams) .setPreferCrls(true) .setNoFallback(true) @@ -635,12 +615,14 @@ public void testTwoWayClientAcceptedWithSingleCRL() throws Throwable { // CRL contains "ladybug" certificate but client sends "green june" certificate crlStreams.add(new FileInputStream("target/test-classes/ca/crl/ladybug-revoked.pem")); + DefinedCAIdentity secondCA = caGenerationTool.getDefinedCAIdentity(Identity.SECOND_CA); + DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY); SSLContext serverContext = new SSLContextBuilder() - .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore")) - .setKeyManager(getKeyManager("/jks/firefly.keystore")) + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(firefly.createKeyManager()) .setTrustManager(X509RevocationTrustManager.builder() .setTrustManagerFactory(getTrustManagerFactory()) - .setTrustStore(createKeyStore("/jks/ca.truststore2")) + .setTrustStore(secondCA.loadKeyStore()) .setCrlStreams(crlStreams) .setPreferCrls(true) .setNoFallback(true) @@ -666,11 +648,13 @@ public void testTwoWayClientRejectedWithMultipleCRL() throws Throwable { crlStreams.add(new FileInputStream("target/test-classes/ca/crl/ladybug-revoked.pem")); crlStreams.add(new FileInputStream("target/test-classes/ca/crl/firefly-revoked.pem")); + DefinedCAIdentity secondCA = caGenerationTool.getDefinedCAIdentity(Identity.SECOND_CA); + DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY); SSLContext serverContext = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/firefly.keystore")) + .setKeyManager(firefly.createKeyManager()) .setTrustManager(X509RevocationTrustManager.builder() .setTrustManagerFactory(getTrustManagerFactory()) - .setTrustStore(createKeyStore("/jks/ca.truststore2")) + .setTrustStore(secondCA.loadKeyStore()) .setCrlStreams(crlStreams) .setPreferCrls(true) .setNoFallback(true) @@ -694,12 +678,14 @@ public void testTwoWayClientAcceptedWithMultipleCRL() throws Throwable { crlStreams.add(new FileInputStream("target/test-classes/ca/crl/ladybug-revoked.pem")); crlStreams.add(new FileInputStream("target/test-classes/ca/crl/firefly-revoked.pem")); + DefinedCAIdentity secondCA = caGenerationTool.getDefinedCAIdentity(Identity.SECOND_CA); + DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY); SSLContext serverContext = new SSLContextBuilder() - .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore")) - .setKeyManager(getKeyManager("/jks/firefly.keystore")) + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(firefly.createKeyManager()) .setTrustManager(X509RevocationTrustManager.builder() .setTrustManagerFactory(getTrustManagerFactory()) - .setTrustStore(createKeyStore("/jks/ca.truststore2")) + .setTrustStore(secondCA.loadKeyStore()) .setCrlStreams(crlStreams) .setPreferCrls(true) .setNoFallback(true) @@ -714,12 +700,14 @@ public void testTwoWayClientAcceptedWithMultipleCRL() throws Throwable { @Test public void testOcspGood() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); SSLContext serverContext = new SSLContextBuilder() - .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore")) - .setKeyManager(getKeyManager("/jks/scarab.keystore")) + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(scarab.createKeyManager()) .setTrustManager(X509RevocationTrustManager.builder() .setTrustManagerFactory(getTrustManagerFactory()) - .setTrustStore(createKeyStore("/jks/ca.truststore")) + .setTrustStore(ca.loadKeyStore()) .setOcspResponderCert(ocspResponderCertificate) .build()) .setNeedClientAuth(true) @@ -729,6 +717,44 @@ public void testOcspGood() throws Throwable { "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=ocspCheckedGood", false); } + @Test + public void testOcspRevoked() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); + SSLContext serverContext = new SSLContextBuilder() + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(scarab.createKeyManager()) + .setTrustManager(X509RevocationTrustManager.builder() + .setTrustManagerFactory(getTrustManagerFactory()) + .setTrustStore(ca.loadKeyStore()) + .setOcspResponderCert(ocspResponderCertificate) + .build()) + .setNeedClientAuth(true) + .build().create(); + + performConnectionTest(serverContext, "protocol://test-two-way-ocsp-revoked.org", false, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Scarab", + "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=ocspCheckedRevoked", false); + } + + @Test + public void testOcspUnknown() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); + SSLContext serverContext = new SSLContextBuilder() + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(scarab.createKeyManager()) + .setTrustManager(X509RevocationTrustManager.builder() + .setTrustManagerFactory(getTrustManagerFactory()) + .setTrustStore(ca.loadKeyStore()) + .setOcspResponderCert(ocspResponderCertificate) + .build()) + .setNeedClientAuth(true) + .build().create(); + + performConnectionTest(serverContext, "protocol://test-two-way-ocsp-unknown.org", false, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Scarab", + "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=ocspCheckedUnknown", false); + } + @Test public void testOcspMaxCertPathNeg1() throws Throwable { ocspMaxCertPathCommon(-1, false); @@ -750,12 +776,14 @@ public void testOcspMaxCertPathOkay() throws Throwable { } private void ocspMaxCertPathCommon(int maxCertPath, boolean expectValid) throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); SSLContext serverContext = new SSLContextBuilder() - .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore")) - .setKeyManager(getKeyManager("/jks/scarab.keystore")) + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(scarab.createKeyManager()) .setTrustManager(X509RevocationTrustManager.builder() .setTrustManagerFactory(getTrustManagerFactory()) - .setTrustStore(createKeyStore("/jks/ca.truststore")) + .setTrustStore(ca.loadKeyStore()) .setOcspResponderCert(ocspResponderCertificate) .setMaxCertPath(maxCertPath) .build()) @@ -768,17 +796,18 @@ private void ocspMaxCertPathCommon(int maxCertPath, boolean expectValid) throws @Test public void testClientSideOcsp() throws Throwable { SSLContext serverContextGood = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/ocsp-checked-good.keystore")) + .setKeyManager(goodIdentity.createKeyManager()) .build().create(); SSLContext serverContextRevoked = new SSLContextBuilder() - .setKeyManager(getKeyManager("/jks/ocsp-checked-revoked.keystore")) + .setKeyManager(revokedIdentity.createKeyManager()) .build().create(); + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); SSLContext clientContext = new SSLContextBuilder() .setTrustManager(X509RevocationTrustManager.builder() .setTrustManagerFactory(getTrustManagerFactory()) - .setTrustStore(createKeyStore("/jks/ca.truststore")) + .setTrustStore(ca.loadKeyStore()) .setOcspResponderCert(ocspResponderCertificate) .build()) .setClientMode(true) @@ -794,8 +823,38 @@ public void testClientSideOcsp() throws Throwable { } } + @Test + public void testWantClientAuthWithCorrectCertificate() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); + SSLContext serverContext = new SSLContextBuilder() + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(scarab.createKeyManager()) + .setTrustManager(ca.createTrustManager()) + .setWantClientAuth(true) + .build().create(); + + performConnectionTest(serverContext, "protocol://test-two-way.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Scarab", + "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Ladybird", false); + } + + @Test + public void testWantClientAuthWithIncorrectCertificate() throws Throwable { + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); + SSLContext serverContext = new SSLContextBuilder() + .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore())) + .setKeyManager(scarab.createKeyManager()) + .setTrustManager(ca.createTrustManager()) + .setWantClientAuth(true) + .build().create(); + + performConnectionTest(serverContext, "protocol://test-one-way.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Scarab", + null, true); + } + private void performConnectionTest(SSLContext serverContext, String clientUri, boolean expectValid, String expectedServerPrincipal, String expectedClientPrincipal, boolean oneWay) throws Throwable { - System.setProperty("wildfly.config.url", SSLAuthenticationTest.class.getResource("wildfly-ssl-test-config-v1_7.xml").toExternalForm()); + System.setProperty("wildfly.config.url", SSLAuthenticationTest.class.getResource("ssl-authentication-config.xml").toExternalForm()); AccessController.doPrivileged((PrivilegedAction) () -> Security.insertProviderAt(WildFlyElytronPasswordProvider.getInstance(), 1)); AuthenticationContext context = AuthenticationContext.getContextManager().get(); @@ -863,10 +922,10 @@ private void testCommunication(SSLContext serverContext, SSLContext clientContex } if (oneWay) { - assertFalse(clientSocket.getSession().getProtocol().equals("TLSv1.3")); // since TLS 1.3 is not enabled by default (ELY-1917) + assertNotEquals("TLSv1.3", clientSocket.getSession().getProtocol());// since TLS 1.3 is not enabled by default (ELY-1917) } else { - assertFalse(serverSocket.getSession().getProtocol().equals("TLSv1.3")); // since TLS 1.3 is not enabled by default - assertFalse(clientSocket.getSession().getProtocol().equals("TLSv1.3")); // since TLS 1.3 is not enabled by default + 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) { diff --git a/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java b/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java index 9e61933f29a..392b291de8d 100644 --- a/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java +++ b/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java @@ -88,8 +88,9 @@ */ public class SSLv2HelloAuthenticationTest { + private static final String CLIENT_CONFIG = "sslv2-hello-authentication-config.xml"; private static final char[] PASSWORD = "Elytron".toCharArray(); - private static final String CA_JKS_LOCATION = "./target/test-classes/ca/jks"; + private static final String CA_JKS_LOCATION = "./target/test-classes/ca/pkcs12"; private static File ladybirdFile = null; private static File scarabFile = null; private static File beetlesFile = null; @@ -119,7 +120,7 @@ public static void setUp() throws Exception{ createKeyStores(ladybirdFile, scarabFile, beetlesFile, trustFile); - securityRealm = new KeyStoreBackedSecurityRealm(loadKeyStore("/ca/jks/beetles.keystore")); + securityRealm = new KeyStoreBackedSecurityRealm(loadKeyStore("/ca/pkcs12/beetles.keystore")); securityDomain = SecurityDomain.builder() .addRealm("KeystoreRealm", securityRealm) @@ -162,7 +163,7 @@ public void testOneWaySSLv2HelloProtocolMatch() throws Exception { SSLContext serverContext = new SSLContextBuilder() .setSecurityDomain(securityDomain) - .setKeyManager(getKeyManager("/ca/jks/scarab.keystore")) + .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore")) .setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list))) .build().create(); @@ -170,7 +171,7 @@ public void testOneWaySSLv2HelloProtocolMatch() throws Exception { SecurityIdentity identity = performConnectionTest(serverContext, "protocol://one-way-sslv2hello.org", - "wildfly-ssl-test-config-v1_6.xml", + CLIENT_CONFIG, enabledProtocols, // We expect client and server socket to only have SSLv2Hello and TLSv1 enabled "TLSv1"); // We expect the negotiated protocol to be TLSv1, as SSLv2Hello is a pseudo-protocol } @@ -187,7 +188,7 @@ public void testTwoWaySSLv2HelloProtocolMatch() throws Exception { SSLContext serverContext = new SSLContextBuilder() .setSecurityDomain(securityDomain) - .setKeyManager(getKeyManager("/ca/jks/scarab.keystore")) + .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore")) .setTrustManager(getCATrustManager()) .setNeedClientAuth(true) .setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list))) @@ -197,7 +198,7 @@ public void testTwoWaySSLv2HelloProtocolMatch() throws Exception { SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-two-way-sslv2hello.org", - "wildfly-ssl-test-config-v1_6.xml", + CLIENT_CONFIG, enabledProtocols, // We expect client and server socket to only have SSLv2Hello and TLSv1 enabled "TLSv1"); // We expect the negotiated protocol to be TLSv1, as SSLv2Hello is a pseudo-protocol @@ -214,7 +215,7 @@ public void testTwoWaySSLv2HelloProtocolMatch() throws Exception { public void testTwoWaySSLv2HelloNotEnabled() throws Exception { SSLContext serverContext = new SSLContextBuilder() .setSecurityDomain(securityDomain) - .setKeyManager(getKeyManager("/ca/jks/scarab.keystore")) + .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore")) .setTrustManager(getCATrustManager()) .setNeedClientAuth(true) .build().create(); @@ -223,7 +224,7 @@ public void testTwoWaySSLv2HelloNotEnabled() throws Exception { SecurityIdentity identity = performConnectionTest(serverContext, "protocol://two-way-no-sslv2hello.org", - "wildfly-ssl-test-config-v1_6.xml", + CLIENT_CONFIG, enabledProtocols, // We expect the default protocols to be enabled i.e. SSLv2Hello should only be enabled if explicitly configured "TLSv1.2"); // We expect the negotiated protocol to be the highest version protocol in common @@ -243,7 +244,7 @@ public void testTwoWaySSLv2HelloNoClientSupport() throws Exception { SSLContext serverContext = new SSLContextBuilder() .setSecurityDomain(securityDomain) - .setKeyManager(getKeyManager("/ca/jks/scarab.keystore")) + .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore")) .setTrustManager(getCATrustManager()) .setNeedClientAuth(true) .setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list))) @@ -254,7 +255,7 @@ public void testTwoWaySSLv2HelloNoClientSupport() throws Exception { SecurityIdentity identity = performConnectionTest(serverContext, "protocol://two-way-no-sslv2hello.org", - "wildfly-ssl-test-config-v1_6.xml", + CLIENT_CONFIG, enabledClientProtocols, enabledServerProtocols, "TLSv1"); // We expect the negotiated protocol to be the highest version protocol in common @@ -273,7 +274,7 @@ public void testTwoWaySSlv2HelloNoServerSupport() throws Exception { SSLContext serverContext = new SSLContextBuilder() .setSecurityDomain(securityDomain) - .setKeyManager(getKeyManager("/ca/jks/scarab.keystore")) + .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore")) .setTrustManager(getCATrustManager()) .setNeedClientAuth(true) .setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list))) @@ -284,7 +285,7 @@ public void testTwoWaySSlv2HelloNoServerSupport() throws Exception { SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-two-way-sslv2hello.org", - "wildfly-ssl-test-config-v1_6.xml", + CLIENT_CONFIG, clientEnabledProtocols, serverEnabledProtocols, "NONE"); // handshake is expected to fail, which in turn returns an empty SSLSession @@ -376,7 +377,7 @@ private static X509ExtendedKeyManager getKeyManager(final String keystorePath) t */ private static X509TrustManager getCATrustManager() throws Exception { TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); - trustManagerFactory.init(loadKeyStore("/ca/jks/ca.truststore")); + trustManagerFactory.init(loadKeyStore("/ca/pkcs12/ca.truststore")); for (TrustManager current : trustManagerFactory.getTrustManagers()) { if (current instanceof X509TrustManager) { @@ -388,13 +389,13 @@ private static X509TrustManager getCATrustManager() throws Exception { } private static KeyStore loadKeyStore() throws Exception{ - KeyStore ks = KeyStore.getInstance("JKS"); + KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(null,null); return ks; } private static KeyStore loadKeyStore(final String path) throws Exception { - KeyStore keyStore = KeyStore.getInstance("jks"); + KeyStore keyStore = KeyStore.getInstance("PKCS12"); try (InputStream caTrustStoreFile = SSLAuthenticationTest.class.getResourceAsStream(path)) { keyStore.load(caTrustStoreFile, PASSWORD); } diff --git a/tests/base/src/test/java/org/wildfly/security/ssl/TLS13AuthenticationTest.java b/tests/base/src/test/java/org/wildfly/security/ssl/TLS13AuthenticationTest.java index 422fbf89712..826916e29ca 100644 --- a/tests/base/src/test/java/org/wildfly/security/ssl/TLS13AuthenticationTest.java +++ b/tests/base/src/test/java/org/wildfly/security/ssl/TLS13AuthenticationTest.java @@ -23,12 +23,9 @@ import java.io.Closeable; import java.io.IOException; -import java.io.InputStream; import java.net.InetAddress; import java.net.URI; import java.security.AccessController; -import java.security.KeyStore; -import java.security.KeyStoreException; import java.security.PrivilegedAction; import java.security.Security; import java.util.Locale; @@ -36,20 +33,13 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedKeyManager; -import javax.net.ssl.X509TrustManager; import org.junit.AfterClass; -import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.wildfly.security.WildFlyElytronProvider; @@ -62,6 +52,8 @@ import org.wildfly.security.permission.PermissionVerifier; import org.wildfly.security.ssl.test.util.CAGenerationTool; import org.wildfly.security.ssl.test.util.CAGenerationTool.Identity; +import org.wildfly.security.ssl.test.util.DefinedCAIdentity; +import org.wildfly.security.ssl.test.util.DefinedIdentity; import org.wildfly.security.x500.principal.X500AttributePrincipalDecoder; /** @@ -71,23 +63,22 @@ */ public class TLS13AuthenticationTest { + private static final String CLIENT_CONFIG = "tls13-authentication-config.xml"; private static final char[] PASSWORD = "Elytron".toCharArray(); - private static final String CA_JKS_LOCATION = "./target/test-classes/jks"; + private static final String CA_JKS_LOCATION = "./target/test-classes/pkcs12"; private static CAGenerationTool caGenerationTool = null; private static SecurityDomain securityDomain = null; @BeforeClass public static void setUp() throws Exception{ - Assume.assumeTrue("Skipping TLS13AuthenticationTest suite, tests are not being run on JDK 11.", - System.getProperty("java.specification.version").equals("11")); caGenerationTool = CAGenerationTool.builder() .setBaseDir(CA_JKS_LOCATION) .setRequestIdentities(Identity.LADYBIRD, Identity.SCARAB) .build(); - SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(loadKeyStore("/jks/beetles.keystore")); + SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(caGenerationTool.getBeetlesKeyStore()); securityDomain = SecurityDomain.builder() .addRealm("KeystoreRealm", securityRealm) .build() @@ -108,15 +99,18 @@ public static void cleanUp() throws IOException { public void testTwoWayTLS13() throws Exception { final String CIPHER_SUITE = "TLS_AES_128_GCM_SHA256"; + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); + SSLContext serverContext = new SSLContextBuilder() .setSecurityDomain(securityDomain) .setCipherSuiteSelector(CipherSuiteSelector.fromNamesString(CIPHER_SUITE)) - .setKeyManager(getKeyManager("/jks/scarab.keystore")) - .setTrustManager(getCATrustManager()) + .setKeyManager(scarab.createKeyManager()) + .setTrustManager(ca.createTrustManager()) .setNeedClientAuth(true) .build().create(); - SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-two-way-tls13.org", "wildfly-ssl-test-config-v1_5.xml", CIPHER_SUITE, true); + SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-two-way-tls13.org", CLIENT_CONFIG, CIPHER_SUITE, true); assertNotNull(identity); assertEquals("Principal Name", "ladybird", identity.getPrincipal().getName()); } @@ -127,15 +121,18 @@ public void testDifferentPreferredTLS13Suites() throws Exception { final String PREFERRED_CIPHER_SUITE = "TLS_AES_256_GCM_SHA384"; final String SERVER_CIPHER_SUITE = String.format("%s:%s", PREFERRED_CIPHER_SUITE, REQUIRED_CIPHER_SUITE); + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); + SSLContext serverContext = new SSLContextBuilder() .setSecurityDomain(securityDomain) .setCipherSuiteSelector(CipherSuiteSelector.fromNamesString(SERVER_CIPHER_SUITE)) - .setKeyManager(getKeyManager("/jks/scarab.keystore")) - .setTrustManager(getCATrustManager()) + .setKeyManager(scarab.createKeyManager()) + .setTrustManager(ca.createTrustManager()) .setNeedClientAuth(true) .build().create(); - SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-different-preferred-tls13-suites.org", "wildfly-ssl-test-config-v1_5.xml", REQUIRED_CIPHER_SUITE, true); + SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-different-preferred-tls13-suites.org", CLIENT_CONFIG, REQUIRED_CIPHER_SUITE, true); assertNotNull(identity); assertEquals("Principal Name", "ladybird", identity.getPrincipal().getName()); } @@ -145,18 +142,21 @@ public void testClientTLS12Only() throws Exception { final String TLS13_CIPHER_SUITE = "TLS_AES_128_GCM_SHA256"; final String TLS12_CIPHER_SUITE = "TLS_RSA_WITH_AES_128_CBC_SHA256"; // TLS v1.2 + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); + SSLContext serverContext = new SSLContextBuilder() .setSecurityDomain(securityDomain) .setCipherSuiteSelector(CipherSuiteSelector.aggregate( CipherSuiteSelector.fromNamesString(TLS13_CIPHER_SUITE), CipherSuiteSelector.fromString(TLS12_CIPHER_SUITE) )) - .setKeyManager(getKeyManager("/jks/scarab.keystore")) - .setTrustManager(getCATrustManager()) + .setKeyManager(scarab.createKeyManager()) + .setTrustManager(ca.createTrustManager()) .setNeedClientAuth(true) .build().create(); - SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-client-tls12-only.org", "wildfly-ssl-test-config-v1_5.xml", TLS12_CIPHER_SUITE, false); + SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-client-tls12-only.org", CLIENT_CONFIG, TLS12_CIPHER_SUITE, false); assertNotNull(identity); assertEquals("Principal Name", "ladybird", identity.getPrincipal().getName()); } @@ -165,15 +165,18 @@ public void testClientTLS12Only() throws Exception { public void testServerTLS12Only() throws Exception { final String SERVER_CIPHER_SUITE = "TLS_RSA_WITH_AES_128_CBC_SHA256"; // TLS v1.2 + DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA); + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); + SSLContext serverContext = new SSLContextBuilder() .setSecurityDomain(securityDomain) .setCipherSuiteSelector(CipherSuiteSelector.fromString(SERVER_CIPHER_SUITE)) - .setKeyManager(getKeyManager("/jks/scarab.keystore")) - .setTrustManager(getCATrustManager()) + .setKeyManager(scarab.createKeyManager()) + .setTrustManager(ca.createTrustManager()) .setNeedClientAuth(true) .build().create(); - SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-server-tls12-only.org", "wildfly-ssl-test-config-v1_5.xml", SERVER_CIPHER_SUITE, false); + SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-server-tls12-only.org", CLIENT_CONFIG, SERVER_CIPHER_SUITE, false); assertNotNull(identity); assertEquals("Principal Name", "ladybird", identity.getPrincipal().getName()); } @@ -182,12 +185,14 @@ public void testServerTLS12Only() throws Exception { public void testOneWayTLS13() throws Exception { final String CIPHER_SUITE = "TLS_AES_128_GCM_SHA256"; + DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB); + SSLContext serverContext = new SSLContextBuilder() .setCipherSuiteSelector(CipherSuiteSelector.fromNamesString(CIPHER_SUITE)) - .setKeyManager(getKeyManager("/jks/scarab.keystore")) + .setKeyManager(scarab.createKeyManager()) .build().create(); - SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-one-way-tls13.org", "wildfly-ssl-test-config-v1_5.xml", CIPHER_SUITE, true); + SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-one-way-tls13.org", CLIENT_CONFIG, CIPHER_SUITE, true); assertNull(identity); } @@ -244,53 +249,6 @@ private SecurityIdentity performConnectionTest(SSLContext serverContext, String } } - /** - * Get the key manager backed by the specified key store. - * - * @param keystorePath the path to the keystore with X509 private key - * @return the initialised key manager. - */ - private static X509ExtendedKeyManager getKeyManager(final String keystorePath) throws Exception { - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); - keyManagerFactory.init(loadKeyStore(keystorePath), PASSWORD); - - for (KeyManager current : keyManagerFactory.getKeyManagers()) { - if (current instanceof X509ExtendedKeyManager) { - return (X509ExtendedKeyManager) current; - } - } - - throw new IllegalStateException("Unable to obtain X509ExtendedKeyManager."); - } - - /** - * Get the trust manager that trusts all certificates signed by the certificate authority. - * - * @return the trust manager that trusts all certificates signed by the certificate authority. - * @throws KeyStoreException - */ - private static X509TrustManager getCATrustManager() throws Exception { - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509"); - trustManagerFactory.init(loadKeyStore("/jks/ca.truststore")); - - for (TrustManager current : trustManagerFactory.getTrustManagers()) { - if (current instanceof X509TrustManager) { - return (X509TrustManager) current; - } - } - - throw new IllegalStateException("Unable to obtain X509TrustManager."); - } - - private static KeyStore loadKeyStore(final String path) throws Exception { - KeyStore keyStore = KeyStore.getInstance("jks"); - try (InputStream caTrustStoreFile = SSLAuthenticationTest.class.getResourceAsStream(path)) { - keyStore.load(caTrustStoreFile, PASSWORD); - } - - return keyStore; - } - private void safeClose(Closeable closeable) { try { closeable.close(); diff --git a/tests/base/src/test/java/org/wildfly/security/ssl/TestingOcspServer.java b/tests/base/src/test/java/org/wildfly/security/ssl/TestingOcspServer.java index 81f641e08c5..027df60a885 100644 --- a/tests/base/src/test/java/org/wildfly/security/ssl/TestingOcspServer.java +++ b/tests/base/src/test/java/org/wildfly/security/ssl/TestingOcspServer.java @@ -18,6 +18,7 @@ package org.wildfly.security.ssl; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; @@ -42,6 +43,7 @@ import org.mockserver.integration.ClientAndServer; import org.mockserver.matchers.Times; import org.mockserver.model.Header; +import org.mockserver.model.HttpRequest; import org.mockserver.model.HttpResponse; import org.mockserver.model.NottableString; import org.wildfly.common.iteration.ByteIterator; @@ -126,32 +128,13 @@ public void start() throws Exception { .withMethod("POST") .withPath("/ocsp"), Times.unlimited()) - .respond(request -> { - ByteBuf buffer = Unpooled.wrappedBuffer(request.getBody().getRawBytes()); - FullHttpRequest nettyRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.POST, request.getPath().getValue(), buffer); - for (Header header : request.getHeaderList()) { - for (NottableString value : header.getValues()) { - nettyRequest.headers().add(header.getName().getValue(), value.getValue()); - } - } - - FullHttpResponse nettyResponse; - try { - nettyResponse = servlet.service(nettyRequest, new ServletURI(request.getPath().getValue()), null, SslReverseProxyMode.NONE); - } catch (Exception e) { - throw new RuntimeException(e); - } - - HttpResponse response = response() - .withStatusCode(nettyResponse.status().code()) - .withBody(nettyResponse.content().array()); - - for (Map.Entry header : nettyResponse.headers()) { - response.withHeader(header.getKey(), header.getValue()); - } - - return response; - }); + .respond(request -> getHttpResponse(request, servlet)); + server.when( + request() + .withMethod("GET") + .withPath("/ocsp/.*"), + Times.unlimited()) + .respond(request -> getHttpResponse(request, servlet)); } public void stop() throws SQLException { @@ -198,4 +181,40 @@ public void revokeCertificate(int id, int reason) throws SQLException { statement.execute(); } + + + public HttpResponse getHttpResponse(HttpRequest request, HttpOcspServlet servlet){ + byte[] body; + HttpMethod method; + if (request.getBody() == null) { + method = HttpMethod.GET; + body = request.getPath().getValue().split("/ocsp/", 2)[1].getBytes(UTF_8); + } else { + method = HttpMethod.POST; + body = request.getBody().getRawBytes(); + } + ByteBuf buffer = Unpooled.wrappedBuffer(body); + FullHttpRequest nettyRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, method, request.getPath().getValue(), buffer); + for (Header header : request.getHeaderList()) { + for (NottableString value : header.getValues()) { + nettyRequest.headers().add(header.getName().getValue(), value.getValue()); + } + } + + FullHttpResponse nettyResponse; + try { + nettyResponse = servlet.service(nettyRequest, new ServletURI(request.getPath().getValue()), null, SslReverseProxyMode.NONE); + } catch (Exception e) { + throw new RuntimeException(e); + } + + HttpResponse response = response() + .withStatusCode(nettyResponse.status().code()) + .withBody(nettyResponse.content().array()); + + for (Map.Entry header : nettyResponse.headers()) { + response.withHeader(header.getKey(), header.getValue()); + } + return response; + } } \ No newline at end of file diff --git a/tests/base/src/test/java/org/wildfly/security/util/PasswordBasedEncryptionUtilTest.java b/tests/base/src/test/java/org/wildfly/security/util/PasswordBasedEncryptionUtilTest.java index b0fc5420197..d3e511c4547 100644 --- a/tests/base/src/test/java/org/wildfly/security/util/PasswordBasedEncryptionUtilTest.java +++ b/tests/base/src/test/java/org/wildfly/security/util/PasswordBasedEncryptionUtilTest.java @@ -183,9 +183,9 @@ private void checkPb(String secret, String salt, int iteration, String pbGenerat String crossDecrypted = new String(decryptUtil.decodeAndDecrypt(pbGenerated)); String decrypted = new String(decryptUtil.decodeAndDecrypt(encrypted)); - Assert.assertTrue("Elytron in PB compatible mode failed", decrypted.equals(secret)); - Assert.assertTrue("PicketBox encrypted, Elytron decrypted in compatible mode, failed", crossDecrypted.equals(secret)); - Assert.assertTrue("Elytron in compatible mode encrypted, PicketBox encrypted must be the same", pbGenerated.equals(encrypted)); + Assert.assertEquals("Elytron in PB compatible mode failed", secret, decrypted); + Assert.assertEquals("PicketBox encrypted, Elytron decrypted in compatible mode, failed", secret, crossDecrypted); + Assert.assertEquals("Elytron in compatible mode encrypted, PicketBox encrypted must be the same", encrypted, pbGenerated); } diff --git a/tests/base/src/test/java/org/wildfly/security/x500/X500AttributePrincipalDecoderTest.java b/tests/base/src/test/java/org/wildfly/security/x500/X500AttributePrincipalDecoderTest.java index 19a3fa89433..e383455e799 100644 --- a/tests/base/src/test/java/org/wildfly/security/x500/X500AttributePrincipalDecoderTest.java +++ b/tests/base/src/test/java/org/wildfly/security/x500/X500AttributePrincipalDecoderTest.java @@ -19,6 +19,7 @@ package org.wildfly.security.x500; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import javax.security.auth.x500.X500Principal; @@ -61,9 +62,40 @@ public void testDecodeAttributeWithSubrange() { assertEquals("jboss.redhat.com", decoder.getName(principal)); } + @Test + public void testDecodeWithAggregation() { + X500Principal principal = new X500Principal("cn=bob.smith,cn=bob,ou=people,dc=example,dc=redhat,dc=com"); + PrincipalDecoder dcDecoder = new X500AttributePrincipalDecoder(X500.OID_DC); + PrincipalDecoder cnDecoder = new X500AttributePrincipalDecoder(X500.OID_AT_COMMON_NAME, 1); + PrincipalDecoder aggregateDecoder = PrincipalDecoder.aggregate(cnDecoder, dcDecoder); + assertEquals("bob.smith", aggregateDecoder.getName(principal)); + aggregateDecoder = PrincipalDecoder.aggregate(dcDecoder, cnDecoder); + assertEquals("example.redhat.com", aggregateDecoder.getName(principal)); + + principal = new X500Principal("cn=bob.smith,ou=people,dc=example,dc=redhat"); + cnDecoder = new X500AttributePrincipalDecoder(X500.OID_AT_COMMON_NAME); + PrincipalDecoder ouDecoder = new X500AttributePrincipalDecoder(X500.OID_AT_ORGANIZATIONAL_UNIT_NAME, 1); + dcDecoder = new X500AttributePrincipalDecoder(X500.OID_DC, 1); + PrincipalDecoder dcDecoder1 = new X500AttributePrincipalDecoder(X500.OID_DC, 1, 1); + aggregateDecoder = PrincipalDecoder.aggregate(dcDecoder1, dcDecoder, ouDecoder, cnDecoder); + assertEquals("redhat", aggregateDecoder.getName(principal)); + aggregateDecoder = PrincipalDecoder.aggregate(dcDecoder, dcDecoder1, ouDecoder, cnDecoder); + assertEquals("example", aggregateDecoder.getName(principal)); + aggregateDecoder = PrincipalDecoder.aggregate(cnDecoder, dcDecoder1, dcDecoder, ouDecoder); + assertEquals("bob.smith", aggregateDecoder.getName(principal)); + + principal = new X500Principal("cn=bob.smith,dc=example,dc=redhat"); + aggregateDecoder = PrincipalDecoder.aggregate(ouDecoder); + assertNull(aggregateDecoder.getName(principal)); + aggregateDecoder = PrincipalDecoder.aggregate(dcDecoder, ouDecoder); + assertEquals("example", aggregateDecoder.getName(principal)); + aggregateDecoder = PrincipalDecoder.aggregate(dcDecoder, ouDecoder, dcDecoder1); + assertEquals("example", aggregateDecoder.getName(principal)); + } + @Test public void testDecodeWithConcatenation() { - X500Principal principal; new X500Principal("cn=bob.smith,cn=bob,ou=people,dc=example,dc=redhat,dc=com"); + X500Principal principal; PrincipalDecoder dcDecoder, dcDecoder1, cnDecoder, ouDecoder, concatenatingDecoder; principal = new X500Principal("cn=bob.smith,cn=bob,ou=people,dc=example,dc=redhat,dc=com"); dcDecoder = new X500AttributePrincipalDecoder(X500.OID_DC); diff --git a/tests/base/src/test/resources/org/wildfly/security/auth/client/wildfly-masked-password-ssl-config-v1_4.xml b/tests/base/src/test/resources/org/wildfly/security/auth/client/wildfly-masked-password-ssl-config-v1_4.xml index d458d71f173..133acd78f2d 100644 --- a/tests/base/src/test/resources/org/wildfly/security/auth/client/wildfly-masked-password-ssl-config-v1_4.xml +++ b/tests/base/src/test/resources/org/wildfly/security/auth/client/wildfly-masked-password-ssl-config-v1_4.xml @@ -21,10 +21,11 @@ - + + - + diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/ocsp-responder.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/ocsp-responder.xml index 50b99e567d7..8b0d7755811 100644 --- a/tests/base/src/test/resources/org/wildfly/security/ssl/ocsp-responder.xml +++ b/tests/base/src/test/resources/org/wildfly/security/ssl/ocsp-responder.xml @@ -19,7 +19,7 @@ JKS - password=Elytron,keystore=file:target/test-classes/jks/ocsp-responder.keystore + password=Elytron,keystore=file:target/test-classes/pkcs12/ocsp-responder.keystore SHA256withRSA diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_7.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml similarity index 86% rename from tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_7.xml rename to tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml index a323343d443..2f5ab506492 100644 --- a/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_7.xml +++ b/tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml @@ -17,44 +17,49 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> + + - - + + + - - + + + - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_6.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/sslv2-hello-authentication-config.xml similarity index 86% rename from tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_6.xml rename to tests/base/src/test/resources/org/wildfly/security/ssl/sslv2-hello-authentication-config.xml index 197b3d44798..c3ca9fc93d3 100644 --- a/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_6.xml +++ b/tests/base/src/test/resources/org/wildfly/security/ssl/sslv2-hello-authentication-config.xml @@ -17,14 +17,17 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> + + - - + + + - - + + diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_5.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/tls13-authentication-config.xml similarity index 90% rename from tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_5.xml rename to tests/base/src/test/resources/org/wildfly/security/ssl/tls13-authentication-config.xml index e23e2fa28ee..e9146013afc 100644 --- a/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_5.xml +++ b/tests/base/src/test/resources/org/wildfly/security/ssl/tls13-authentication-config.xml @@ -17,14 +17,17 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> + + - - + + + - - + + diff --git a/tests/common/pom.xml b/tests/common/pom.xml index 12b05419de4..106ac094654 100644 --- a/tests/common/pom.xml +++ b/tests/common/pom.xml @@ -5,7 +5,7 @@ wildfly-elytron-parent org.wildfly.security - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml @@ -74,7 +74,7 @@ test - org.glassfish + org.eclipse.parsson jakarta.json test 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 86526400a81..e28f884d999 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 @@ -55,7 +55,8 @@ import org.wildfly.security.x500.cert.X509CertificateExtension; /** - * A tool for generating a complete set of certificates backed by a generated certificate authority. + * A tool for generating a complete set of certificates backed by a generated + * certificate authority. * * @author Darran Lofthouse */ @@ -65,12 +66,13 @@ public class CAGenerationTool implements Closeable { private static final String BEETLES_STORE = "beetles.keystore"; private static final String KEY_ALGORITHM = "RSA"; - private static final String KEYSTORE_TYPE = "JKS"; // TODO Switch to PKCS#12 + private static final String KEYSTORE_TYPE = "PKCS12"; private static final int OCSP_PORT = 4854; - private static final char[] PASSWORD = "Elytron".toCharArray(); + static final char[] PASSWORD = "Elytron".toCharArray(); private static final Set BEETLES = Collections - .unmodifiableSet(new HashSet<>(Arrays.asList(Identity.LADYBIRD, Identity.SCARAB, Identity.DUNG, Identity.FIREFLY))); + .unmodifiableSet( + new HashSet<>(Arrays.asList(Identity.LADYBIRD, Identity.SCARAB, Identity.DUNG, Identity.FIREFLY))); private static final Predicate INCLUDE_IN_BEETLES = BEETLES::contains; private final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); @@ -79,6 +81,8 @@ public class CAGenerationTool implements Closeable { private final File workingDir; + private volatile boolean closed = false; + protected CAGenerationTool(Builder builder) throws Exception { // Ensure we have the directory created to hold the resulting KeyStores workingDir = new File(builder.baseDir); @@ -108,13 +112,58 @@ protected CAGenerationTool(Builder builder) throws Exception { } } + public DefinedIdentity getDefinedIdentity(final Identity identity) { + if (identity.isCertificateAuthority()) { + return getDefinedCAIdentity(identity); + } + + if (!certificateMap.containsKey(identity)) { + throw new IllegalStateException(String.format("Identity %s has not been created.", identity.toString())); + } + + X509Certificate certificate = certificateMap.get(identity); + + return new DefinedIdentity(this, identity, certificate); + } + + public DefinedCAIdentity getDefinedCAIdentity(final Identity identity) { + if (!identity.isCertificateAuthority()) { + throw new IllegalStateException( + String.format("Identity %s is not a CertificateAuthority", identity.toString())); + } + + if (!caMap.containsKey(identity)) { + throw new IllegalStateException(String.format("Identity %s has not been created.", identity.toString())); + } + + CAState caState = caMap.get(identity); + return new DefinedCAIdentity(this, identity, caState.issuerCertificate, caState.signingKey); + } + + public KeyStore getBeetlesKeyStore() { + return loadKeyStore(new File(workingDir, BEETLES_STORE)); + } + + public String getKeyStoreType() { + return KEYSTORE_TYPE; + } + + /** + * @deprecated Use {@link CommonIdentity#getCertificate()} instead. + */ + @Deprecated() public X509Certificate getCertificate(final Identity identity) { return certificateMap.get(identity); } + /** + * @deprecated Use {@link DefinedCAIdentity#getPrivateKey()} instead. + */ + @Deprecated() public PrivateKey getPrivateKey(final Identity identity) { if (!identity.isCertificateAuthority()) { - throw new IllegalStateException(String.format("Identity %s if not a CertificateAuthority", identity.toString())); + throw new IllegalStateException( + String.format("Identity %s if not a CertificateAuthority", identity.toString())); } return caMap.computeIfAbsent(identity, this::createCA).signingKey; @@ -126,7 +175,8 @@ private CAState createCA(final Identity identity) { Identity signedBy = identity.getSignedBy(); if (signedBy == null) { // As a root CA it will require a self signed certificate. - SelfSignedX509CertificateAndSigningKey issuerSelfSignedX509CertificateAndSigningKey = SelfSignedX509CertificateAndSigningKey.builder() + SelfSignedX509CertificateAndSigningKey issuerSelfSignedX509CertificateAndSigningKey = SelfSignedX509CertificateAndSigningKey + .builder() .setDn(identity.getPrincipal()) .setKeyAlgorithmName(KEY_ALGORITHM) .setSignatureAlgorithmName(SIGNATURE_ALGORTHM) @@ -147,8 +197,8 @@ private CAState createCA(final Identity identity) { .setSerialNumber(BigInteger.valueOf(signerState.serialNumber++)) .addExtension(new BasicConstraintsExtension(false, true, -1)) .addExtension(new AuthorityInformationAccessExtension(Collections.singletonList( - new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp")) - ))) + new AccessDescription(OID_AD_OCSP, + new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp"))))) .build(); caState.issuerCertificate = intermediateIssuerCertificate; @@ -175,24 +225,72 @@ private CAState createCA(final Identity identity) { return caState; } - public X509Certificate createIdentity(final String alias, final X500Principal principal, final String keyStoreName, - final Identity ca, final X509CertificateExtension... extensions) { - KeyPair keyPair = keyPairGenerator.generateKeyPair(); + private X509Certificate createCustomCertificate(final Identity ca, final X500Principal principal, + final KeyPair keyPair, final X509CertificateExtension... extensions) throws CertificateException{ + CAState caState = caMap.computeIfAbsent(ca, this::createCA); + X509CertificateBuilder certificateBuilder = new X509CertificateBuilder() + .setIssuerDn(ca.getPrincipal()) + .setSubjectDn(principal) + .setSignatureAlgorithmName(SIGNATURE_ALGORTHM) + .setSigningKey(caState.signingKey) + .setPublicKey(keyPair.getPublic()) + .setSerialNumber(BigInteger.valueOf(caState.serialNumber++)) + .addExtension(new BasicConstraintsExtension(false, false, -1)); + for (X509CertificateExtension currentExtension : extensions) { + certificateBuilder.addExtension(currentExtension); + } + + return certificateBuilder.build(); + } + + CustomIdentity createCustomIdentity(final String alias, final X500Principal principal, final String keyStoreName, + final Identity ca, final X509CertificateExtension... extensions) { try { - X509CertificateBuilder certificateBuilder = new X509CertificateBuilder() - .setIssuerDn(ca.getPrincipal()) - .setSubjectDn(principal) - .setSignatureAlgorithmName(SIGNATURE_ALGORTHM) - .setSigningKey(caState.signingKey) - .setPublicKey(keyPair.getPublic()) - .setSerialNumber(BigInteger.valueOf(caState.serialNumber++)) - .addExtension(new BasicConstraintsExtension(false, false, -1)); - for (X509CertificateExtension currentExtension : extensions) { - certificateBuilder.addExtension(currentExtension); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + X509Certificate builtCertificate = createCustomCertificate(ca, principal, keyPair, extensions); + + File keyStoreFile = new File(workingDir, keyStoreName); + KeyStore keyStore = createEmptyKeyStore(); + + List certificates = new ArrayList<>(); + certificates.add(builtCertificate); + + Identity caIdentity = ca; + CAState caState; + + do { + caState = caMap.get(caIdentity); // We just created a signed cert above, the complete chain must be + // present. + certificates.add(caState.issuerCertificate); + caIdentity = caIdentity.getSignedBy(); + } while (caIdentity != null); + + keyStore.setKeyEntry(alias, keyPair.getPrivate(), PASSWORD, + certificates.toArray(new X509Certificate[certificates.size()])); + try (OutputStream out = new FileOutputStream(keyStoreFile)) { + keyStore.store(out, PASSWORD); } - X509Certificate builtCertificate = certificateBuilder.build(); + + return new CustomIdentity(this, builtCertificate, keyStoreFile); + + } catch (IOException | KeyStoreException | CertificateException | NoSuchAlgorithmException e) { + throw new RuntimeException("Umnable to create identity", e); + } + } + + /** + * @deprecated Use + * {@link #createIdentity(String, X500Principal, String, X509CertificateExtension...)} + * instead. + */ + @Deprecated + public X509Certificate createIdentity(final String alias, final X500Principal principal, final String keyStoreName, + final Identity ca, final X509CertificateExtension... extensions) { + try { + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + X509Certificate builtCertificate = createCustomCertificate(ca, principal, keyPair, extensions); File keyStoreFile = new File(workingDir, keyStoreName); KeyStore keyStore = createEmptyKeyStore(); @@ -201,9 +299,14 @@ public X509Certificate createIdentity(final String alias, final X500Principal pr certificates.add(builtCertificate); Identity caIdentity = ca; + CAState caState; + do { - caState = caMap.get(caIdentity); // We just created a signed cert above, the complete chain must be present. - keyStore.setCertificateEntry(caIdentity.toString(), caState.issuerCertificate); // This could be removed as the cert chain is added to the Entry. + caState = caMap.get(caIdentity); // We just created a signed cert above, the complete chain must be + // present. + keyStore.setCertificateEntry(caIdentity.toString(), caState.issuerCertificate); // This could be removed + // as the cert chain is + // added to the Entry. certificates.add(caState.issuerCertificate); caIdentity = caIdentity.getSignedBy(); } while (caIdentity != null); @@ -220,7 +323,8 @@ public X509Certificate createIdentity(final String alias, final X500Principal pr } } - public X509Certificate createSelfSignedIdentity(final String alias, final X500Principal principal, final String keyStoreName) { + private X509Certificate createSelfSignedIdentity(final String alias, final X500Principal principal, + final String keyStoreName) { SelfSignedX509CertificateAndSigningKey selfSignedIdentity = SelfSignedX509CertificateAndSigningKey.builder() .setDn(principal) .setKeyAlgorithmName(KEY_ALGORITHM) @@ -256,7 +360,7 @@ private X509Certificate createIdentity(final Identity identity) { private static KeyStore createEmptyKeyStore() { try { KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE); - ks.load(null,null); + ks.load(null, null); return ks; } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) { @@ -264,7 +368,15 @@ private static KeyStore createEmptyKeyStore() { } } - private static KeyStore loadKeyStore(final File location) { + File getKeyStoreFile(Identity identity) { + return new File(workingDir, identity.getKeyStoreName()); + } + + KeyStore loadKeyStore(final Identity identity) { + return loadKeyStore(getKeyStoreFile(identity)); + } + + static KeyStore loadKeyStore(final File location) { try (InputStream caTrustStoreFile = new FileInputStream(location)) { KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE); keyStore.load(caTrustStoreFile, PASSWORD); @@ -275,8 +387,15 @@ private static KeyStore loadKeyStore(final File location) { } } + void assertNotClosed() { + if (closed) { + throw new IllegalStateException("The CAGenerationTool is closed."); + } + } + @Override public void close() throws IOException { + closed = true; workingDir.delete(); } @@ -306,7 +425,8 @@ public enum Identity { CA, true, null), ROVE("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=Rove", INTERMEDIATE, false, "rove.keystore"), - SECOND_CA("CN=Wildfly CA, ST=Wildfly, C=CA, EMAILADDRESS=admin@wildfly.org O=Another Root Certificate Authority", + SECOND_CA( + "CN=Wildfly CA, ST=Wildfly, C=CA, EMAILADDRESS=admin@wildfly.org O=Another Root Certificate Authority", null, true, "ca.truststore2"), LADYBUG("OU=Wildfly, O=Wildfly, C=CA, ST=Wildfly, CN=Ladybug", SECOND_CA, false, "ladybug.keystore"), @@ -318,8 +438,9 @@ public enum Identity { private final boolean ca; private final String keyStoreName; - private Identity(final String distinguishedName, final Identity signedBy, final boolean ca, final String keyStoreName) { - this.principal = new X500Principal(distinguishedName); + private Identity(final String distinguishedName, final Identity signedBy, final boolean ca, + final String keyStoreName) { + this.principal = new X500Principal(distinguishedName); this.signedBy = signedBy; this.ca = ca; this.keyStoreName = keyStoreName; diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CommonIdentity.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CommonIdentity.java new file mode 100644 index 00000000000..ffd89fbdf0a --- /dev/null +++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CommonIdentity.java @@ -0,0 +1,73 @@ +/* + * Copyright 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.ssl.test.util; + +import java.io.File; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.X509ExtendedKeyManager; + +public abstract class CommonIdentity { + + protected final CAGenerationTool caGenerationTool; + private final X509Certificate certificate; + + CommonIdentity(CAGenerationTool caGenerationTool, X509Certificate certificate) { + this.caGenerationTool = caGenerationTool; + this.certificate = certificate; + } + + public X509Certificate getCertificate() { + caGenerationTool.assertNotClosed(); + + return certificate; + } + + public String getKeyStoreType() { + return caGenerationTool.getKeyStoreType(); + } + + public abstract KeyStore loadKeyStore(); + + public abstract File getKeyStoreFile(); + + public X509ExtendedKeyManager createKeyManager() { + caGenerationTool.assertNotClosed(); + + try { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); + keyManagerFactory.init(loadKeyStore(), CAGenerationTool.PASSWORD); + + for (KeyManager current : keyManagerFactory.getKeyManagers()) { + if (current instanceof X509ExtendedKeyManager) { + return (X509ExtendedKeyManager) current; + } + } + } catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException e) { + throw new IllegalStateException("Unable to obtain X509ExtendedKeyManager.", e); + } + + throw new IllegalStateException("Unable to obtain X509ExtendedKeyManager."); + } + +} diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CustomIdentity.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CustomIdentity.java new file mode 100644 index 00000000000..f7f2100c53f --- /dev/null +++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CustomIdentity.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.ssl.test.util; + +import java.io.File; +import java.security.KeyStore; +import java.security.cert.X509Certificate; + +public class CustomIdentity extends CommonIdentity { + + private final File keyStoreFile; + + CustomIdentity(CAGenerationTool caGenerationTool, X509Certificate certificate, File keyStoreFile) { + super(caGenerationTool, certificate); + this.keyStoreFile = keyStoreFile; + } + + @Override + public KeyStore loadKeyStore() { + return CAGenerationTool.loadKeyStore(keyStoreFile); + } + + @Override + public File getKeyStoreFile() { + return keyStoreFile; + } + +} diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedCAIdentity.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedCAIdentity.java new file mode 100644 index 00000000000..4b01b4af6c8 --- /dev/null +++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedCAIdentity.java @@ -0,0 +1,74 @@ +/* + * Copyright 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.ssl.test.util; + +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; + +import org.wildfly.security.ssl.test.util.CAGenerationTool.Identity; +import org.wildfly.security.x500.cert.X509CertificateExtension; + +public class DefinedCAIdentity extends DefinedIdentity { + + private final PrivateKey privateKey; + + DefinedCAIdentity(CAGenerationTool caGenerationTool, Identity identity, + X509Certificate certificate, PrivateKey privateKey) { + super(caGenerationTool, identity, certificate); + this.privateKey = privateKey; + } + + public CustomIdentity createIdentity(final String alias, final X500Principal principal, + final String keyStoreName, final X509CertificateExtension... extensions) { + caGenerationTool.assertNotClosed(); + + return caGenerationTool.createCustomIdentity(alias, principal, keyStoreName, identity, extensions); + } + + + public PrivateKey getPrivateKey() { + caGenerationTool.assertNotClosed(); + + return privateKey; + } + + public X509TrustManager createTrustManager() { + caGenerationTool.assertNotClosed(); + + try { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX"); + trustManagerFactory.init(caGenerationTool.loadKeyStore(identity)); + + for (TrustManager current : trustManagerFactory.getTrustManagers()) { + if (current instanceof X509TrustManager) { + return (X509TrustManager) current; + } + } + } catch (NoSuchAlgorithmException | KeyStoreException e) { + throw new IllegalStateException("Unable to obtain X509TrustManager.", e); + } + + throw new IllegalStateException("Unable to obtain X509TrustManager."); + } +} diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedIdentity.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedIdentity.java new file mode 100644 index 00000000000..0983b2e928d --- /dev/null +++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedIdentity.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.security.ssl.test.util; + +import java.io.File; +import java.security.KeyStore; +import java.security.cert.X509Certificate; + +import org.wildfly.security.ssl.test.util.CAGenerationTool.Identity; + +public class DefinedIdentity extends CommonIdentity { + + protected final Identity identity; + + DefinedIdentity(CAGenerationTool caGenerationTool, + Identity identity, + X509Certificate certificate) { + super(caGenerationTool, certificate); + this.identity = identity; + } + + public KeyStore loadKeyStore() { + caGenerationTool.assertNotClosed(); + + return caGenerationTool.loadKeyStore(identity); + } + + @Override + public File getKeyStoreFile() { + return caGenerationTool.getKeyStoreFile(identity); + } + +} diff --git a/tool/pom.xml b/tool/pom.xml index f19c2a561ef..6cf2c73c159 100644 --- a/tool/pom.xml +++ b/tool/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT 4.0.0 @@ -304,6 +304,10 @@ jboss-logging-processor provided + + org.aesh + aesh + diff --git a/tool/src/main/java/org/wildfly/security/tool/CredentialStoreCommand.java b/tool/src/main/java/org/wildfly/security/tool/CredentialStoreCommand.java index 62b2637a773..3f6d765d73a 100644 --- a/tool/src/main/java/org/wildfly/security/tool/CredentialStoreCommand.java +++ b/tool/src/main/java/org/wildfly/security/tool/CredentialStoreCommand.java @@ -46,7 +46,6 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionGroup; import org.apache.commons.cli.Options; @@ -69,6 +68,10 @@ import org.wildfly.security.password.interfaces.ClearPassword; import org.wildfly.security.pem.Pem; import org.wildfly.security.ssh.util.SshUtil; +import org.wildfly.security.tool.help.DescriptionSection; +import org.wildfly.security.tool.help.HelpCommand; +import org.wildfly.security.tool.help.OptionsSection; +import org.wildfly.security.tool.help.UsageSection; import static org.wildfly.security.tool.Params.ALIAS_PARAM; import static org.wildfly.security.tool.Params.CREATE_CREDENTIAL_STORE_PARAM; @@ -92,8 +95,8 @@ */ class CredentialStoreCommand extends Command { - public static int ACTION_NOT_DEFINED = 5; - public static int ALIAS_NOT_FOUND = 6; + public static final int ACTION_NOT_DEFINED = 5; + public static final int ALIAS_NOT_FOUND = 6; public static final String RSA_ALGORITHM = "RSA"; public static final String DSA_ALGORITHM = "DSA"; @@ -959,13 +962,15 @@ protected Set aliases() { */ @Override public void help() { - HelpFormatter help = new HelpFormatter(); - help.setWidth(WIDTH); - help.printHelp(ElytronToolMessages.msg.cmdHelp(getToolCommand(), CREDENTIAL_STORE_COMMAND), - ElytronToolMessages.msg.cmdLineCredentialStoreHelpHeader().concat(ElytronToolMessages.msg.cmdLineActionsHelpHeader()), - options, - "", - true); + OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.cmdLineActionsHelpHeader(), options); + UsageSection usageSection = new UsageSection(CREDENTIAL_STORE_COMMAND, null); + DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdLineCredentialStoreHelpHeader()); + HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder() + .description(descriptionSection) + .usage(usageSection) + .options(optionsSection) + .build(); + helpCommand.printHelp(); } static Map parseCredentialStoreProperties(final String attributeString) { diff --git a/tool/src/main/java/org/wildfly/security/tool/ElytronTool.java b/tool/src/main/java/org/wildfly/security/tool/ElytronTool.java index 0d0d4193a10..1111c9ad6af 100644 --- a/tool/src/main/java/org/wildfly/security/tool/ElytronTool.java +++ b/tool/src/main/java/org/wildfly/security/tool/ElytronTool.java @@ -20,10 +20,17 @@ import org.apache.commons.cli.AlreadySelectedException; import org.apache.commons.cli.Option; import org.wildfly.security.WildFlyElytronProvider; +import org.wildfly.security.tool.help.CommandsSection; +import org.wildfly.security.tool.help.DescriptionSection; +import org.wildfly.security.tool.help.HelpCommand; +import org.wildfly.security.tool.help.OptionsSection; +import org.wildfly.security.tool.help.UsageSection; import java.security.Security; import java.util.HashMap; import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; /** * Elytron Tool main class which drives all registered commands. @@ -36,11 +43,11 @@ public class ElytronTool { /** * status code for unrecognized command */ - public static int ElytronToolExitStatus_unrecognizedCommand = 1; + public static final int ElytronToolExitStatus_unrecognizedCommand = 1; /** * status code for no problems */ - public static int ElytronToolExitStatus_OK = 0; + public static final int ElytronToolExitStatus_OK = 0; private Map commandRegistry = new HashMap<>(); /** @@ -131,15 +138,27 @@ private static void configureLogManager() { } private void generalHelp() { - System.out.print(ElytronToolMessages.msg.generalHelpTitle()); - System.out.println(); - for (Command c: commandRegistry.values()) { - if (scriptName != null) { - c.setToolCommand(scriptName); - } - c.help(); - System.out.println(); - } + DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdElytronToolDescription()); + UsageSection usageSection = new UsageSection(null, null); + OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.generalHelpOptionsOpening(), null); + + // Using SortedMap so commands are in alphabetical order + SortedMap commandsMap = new TreeMap<>(); + commandsMap.put(CredentialStoreCommand.CREDENTIAL_STORE_COMMAND, ElytronToolMessages.msg.cmdLineCredentialStoreHelpHeader()); + commandsMap.put(VaultCommand.VAULT_COMMAND, ElytronToolMessages.msg.cmdVaultHelpHeader()); + commandsMap.put(FileSystemRealmCommand.FILE_SYSTEM_REALM_COMMAND, ElytronToolMessages.msg.cmdFileSystemRealmHelpHeader()); + commandsMap.put(FileSystemEncryptRealmCommand.FILE_SYSTEM_ENCRYPT_COMMAND, ElytronToolMessages.msg.cmdFileSystemEncryptHelpHeader()); + commandsMap.put(MaskCommand.MASK_COMMAND, ElytronToolMessages.msg.cmdMaskHelpHeader()); + commandsMap.put(FileSystemRealmIntegrityCommand.FILE_SYSTEM_REALM_INTEGRITY_COMMAND, ElytronToolMessages.msg.cmdFileSystemIntegrityHelpHeader()); + CommandsSection commandsSection = new CommandsSection(commandsMap); + + HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder() + .description(descriptionSection) + .usage(usageSection) + .options(optionsSection) + .commands(commandsSection) + .build(); + helpCommand.printHelp(); } Command findCommand(String commandName) { diff --git a/tool/src/main/java/org/wildfly/security/tool/ElytronToolMessages.java b/tool/src/main/java/org/wildfly/security/tool/ElytronToolMessages.java index 6a023c4f1d2..4b876d33b25 100644 --- a/tool/src/main/java/org/wildfly/security/tool/ElytronToolMessages.java +++ b/tool/src/main/java/org/wildfly/security/tool/ElytronToolMessages.java @@ -105,25 +105,25 @@ public interface ElytronToolMessages extends BasicLogger { "Provider must be installed through java.security file or through service loader from properly packaged jar file on classpath.") String cmdLineCustomCredentialStoreProviderDesc(); - @Message(id = NONE, value = "Create credential store (Action)") + @Message(id = NONE, value = "* Create credential store") String cmdLineCreateCredentialStoreDesc(); @Message(id = NONE, value = "Credential store type") String cmdLineCredentialStoreTypeDesc(); - @Message(id = NONE, value = "Add new alias to the credential store (Action)") + @Message(id = NONE, value = "* Add new alias to the credential store") String cmdLineAddAliasDesc(); - @Message(id = NONE, value = "Remove alias from the credential store (Action)") + @Message(id = NONE, value = "* Remove alias from the credential store") String cmdLineRemoveAliasDesc(); - @Message(id = NONE, value = "Check if alias exists within the credential store (Action)") + @Message(id = NONE, value = "* Check if alias exists within the credential store") String cmdLineCheckAliasDesc(); - @Message(id = NONE, value = "Display all aliases (Action)") + @Message(id = NONE, value = "* Display all aliases") String cmdLineAliasesDesc(); - @Message(id = NONE, value = "Display all types of stored credentials for given alias (Action)") + @Message(id = NONE, value = "* Display all types of stored credentials for given alias") String cmdLineAliasTypes(); @Message(id = NONE, value = "Generate private and public key pair and store them as a KeyPairCredential") @@ -159,7 +159,7 @@ public interface ElytronToolMessages extends BasicLogger { @Message(id = NONE, value = "Print summary, especially command how to create this credential store") String cmdLinePrintSummary(); - @Message(id = NONE, value = "Get help with usage of this command (Action)") + @Message(id = NONE, value = "* Get help with usage of this command") String cmdLineHelp(); @Message(id = NONE, value = "Alias \"%s\" exists") @@ -281,7 +281,7 @@ public interface ElytronToolMessages extends BasicLogger { @Message(id = NONE, value = "CLI command to add new credential store:%n") String cliCommandToNewCredentialStore(); - @Message(id = NONE, value = "Bulk conversion with options listed in description file. All options have no default value and should be set in the file. (Action)%n" + + @Message(id = NONE, value = "* Bulk conversion with options listed in description file. All options have no default value and should be set in the file.%n" + "All options are required with the exceptions:%n" + " - \"properties\" option%n - \"type\" option (defaults to \"KeyStoreCredentialStore\")%n - \"credential-store-provider\" option%n - \"other-providers\" option%n" + " - \"salt\" and \"iteration\" options can be omitted when plain-text password is used%n" + @@ -406,7 +406,7 @@ public interface ElytronToolMessages extends BasicLogger { String longOptionDescription(String option, String longOption); // filesystem-realm command - @Message(id = NONE, value = "'FileSystemRealm' command is used to convert legacy properties files and scripts to an Elytron FileSystemRealm.") + @Message(id = NONE, value = "\"filesystem-realm\" command is used to convert legacy properties files and scripts to an Elytron FileSystemRealm.") String cmdFileSystemRealmHelpHeader(); @Message(id = NONE, value = "The relative or absolute path to the users file.") @@ -489,7 +489,7 @@ public interface ElytronToolMessages extends BasicLogger { @Message(id = NONE, value = "Name of the security-domain to be configured.") String cmdFileSystemRealmSecurityDomainNameDesc(); - @Message(id = NONE, value = "Bulk conversion with options listed in description file. Optional options have default values, required options do not. (Action) %n" + + @Message(id = NONE, value = "* Bulk conversion with options listed in description file. Optional options have default values, required options do not.%n" + "The options fileSystemRealmName and securityDomainName are optional. %n" + "These optional options have default values of: converted-properties-filesystem-realm and converted-properties-security-domain. %n" + "Values are required for the following options: users-file, roles-file, and output-location. %n" + @@ -498,7 +498,7 @@ public interface ElytronToolMessages extends BasicLogger { "Blocks of options must be separated by a blank line.") String cmdFileSystemRealmBulkConvertDesc(); - @Message(id = NONE, value = "Bulk conversion with options listed in description file. Optional options have default values, required options do not. (Action) %n" + + @Message(id = NONE, value = "* Bulk conversion with options listed in description file. Optional options have default values, required options do not. %n" + "The options realm-name, hash-encoding, levels, secret-key, create, populate, keystore, type, password, password-env, and key-pair are optional. %n" + "Values are required for the following options: input-location, output-location, and credential-store. %n" + "The default values of realm-name, hash-encoding, hash-charset, levels, secret-key, create, and populate are encrypted-filesystem-realm, BASE64, UTF-8, 2, key, true, and true respectively. %n" + @@ -508,7 +508,7 @@ public interface ElytronToolMessages extends BasicLogger { "Blocks of options must be separated by a blank line.") String cmdFileSystemRealmEncryptBulkConvertDesc(); - @Message(id = NONE, value = "Bulk conversion with options listed in description file. (Action)" + + @Message(id = NONE, value = "* Bulk conversion with options listed in description file. " + "Optional options have defaults and can be skipped ([type, default_or_NULL]), required options do not (). %n" + "One of either password or password-env is required. %n" + "Blocks of options must be separated by a blank line; order is not important. Syntax: %n" + @@ -519,7 +519,7 @@ public interface ElytronToolMessages extends BasicLogger { String cmdFileSystemRealmIntegrityBulkConvertDesc(); // filesystem-realm encrypt command - @Message(id = NONE, value = "'FileSystemRealmEncrypt' command is used to convert non-empty, un-encrypted FileSystemSecurityRealm(s) to encrypted FileSystemSecurityRealm(s) with a SecretKey.") + @Message(id = NONE, value = "\"filesystem-realm-encrypt\" command is used to convert non-empty, un-encrypted FileSystemSecurityRealm(s) to encrypted FileSystemSecurityRealm(s) with a SecretKey.") String cmdFileSystemEncryptHelpHeader(); @Message(id = NONE, value = "Secret Key was not found in the Credential Store at %s, and populate option was not set. Skipping descriptor file block number %d.") @@ -669,7 +669,7 @@ public interface ElytronToolMessages extends BasicLogger { @Message(id = NONE, value = "Should file %s be overwritten? (y/n) ") String shouldFileBeOverwritten(String file); - @Message(id = NONE, value = "\nSome of the parameters below are mutually exclusive actions which are marked with (Action) in the description.") + @Message(id = NONE, value = "Some of the parameters below are mutually exclusive actions which are marked with * in the description.") String cmdLineActionsHelpHeader(); @Message(id = NONE, value = "Key size (bits).") @@ -738,8 +738,13 @@ public interface ElytronToolMessages extends BasicLogger { @Message(id = NONE, value = "No Credential Store location or Secret Key Alias specified.") MissingOptionException missingCredentialStoreSecretKey(); + @Message(id = NONE, value = "To get list of options for a specific command, please specify the command by using ./elytron-tool.sh [command] --help") + String generalHelpOptionsOpening(); + + @Message(id = NONE, value = "A tool that assists with Elytron configuration") + String cmdElytronToolDescription(); + // Numeric Errors @Message(id = 35, value = "Only one of '%s' and '%s' can be specified at the same time") IllegalArgumentException mutuallyExclusiveOptions(String first, String second); - } diff --git a/tool/src/main/java/org/wildfly/security/tool/FileSystemEncryptRealmCommand.java b/tool/src/main/java/org/wildfly/security/tool/FileSystemEncryptRealmCommand.java index c5e858827a4..844332d76de 100644 --- a/tool/src/main/java/org/wildfly/security/tool/FileSystemEncryptRealmCommand.java +++ b/tool/src/main/java/org/wildfly/security/tool/FileSystemEncryptRealmCommand.java @@ -65,7 +65,6 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionGroup; import org.apache.commons.cli.Options; @@ -73,6 +72,10 @@ import org.wildfly.security.auth.realm.FileSystemSecurityRealm; import org.wildfly.security.auth.realm.FileSystemSecurityRealmBuilder; import org.wildfly.security.password.spec.Encoding; +import org.wildfly.security.tool.help.DescriptionSection; +import org.wildfly.security.tool.help.HelpCommand; +import org.wildfly.security.tool.help.OptionsSection; +import org.wildfly.security.tool.help.UsageSection; /** * Elytron-Tool command to convert un-encrypted FileSystemRealms into an encrypted realm with the use of a SecretKey. @@ -576,13 +579,15 @@ public void execute(String[] args) throws Exception { */ @Override public void help() { - HelpFormatter help = new HelpFormatter(); - help.setWidth(WIDTH); - help.printHelp(ElytronToolMessages.msg.cmdHelp(getToolCommand(), FILE_SYSTEM_ENCRYPT_COMMAND), - ElytronToolMessages.msg.cmdFileSystemEncryptHelpHeader(), - options, - "", - true); + OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.cmdLineActionsHelpHeader(), options); + UsageSection usageSection = new UsageSection(FILE_SYSTEM_ENCRYPT_COMMAND, null); + DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdFileSystemEncryptHelpHeader()); + HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder() + .description(descriptionSection) + .usage(usageSection) + .options(optionsSection) + .build(); + helpCommand.printHelp(); } /** diff --git a/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmCommand.java b/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmCommand.java index 2bf21d4e484..aff167ece0b 100644 --- a/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmCommand.java +++ b/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmCommand.java @@ -48,7 +48,6 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.lang3.ArrayUtils; @@ -63,6 +62,10 @@ import org.wildfly.security.password.interfaces.DigestPassword; import org.wildfly.security.password.spec.DigestPasswordSpec; import org.wildfly.security.password.spec.PasswordSpec; +import org.wildfly.security.tool.help.DescriptionSection; +import org.wildfly.security.tool.help.HelpCommand; +import org.wildfly.security.tool.help.OptionsSection; +import org.wildfly.security.tool.help.UsageSection; /** * Elytron-Tool command to convert legacy properties file into a FileSystemRealm. @@ -298,13 +301,15 @@ public void execute(String[] args) throws Exception { */ @Override public void help() { - HelpFormatter help = new HelpFormatter(); - help.setWidth(WIDTH); - help.printHelp(ElytronToolMessages.msg.cmdHelp(getToolCommand(), FILE_SYSTEM_REALM_COMMAND), - ElytronToolMessages.msg.cmdFileSystemRealmHelpHeader().concat(ElytronToolMessages.msg.cmdLineActionsHelpHeader()), - options, - "", - true); + OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.cmdLineActionsHelpHeader(), options); + UsageSection usageSection = new UsageSection(FILE_SYSTEM_REALM_COMMAND, null); + DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdFileSystemRealmHelpHeader()); + HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder() + .description(descriptionSection) + .usage(usageSection) + .options(optionsSection) + .build(); + helpCommand.printHelp(); } @Override diff --git a/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmIntegrityCommand.java b/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmIntegrityCommand.java index 176b824dfd1..33de909669f 100644 --- a/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmIntegrityCommand.java +++ b/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmIntegrityCommand.java @@ -72,7 +72,6 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionGroup; import org.apache.commons.cli.Options; @@ -80,6 +79,10 @@ import org.wildfly.security.auth.realm.FileSystemSecurityRealm; import org.wildfly.security.auth.realm.FileSystemSecurityRealmBuilder; import org.wildfly.security.password.spec.Encoding; +import org.wildfly.security.tool.help.DescriptionSection; +import org.wildfly.security.tool.help.HelpCommand; +import org.wildfly.security.tool.help.OptionsSection; +import org.wildfly.security.tool.help.UsageSection; /** * Elytron Tool command to enable integrity checking in filesystem realms that previously did not have it enabled. If @@ -610,13 +613,15 @@ public void execute(String[] args) throws Exception { /** Displays the help screen for the command */ @Override public void help() { - HelpFormatter help = new HelpFormatter(); - help.setWidth(WIDTH); - help.printHelp(ElytronToolMessages.msg.cmdHelp(getToolCommand(), FILE_SYSTEM_REALM_INTEGRITY_COMMAND), - ElytronToolMessages.msg.cmdFileSystemIntegrityHelpHeader(), - options, - "", - true); + OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.cmdLineActionsHelpHeader(), options); + UsageSection usageSection = new UsageSection(FILE_SYSTEM_REALM_INTEGRITY_COMMAND, null); + DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdFileSystemIntegrityHelpHeader()); + HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder() + .description(descriptionSection) + .usage(usageSection) + .options(optionsSection) + .build(); + helpCommand.printHelp(); } /** diff --git a/tool/src/main/java/org/wildfly/security/tool/MaskCommand.java b/tool/src/main/java/org/wildfly/security/tool/MaskCommand.java index bf029f0e6ee..154a95d9582 100644 --- a/tool/src/main/java/org/wildfly/security/tool/MaskCommand.java +++ b/tool/src/main/java/org/wildfly/security/tool/MaskCommand.java @@ -23,9 +23,12 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.wildfly.security.tool.help.DescriptionSection; +import org.wildfly.security.tool.help.HelpCommand; +import org.wildfly.security.tool.help.OptionsSection; +import org.wildfly.security.tool.help.UsageSection; import org.wildfly.security.util.PasswordBasedEncryptionUtil; import static org.wildfly.security.tool.Params.DEBUG_PARAM; @@ -158,12 +161,14 @@ static char[] decryptMasked(String maskedPassword) throws GeneralSecurityExcepti */ @Override public void help() { - HelpFormatter help = new HelpFormatter(); - help.setWidth(WIDTH); - help.printHelp(ElytronToolMessages.msg.cmdHelp(getToolCommand(), MASK_COMMAND), - ElytronToolMessages.msg.cmdMaskHelpHeader().concat(ElytronToolMessages.msg.cmdLineActionsHelpHeader()), - options, - "", - true); + OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.cmdLineActionsHelpHeader(), options); + UsageSection usageSection = new UsageSection(MASK_COMMAND, null); + DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdMaskHelpHeader()); + HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder() + .description(descriptionSection) + .usage(usageSection) + .options(optionsSection) + .build(); + helpCommand.printHelp(); } } diff --git a/tool/src/main/java/org/wildfly/security/tool/VaultCommand.java b/tool/src/main/java/org/wildfly/security/tool/VaultCommand.java index 3b3b2b2ee86..3637c9ba667 100644 --- a/tool/src/main/java/org/wildfly/security/tool/VaultCommand.java +++ b/tool/src/main/java/org/wildfly/security/tool/VaultCommand.java @@ -54,7 +54,6 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.wildfly.security.auth.SupportLevel; @@ -67,6 +66,10 @@ import org.wildfly.security.credential.store.impl.KeyStoreCredentialStore; import org.wildfly.security.credential.store.impl.VaultCredentialStore; import org.wildfly.security.password.interfaces.ClearPassword; +import org.wildfly.security.tool.help.DescriptionSection; +import org.wildfly.security.tool.help.HelpCommand; +import org.wildfly.security.tool.help.OptionsSection; +import org.wildfly.security.tool.help.UsageSection; import org.wildfly.security.util.PasswordBasedEncryptionUtil; /** @@ -85,6 +88,7 @@ public class VaultCommand extends Command { public static final String VAULT_COMMAND = "vault"; public static final String FAIL_IF_EXIST_PARAM = "fail-if-exist"; + public static final String MASK_PREFIX = "MASK-"; // convert options public static final String KEYSTORE_PASSWORD_PARAM = "keystore-password"; @@ -244,13 +248,15 @@ private void checkInvalidOptions(String... invalidOptions) throws Exception { */ @Override public void help() { - HelpFormatter help = new HelpFormatter(); - help.setWidth(WIDTH); - help.printHelp(ElytronToolMessages.msg.cmdHelp(getToolCommand(), VAULT_COMMAND), - ElytronToolMessages.msg.cmdVaultHelpHeader().concat(ElytronToolMessages.msg.cmdLineActionsHelpHeader()), - options, - "", - true); + OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.cmdLineActionsHelpHeader(), options); + UsageSection usageSection = new UsageSection(VAULT_COMMAND, null); + DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdVaultHelpHeader()); + HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder() + .description(descriptionSection) + .usage(usageSection) + .options(optionsSection) + .build(); + helpCommand.printHelp(); } private String convertedStoreName(String encryptionDirectory, Map implProps) { @@ -391,7 +397,7 @@ private List parseDescriptorFile(String descriptorFileLocation) thro } private CredentialSourceProtectionParameter getCredentialStoreProtectionParameter(final String vaultPassword, final String salt, final int iterationCount) throws GeneralSecurityException { - char[] password = vaultPassword.startsWith("MASK-") ? decodeMaskedPassword(vaultPassword.substring("MASK-".length()), salt, iterationCount) + char[] password = vaultPassword.startsWith(MASK_PREFIX) ? decodeMaskedPassword(vaultPassword.substring(MASK_PREFIX.length()), salt, iterationCount) : vaultPassword.toCharArray(); return new CredentialStore.CredentialSourceProtectionParameter( IdentityCredentials.NONE.withCredential( @@ -399,7 +405,7 @@ private CredentialSourceProtectionParameter getCredentialStoreProtectionParamete } private CredentialSourceProtectionParameter getVaultCredentialStoreProtectionParameter(final String keyStoreURL, final String vaultPassword, final String salt, final int iterationCount, final String secretKeyAlias) throws GeneralSecurityException, IOException { - char[] password = vaultPassword.startsWith("MASK-") ? decodeMaskedPassword(vaultPassword.substring("MASK-".length()), salt, iterationCount) + char[] password = vaultPassword.startsWith(MASK_PREFIX) ? decodeMaskedPassword(vaultPassword.substring(MASK_PREFIX.length()), salt, iterationCount) : vaultPassword.toCharArray(); final KeyStore keyStore = KeyStore.getInstance(defaultKeyStoreType); try (FileInputStream in = new FileInputStream(new File(keyStoreURL))) { @@ -445,7 +451,7 @@ private void printSummary (String keystorePassword, String salt, int iterationCo if (keystorePassword != null) { password = keystorePassword; if (salt != null && iterationCount > -1) { - password = keystorePassword.startsWith("MASK-") ? keystorePassword + ";" + salt + ";" + String.valueOf(iterationCount) + password = keystorePassword.startsWith(MASK_PREFIX) ? keystorePassword + ";" + salt + ";" + iterationCount : MaskCommand.computeMasked(keystorePassword, salt, iterationCount); } } diff --git a/tool/src/main/java/org/wildfly/security/tool/help/CommandsSection.java b/tool/src/main/java/org/wildfly/security/tool/help/CommandsSection.java new file mode 100644 index 00000000000..46fa6086c9c --- /dev/null +++ b/tool/src/main/java/org/wildfly/security/tool/help/CommandsSection.java @@ -0,0 +1,114 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.tool.help; + +import java.util.Map; + +/** + * Command section of Elytron help command + * + * @author Petr Beran + */ +public class CommandsSection extends HelpSection { + + private final String sectionTitle; + private final Map sectionContent; + + public CommandsSection(Map commands) { + this.sectionTitle = "Commands"; + this.sectionContent = commands; + } + + @Override + public void printHelp() { + formatAndPrintTitle(sectionTitle); + if (sectionContent != null) { + + // Find the longest commandName + // This is needed to make sure that all descriptions start at the same index + int longestCommand = 0; + for (String command : sectionContent.keySet()) { + if (command.length() > longestCommand) { + longestCommand = command.length(); + } + } + for (Map.Entry command : sectionContent.entrySet()) { + formatAndPrintCommand(command.getKey(), command.getValue(), longestCommand); + } + } + } + + /** + * Formats and prints command and it's respective description + * + * @param commandName Command's name + * @param commandDescription Command's description + * @param longestCommand Length of the longest commands. Ensures that all descriptions start at the same column + */ + protected void formatAndPrintCommand(String commandName, final CharSequence commandDescription, final int longestCommand) { + CharSequence descriptionText = commandDescription; + final StringBuilder stringBuilder = new StringBuilder(); + + int minCommandAndDescGap = 4; // Gap between the longest commandName and its commandDescription + int commandDescriptionStartingIndex = longestCommand + minCommandAndDescGap + leftPadding; // Starting index of all commandDescriptions in the map + int commandDescriptionLength = lineWidth - commandDescriptionStartingIndex; + + appendGap(stringBuilder, leftPadding); + stringBuilder.append(commandName); + + // Append a gap so that all commandDescriptions in the map start at the same index + int realGap = commandDescriptionStartingIndex - leftPadding - commandName.length(); + appendGap(stringBuilder, realGap); + + // If the commandDescription fits one line, simply append it + if (descriptionText.length() <= commandDescriptionLength) { + stringBuilder.append(descriptionText); + stringBuilder.append(System.lineSeparator()); + } + else { + int lineIndex = checkForWhitespaceIndex(descriptionText, commandDescriptionLength); + + // Append the commandDescription that fits on a single line and remove it from the descriptionText + stringBuilder.append(descriptionText.subSequence(0,lineIndex)); + descriptionText = descriptionText.subSequence(lineIndex+1, descriptionText.length()); + stringBuilder.append(System.lineSeparator()); + + // Appends commandDescriptions from second row onward + while(0 < descriptionText.length()) { + + // Append a gap so that all commandDescriptions in the map start at the same index + appendGap(stringBuilder, commandDescriptionStartingIndex); + + // If the commandDescription fits one line, simply append it and end the while loop + if (descriptionText.length() <= commandDescriptionLength) { + stringBuilder.append(descriptionText); + stringBuilder.append(System.lineSeparator()); + break; + } + + lineIndex = checkForWhitespaceIndex(descriptionText, commandDescriptionLength); + + // Append the commandDescription that fits on a single line and remove it from the descriptionText + stringBuilder.append(descriptionText.subSequence(0,lineIndex)); + descriptionText = descriptionText.subSequence(lineIndex+1, descriptionText.length()); + stringBuilder.append(System.lineSeparator()); + } + } + printText(stringBuilder.toString()); + } +} diff --git a/tool/src/main/java/org/wildfly/security/tool/help/DescriptionSection.java b/tool/src/main/java/org/wildfly/security/tool/help/DescriptionSection.java new file mode 100644 index 00000000000..8721093e15f --- /dev/null +++ b/tool/src/main/java/org/wildfly/security/tool/help/DescriptionSection.java @@ -0,0 +1,43 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.tool.help; + +/** + * Description section of Elytron help command + * + * @author Petr Beran + */ +public class DescriptionSection extends HelpSection { + + private final String sectionTitle; + private final String sectionContent; + + + public DescriptionSection(String sectionContent) { + this.sectionTitle = "Description"; + this.sectionContent = sectionContent; + } + + @Override + public void printHelp() { + formatAndPrintTitle(sectionTitle); + if (sectionContent != null) { + formatAndPrintSectionContext(sectionContent); + } + } +} diff --git a/tool/src/main/java/org/wildfly/security/tool/help/HelpCommand.java b/tool/src/main/java/org/wildfly/security/tool/help/HelpCommand.java new file mode 100644 index 00000000000..342cd2457af --- /dev/null +++ b/tool/src/main/java/org/wildfly/security/tool/help/HelpCommand.java @@ -0,0 +1,115 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.tool.help; + +import org.aesh.readline.tty.terminal.TerminalConnection; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +/** + * Provides help for the Elytron Tool command + * + * @author Petr Beran + */ +public class HelpCommand { + + private final List helpSections; + private static TerminalConnection terminalConnection; + + private HelpCommand(HelpCommandBuilder helpCommandBuilder) { + this.helpSections = helpCommandBuilder.helpSections; + } + + /** + * Displays all sections for the help command + */ + public void printHelp() { + if (terminalConnection == null) { + try { + terminalConnection = new TerminalConnection(Charset.defaultCharset(), System.in, System.out); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + terminalConnection.write(System.lineSeparator()); + for (HelpSection helpSection : helpSections){ + helpSection.printHelp(); + } + terminalConnection.close(); + } + + public static TerminalConnection getTerminal() { + return terminalConnection; + } + + public static class HelpCommandBuilder { + + private UsageSection usageSection; + private DescriptionSection descriptionSection; + private CommandsSection commandsSection; + private OptionsSection optionsSection; + + private final List helpSections = new ArrayList<>(); + + private HelpCommandBuilder() {} + + public static HelpCommandBuilder builder() { + return new HelpCommandBuilder(); + } + + public HelpCommandBuilder usage(UsageSection usageSection) { + this.usageSection = usageSection; + return this; + } + + public HelpCommandBuilder description(DescriptionSection descriptionSection) { + this.descriptionSection = descriptionSection; + return this; + } + + public HelpCommandBuilder commands(CommandsSection commandsSection) { + this.commandsSection = commandsSection; + return this; + } + + public HelpCommandBuilder options(OptionsSection optionsSection) { + this.optionsSection = optionsSection; + return this; + } + + public HelpCommand build() { + // Ensures that all sections are in specific order and the order cannot be tampered with + if (descriptionSection != null) { + helpSections.add(descriptionSection); + } + if (usageSection != null) { + helpSections.add(usageSection); + } + if (commandsSection != null) { + helpSections.add(commandsSection); + } + if (optionsSection != null) { + helpSections.add(optionsSection); + } + return new HelpCommand(this); + } + } +} diff --git a/tool/src/main/java/org/wildfly/security/tool/help/HelpSection.java b/tool/src/main/java/org/wildfly/security/tool/help/HelpSection.java new file mode 100644 index 00000000000..a6f5a6727f4 --- /dev/null +++ b/tool/src/main/java/org/wildfly/security/tool/help/HelpSection.java @@ -0,0 +1,122 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.tool.help; + +import org.aesh.readline.terminal.formatting.CharacterType; +import org.aesh.readline.terminal.formatting.Color; +import org.aesh.readline.terminal.formatting.TerminalColor; +import org.aesh.readline.terminal.formatting.TerminalString; +import org.aesh.readline.terminal.formatting.TerminalTextStyle; +import org.aesh.readline.tty.terminal.TerminalConnection; + +/** + * General section of Elytron help command + * All Elytron help command sections should extend this one + * + * @author Petr Beran + */ +public abstract class HelpSection { + + final int leftPadding = 4; + final int lineWidth = 120; + final int textWidth = lineWidth - leftPadding; + + /** + * Displays help of specific section + */ + public abstract void printHelp(); + + /** + * Formats and prints a simple block of text + * For printing commands see {@link CommandsSection} + * + * @param text Text to print + */ + protected void formatAndPrintSectionContext(final CharSequence text) { + final StringBuilder stringBuilder = new StringBuilder(); + CharSequence contentText = text; + while(0 < contentText.length()) { + appendGap(stringBuilder, leftPadding); + // If the text fits one line, simply append it and end the while loop + if (contentText.length() <= textWidth) { + stringBuilder.append(contentText); + stringBuilder.append(System.lineSeparator()); + break; + } + int lineIndex = checkForWhitespaceIndex(contentText, textWidth); + // Append the text that fits on a single line and remove it from the contentText + stringBuilder.append(contentText.subSequence(0,lineIndex)); + contentText = contentText.subSequence(lineIndex+1, contentText.length()); + stringBuilder.append(System.lineSeparator()); + } + printText(stringBuilder.toString()); + } + + /** + * Formats and prints headers across all sections + * + * @param sectionTitle Title to format and print + */ + protected void formatAndPrintTitle(String sectionTitle) { + String titleText = new TerminalString(sectionTitle.toUpperCase(), + new TerminalColor(Color.CYAN, Color.DEFAULT, Color.Intensity.BRIGHT), + new TerminalTextStyle(CharacterType.BOLD)).toString(); + printText(titleText); + System.out.print(System.lineSeparator()); + } + + /** + * Finds the index of text that still fits on a single line and is a whitespace. + * We don't want to break words at the end of the line + * + * @param text Text to iterate + * @param maxWidth Max width of the line, start of the iteration + * @return Last whitespace index before the end of the line + */ + protected int checkForWhitespaceIndex(CharSequence text, int maxWidth) { + int lastWhitespaceIndex = maxWidth; + while (0 <= lastWhitespaceIndex && !Character.isWhitespace(text.charAt(lastWhitespaceIndex))) { + lastWhitespaceIndex--; + } + return lastWhitespaceIndex; + } + + /** + * Appends a gap of certain width + * + * @param text Text to which the gap should be appended + * @param gapWidth Width of the gap + */ + protected void appendGap(StringBuilder text, int gapWidth) { + for (int i = 0; i < gapWidth; i++){ + text.append(' '); + } + } + + /** + * Prints the text via system terminal and adds a line separator at the end. Doesn't add any formatting. + * + * @param text Text to print. Leave {@code null} for just a line separator. + */ + void printText(String text) { + TerminalConnection terminalConnection = HelpCommand.getTerminal(); + if (text != null) { + terminalConnection.write(text); + } + } +} diff --git a/tool/src/main/java/org/wildfly/security/tool/help/OptionsSection.java b/tool/src/main/java/org/wildfly/security/tool/help/OptionsSection.java new file mode 100644 index 00000000000..d82f0186619 --- /dev/null +++ b/tool/src/main/java/org/wildfly/security/tool/help/OptionsSection.java @@ -0,0 +1,55 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.tool.help; + +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.wildfly.security.tool.ElytronToolMessages; + +/** + * Options section of Elytron help tool + * @author Petr Beran + */ +public class OptionsSection extends HelpSection { + + private final String sectionTitle; + private final String sectionHeader; + private final Options sectionContent; + + public OptionsSection(String sectionHeader, Options options) { + this.sectionTitle = "Options"; + this.sectionHeader = sectionHeader; + this.sectionContent = options; + } + + @Override + public void printHelp() { + formatAndPrintTitle(sectionTitle); + if (sectionHeader != null) { + formatAndPrintSectionContext(sectionHeader); + } + if (sectionContent != null) { + HelpFormatter help = new HelpFormatter(); + help.setSyntaxPrefix(""); + help.setLeftPadding(4); + help.setWidth(120); + help.printHelp(ElytronToolMessages.msg.cmdHelp("", ""), sectionContent); + printText(null); + } + } +} diff --git a/tool/src/main/java/org/wildfly/security/tool/help/UsageSection.java b/tool/src/main/java/org/wildfly/security/tool/help/UsageSection.java new file mode 100644 index 00000000000..01c235c318d --- /dev/null +++ b/tool/src/main/java/org/wildfly/security/tool/help/UsageSection.java @@ -0,0 +1,56 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.security.tool.help; + +/** + * Usage section of Elytron help command + * + * @author Petr Beran + */ +public class UsageSection extends HelpSection { + + private final String sectionTitle; + private final String sectionContent; + + public UsageSection(String command, String option) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("./elytron-tool.sh"); + + if (command != null) { + stringBuilder.append(" " + command); + } + else { + stringBuilder.append(" [command]"); + } + + if (option != null) { + stringBuilder.append(" " + option); + } + else { + stringBuilder.append(" [options]"); + } + this.sectionTitle = "Usage"; + this.sectionContent = stringBuilder.toString(); + } + + @Override + public void printHelp() { + formatAndPrintTitle(sectionTitle); + formatAndPrintSectionContext(sectionContent); + } +} diff --git a/tool/src/test/java/org/wildfly/security/tool/FileSystemEncryptRealmCommandTest.java b/tool/src/test/java/org/wildfly/security/tool/FileSystemEncryptRealmCommandTest.java index 6f698df14ff..32ff3c29f2d 100644 --- a/tool/src/test/java/org/wildfly/security/tool/FileSystemEncryptRealmCommandTest.java +++ b/tool/src/test/java/org/wildfly/security/tool/FileSystemEncryptRealmCommandTest.java @@ -23,13 +23,17 @@ import java.io.File; import java.io.FileNotFoundException; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Comparator; import java.util.HashMap; import java.util.Map; import javax.crypto.SecretKey; import org.apache.commons.cli.MissingArgumentException; +import org.junit.AfterClass; import org.junit.Test; import org.wildfly.security.auth.principal.NamePrincipal; import org.wildfly.security.auth.realm.FileSystemSecurityRealm; @@ -229,6 +233,18 @@ public void testSingleUserAndVerify() throws Exception { existingIdentity.dispose(); } + @AfterClass + public static void cleanup() throws Exception { + //cleanup after testBulkWithoutNames test + Path bulkWithoutNamesFolderPath = Paths.get(RELATIVE_BASE_DIR + "fs-encrypted-realms/bulk-encryption-conversion-desc-without-names"); + if (bulkWithoutNamesFolderPath.toFile().exists()) { + Files.walk(bulkWithoutNamesFolderPath) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + private boolean fileExists(String path) { File tempFile = new File(path); return tempFile.exists(); diff --git a/tool/src/test/java/org/wildfly/security/tool/MaskCommandTest.java b/tool/src/test/java/org/wildfly/security/tool/MaskCommandTest.java index 3ce57f7b912..36327402573 100644 --- a/tool/src/test/java/org/wildfly/security/tool/MaskCommandTest.java +++ b/tool/src/test/java/org/wildfly/security/tool/MaskCommandTest.java @@ -17,6 +17,7 @@ */ package org.wildfly.security.tool; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.wildfly.security.tool.Params.LINE_SEPARATOR; @@ -51,7 +52,7 @@ public void maskCompatibilityCheck() throws Exception { String retVal = executeCommandAndCheckStatusAndGetOutput(args); String retValNoNewLine = retVal.substring(0, retVal.indexOf(LINE_SEPARATOR)); - assertTrue("output has to be the as pre-generated one", ("MASK-" + pbGenerated + ";" + "ASDF1234" + ";" + 123).equals(retValNoNewLine)); + assertEquals("output has to be the as pre-generated one", "MASK-" + pbGenerated + ";" + "ASDF1234" + ";" + 123, retValNoNewLine); } @Test @@ -64,7 +65,7 @@ public void testMissingSaltAndIteration() { String[] retValLines = retVal.split(LINE_SEPARATOR); assertTrue("Message about invalid salt parameter must be present", retValLines[0].contains("Invalid \"salt\" parameter. Generated value")); - assertTrue("Message about invalid iteration parameter must be present", ("Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.").equals(retValLines[1])); + assertEquals("Message about invalid iteration parameter must be present", "Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.", retValLines[1]); assertTrue("Message about invalid salt parameter must be present", retValLines[2].contains("MASK-")); } @@ -80,8 +81,9 @@ public void testMissingIteration() { String retVal = executeCommandAndCheckStatusAndGetOutput(args); String[] retValLines = retVal.split(LINE_SEPARATOR); - assertTrue("Message about invalid iteration parameter must be present", ("Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.").equals(retValLines[0])); - assertTrue("Output has to be the as pre-generated one", ("MASK-" + pregenerated + ";" + salt + ";" + defaultIteration).equals(retValLines[1])); + assertEquals("Message about invalid iteration parameter must be present", "Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.", + retValLines[0]); + assertEquals("Output has to be the as pre-generated one", "MASK-" + pregenerated + ";" + salt + ";" + defaultIteration, retValLines[1]); } @Test @@ -124,12 +126,13 @@ public void testIterationAsStringValue() { String[] args = { "--secret", secret, "--salt", salt, "--iteration", "abcd" }; + String retVal = executeCommandAndCheckStatusAndGetOutput(args); String[] retValLines = retVal.split(LINE_SEPARATOR); - assertTrue("IllegalArgumentException must be present", ("java.lang.IllegalArgumentException: ELYTOOL00007: Invalid \"iteration\" value. Must be an integer between 1 and 2147483647, inclusive").equals(retValLines[0])); - assertTrue("Message about invalid iteration parameter must be present", ("Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.").equals(retValLines[1])); - assertTrue("Output has to be the as pre-generated one", ("MASK-" + pregenerated + ";" + salt + ";" + defaultIteration).equals(retValLines[2])); + assertEquals("IllegalArgumentException must be present", "java.lang.IllegalArgumentException: ELYTOOL00007: Invalid \"iteration\" value. Must be an integer between 1 and 2147483647, inclusive", retValLines[0]); + assertEquals("Message about invalid iteration parameter must be present", "Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.", retValLines[1]); + assertEquals("Output has to be the as pre-generated one", "MASK-" + pregenerated + ";" + salt + ";" + defaultIteration, retValLines[2]); } @Test @@ -144,9 +147,9 @@ public void testIterationAsLongMax() { String retVal = executeCommandAndCheckStatusAndGetOutput(args); String[] retValLines = retVal.split(LINE_SEPARATOR); - assertTrue("IllegalArgumentException must be present", ("java.lang.IllegalArgumentException: ELYTOOL00007: Invalid \"iteration\" value. Must be an integer between 1 and 2147483647, inclusive").equals(retValLines[0])); - assertTrue("Message about invalid iteration parameter must be present", ("Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.").equals(retValLines[1])); - assertTrue("Output has to be the as pre-generated one", ("MASK-" + pregenerated + ";" + salt + ";" + defaultIteration).equals(retValLines[2])); + assertEquals("IllegalArgumentException must be present", "java.lang.IllegalArgumentException: ELYTOOL00007: Invalid \"iteration\" value. Must be an integer between 1 and 2147483647, inclusive", retValLines[0]); + assertEquals("Message about invalid iteration parameter must be present", "Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.", retValLines[1]); + assertEquals("Output has to be the as pre-generated one", "MASK-" + pregenerated + ";" + salt + ";" + defaultIteration, retValLines[2]); } @Test @@ -161,9 +164,9 @@ public void testIterationAsNegativeValue() { String retVal = executeCommandAndCheckStatusAndGetOutput(args); String[] retValLines = retVal.split(LINE_SEPARATOR); - assertTrue("IllegalArgumentException must be present", ("java.lang.IllegalArgumentException: ELYTOOL00007: Invalid \"iteration\" value. Must be an integer between 1 and 2147483647, inclusive").equals(retValLines[0])); - assertTrue("Message about invalid iteration parameter must be present", ("Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.").equals(retValLines[1])); - assertTrue("Output has to be the as pre-generated one", ("MASK-" + pregenerated + ";" + salt + ";" + defaultIteration).equals(retValLines[2])); + assertEquals("IllegalArgumentException must be present", "java.lang.IllegalArgumentException: ELYTOOL00007: Invalid \"iteration\" value. Must be an integer between 1 and 2147483647, inclusive",retValLines[0]); + assertEquals("Message about invalid iteration parameter must be present", "Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.",retValLines[1]); + assertEquals("Output has to be the as pre-generated one", "MASK-" + pregenerated + ";" + salt + ";" + defaultIteration, retValLines[2]); } @Test @@ -186,4 +189,18 @@ public void testDuplicateOptions() { Assert.assertTrue(output.contains("Option \"secret\" specified more than once. Only the first occurrence will be used.")); Assert.assertFalse(output.contains("Option \"iteration\" specified more than once. Only the first occurrence will be used")); } + + @Test + public void testDecryptMasked() throws Exception { + final String originalSecret = "super_secret"; + final String salt = "ASDF1234"; + final int iterationCount = 123; + final String preGeneratedMaskedPassword = "MASK-088WUKotOwu7VOS8xRj.Rr;ASDF1234;123"; + + char[] decryptedSecret = MaskCommand.decryptMasked(preGeneratedMaskedPassword); + + Assert.assertNotNull("Decrypted secret should not be null", decryptedSecret); + Assert.assertEquals("Decrypted secret should match the original secret", originalSecret, new String(decryptedSecret)); + } + } diff --git a/tool/src/test/java/org/wildfly/security/tool/VaultCommandTest.java b/tool/src/test/java/org/wildfly/security/tool/VaultCommandTest.java index e53cc183589..c456ca44661 100644 --- a/tool/src/test/java/org/wildfly/security/tool/VaultCommandTest.java +++ b/tool/src/test/java/org/wildfly/security/tool/VaultCommandTest.java @@ -384,7 +384,7 @@ public void bulkConversionBasicTest() throws Exception { // conversion String output = executeCommandAndCheckStatusAndGetOutput(args); String[] parts = output.split("converted to credential store"); - Assert.assertTrue("Three credential stores has to be created", parts.length == 4); + Assert.assertEquals("Three credential stores has to be created", 4, parts.length); Assert.assertTrue("Check file names must pass", output.indexOf("vault-v1/vault-jceks.keystore") > 0 && output.indexOf("vault-v1-more/vault-jceks.keystore") > 0); // check result @@ -508,7 +508,7 @@ private void executeVaultCommandWithParams(String[] args, boolean shouldPass, St String message = "Execution of vault command with arguments {" + String.join(" ", args) + "} should" + (shouldPass? " succeeded ": " failed ") + "but it" + (shouldPass? " failed": " succeeded"); - Assert.assertTrue(message, passed == shouldPass); + Assert.assertEquals(message, shouldPass, passed); if (expectedOutput != null) { Assert.assertTrue("Command output should contain \"" + expectedOutput + "\"", output.contains(expectedOutput)); diff --git a/tool/src/test/resources/bulk-encryption-conversion-desc-without-names b/tool/src/test/resources/bulk-encryption-conversion-desc-without-names index d28bded00a9..d51ee5e5b79 100644 --- a/tool/src/test/resources/bulk-encryption-conversion-desc-without-names +++ b/tool/src/test/resources/bulk-encryption-conversion-desc-without-names @@ -1,22 +1,22 @@ input-location:target/test-classes/filesystem-encrypt/fs-unencrypted-realms/multiple-credential-types -output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms +output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms/bulk-encryption-conversion-desc-without-names credential-store:target/test-classes/filesystem-encrypt/mycredstore.cs create:true levels:1 input-location:target/test-classes/filesystem-encrypt/fs-unencrypted-realms/level-4 -output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms +output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms/bulk-encryption-conversion-desc-without-names credential-store:target/test-classes/filesystem-encrypt/mycredstore.cs create:true levels:4 input-location:target/test-classes/filesystem-encrypt/fs-unencrypted-realms/fsRealmCharset -output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms +output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms/bulk-encryption-conversion-desc-without-names credential-store:target/test-classes/filesystem-encrypt/mycredstore.cs create:true input-location:target/test-classes/filesystem-encrypt/fs-unencrypted-realms/hashencoding -output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms +output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms/bulk-encryption-conversion-desc-without-names hash-encoding:hex credential-store:target/test-classes/filesystem-encrypt/mycredstore.cs -create:true +create:true \ No newline at end of file diff --git a/util/pom.xml b/util/pom.xml index 9706c48891a..4ee89f9b23a 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT 4.0.0 diff --git a/wildfly-elytron/pom.xml b/wildfly-elytron/pom.xml index 1c21d79333d..2bf06d9be43 100644 --- a/wildfly-elytron/pom.xml +++ b/wildfly-elytron/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT 4.0.0 @@ -293,6 +293,10 @@ org.wildfly.security wildfly-elytron-digest + + org.wildfly.security + wildfly-elytron-dynamic-ssl + org.wildfly.security wildfly-elytron-encryption @@ -546,14 +550,14 @@ com.github.siom79.japicmp japicmp-maven-plugin - 0.13.0 + 0.20.0 false org.wildfly.security wildfly-elytron - 2.2.6.Final + 2.6.0.Final jar @@ -633,6 +637,11 @@ wildfly-elytron-digest ${project.version} + + org.wildfly.security + wildfly-elytron-dynamic-ssl + ${project.version} + org.wildfly.security wildfly-elytron-http @@ -945,6 +954,8 @@ org.wildfly.security.auth.client.ElytronMessages_$logger + org.wildfly.security.auth.util.ElytronMessages_$logger + org.wildfly.security.auth.util.GSSCredentialSecurityFactory true diff --git a/x500/base/pom.xml b/x500/base/pom.xml index 35cae1ea990..740db174f50 100644 --- a/x500/base/pom.xml +++ b/x500/base/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/x500/base/src/main/java/org/wildfly/security/x500/X500.java b/x500/base/src/main/java/org/wildfly/security/x500/X500.java index 1c1e6ddafd4..0ab9ed83401 100644 --- a/x500/base/src/main/java/org/wildfly/security/x500/X500.java +++ b/x500/base/src/main/java/org/wildfly/security/x500/X500.java @@ -246,12 +246,10 @@ private static boolean createX509CertificateChain(final X509Certificate firstCer return false; } for (X509Certificate issuerCertificate : issuerCertificates) { - if (issuedBy(firstCertificate, issuerCertificate)) { + if (issuedBy(firstCertificate, issuerCertificate) && createX509CertificateChain(issuerCertificate, certificateChain, certificatesMap)) { // recurse - if (createX509CertificateChain(issuerCertificate, certificateChain, certificatesMap)) { - certificateChain.add(firstCertificate); - return true; - } + certificateChain.add(firstCertificate); + return true; } } return false; diff --git a/x500/base/src/main/java/org/wildfly/security/x500/util/X500PrincipalUtil.java b/x500/base/src/main/java/org/wildfly/security/x500/util/X500PrincipalUtil.java index 391d79a1c81..2ae2012821f 100644 --- a/x500/base/src/main/java/org/wildfly/security/x500/util/X500PrincipalUtil.java +++ b/x500/base/src/main/java/org/wildfly/security/x500/util/X500PrincipalUtil.java @@ -168,9 +168,7 @@ public static String[] getAttributeValues(X500Principal principal, String oid, b } } else { // The attribute values will be in reverse order - for (int i = 0; i < len; i++) { - result[i] = strings[i]; - } + System.arraycopy(strings, 0, result, 0, len); } return result; } diff --git a/x500/cert/acme/pom.xml b/x500/cert/acme/pom.xml index 7627c921410..c2040a3d5ed 100644 --- a/x500/cert/acme/pom.xml +++ b/x500/cert/acme/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml @@ -105,7 +105,7 @@ test - org.glassfish + org.eclipse.parsson jakarta.json test diff --git a/x500/cert/acme/src/main/java/org/wildfly/security/x500/cert/acme/AcmeClientSpi.java b/x500/cert/acme/src/main/java/org/wildfly/security/x500/cert/acme/AcmeClientSpi.java index 3003ae0d4c4..452480eb7a9 100644 --- a/x500/cert/acme/src/main/java/org/wildfly/security/x500/cert/acme/AcmeClientSpi.java +++ b/x500/cert/acme/src/main/java/org/wildfly/security/x500/cert/acme/AcmeClientSpi.java @@ -921,10 +921,8 @@ private static long getRetryAfter(HttpURLConnection connection, boolean useDefau } } - if (retryAfterMilli == -1) { - if (useDefaultIfHeaderNotPresent) { - retryAfterMilli = DEFAULT_RETRY_AFTER_MILLI; - } + if (retryAfterMilli == -1 && useDefaultIfHeaderNotPresent) { + retryAfterMilli = DEFAULT_RETRY_AFTER_MILLI; } return retryAfterMilli; } diff --git a/x500/cert/base/pom.xml b/x500/cert/base/pom.xml index 73a0de714fc..6af3f479612 100644 --- a/x500/cert/base/pom.xml +++ b/x500/cert/base/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml diff --git a/x500/cert/util/pom.xml b/x500/cert/util/pom.xml index a9a9a560873..5a548aa9725 100644 --- a/x500/cert/util/pom.xml +++ b/x500/cert/util/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../../pom.xml @@ -55,7 +55,7 @@ org.jboss.logging jboss-logging-processor provided - + diff --git a/x500/deprecated/pom.xml b/x500/deprecated/pom.xml index a8883a3dc6b..27075a20280 100644 --- a/x500/deprecated/pom.xml +++ b/x500/deprecated/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml diff --git a/x500/deprecated/src/main/java/org/wildfly/security/x500/X500AttributePrincipalDecoder.java b/x500/deprecated/src/main/java/org/wildfly/security/x500/X500AttributePrincipalDecoder.java index e311f0f2a21..13169db15af 100644 --- a/x500/deprecated/src/main/java/org/wildfly/security/x500/X500AttributePrincipalDecoder.java +++ b/x500/deprecated/src/main/java/org/wildfly/security/x500/X500AttributePrincipalDecoder.java @@ -31,7 +31,7 @@ * A principal decoder which decodes an attribute from an X.500 principal. * * @author David M. Lloyd - * @deprecated Use {@link org.wildfly.security.x500.principal.X500AttributePrincipalDecoder} instead + * @deprecated Use {@link org.wildfly.security.x500.principal.X500AttributePrincipalDecoder org.wildfly.security.x500.principal.X500AttributePrincipalDecoder} instead */ @Deprecated public final class X500AttributePrincipalDecoder implements PrincipalDecoder { diff --git a/x500/principal/pom.xml b/x500/principal/pom.xml index 59aae9d9af8..341c8a24c22 100644 --- a/x500/principal/pom.xml +++ b/x500/principal/pom.xml @@ -24,7 +24,7 @@ org.wildfly.security wildfly-elytron-parent - 2.2.7.CR1-SNAPSHOT + 2.6.1.CR1-SNAPSHOT ../../pom.xml