Client-cert + LDAP authentication with Elytron

1,081 views
Skip to first unread message

Benito Coelho

unread,
Mar 15, 2023, 1:48:47 PM3/15/23
to WildFly
Hi everyone,

How can I configure a client-cert + LDAP authentication in Elytron, considering the following details:
  • the client-cert authentication must be done first and, only if it fails, should the user be presented with a login form that would authenticate him with LDAP
  • alongside client-cert authentication, I need to use an internal lib that gets a specific field inside the x509 certificate that uniquely identifies the owner. This field is a specific OID one

I'm really new to Elytron. So if anyone could give me some guidance or working examples, I would appreciate it.

Best regards.
Benito.


Cameron Rodriguez

unread,
Mar 16, 2023, 1:25:25 PM3/16/23
to Benito Coelho, WildFly
Hi Benito, I'll answer your question as best as I can. Some of the notes will come from the Elytron migration guide for SSL with client certs[1]. Let me know if you need any clarification.

How can I configure a client-cert + LDAP authentication in Elytron, considering the following details:
  • the client-cert authentication must be done first and, only if it fails, should the user be presented with a login form that would authenticate him with LDAP
{client-cert} You can follow this quickstart[2] to setup the client cert authentication along with  (skip the server side certificate if it's applicable to your case). There's also a blog post on the Elytron website[3] that covers this, but for EJBs instead of WARs, and purely deals with mutual TLS without manipulating the X509 certificate

To implement the fallback, configure the `server-ssl-context` to prefer, but not require, client auth[4]. You can see the CLI commands (`WILDFLY_HOME/bin/jboss-cli.sh`)  below:

# Assuming server-ssl-context named default-ssc
/subsystem=elytron/server-ssl-context=ssc:write-attribute(name=want-client-auth,value=true)
/subsystem=elytron/server-ssl-context=ssc:write-attribute(name=need-client-auth,value=false)

{ldap} Configure an `ldap-realm`, and add it to the security domain you created[5]. This realm will be used if the client cert fails. That section of the documentation also specifies a mechanism that needs to be added to the `http-authentication-factory` or the `sasl-authentication-factory`. You can modify a factory as such:

# Assuming factory name of defaultAuthFactory
/subsystem=elytron/http-authentication-factory=defaultAuthFactory:list-add(name=mechanism-configurations,value={mechanism-name=FORM})

After this you can configure the FORM Servlets as normal (a basic configuration of web.xml is also available[6], although the SPNEGO auth method is not needed)

  • alongside client-cert authentication, I need to use an internal lib that gets a specific field inside the x509 certificate that uniquely identifies the owner. This field is a specific OID one

Within the quickstart, there is configuration of an x500-attribute-principal-decoder, which you can specify with an OID number (or an attribute name). Information on this and other options for manipulating X509 certificates can be found in the Elytron docs on authentication with certificates[7]. I'm not sure if you mean something else by using an internal library.

Best,
 

--
Cameron Rodriguez (he/him)
Software Engineering Intern
WildFly Elytron

Benito Coelho

unread,
Mar 22, 2023, 5:18:44 PM3/22/23
to WildFly
Hi Cameron,

First of all, thank you so much for your answer. I've been following the links and making the examples work, that's why it took me so long to give feedback.

At this moment, I'm trying to implement only the client cert authentication, without authorization. I began with the quickstart[2] and made some changes. Here is what I've done so far with Wildfly 27.0.0.Final:

(A) jboss-cli part

# TrustStore

/subsystem=elytron/key-store=myTrustStore:add(path=server.truststore,relative-to=jboss.server.config.dir,type=JKS,credential-reference={clear-text=secret})


## import the CAs (client certificates won't be stored, only the CAs): root and 2 intermediate CAs
## root CA

/subsystem=elytron/key-store=myTrustStore:import-certificate(alias=root-v5,path=root-v5.cer,credential-reference={clear-text=secret},trust-cacerts=true,validate=false,relative-to=jboss.server.config.dir)

## intermediate CA

/subsystem=elytron/key-store=myTrustStore:import-certificate(alias=jus-v5,path=jus-v5.cer,credential-reference={clear-text=secret},trust-cacerts=true,validate=false,relative-to=jboss.server.config.dir)

## intermediate CA

/subsystem=elytron/key-store=myTrustStore:import-certificate(alias=soluti-v5,path=soluti-v5.cer,credential-reference={clear-text=secret},trust-cacerts=true,validate=false,relative-to=jboss.server.config.dir)


/subsystem=elytron/key-store=myTrustStore:store()


# TrustManager

/subsystem=elytron/trust-manager=myTrustManager:add(key-store=myTrustStore)


# SSL Context

/subsystem=elytron/server-ssl-context=applicationSSC:write-attribute(name=trust-manager, value=myTrustManager)


/subsystem=elytron/server-ssl-context=applicationSSC:write-attribute(name=need-client-auth, value=true)


# (KeyStore) Realm

/subsystem=elytron/key-store-realm=myKeyStoreRealm:add(key-store=myTrustStore)


# Decoder

/subsystem=elytron/x500-attribute-principal-decoder=myDecoder:add(attribute-name=cn)


# Security Domain

/subsystem=elytron/security-domain=mySecurityDomain:add(default-realm=myKeyStoreRealm,permission-mapper=default-permission-mapper,principal-decoder=myDecoder,realms=[{realm=myKeyStoreRealm,role-decoder=groups-to-roles}])


# HTTP Authentication Factory

/subsystem=elytron/http-authentication-factory=myHttpAuthFactory:add(security-domain=mySecurityDomain,http-server-mechanism-factory=global,mechanism-configurations=[{mechanism-name=CLIENT_CERT}])


# Application Security Domain

/subsystem=undertow/application-security-domain=myAppSecurityDomain:add(http-authentication-factory=myHttpAuthFactory)


reload


(B) helloworld-mutual-ssl-secured part

Changes I've made to the application:
  • I removed the authorization part from the web.xml (tags <security-constraint> and <security-role>), because it's not necessary at this moment.
  • I changed the following files to the names (realm and security domain name) used in jboss-cli script above:
    • (web.xml) <realm-name>myAppSecurityDomain</realm-name>
    • (jboss-web.xml) <security-domain>myAppSecurityDomain</security-domain>
As we use A3 certificates from smartcards, I didn't import client certificates inside browser.

After build and deploy, when I access the application (https://localhost:8443/helloworld-mutual-ssl-secured/), I'm asked for client certificate and PIN. After that, I get a "Forbidden" answer (HTTP 403)

I've changed the the logging configuration (like -Djavax.net.debug=ssl:handshake and log level), but it wasn't enough to make the authentication work.

Any idea how can I get it working?


Best,
Benito.

Cameron Rodriguez

unread,
Mar 24, 2023, 11:57:10 AM3/24/23
to Benito Coelho, WildFly
Hey Benito,

It doesn't render well on GitHub, but in the README there's a linked page to add the quickstart user to the server[1]. Try adding that user and see if the authentication works.

Best, 

--
You received this message because you are subscribed to the Google Groups "WildFly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to wildfly+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/wildfly/a5903e56-cd75-4239-9a26-024da8d08484n%40googlegroups.com.

Benito Coelho

unread,
Mar 24, 2023, 1:31:07 PM3/24/23
to Cameron Rodriguez, WildFly
Hello Cameron,

Maybe I didn't express myself well, sorry about that, but I managed to make the quickstart example work. When I changed the example to fit our needs it broke. I can try to add my user as you suggested just to see if it works, but in our case we don't have client certificates stored, only the CA's certificates in the server truststore. So, I can't add every user. We rely on the client certificate being signed by the CAs that we put in the server truststore. Probably the configuration I've made is still lacking something.

Thank you once more for your support.

Best,
Benito

Benito Coelho

unread,
Mar 27, 2023, 9:14:25 AM3/27/23
to WildFly
Hi Cameron,

I added my user using add-user:

[$JBOSS_HOME\bin] add-user -a -u <number-part-of-my-CN> -p '<my-password>' -g JBossAdmin

As the username has some restrictions and I still don't know how to get a specific OID attribute from the digital certificate, I chose a CN substring that matches the number part of my identification. For that, I had to use regex-principal-transformer and chained-principal-transformer:

/subsystem=elytron/regex-principal-transformer=myRegexTransformer:add(pattern=".*:([0-9]+)", replacement="$1")
/subsystem=elytron/chained-principal-transformer=myChainedTransformer:add(principal-transformers=[QuickstartDecoder, myRegexTransformer])

And make the changes to the Security Domain, setting myChainedTransformer and unsetting principal-decoder:

/subsystem=elytron/security-domain=QuickstartDomain:write-attribute(name=pre-realm-principal-transformer,value=myChainedTransformer)
/subsystem=elytron/security-domain=QuickstartDomain:write-attribute(name=principal-decoder,value=undefined)

Besides that, I had to import my certificate into the server truststore under <number-part-of-my-CN> alias. After theses steps, I've accessed the application.
 
But as I said before, I can't add the users to the truststore (and to the application-users), because we rely on the client certificate being signed by a list of CAs certificates present in the server truststore. That is, if the user has a certificate signed by a CA certificate present in the server truststore, that user should be authenticated (of course, besides all the TLS communication that happens and certificate validations). How can I setup this configuration on Wildfly/Elytron?

Best,
Benito.

Cameron Rodriguez

unread,
Mar 27, 2023, 8:11:10 PM3/27/23
to Benito Coelho, WildFly
Ah sorry, I understand what you mean now. For the CA cert authentication take a look at these demo apps:

- `elytron-examples/client-cert-with-authorization`[1], which uses a truststore with CA certificates to authenticate clients, and derives the principal from a CN value or subject alternative name. The users are added to a separate filesystem realm, and principals are determined using an X500 attribute decoder and a regex principal transformer. There's also a blog post[2] explaining this demo.
- `elytron-examples/client-cert-with-authorization-and-evidence-decoder`[3], an alternative version which makes use of an evidence decoder as well.

And here's another guide[4] that implements certificate authentication with password fallback, but the steps are targeted to the management interface, so they will need some slight changes for an application.

Best,


On Mon, Mar 27, 2023 at 9:14 AM Benito Coelho <nitoc...@gmail.com> wrote:
Hi Cameron,

I added my user using add-user:

[$JBOSS_HOME\bin] add-user -a -u <number-part-of-my-CN> -p '<my-password>' -g JBossAdmin

As the username has some restrictions and I still don't know how to get a specific OID attribute from the digital certificate, I chose a CN substring that matches the number part of my identification. For that, I had to use regex-principal-transformer and chained-principal-transformer:

/subsystem=elytron/regex-principal-transformer=myRegexTransformer:add(pattern=".*:([0-9]+)", replacement="$1")
/subsystem=elytron/chained-principal-transformer=myChainedTransformer:add(principal-transformers=[QuickstartDecoder, myRegexTransformer])

And make the changes to the Security Domain, setting myChainedTransformer and unsetting principal-decoder:

/subsystem=elytron/security-domain=QuickstartDomain:write-attribute(name=pre-realm-principal-transformer,value=myChainedTransformer)
/subsystem=elytron/security-domain=QuickstartDomain:write-attribute(name=principal-decoder,value=undefined)

Besides that, I had to import my certificate into the server truststore under <number-part-of-my-CN> alias. After theses steps, I've accessed the application.
 
But as I said before, I can't add the users to the truststore (and to the application-users), because we rely on the client certificate being signed by a list of CAs certificates present in the server truststore. That is, if the user has a certificate signed by a CA certificate present in the server truststore, that user should be authenticated (of course, besides all the TLS communication that happens and certificate validations). How can I setup this configuration on Wildfly/Elytron?

Best,
Benito.
Em sexta-feira, 24 de março de 2023 às 14:31:07 UTC-3, Benito Coelho escreveu:
Hello Cameron,

Maybe I didn't express myself well, sorry about that, but I managed to make the quickstart example work. When I changed the example to fit our needs it broke. I can try to add my user as you suggested just to see if it works, but in our case we don't have client certificates stored, only the CA's certificates in the server truststore. So, I can't add every user. We rely on the client certificate being signed by the CAs that we put in the server truststore. Probably the configuration I've made is still lacking something.

Thank you once more for your support.

Best,
Benito

Benito Coelho

unread,
Mar 29, 2023, 11:19:19 AM3/29/23
to WildFly

Hello Cameron,

I had already seen the client-cert-with-authorization[1], but it also depends on a realm (filesystem realm) that stores known client certificate information. The problem is that in our cenario we only know that they are signed by our list of CAs (truststore) and that they must have a specific attribute (OID 2.16.76.1.3.1) that uniquely identifies them. We don't use information like CN or DN.

Is it possible not to have a configured realm? That is, the user could be authenticated by the mutual TLS, based on the CAs truststore, and the principal should be the OID 2.16.76.1.3.1 value?

PS: I didn't understand what evidence decoders are used for.

Thanks again.
Benito

Cameron Rodriguez

unread,
Mar 31, 2023, 2:01:12 PM3/31/23
to Benito Coelho, WildFly
Hey Benito,

Sounds great, glad that it's working out so far. Just making a correction, the constant-role-mapper is only needed if you want to use Elytron's authorization options (ex. ServletSecurity annotations or web.xml configuration). If not, you can just ignore that. I also added commented it out on the gist.

Best,

--

Cameron Rodriguez (he/him)
Software Engineering Intern
WildFly Elytron

(readding the latest entries since I accidentally pulled the thread off-list)

On Fri, Mar 31, 2023 at 12:41 PM Benito Coelho <nitoc...@gmail.com> wrote:
Hi Cameron, thanks again. I've decided to give you a quick feedback to show what I've done, while I analyse your answer.

So, before your answer I've tried the following configuration, based on [1], using constant-principal-transformer, and succeeded on authentication and authorization:

<constant-principal-transformer name="toTest" constant="test"/>
<http-authentication-factory name="clientCertAuth" security-domain="ApplicationDomain" http-server-mechanism-factory="configuredCert">
    <mechanism-configuration>
        <mechanism mechanism-name="CLIENT_CERT" final-principal-transformer="toTest"/>
    </mechanism-configuration>
</http-authentication-factory>

<application-security-domains>
    <application-security-domain name="other" http-authentication-factory="clientCertAuth" override-deployment-config="true"/>
</application-security-domains>
<server name="default-server">
    <http-listener name="default" socket-binding="http" redirect-socket="https" enable-http2="true"/>
    <https-listener name="https" socket-binding="https" ssl-context="ssl-context-server.keystore" enable-http2="true"/>
    <host name="default-host" alias="localhost">
        <location name="/" handler="welcome-content"/>
        <http-invoker http-authentication-factory="application-http-authentication"/>
    </host>
</server>
<tls>
    <key-stores>
        <key-store name="applicationKS">
            <credential-reference clear-text="password"/>
            <implementation type="JKS"/>
            <file path="application.keystore" relative-to="jboss.server.config.dir"/>
        </key-store>
        <key-store name="serverTS">
            <credential-reference clear-text="truststorepass"/>
            <implementation type="JKS"/>
            <file path="server.truststore" relative-to="jboss.server.config.dir"/>
        </key-store>
        <key-store name="server.keystore">
            <credential-reference clear-text="keystorepass"/>
            <implementation type="JKS"/>
            <file path="server.keystore" relative-to="jboss.server.config.dir"/>
        </key-store>
    </key-stores>
    <key-managers>
        <key-manager name="key-manager-server.keystore" key-store="server.keystore">
            <credential-reference clear-text="keystorepass"/>
        </key-manager>
    </key-managers>
    <trust-managers>
        <trust-manager name="key-manager-serverTS" key-store="serverTS"/>
    </trust-managers>
    <server-ssl-contexts>
        <server-ssl-context name="applicationSSC" key-manager="applicationKM"/>
        <server-ssl-context name="ssl-context-server.keystore" cipher-suite-filter="DEFAULT" protocols="TLSv1.2" want-client-auth="true" need-client-auth="true"
authentication-optional="true" use-cipher-suites-order="false" key-manager="key-manager-server.keystore" trust-manager="key-manager-serverTS"/>
    </server-ssl-contexts>
</tls>

File application-users.properties content:
test

File application-roles.properties content:
test=Users

But the principal value was the DN and I need the OID 2.16.76.1.3.1 value. I tried to use the OID value in the principal decoder, but it didn't work. I guess the reason is what you said about SAN decoder support.

<x500-attribute-principal-decoder name="myDecoder" oid=" 2.16.76.1.3.1" maximum-segments="1"/>

In the server.log part below, logger org.wildfly.security, it's possible to see that the OID is there, but somehow I couldn't access it.

[8]: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
  RFC822Name: nitoc...@gmail.com
  Other-Name: Unrecognized ObjectIdentifier: 1.3.6.1.4.1.311.20.2.3
  Other-Name: Unrecognized ObjectIdentifier: 2.16.76.1.3.1
  Other-Name: Unrecognized ObjectIdentifier: 2.16.76.1.3.6
  Other-Name: Unrecognized ObjectIdentifier: 2.16.76.1.3.5
]

In our application that runs in the production environment and that is being replaced, we had to override the Seam Identity part to get the x509 certificate from the http request and use an internal library to extract the OID 2.16.76.1.3.1 value. I hope to do something like that with the Gist you sent.


Best,
Benito.

On Thu, Mar 30, 2023 at 11:31 PM Cameron Rodriguez <caro...@redhat.com> wrote:
Hey Benito, sorry for the confusion. I've talked with some of the other engineers to help clear things up for me.
 
PS: I didn't understand what evidence decoders are used for.

Evidence decoders select a section of the `Evidence` (an Elytron interface for items used in validation, like certificates) to operate on. You can use a principal decoder on "evidence" to select a principal.

For example, an `x500-subject-evidence-decoder`[1] would retrieve the first certificate's subject from a certificate chain. You could then follow up with `x500-attribute-principal-decoder`[2] to select an attribute in that subject to use as the principal. Each of these would be associated with the security domain.

Is it possible not to have a configured realm?

Yes, instead of the filesystem realm, you can associate a `constant-role-mapper`[3], so that any authenticated users will automatically receive the authorization roles needed. It's also possible to use to set roles based on the principal, but that's a bit more complex.
 
That is, the user could be authenticated by the mutual TLS, based on the CAs truststore, and the principal should be the OID 2.16.76.1.3.1 value?

It seems that OID is part of the Subject Alternative Name, though correct me if I'm wrong. Elytron's SAN decoder doesn't support the OtherName attribute, so a custom EvidenceDecoder implementation would be needed.

I've made a Gist with an example CLI file and custom class. You'll need to package the custom class into a JAR and load it as a server module, described in Custom Components.[4] 


Let me know if you have any questions.

Best,



Benito Coelho

unread,
Apr 11, 2023, 11:13:39 PM4/11/23
to Cameron Rodriguez, WildFly
Hey Cameron,

I've made some progress and finally succeeded in extracting the value relative to the OID 2.16.76.1.3.1 from the digital certificate. I followed your suggestion and packaged a custom class in custom-wf-decoder.jar that depends on an internal lib (that deals with digital certificates). I struggled to make the internal lib available to the custom-wf-decoder.jar. I don't know if it's a good practice, but I packaged the internal lib with its dependencies in a .jar and set it in the <resource-root> inside the <resources> tag, in the module .xml definition I added.

Next I successfully configured a LDAP realm separate from the client-cert configuration, using BASIC auth-method in web.xml.

At this moment, I'm trying to integrate the client-cert and LDAP mechanisms, making the latter the fallback mechanism. The client-cert part is working, but when I skip the client-cert (by not choosing any certificate offered by the browser), the LDAP credentials are asked, as expected, but as I see in server.log the Properties Realm ("ApplicationRealm") is incorrectly used instead of the LDAP Realm ("ldap-realm") and as so I can't authenticate using LDAP (because the used realm is wrong):

TRACE [org.wildfly.security] (default task-1) Handling AvailableRealmsCallback: realms = [ldap-realm]
DEBUG [org.wildfly.security.http.password] (default task-1) Username authentication. Realm: [ldap-realm], Username: [myuser].
TRACE [org.wildfly.security] (default task-1) Handling RealmCallback: selected = [ldap-realm]
TRACE [org.wildfly.security] (default task-1) Handling NameCallback: authenticationName = myuser
(...)
TRACE [org.wildfly.security] (default task-1) Principal assigning: [myuser], pre-realm rewritten: [myuser], realm name: [ApplicationRealm], post-realm rewritten: [myuser ], realm rewritten: [myuser ]
TRACE [org.wildfly.security] (default task-1) PropertiesRealm: identity [myuser] does not exist
DEBUG [org.wildfly.security.http.basic] (default task-1) User myuser authentication failed.

Just to make it clear, the LDAP realm alone is working fine as I've checked it with:

/subsystem=elytron/ldap-realm=ldap-realm:read-identity(identity=myuser)

Here's the configuration so far (I omitted some sensitive info and removed configs not used):

standalone.xml
        <subsystem xmlns="urn:wildfly:elytron:16.0" ...>
            <security-domains>
                <security-domain name="ApplicationDomain" default-realm="ApplicationRealm" permission-mapper="default-permission-mapper"
pre-realm-principal-transformer="principalDecoder" evidence-decoder="customDecoder">
                    <realm name="ApplicationRealm" role-decoder="groups-to-roles"/>
                    <realm name="ldap-realm" role-decoder="from-roles-attribute"/>
                </security-domain>
            </security-domains>
            <security-realms>
                <properties-realm name="ApplicationRealm">
                    <users-properties path="application-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ApplicationRealm"/>
                    <groups-properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
                </properties-realm>
                <ldap-realm name="ldap-realm" dir-context="ldap-connection" ...>
                    <identity-mapping rdn-identifier="uid" use-recursive-search="true" search-base-dn="<omitted>">
                        <attribute-mapping>
                            <attribute from="cn" to="Roles" filter="(uniqueMember={1})" filter-base-dn="<omitted>"/>
                        </attribute-mapping>
                    </identity-mapping>
                </ldap-realm>
            </security-realms>
            <mappers>
                <x500-attribute-principal-decoder name="principalDecoder" oid="2.5.29.17" maximum-segments="1"/>
                <constant-principal-transformer name="toTest" constant="test"/>
                <simple-role-decoder name="groups-to-roles" attribute="groups"/>
                <simple-role-decoder name="from-roles-attribute" attribute="Roles"/>
                <custom-evidence-decoder name="customDecoder" module="com.company.app.custom-decoders" class-name="com.company.app.CustomX509SANDecoder"/>
            </mappers>
            <http>
                <http-authentication-factory name="clientCertAuth" security-domain="ApplicationDomain" http-server-mechanism-factory="configuredCert">
                    <mechanism-configuration>
                        <mechanism mechanism-name="CLIENT_CERT" final-principal-transformer="toTest"/>
                        <mechanism mechanism-name="BASIC">
                            <mechanism-realm realm-name="ldap-realm"/>
                        </mechanism>
                    </mechanism-configuration>
                </http-authentication-factory>
                <configurable-http-server-mechanism-factory name="configuredCert" http-server-mechanism-factory="global">
                    <properties>
                        <property name="org.wildfly.security.http.skip-certificate-verification" value="true"/>
                    </properties>
                </configurable-http-server-mechanism-factory>
            </http>
            <tls>
                <key-stores>
                    <key-store name="applicationKS">
                        <credential-reference clear-text="<omitted>"/>
                        <implementation type="JKS"/>
                        <file path="application.keystore" relative-to="jboss.server.config.dir"/>
                    </key-store>
                    <key-store name="serverTS">
                        <credential-reference clear-text="<omitted>"/>
                        <implementation type="JKS"/>
                        <file path="server.truststore" relative-to="jboss.server.config.dir"/>
                    </key-store>
                    <key-store name="server.keystore">
                        <credential-reference clear-text="<omitted>"/>
                        <implementation type="JKS"/>
                        <file path="server.keystore" relative-to="jboss.server.config.dir"/>
                    </key-store>
                </key-stores>
                <key-managers>
                    <key-manager name="key-manager-server.keystore" key-store="server.keystore">
                        <credential-reference clear-text="<omitted>"/>
                    </key-manager>
                </key-managers>
                <trust-managers>
                    <trust-manager name="key-manager-serverTS" key-store="serverTS"/>
                </trust-managers>
                <server-ssl-contexts>
                    <server-ssl-context name="ssl-context-server.keystore" cipher-suite-filter="DEFAULT" protocols="TLSv1.2" want-client-auth="true" need-client-auth="false"
authentication-optional="true" use-cipher-suites-order="false"
key-manager="key-manager-server.keystore" trust-manager="key-manager-serverTS"/>
                </server-ssl-contexts>
            </tls>

            <dir-contexts>
                <dir-context name="ldap-connection" url="<omitted>" principal="<omitted>">
                    <credential-reference clear-text="<omitted>"/>
                </dir-context>
            </dir-contexts>

        </subsystem>

        <subsystem xmlns="urn:jboss:domain:undertow:13.0" ...>

          <server name="default-server">
                <https-listener name="https" socket-binding="https" ssl-context="ssl-context-server.keystore" enable-http2="true"/>
            </server>
            <application-security-domains>
                <application-security-domain name="other" http-authentication-factory="clientCertAuth"/>
            </application-security-domains>

       
</subsystem>


application-users.properties
test

application-roles.properties
test=guest

[simple-webapp: SecuredServlet.java]
@WebServlet("/secured")
@ServletSecurity(httpMethodConstraints = { @HttpMethodConstraint(value = "GET", rolesAllowed = { "guest" }) })
public class SecuredServlet extends HttpServlet {

[simple-webapp: web.xml]
    <login-config>
        <auth-method>CLIENT-CERT,BASIC</auth-method>
        <realm-name>other</realm-name>
    </login-config>

[simple-webapp: jboss-web.xml]
<jboss-web>
    <security-domain>other</security-domain>
</jboss-web>

Any idea on what am I missing? How can I use the "ldap-realm" as the fallback mechanism effectively?

Best,
Benito.

Cameron Rodriguez

unread,
Apr 12, 2023, 4:29:34 PM4/12/23
to Benito Coelho, WildFly
Hey Benito, great to hear!

On Tue, Apr 11, 2023 at 11:13 PM Benito Coelho <nitoc...@gmail.com> wrote:
Hey Cameron,

I've made some progress and finally succeeded in extracting the value relative to the OID 2.16.76.1.3.1 from the digital certificate. I followed your suggestion and packaged a custom class in custom-wf-decoder.jar that depends on an internal lib (that deals with digital certificates). I struggled to make the internal lib available to the custom-wf-decoder.jar. I don't know if it's a good practice, but I packaged the internal lib with its dependencies in a .jar and set it in the <resource-root> inside the <resources> tag, in the module .xml definition I added.

Yep, no issues with that configuration. If you need to use that library elsewhere, you can package it into a separate module, and then list it as <module name="com.company.app.internal-lib-module-name"> within the <dependency> section of the module.xml. More info on that is in the JBoss Modules manual: https://jboss-modules.github.io/jboss-modules/manual/
 
Next I successfully configured a LDAP realm separate from the client-cert configuration, using BASIC auth-method in web.xml.

At this moment, I'm trying to integrate the client-cert and LDAP mechanisms, making the latter the fallback mechanism. The client-cert part is working, but when I skip the client-cert (by not choosing any certificate offered by the browser), the LDAP credentials are asked, as expected, but as I see in server.log the Properties Realm ("ApplicationRealm") is incorrectly used instead of the LDAP Realm ("ldap-realm") and as so I can't authenticate using LDAP (because the used realm is wrong):

You'll need to make the LDAP realm the default for the security domain, and (optionally) remove the ApplicationRealm. Here are the CLI commands to do that:
 
/subsystem=elytron/security-domain=ApplicationDomain:write-attribute(name=default-realm,value=ldap-realm)
# Remove following line to keep ApplicationRealm
/subsystem=elytron/security-domain=ApplicationDomain:list-remove(name=realms,index=1)
reload

and the resulting configuration should look like this:

<security-domains>
    <security-domain name="ApplicationDomain" default-realm="ldap-realm" permission-mapper="default-permission-mapper"
        pre-realm-principal-transformer="principalDecoder" evidence-decoder="customDecoder">

        <realm name="ldap-realm" role-decoder="from-roles-attribute" />
    </security-domain>
</security-domains>
Best,

--
Cameron Rodriguez (he/him)
Software Engineering Intern
WildFly Elytron

Benito Coelho

unread,
Apr 13, 2023, 5:33:11 PM4/13/23
to Cameron Rodriguez, WildFly
Hey Cameron, 

Yep, no issues with that configuration. If you need to use that library elsewhere, you can package it into a separate module, and then list it as <module name="com.company.app.internal-lib-module-name"> within the <dependency> section of the module.xml. More info on that is in the JBoss Modules manual: https://jboss-modules.github.io/jboss-modules/manual/

I tried that at first but I couldn't make it work. It just worked with the <resource-root>.

You'll need to make the LDAP realm the default for the security domain, and (optionally) remove the ApplicationRealm. Here are the CLI commands to do that:
 
/subsystem=elytron/security-domain=ApplicationDomain:write-attribute(name=default-realm,value=ldap-realm)
# Remove following line to keep ApplicationRealm
/subsystem=elytron/security-domain=ApplicationDomain:list-remove(name=realms,index=1)
reload
and the resulting configuration should look like this:
<security-domains>
    <security-domain name="ApplicationDomain" default-realm="ldap-realm" permission-mapper="default-permission-mapper"
        pre-realm-principal-transformer="principalDecoder" evidence-decoder="customDecoder">
        <realm name="ldap-realm" role-decoder="from-roles-attribute" />
    </security-domain>
</security-domains>

Based on [1], I used realm-mapper and that worked out!!! The resulting configuration became:


...
                <constant-realm-mapper name="app-realm-mapper" realm-name="ApplicationRealm"/>
                <constant-realm-mapper name="ldap-realm-mapper" realm-name="ldap-realm-trt3"/>
...
                <http-authentication-factory name="clientCertAuth" security-domain="ApplicationDomain" http-server-mechanism-factory="configuredCert">
                    <mechanism-configuration>
                        <mechanism mechanism-name="CLIENT_CERT" final-principal-transformer="toTest" realm-mapper="app-realm-mapper"/>
                        <mechanism mechanism-name="BASIC">
                            <mechanism-realm realm-name="ldap-realm-trt3" realm-mapper="ldap-realm-mapper"/>
                        </mechanism>
                    </mechanism-configuration>
                </http-authentication-factory>

I did it before seeing your answer, but I'll also try what you said (change the default realm).

Well, then I started to look at the server.log and found 3 exceptions that I'm trying solve:

1) javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown

DEBUG [io.undertow.request] (default I/O-10) UT005013: An IOException occurred: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:337)
    at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:293)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:186)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:171)
    at java.base/sun.security.ssl.SSLEngineImpl.decode(SSLEngineImpl.java:681)
    at java.base/sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:636)
    at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:454)
    at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:433)
    at io.undertow.core@2.3.0.Final//io.undertow.server.protocol.http.ALPNLimitingSSLEngine.unwrap(ALPNLimitingSSLEngine.java:142)
    at io.undertow.core@2.3.0.Final//io.undertow.protocols.ssl.SslConduit.engineUnwrap(SslConduit.java:691)
    at io.undertow.core@2.3.0.Final//io.undertow.protocols.ssl.SslConduit.doUnwrap(SslConduit.java:801)
    at io.undertow.core@2.3.0.Final//io.undertow.protocols.ssl.SslConduit.doHandshake(SslConduit.java:672)
    at io.undertow.core@2.3.0.Final//io.undertow.protocols.ssl.SslConduit$SslReadReadyHandler.readReady(SslConduit.java:1239)
    at org.jboss.xnio.nio@3.8.8.Final//org.xnio.nio.NioSocketConduit.handleReady(NioSocketConduit.java:89)
    at org.jboss.xnio.nio@3.8.8.Final//org.xnio.nio.WorkerThread.run(WorkerThread.java:591)

Failed attempts:
  • import server certificate and its CA certificate into the JDK cacerts file
  • use of System.setProperty in simple-webapp to setup javax.net.ssl.trustStore, javax.net.ssl.trustStorePassword and javax.net.ssl.trustStoreType (the truststore file used contained both server certificate and its CA certificate)
  • import server and its CA certificate into browser's trusted root certificates
  • same attempts above but with one server certificate that I created with keytool (and didn't use a CA)
Honestly I don't know what else I can try in this case.


2) java.nio.channels.ClosedChannelException

DEBUG [io.undertow.request] (default I/O-11) UT005013: An IOException occurred: java.nio.channels.ClosedChannelException
    at io.undertow.core@2.3.0.Final//io.undertow.protocols.ssl.SslConduit.doWrap(SslConduit.java:919)
    at io.undertow.core@2.3.0.Final//io.undertow.protocols.ssl.SslConduit.doHandshake(SslConduit.java:673)
    at io.undertow.core@2.3.0.Final//io.undertow.protocols.ssl.SslConduit$SslReadReadyHandler.readReady(SslConduit.java:1239)
    at org.jboss.xnio.nio@3.8.8.Final//org.xnio.nio.NioSocketConduit.handleReady(NioSocketConduit.java:89)
    at org.jboss.xnio.nio@3.8.8.Final//org.xnio.nio.WorkerThread.run(WorkerThread.java:591)

3)  java.lang.IllegalAccessException
TRACE [org.wildfly.security] (default task-1) X550Name.asX500Principal() is not available.: java.lang.IllegalAccessException: access to public member failed: sun.security.x509.X500Name.asX500Principal[Ljava.lang.Object;@f193d47/invokeVirtual, from public Lookup
    at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:942)
    at java.base/java.lang.invoke.MethodHandles$Lookup.checkAccess(MethodHandles.java:2206)
    at java.base/java.lang.invoke.MethodHandles$Lookup.checkMethod(MethodHandles.java:2146)
    at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectMethodCommon(MethodHandles.java:2290)
    at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectMethodNoSecurityManager(MethodHandles.java:2283)
    at java.base/java.lang.invoke.MethodHandles$Lookup.unreflect(MethodHandles.java:1747)
    at org.wildfly.security.elytron-base@2.0.0.Final//org.wildfly.security.x500.util.X500PrincipalUtil.<clinit>(X500PrincipalUtil.java:58)
    at org.wildfly.security.elytron-base@2.0.0.Final//org.wildfly.security.x500.principal.X500AttributePrincipalDecoder.getName(X500AttributePrincipalDecoder.java:177)
    at org.wildfly.security.elytron-base@2.0.0.Final//org.wildfly.security.auth.server.PrincipalDecoder.lambda$asPrincipalRewriter$0(PrincipalDecoder.java:57)
    at java.base/java.util.function.Function.lambda$andThen$1(Function.java:88)

I haven't addressed the other 2 exceptions above.

Best,
Benito.

Cameron Rodriguez

unread,
Apr 14, 2023, 1:38:43 PM4/14/23
to Benito Coelho, WildFly
Hey Benito,
The second trace is likely related to the first, and the third is just a warning for some optimizations the server tries to make, so I'll skip those.

Since the keystore and truststore are already in the standalone configuration, they don't need to be put elsewhere, so you won't need to import them into the JDK cacerts or set the javax.net.ssl properties. It seems that there's an issue with either the certificate itself, or how Elytron is configured to handle them. A few things you can try:
  • In the JBoss CLI, check `/subsystem=elytron/key-store=server-trust-store-name:read-aliases()` to make sure the identities were loaded correctly
  • Check if helloworld-mutual-ssl-secured[1] works on a fresh WildFly copy, without modification. If not, the issue is likely on your end and I might not be able to help much.
  • If that works fine, you can clone your current configuration, and test it with another X500 attribute (like a plain `x500-attribute-prinicipal-decoder` for the CN[2]) to see if that works. Depending on the output it might point either way.
If you have more complete trace-level logs of the above, I can take a look through them. If you prefer, you can scrub them of details and/or email me off-list with the contents.

One more option would be to look at the authentication flow. I could try to look at a curl trace to see if anything stands out. This command should give a decent output, assuming all certificate and key files are PEM-formatted. Replace `capath` with `cacert` if the server certificate chain is a single file, and use `curl --insecure [other options]` if the connection fails to validate the hostname:

curl --capath <server_cert_dir> --cert <client_cert> --key <client_key> --trace /PATH/TO/trace-file.txt https://localhost:8080/application-name/inner_page

Best,

Benito Coelho

unread,
Apr 17, 2023, 10:07:46 AM4/17/23
to Cameron Rodriguez, WildFly
Hey Cameron, always good to hear from you.

  • In the JBoss CLI, check `/subsystem=elytron/key-store=server-trust-store-name:read-aliases()` to make sure the identities were loaded correctly
Yes, the identities were loaded correctly, including the "CN=Elytron CA..." identity.

  • Check if helloworld-mutual-ssl-secured[1] works on a fresh WildFly copy, without modification. If not, the issue is likely on your end and I might not be able to help much.
Apparently, the helloworld-mutual-ssl-secured example is missing the role configuration (in link https://github.com/wildfly/quickstart/blob/main/shared-doc/add-application-user.adoc).  I guess it should be:

$ {jbossHomeName}/bin/add-user.sh -a -u quickstartUser -p 'quickstartPwd1!' -g JBossAdmin

After that modification, the example worked out. Next, I checked the server.log file and I didn't find the certificate_unknown error. Then I changed the root logger level to DEBUG (because that level is the one I've been using):

            <root-logger>
                <level name="DEBUG"/>
                <handlers>
                    <handler name="CONSOLE"/>
                    <handler name="FILE"/>
                </handlers>
            </root-logger>

And both certificate_unknown and java.nio.channels.ClosedChannelException errors were there in the server.log. As I was used to setting the DEBUG level for the root logger (and -Djavax.net.debug=ssl:handshake as JVM parameters), these exceptions were always there. So, why won't they show in default log level (INFO)? Are they expected and I don't have to deal with them?

Anyway, I'll email you off-list more trace-log levels and also the curl output.

Best,
Benito.


--
You received this message because you are subscribed to a topic in the Google Groups "WildFly" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/wildfly/J4L5NPylHd4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to wildfly+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/wildfly/CAD2gQZo%3D652JegXOj5D03tyJ_Zu-Fd6u3bCqiJYKKb%2BVw%2B6pHg%40mail.gmail.com.

Cameron Rodriguez

unread,
Apr 19, 2023, 6:06:55 PM4/19/23
to Benito Coelho, WildFly
Hey Benito, thanks for the details.

On Mon, Apr 17, 2023 at 10:07 AM Benito Coelho <nitoc...@gmail.com> wrote:
Apparently, the helloworld-mutual-ssl-secured example is missing the role configuration (in link https://github.com/wildfly/quickstart/blob/main/shared-doc/add-application-user.adoc).  I guess it should be:

$ {jbossHomeName}/bin/add-user.sh -a -u quickstartUser -p 'quickstartPwd1!' -g JBossAdmin

Yeah, unfortunately those pages don't render well before they're compiled into HTML. Glad you got it working!
 
After that modification, the example worked out. Next, I checked the server.log file and I didn't find the certificate_unknown error. Then I changed the root logger level to DEBUG (because that level is the one I've been using):

            <root-logger>
                <level name="DEBUG"/>
                <handlers>
                    <handler name="CONSOLE"/>
                    <handler name="FILE"/>
                </handlers>
            </root-logger>

And both certificate_unknown and java.nio.channels.ClosedChannelException errors were there in the server.log. As I was used to setting the DEBUG level for the root logger (and -Djavax.net.debug=ssl:handshake as JVM parameters), these exceptions were always there. So, why won't they show in default log level (INFO)? Are they expected and I don't have to deal with them?
 
After taking a look through the logs[1], the errors don't seem to be significant. It seems that the server rejects a few certificates and then accepts one, and X509 decoding appears to run properly from there on. I'm not sure what those errors are supposed to represent (a side-effect of multithreading?), so I'll check if someone else on the team is more familiar with it. In any case, I don't think they indicate larger issues, and likely would've been raised to WARN or a higher level if it affected the authentication process.

Otherwise, do you run into any failures when authenticating with the client-cert + LDAP fallback? If you're able to use it, you should be good to go! Otherwise, WARN or higher level logs from either `org.wildfly.security.*` or `io.undertow.*` would be things to look for. Let me know if you run into any issues while testing.

Best,


[1]: For the context of others, these are the types of errors thrown:

****-**-** **:**:**,*** DEBUG [io.undertow.request] (default I/O-10) UT005013: An IOException occurred: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown

    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:358)
    at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:293)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:204)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
    at java.base/sun.security.ssl.SSLEngineImpl.decode(SSLEngineImpl.java:736)
    at java.base/sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:691)
    at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:506)
    at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:482)
    at io.under...@2.3.0.Final//io.undertow.server.protocol.http.ALPNLimitingSSLEngine.unwrap(ALPNLimitingSSLEngine.java:142)
    at io.under...@2.3.0.Final//io.undertow.protocols.ssl.SslConduit.engineUnwrap(SslConduit.java:691)
    at io.under...@2.3.0.Final//io.undertow.protocols.ssl.SslConduit.doUnwrap(SslConduit.java:801)
    at io.under...@2.3.0.Final//io.undertow.protocols.ssl.SslConduit.doHandshake(SslConduit.java:672)
    at io.under...@2.3.0.Final//io.undertow.protocols.ssl.SslConduit$SslReadReadyHandler.readReady(SslConduit.java:1239)
    at org.jboss...@3.8.8.Final//org.xnio.nio.NioSocketConduit.handleReady(NioSocketConduit.java:89)
    at org.jboss...@3.8.8.Final//org.xnio.nio.WorkerThread.run(WorkerThread.java:591)

****-**-** **:**:**,*** DEBUG [io.undertow.request] (default I/O-1) UT005013: An IOException occurred: java.nio.channels.ClosedChannelException
    at io.under...@2.3.0.Final//io.undertow.protocols.ssl.SslConduit.doWrap(SslConduit.java:919)
    at io.under...@2.3.0.Final//io.undertow.protocols.ssl.SslConduit.doHandshake(SslConduit.java:673)
    at io.under...@2.3.0.Final//io.undertow.protocols.ssl.SslConduit$SslReadReadyHandler.readReady(SslConduit.java:1239)
    at org.jboss...@3.8.8.Final//org.xnio.nio.NioSocketConduit.handleReady(NioSocketConduit.java:89)
    at org.jboss...@3.8.8.Final//org.xnio.nio.WorkerThread.run(WorkerThread.java:591)

Benito Coelho

unread,
May 8, 2023, 4:35:35 PM5/8/23
to Cameron Rodriguez, WildFly
Hey Cameron,


After taking a look through the logs[1], the errors don't seem to be significant. It seems that the server rejects a few certificates and then accepts one, and X509 decoding appears to run properly from there on. I'm not sure what those errors are supposed to represent (a side-effect of multithreading?), so I'll check if someone else on the team is more familiar with it. In any case, I don't think they indicate larger issues, and likely would've been raised to WARN or a higher level if it affected the authentication process.

I was thinking the same: debug and trace exceptions don't seem to be a problem.

Otherwise, do you run into any failures when authenticating with the client-cert + LDAP fallback? If you're able to use it, you should be good to go! Otherwise, WARN or higher level logs from either `org.wildfly.security.*` or `io.undertow.*` would be things to look for. Let me know if you run into any issues while testing.

I didn't run into any failures after all the configurations we've made, but at first I was worried about some problem that could be a consequence of those exceptions.

Everything was working fine until we deployed the application into an environment that has a reverse proxy. The client-cert part stopped working and we think that it's related to the way Wildfly gets the client certificate. In this environment, the reverse proxy (HAProxy) passes the client certificate through the "X-Client-Certificate" request header to the Wildfly (and to the application). Is there a way to configure Elytron where to look for the client certificate (in this case, in a request header), besides the default "place" which I guess is a request attribute?

Thanks once more for everything.

Best,
Benito

Cameron Rodriguez

unread,
May 9, 2023, 2:46:08 PM5/9/23
to Benito Coelho, WildFly
Hey Benito, great to hear about the success!

On Mon, May 8, 2023 at 4:35 PM Benito Coelho <nitoc...@gmail.com> wrote:
Everything was working fine until we deployed the application into an environment that has a reverse proxy. The client-cert part stopped working and we think that it's related to the way Wildfly gets the client certificate. In this environment, the reverse proxy (HAProxy) passes the client certificate through the "X-Client-Certificate" request header to the Wildfly (and to the application). Is there a way to configure Elytron where to look for the client certificate (in this case, in a request header), besides the default "place" which I guess is a request attribute?

I haven't been able to verify this myself, but you should be able to configure Undertow to use the Proxy Protocol v1[1]:

/subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=proxy-protocol,value=true)

If that doesn't work, you can also try enabling support for standard `X-Forwarded-To` Headers:

/subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=proxy-address-forwarding,value=true)

Best,


Benito Coelho

unread,
May 9, 2023, 9:45:29 PM5/9/23
to Cameron Rodriguez, WildFly
Hey Cameron,


I haven't been able to verify this myself, but you should be able to configure Undertow to use the Proxy Protocol v1[1]:

/subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=proxy-protocol,value=true)

If that doesn't work, you can also try enabling support for standard `X-Forwarded-To` Headers:

/subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=proxy-address-forwarding,value=true)

I've tried both, separately, but without success. Maybe the custom evidence decoder doesn't look for the client certificate inside those "X-Client..." headers. Is there a way for me to look for the client certificate myself, through the HttpServletRequest or something similar, using the Evidence Decoder or another component?

Thanks again!

Best,
Benito

Cameron Rodriguez

unread,
May 10, 2023, 4:31:35 PM5/10/23
to Benito Coelho, WildFly
Hey Benito,

On Tue, May 9, 2023 at 9:45 PM Benito Coelho <nitoc...@gmail.com> wrote:
Hey Cameron,

I haven't been able to verify this myself, but you should be able to configure Undertow to use the Proxy Protocol v1[1]:

/subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=proxy-protocol,value=true)

If that doesn't work, you can also try enabling support for standard `X-Forwarded-To` Headers:

/subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=proxy-address-forwarding,value=true)

I've tried both, separately, but without success. Maybe the custom evidence decoder doesn't look for the client certificate inside those "X-Client..." headers. Is there a way for me to look for the client certificate myself, through the HttpServletRequest or something similar, using the Evidence Decoder or another component?

Thanks again!

 Just wanted to get some clarification, is the TLS handshake (not the client cert authentication) being made with the reverse proxy, or with WildFly itself? The CLIENT_CERT mechanism retrieves the certificate from Java's TLS session information, so if the connection is not with the server, then a different mechanism needs to be used.

Thanks,

Benito Coelho

unread,
May 10, 2023, 5:00:08 PM5/10/23
to Cameron Rodriguez, WildFly
Hey Cameron,

 Just wanted to get some clarification, is the TLS handshake (not the client cert authentication) being made with the reverse proxy, or with WildFly itself? The CLIENT_CERT mechanism retrieves the certificate from Java's TLS session information, so if the connection is not with the server, then a different mechanism needs to be used.

The TLS handshake is made with the reverse proxy. After that, I believe another TLS communication is made between the reverse proxy and the Wildfly.

Best,
Benito.

Farah Juma

unread,
May 10, 2023, 6:16:44 PM5/10/23
to WildFly
Haven't looked at all of the details here but just wanted to quickly note that if the client cert is being verified externally, we also have the External HTTP mechanism that just relies on HttpServerRequest#getRemoteUser().

In case that helps at all, more details about the External mechanism can be found here.

Benito Coelho

unread,
May 11, 2023, 2:42:23 PM5/11/23
to Farah Juma, WildFly
Hey Farah,

Haven't looked at all of the details here but just wanted to quickly note that if the client cert is being verified externally, we also have the External HTTP mechanism that just relies on HttpServerRequest#getRemoteUser().
In case that helps at all, more details about the External mechanism can be found here.

The External HTTP mechanism relies on AJP? If so, I think the reverse proxy we use (HAProxy) doesn't support it. Besides that, we used some specific wildfly components, like custom evidence decoders to extract specific information from the client certificate, and I really don't know if they would still work.

I was wondering if there is a way to make the wildfly consider the "X-Client..." header instead of the standard request attributes to get the client certificate (supposing that that is how it works). Or should we try another approach?

Thanks for the support.

Best,
Benito

--
You received this message because you are subscribed to a topic in the Google Groups "WildFly" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/wildfly/J4L5NPylHd4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to wildfly+u...@googlegroups.com.

Farah Juma

unread,
May 11, 2023, 2:52:48 PM5/11/23
to WildFly
On Thursday, May 11, 2023 at 2:42:23 PM UTC-4 Benito Coelho wrote:
Hey Farah,

Haven't looked at all of the details here but just wanted to quickly note that if the client cert is being verified externally, we also have the External HTTP mechanism that just relies on HttpServerRequest#getRemoteUser().
In case that helps at all, more details about the External mechanism can be found here.

The External HTTP mechanism relies on AJP? If so, I think the reverse proxy we use (HAProxy) doesn't support it. Besides that, we used some specific wildfly components, like custom evidence decoders to extract specific information from the client certificate, and I really don't know if they would still work.

The External HTTP mechanism just relies on being able to retrieve the user that's been authenticated externally using HttpServerRequest#getRemoteUser(), e.g., see:


I'm not sure if that value is available or not when using HAProxy but just thought I'd mention it in case it is.
 

I was wondering if there is a way to make the wildfly consider the "X-Client..." header instead of the standard request attributes to get the client certificate (supposing that that is how it works). Or should we try another approach?

The CLIENT_CERT HTTP mechanism relies on getting the certificate from the TLS session info (i.e., using HttpServerRequest#getPeerCertificates()).

Anh Tu

unread,
Feb 10, 2025, 7:10:16 PM2/10/25
to WildFly
Hi Benito ,
I know this was two years old discussion; however, I have the similar issue as yours, I followed the instructions and what you had here and created a customEvidenceDecoder to exact the CN from the client's X509Certificate. I am still unable to make it success. If you don't mind, could you please share your CustomX509SanDecoder. I also attached my customEvidenceDecoder at the bottom of this.
Note that, I have the same issue that I can't import every client's cert into the server truststore. However, they all signed and trusted by the same root CA and intermediate which I had imported those into the server truststore. The application is started successfully, however when I open the webpage from the browser and select the client's cert, it gave 500 internal error and the log 

2025-02-10 15:09:28,368 DEBUG [io.undertow.request.error-response] (default task-1) Setting error code 500 for exchange HttpServerExchange{ GET /admin-web}: java.lang.RuntimeException
        at io.under...@2.2.19.Final//io.undertow.server.HttpServerExchange.setStatusCode(HttpServerExchange.java:1484)
        at org.wildfly.security.ely...@1.10.1.Final//org.wildfly.elytron.web.undertow.server.SecurityContextImpl.authenticate(SecurityContextImpl.java:110)
        at org.wildfly.security.elytron...@1.10.1.Final//org.wildfly.elytron.web.undertow.server.servlet.ServletSecurityContextImpl.authenticate(ServletSecurityContextImpl.java:115)
        at io.undert...@2.2.19.Final//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:55)
        at io.under...@2.2.19.Final//io.undertow.server.handlers.DisableCacheHandler.handleRequest(DisableCacheHandler.java:33)
        at io.under...@2.2.19.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at io.under...@2.2.19.Final//io.undertow.security.handlers.AuthenticationConstraintHandler.handleRequest(AuthenticationConstraintHandler.java:53)
        at io.under...@2.2.19.Final//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
        at io.undert...@2.2.19.Final//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
        at io.undert...@2.2.19.Final//io.undertow.servlet.handlers.security.ServletSecurityConstraintHandler.handleRequest(ServletSecurityConstraintHandler.java:59)
        at io.under...@2.2.19.Final//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
        at org.wildfly.security.elytron...@1.10.1.Final//org.wildfly.elytron.web.undertow.server.servlet.CleanUpHandler.handleRequest(CleanUpHandler.java:38)
        at io.under...@2.2.19.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at org.wildfly.ext...@26.1.3.Final//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
        at io.under...@2.2.19.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at org.wildfly.ext...@26.1.3.Final//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
        at io.undert...@2.2.19.Final//io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52)
        at io.under...@2.2.19.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at io.undert...@2.2.19.Final//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:275)
        at io.undert...@2.2.19.Final//io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:79)
        at io.undert...@2.2.19.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:134)
        at io.undert...@2.2.19.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:131)
        at io.undert...@2.2.19.Final//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
        at io.undert...@2.2.19.Final//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
        at org.wildfly.ext...@26.1.3.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1544)
        at org.wildfly.ext...@26.1.3.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1544)
        at org.wildfly.ext...@26.1.3.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1544)
        at org.wildfly.ext...@26.1.3.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1544)
        at io.undert...@2.2.19.Final//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:255)
        at io.undert...@2.2.19.Final//io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:79)
        at io.undert...@2.2.19.Final//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:100)
        at io.under...@2.2.19.Final//io.undertow.server.Connectors.executeRootHandler(Connectors.java:387)
        at io.under...@2.2.19.Final//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:852)
        at org.jbos...@2.4.0.Final//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
        at org.jbos...@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
        at org.jbos...@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
        at org.jbos...@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
        at org.jbo...@3.8.7.Final//org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282)
        at java.base/java.lang.Thread.run(Thread.java:829)

And here is my customerEvidenceDecoder:

public class CustomEvidenceDecoder implements EvidenceDecoder {
@Override
public Principal getPrincipal(Evidence evidence) {
if (! (evidence instanceof X509PeerCertificateChainEvidence)) {
return null;
}

X509Certificate certificate = ((X509PeerCertificateChainEvidence) evidence).getFirstCertificate();
List<String>  subjectAltNames = getSubjectAlternativeNames(certificate);

if ( CollectionUtils.isNotEmpty(subjectAltNames) )  {
xmlLog.info("getPrincipal - Found subject alt name: " + subjectAltNames.get(1));
return new NamePrincipal((String) subjectAltNames.get(1));
}
return null;
}


public List<String> getSubjectAlternativeNames(X509Certificate certificate) {
List<String> identities = new ArrayList<String>();
try {
Collection<List<?>> altNames = certificate.getSubjectAlternativeNames();
if ( CollectionUtils.isEmpty(altNames) )
return Collections.emptyList();
for (List<?> item : altNames) {
Integer type = (Integer) item.get(0);
xmlLog.info("getSubjectAlternativeNames - type: " + type);
if (type == 0 || type == 2 || type == 1) {
try {
ASN1InputStream decoder = null;
if (item.toArray()[1] instanceof byte[])
decoder = new ASN1InputStream((byte[]) item.toArray()[1]);
else if (item.toArray()[1] instanceof String)
identities.add((String) item.toArray()[1]);
if (decoder == null)
continue;
DEREncodable encoded = decoder.readObject();
encoded = ((DERSequence) encoded).getObjectAt(1);
encoded = ((DERTaggedObject) encoded).getObject();
encoded = ((DERTaggedObject) encoded).getObject();
String identity = ((DERUTF8String) encoded).getString();
xmlLog.info("getSubjectAlternativeNames - Subject identity: " + identity);
identities.add(identity);
} catch (UnsupportedEncodingException e) {
xmlLog.error("getSubjectAlternativeNames - Error decoding subjectAltName" + e.getLocalizedMessage());
} catch (Exception e) {
xmlLog.error("getSubjectAlternativeNames - Error decoding subjectAltName" + e.getLocalizedMessage());
}
} else {
xmlLog.info("getSubjectAlternativeNames - SubjectAltName of invalid type found: " + certificate.getType() + " Subj Alt Names " + certificate.getSubjectAlternativeNames() + " ");
}
}

} catch (CertificateParsingException e) {
xmlLog.error("getSubjectAlternativeNames - Error parsing SubjectAltName in certificate: " + certificate + "\r\nerror:" + e.getLocalizedMessage());
}
return identities;
}

Thanks,
Anh
Reply all
Reply to author
Forward
0 new messages