Saturday, December 12. 2020
Masking passwords for the wildfly-config.xml file
Today's entry is a summary about how to mask passwords inside the wildfly-config.xml file. This file is the client side configuration currently recommended to be used for calling an Enterprise Java Bean (EJB) deployed in a wildfly server. The EJB endpoints are secured by default in wildfly (username and password) and that information needs to be present in the configuration. Masked passwords are used (directly or indirectly) to not display plain passwords inside that file. But the masking process is not as easy as expected, so I decided to create a new blog entry.
Let's start just downloading and installing the new wildfly 21. The server is just exploded and a management (for the console) and an application user (for the EJB interface) are created.
wget https://download.jboss.org/wildfly/21.0.1.Final/wildfly-21.0.1.Final.zip
unzip wildfly-21.0.1.Final.zip
cd wildfly-21.0.1.Final/bin/
./add-user.sh -u admin -p admin
./add-user.sh -a -u ejbuser -p password123
./standalone.sh
Now a very simple test application is created. The hello-worl-ejb app is just a hello world but using the EJB technology. For that an interface is needed.
public interface HelloWorldRemote {
public String sayHello(String name);
}
And the implementation which is annotated to be a stateless enterprise java bean.
@Stateless
@Remote(HelloWorldRemote.class)
public class HelloWorldBean implements HelloWorldRemote {
@Override
public String sayHello(String name) {
return "Hello " + (name == null? "World" : name) + "!";
}
}
Finally a client is developed to call the previous bean and display the resulting salute.
public class HelloWorldClient {
public static void main(String... args) throws Exception {
Properties jndiProperties = new Properties();
jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory");
InitialContext context = new InitialContext(jndiProperties);
final String lookupName = "ejb:/hello-world-ejb/HelloWorldBean!es.rickyepoderi.ejb.HelloWorldRemote";
HelloWorldRemote helloWorld = (HelloWorldRemote) context.lookup(lookupName);
System.out.println(helloWorld.sayHello(args.length > 0? args[0] : null));
}
}
As you see, in the client nothing is defined about the URL or the login information (although you can use other properties the idea is using the wildfly-config.xml file). My first try is just using a clear password inside a minimal configuration to connect using the new http invocation (for more information about EJB in wildfly you can check this detailed post). So, with no masking, the configuration file can be the following.
<configuration>
<authentication-client xmlns="urn:elytron:client:1.4">
<credential-stores>
<authentication-rules>
<rule use-configuration="clear"/>
</authentication-rules>
<authentication-configurations>
<configuration name="clear">
<sasl-mechanism-selector selector="DIGEST-MD5"/>
<set-user-name name="ejbuser"/>
<credentials>
<clear-password password="password123"/>
</credentials>
</configuration>
</authentication-configurations>
</authentication-client>
<http-client xmlns="urn:wildfly-http-client:1.0">
<defaults></defaults>
</http-client>
<discovery xmlns="urn:wildfly-discovery:1.0">
<discovery-provider>
<static>
<service uri="http://localhost:8080/wildfly-services" abstract-type="ejb" abstract-type-authority="jboss">
<attribute name="ejb-module" value="hello-world-ejb" />
</service>
</static>
</discovery-provider>
</discovery>
</configuration>
The file is quite easy, in the urn:elytron:client section there is only one authentication configuration clear, which specifies the username and password in plain text, and is directly assigned to all the EJB connections by the rule. The urn:wildfly-discovery part is used to configure the wildfly server URL and urn:wildfly-http-client is just added to know that it exists and it can be used to add specific details for the http client. The final project for the sample application can be downloaded at the end of the entry. Just package the application, deploy the resulting war file into the wildfly server and execute using the exec plugin.
cd hello-world-ejb
mvn clean package
# deploy target/hello-world-ejb.war
mvn exec:java
...
[INFO] --- exec-maven-plugin:3.0.0:java (default-cli) @ hello-world-ejb ---
...
Hello World!
This configuration works but the password for the ejbuser is displayed in plain inside the file. How can it be masked? The only useful information I found about the subject is this entry in Ashley Abdel-Sayed's Blog. Mainly it says that you can use a masked password but some java code is needed. Please do not try to use the elytron-tool.sh command for creating them, its mask option is for picketlink integration and it does not return a valid masked password. Maybe this changes in the future, but now this tool is not suitable for the task. Therefore I have developed another program that using Ashley's idea can be executed to create one. The project can also be downloaded at the end of the entry and it accepts different arguments.
cd masked-pasword
mvn clean package
mvn exec:java -Dexec.args="-s 12345678 -i 100 password123"
...
Masked Password: $masked-MD5-DES$somearbitrarycrazystringthatdoesnotmatter$100$MTIzNDU2Nzg=$/Nym2s/dssNzlEkNlD+ycQ==$o8IbHwo2ZNk=
Snippet: <masked-password iteration-count="100" salt="12345678" initialization-vector="o8IbHwo2ZNk=" masked-password="/Nym2s/dssNzlEkNlD+ycQ=="/>
Password Verified 'true'
...
In the previous example default algorithm is used to create a masked password for password123 with salt 12345678 and one hundred iterations. The tool has a simple usage information that can be used to specify different options (algorithm for example). So now a new authentication configuration can be added to use the previous masked password.
<authentication-client xmlns="urn:elytron:client:1.4">
<authentication-rules>
<rule use-configuration="masked"/>
</authentication-rules>
<authentication-configurations>
<configuration name="masked">
<sasl-mechanism-selector selector="DIGEST-MD5"/>
<set-user-name name="ejbuser"/>
<credentials>
<masked-password iteration-count="100" salt="12345678" initialization-vector="o8IbHwo2ZNk=" masked-password="/Nym2s/dssNzlEkNlD+ycQ=="/>
</credentials>
</configuration>
</authentication-configurations>
</authentication-client>
The clear password is substituted by the masked one reported by the utility program and the new configuration masked is used by the rule. And this works perfectly. The password is masked (reversible algorithm) but at least it is not in plain.
But let's add a final configuration. The wildfly-config.xml can also be configured to use a credential store. A store that contains secret information like passwords. It can be created using the elytron-tool.sh utility.
cd ${WILDFLY_HOME}/bin
./elytron-tool.sh credential-store --create --location "store.jceks" --password password123
./elytron-tool.sh credential-store --location "store.jceks" --password password123 --add ejbpwd --secret password123
cp store.jceks /path/to/hello-world-ejb/
The previous execution initializes a store.jceks credential store with password password123 and adds the alias ejbpwd with the password for the ejbuser inside it. The store file is moved to the project folder to be referenced by the configuration. Now the wildfly-config.xml uses the store and the alias to obtain the password for the EJB user. But the general password for the store should also be provided in the configuration... And how do we provide it not being in clear? Using a masked password of course. So in this last option the authentication section looks like this.
<authentication-client xmlns="urn:elytron:client:1.4">
<credential-stores>
<credential-store name="store">
<attributes>
<attribute name="keyStoreType" value="JCEKS" />
<attribute name="location" value="./store.jceks" />
</attributes>
<protection-parameter-credentials>
<masked-password iteration-count="100" salt="12345678" initialization-vector="o8IbHwo2ZNk=" masked-password="/Nym2s/dssNzlEkNlD+ycQ=="/>
</protection-parameter-credentials>
</credential-store>
</credential-stores>
<authentication-rules>
<rule use-configuration="store"/>
</authentication-rules>
<authentication-configurations>
<configuration name="store">
<sasl-mechanism-selector selector="DIGEST-MD5"/>
<set-user-name name="ejbuser"/>
<credentials>
<credential-store-reference store="store" alias="ejbpwd"/>
</credentials>
</configuration>
</authentication-configurations>
</authentication-client>
This last configuration is the most complete. The authentication section uses a credential store. It can handle several aliases with different secrets associated. And the general password for the store is presented using a masked password.
So the summary is that although the wildfly-config.xml can manage masked passwords to not present them in plain, the information is not very clear and you need some java code to generate them. In this entry a maven project (you can download it from here) was used to generate the masked passwords following the same idea that Ashley Abdel-Sayed presented in his blog. This way it's easier to create them. Finally the maven project for the EJB test application can also be downloaded from here. The final wildfly-config.xml in the project contains the three authentication configurations presented in the entry. You can switch from one to the other just modifying the rule (use-configuration attribute).
Best regards!
Saturday, November 28. 2020
Can wildfly use BCFIPS provider for SSL running with jdk-11?
This time I am going to talk about the Bouncy Castle FIPS, wildfly and JDK version 11. It is known that BCFIPS can be configured with wildfly and jdk version 8 (this link is the EAP documentation but it also works for upstream wildfly). There is a step in which you need to add the bouncy castle jar files inside the ext directory of the JDK. But this is not possible since version 9, because the extension mechanism was removed. On the other hand, the certification of BCFIPS for jdk-11 is not absolutely clear to me, because in the faq it says that only jdk 7 and 8 are certified but in the roadmap version 1.0.2 certifies jre 11. So I assume that jdk 11 is valid for 1.0.2 but the faq page was not updated.
In general there is a big problem with wildfly if the extension mechanism is not present. The JDK needs to access to the BC classes in order to configure the provider, and in wildfly the jboss-modules project disallows adding jars globally. But let's go step by step, first just try a simple standalone java class. This is the sample.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpURLConnectionExample {
public static void main(String[] args) throws Exception {
URL obj = new URL(args[0]);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();ยก
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
System.out.println("GET Response Code: " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
StringBuffer response = new StringBuffer();
String inputLine;
while ((inputLine = in.readLine()) != null) {
response.append(inputLine).append(System.getProperty("line.separator"));
}
in.close();
System.out.println(response.toString());
} else {
System.out.println("GET request not worked");
}
}
}
The idea is configuring a JDK-11 to use the BCFIPS provider at JVM level. So just download a JDK-11 from adopt-openjdk and install it.
wget https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.9.1%2B1/OpenJDK11U-jdk_x64_linux_hotspot_11.0.9.1_1.tar.gz
tar zxvf OpenJDK11U-jdk_x64_linux_hotspot_11.0.9.1_1.tar.gz
After that the JDK needs to be configured to use the BCFIPS provider. For that, and following the documentation, the file ${JAVA_HOME}/conf/security/java.security is modified to add the new providers.
security.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
security.provider.2=com.sun.net.ssl.internal.ssl.Provider BCFIPS
security.provider.3=SUN
security.provider.4=SunRsaSign
security.provider.5=SunEC
security.provider.6=SunJCE
security.provider.7=SunJGSS
security.provider.8=SunSASL
security.provider.9=XMLDSig
security.provider.10=SunPCSC
security.provider.11=JdkLDAP
security.provider.12=JdkSASL
security.provider.13=SunPKCS11
The BCFIPS provider is added at first position and the ssl one is configured to use it at second position. The rest of the providers present in the default configuration are just moved after them (SunJSSE is removed because now is at position 2 and configured to use the BCFIPS). In order to execute the previous java file we need the default cacerts file converted to PKCS format, as BCFIPS only accepts this format.
cp ${JAVA_HOME}/lib/security/cacerts .
keytool -importkeystore -srckeystore cacerts -destkeystore cacerts.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass changeit -deststorepass changeit
Now compile and execute the sample class just adding the BC jar inside the classpath.
${JAVA_HOME}/bin/javac HttpURLConnectionExample.java
${JAVA_HOME}/bin/java -cp bc-fips-1.0.2.jar:. -Djavax.net.debug=all -Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStore=cacerts.p12 HttpURLConnectionExample https://blogs.nologin.es/rickyepoderi/
And it works. The BCFIPS can be executed successfully as a client and we can connect to retrieve the HTTPS page with it. But, as commented before, this will not work with wildfly. Because the JavaEE server uses jboss-modules and it avoids the access from normal java.base module to the BC jar. There is no way of adding the file to the JDK module classpath. In version 8 this was solved using the EXT directory but now I do not know how to overcome this. My only idea was adding the jar as boot classpath, adding the following option.
-Xbootclasspath/a:/path/to/bc-fips-1.0.2.jar
Sadly this idea does not work. The BC provider code uses in several places a call similar to XXX.class.getClassLoader().load("a.internal.jdk.class") to load classes from the SUN provider. If using the boot classpath the getClassLoader method returns null and it breaks the provider. I did a quick hack just changing the previous call with Class.forName. More or less I changed the following classes.
find . -type f -exec grep "Class.forName(" {} \; -print
Class def = Class.forName(className);
Class provClass = Class.forName("sun.security.jca.Providers");
./org/bouncycastle/jcajce/provider/BouncyCastleFipsProvider.java
Class def = Class.forName("sun.security.internal.spec.TlsPrfParameterSpec");
Class def = Class.forName("sun.security.internal.spec.TlsRsaPremasterSecretParameterSpec");
Class def = Class.forName("sun.security.internal.spec.TlsPrfParameterSpec");
Class def = Class.forName("sun.security.internal.spec.TlsRsaPremasterSecretParameterSpec");
./org/bouncycastle/jcajce/provider/ProvSunTLSKDF.java
Class def = Class.forName(className);
./org/bouncycastle/jcajce/provider/ClassUtil.java
return Class.forName(className);
./org/bouncycastle/jcajce/provider/GcmSpecUtil.java
Class def = Class.forName(className);
./org/bouncycastle/jcajce/provider/BaseSingleBlockCipher.java
Class.forName(className);
With the hack present the previous java works successfully using the BCFIPS provider configured inside the boot classpath. Note that I changed to use the sources directory with the modified classes.
${JAVA_HOME}/bin/java -Xbootclasspath/a:/path/to/bc-fips-1.0.2-sources -Djavax.net.debug=all -Djavax.net.ssl.trustStorePassword=changeit -Djavax.net.ssl.trustStore=cacerts.p12 HttpURLConnectionExample https://blogs.nologin.es/rickyepoderi/
Moving the same hack to wildfly also works. These are the configuring steps:
Modify the standalone.conf to include the modified provider:
JAVA_OPTS="$JAVA_OPTS -Xbootclasspath/a:/path/to/bc-fips-1.0.2-sources"
Create a certificate in the ${WILDFLY_HOME}/standalone/configuration folder and copy the previous cacerts.p12 to it:
keytool -genkeypair -alias localhost -keyalg RSA -keysize 2048 -validity 365 -keystore ${WILDFLY_HOME}/standalone/configuration/keystore.p12 -storetype PKCS12 -dname "CN=localhost" -storepass changeit -ext SAN=dns:localhost cp cacerts.p12 ${WILDFLY_HOME}/standalone/configuration/
Start the server using the configured JAVA_HOME.
export JAVA_HOME="/path/to/jdk-11.0.9.1+1" ./standalone.sh
Under the CLI command line, configure elytron to use all the previous stuff.
/subsystem=elytron/key-store=localhost:add(type=pkcs12, relative-to=jboss.server.config.dir, path=keystore.p12, credential-reference={clear-text=changeit}) /subsystem=elytron/key-store=ca:add(type=pkcs12, relative-to=jboss.server.config.dir, path=cacerts.p12, credential-reference={clear-text=changeit}) /subsystem=elytron/key-manager=localhost-manager:add(key-store=localhost, credential-reference={clear-text=changeit}) /subsystem=elytron/trust-manager=ca-manager:add(key-store=ca) /subsystem=elytron/server-ssl-context=localhost-context:add(key-manager=localhost-manager, trust-manager=ca-manager) batch /subsystem=undertow/server=default-server/https-listener=https:undefine-attribute(name=security-realm) /subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=ssl-context, value=localhost-context) run-batch
Change the ApplicationRealm to also use the p12 certificates:
/core-service=management/security-realm=ApplicationRealm/server-identity=ssl:write-attribute(name=keystore-provider, value=PKCS12) /core-service=management/security-realm=ApplicationRealm/server-identity=ssl:write-attribute(name=alias, value=localhost) /core-service=management/security-realm=ApplicationRealm/server-identity=ssl:write-attribute(name=keystore-path, value=keystore.p12) /core-service=management/security-realm=ApplicationRealm/server-identity=ssl:write-attribute(name=keystore-password, value=changeit) /core-service=management/security-realm=ApplicationRealm/server-identity=ssl:write-attribute(name=key-password, value=changeit)
And it works. Now the wildfly server is using BCFIPS. But obviously the solution is extremely hacky and unmanageable.
So the summary is that I could not add Bouncy Castle FIPS to wildfly using jdk 11 at the moment (I am talking about adding BCFIPS for SSL, using the provider programmatically is valid). BCFIPS seems to be certified with JRE version 11, but the necessary jars cannot be added to classpath when using wildfly and jboss-modules. My idea of using the boot classpath failed because of how BCFIPS loads the sun internal classes. So no luck, sometimes you get the bear and sometimes the bear gets you, and today it was the second option . Anyway I wanted to backup my tests in detail here. if I do not summarize them in the blog I will forget the steps that were tried and, more important, what was the root problem.
Regards!Sunday, April 5. 2020
JavaEE 8 Security API in Wildfly
Unexpectedly I am going to write a continuation of the previous entry about JASPI. The past week I needed to work with the new Java EE Security API Specification (JSR 375) and, because it is based on JASPI, the new specification is somewhat related to the previous post. There are several good articles over the internet about the new javaee security framework, for example Jakarta EE 8 Security API by baeldung or the overview done by DZone. Today's entry is just going to use the JSR 375 specification with a Wildfly 19 server.
Following the previous guides about the new API, a little sample application is going to be developed. The framework lets you configure the application login just defining a ApplicationScoped bean. The bean defines the authentication mechanism and the storage to use.
package es.rickyepoderi;
import javax.enterprise.context.ApplicationScoped;
import javax.security.enterprise.authentication.mechanism.http.FormAuthenticationMechanismDefinition;
import javax.security.enterprise.authentication.mechanism.http.LoginToContinue;
import javax.security.enterprise.identitystore.LdapIdentityStoreDefinition;
@FormAuthenticationMechanismDefinition(loginToContinue = @LoginToContinue(
loginPage = "/login.html",
errorPage = "/login-error.html"
))
@LdapIdentityStoreDefinition(
url="ldap://localhost:1389",
bindDn="cn=Directory Manager",
bindDnPassword="XXXX",
callerSearchBase="ou=People,dc=sample,dc=com",
groupSearchBase="ou=Groups,dc=sample,dc=com",
groupNameAttribute="cn",
groupMemberAttribute="uniqueMember"
)
@ApplicationScoped
public class ApplicationConfig {
}
So the application is going to use a FORM login (an HTML page will be shown to the user to login) and the user info are going to be retrieved from an LDAP server. The API gives more options for the mechanism (BasicAuthenticationMechanismDefinition and CustomFormAuthenticationMechanismDefinition) and the storage (DatabaseIdentityStoreDefinition or a custom IdentityStore). With the bean done, a HelloServlet is introduced.
package es.rickyepoderi;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.HttpConstraint;
import javax.servlet.annotation.ServletSecurity;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet({"/hello"})
@ServletSecurity(@HttpConstraint(rolesAllowed={"static-group1"}))
public class HelloWorldServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try (PrintWriter writer = response.getWriter()) {
response.setContentType("text/plain;charset=UTF-8");
writer.append("Hello ")
.append(request.getUserPrincipal() == null? "World" : request.getUserPrincipal().getName())
.append("!");
}
}
}
The Servlet is annotated to request a role (static-group1) which will be a group of the user in the LDAP repository. In my humble opinion the security API needs to be extended to apply these constraints in other places, right now only servlets can be annotated and, if I am not wrong, this is quite short (why not annotate other generic URLs that can be JSP, JSF, jax-rs,...). Adding a general place (in the bean for example?) to include constraints for generic URLs would be awesome. I have to say that I am still surprised about this, if I missed something please add a comment in the bottom section.
The final step when using wildfly is defining the jaspitest security domain. Why? Because the JSR 375 works with JASPI and, therefore, the application should be configured to use a JASPI capable security domain. The default security domain other configured by wildfly is not JASPI. In a common application the jboss-web.xml should be placed setting the module to use.
<jboss-web>
<security-domain>jaspitest</security-domain>
</jboss-web>
And that is all. The application should work in wildfly 19 for example. Just download, install it and deploy the application. You can download the maven project for the sample app from here.
wget https://download.jboss.org/wildfly/19.0.0.Final/wildfly-19.0.0.Final.zip
unzip wildfly-19.0.0.Final.zip
cd wildfly-19.0.0.Final/bin
./add-user.sh -u admin -p XXXX
./standalone.sh
./jboss-cli.sh --connect
deploy /path/to/javaee8sec/target/javaee8sec.war
But what if I do not want to specify the jboss-web.xml in the application, in the end this is not part of the standard. Well, the solution is quite simple, you can just change the default security domain for the web/undertow to jaspitest.
./jboss-cli.sh --connect
/subsystem=undertow:write-attribute(name=default-security-domain, value=jaspitest)
/:reload
The application now will work without the jboss-web.xml, because the jaspitest security domain is used by default by the apps that do not specify one. But what happens with elytron, in the previous entry we saw that the new security subsystem supports JASPI too, so, how can we configure the server to use it instead of the old security domain? Just like it was shown in the old entry, an undertow/elytron domain should be configured with JASPI enabled but not integrated. Here it is an example to configure elytron globally and enable JASPI for the security API.
cd wildfly-19.0.0.Final
bin/jboss-cli.sh --file=docs/examples/enable-elytron.cli
cd bin
./standalone.sh
./jboss-cli.sh --connect
/subsystem=undertow/application-security-domain=other:write-attribute(name=integrated-jaspi, value=false)
/subsystem=undertow:write-attribute(name=default-security-domain, value=other)
/:reload
The provided enable-elytron.cli is first used to completely configure elytron security (now everything in wildfly uses elytron instead of the old security system). Finally the default other domain is changed to set integrated-jaspi to false. Remember that this property makes that JASPI alone is enough to authenticate users, if false the user is also located in the associated realm inside elytron and for the security API that needs to be avoided.
As a summary wildfly 19 is JavaEE 8 certified and, therefore, the new security API is available. In the entry a little sample application was deployed to authenticate users against an LDAP server. The final points were used to avoid the inclusion of the jboss-web.xml in the application and configure the server to automatically use a domain that is JASPI enabled, using both, the old jaspitest domain and the new elytron susbsystem.
Best regards!
Comments