Bug 66009 - M-TLS Fails, no user is found because "OID.2.5.4.5" is used as field name instead of "SERIALNUMBER", in Subject
Summary: M-TLS Fails, no user is found because "OID.2.5.4.5" is used as field name ins...
Status: RESOLVED FIXED
Alias: None
Product: Tomcat 9
Classification: Unclassified
Component: Connectors (show other bugs)
Version: 9.0.62
Hardware: Other Linux
: P2 normal (vote)
Target Milestone: -----
Assignee: Tomcat Developers Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2022-04-12 09:07 UTC by Maikel
Modified: 2022-04-14 10:55 UTC (History)
0 users



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Maikel 2022-04-12 09:07:51 UTC
We upgraded from Tomcat 9.0.60 to 9.0.62 and the Mutual-TLS failed.

Logging from Tomcat 9.0.60 (M-TLS Works)
01 org.apache.catalina.authenticator.AuthenticatorBase.invoke Security checking request POST /speer/soap/services/somefunctionality
02 org.apache.catalina.realm.RealmBase.findSecurityConstraints   Checking constraint 'SecurityConstraint[MijnvfRealm]' against POST /services/somefunctionality --> true
03 org.apache.catalina.realm.RealmBase.findSecurityConstraints   Checking constraint 'SecurityConstraint[MijnvfRealm]' against POST /services/somefunctionality --> true
04 org.apache.catalina.authenticator.AuthenticatorBase.invoke Calling hasUserDataPermission()
05 org.apache.catalina.realm.RealmBase.hasUserDataPermission   User data constraint already satisfied
06 org.apache.catalina.authenticator.AuthenticatorBase.invoke Calling authenticate()
07 org.apache.catalina.realm.CombinedRealm.authenticate Attempting to authenticate user [CN=cn, O=o, L=l, ST=st, C=c, SERIALNUMBER=00000001804415183000] with realm [org.apache.catalina.realm.UserDatabaseRealm]
08 org.apache.catalina.realm.RealmBase.authenticate Authenticating client certificate chain
09 org.apache.catalina.realm.RealmBase.authenticate  Checking validity for 'CN=cn, O=o, L=l, ST=st, C=c, SERIALNUMBER=00000001804415183000'
10 org.apache.catalina.realm.RealmBase.authenticate  Checking validity for 'CN=cnCA, O=o, C=c'
11 org.apache.catalina.realm.RealmBase.getPrincipal Got user name from X509 certificate: [CN=cn, O=o, L=l, ST=st, C=c, SERIALNUMBER=00000001804415183000]
12 org.apache.catalina.realm.CombinedRealm.authenticate Authenticated user [CN=cn, O=o, L=l, ST=st, C=c, SERIALNUMBER=00000001804415183000] with realm [org.apache.catalina.realm.UserDatabaseRealm]
13 org.apache.catalina.authenticator.AuthenticatorBase.register Authenticated 'CN=cn, O=o, L=l, ST=st, C=c, SERIALNUMBER=00000001804415183000' with type 'CLIENT_CERT'
14 org.apache.catalina.authenticator.AuthenticatorBase.invoke Calling accessControl()
15 org.apache.catalina.realm.RealmBase.hasResourcePermission   Checking roles GenericPrincipal[CN=cn, O=o, L=l, ST=st, C=c, SERIALNUMBER=00000001804415183000()]
16 org.apache.catalina.realm.RealmBase.hasRole Username [CN=cn, O=o, L=l, ST=st, C=c, SERIALNUMBER=00000001804415183000] has role [correctUser]
17 org.apache.catalina.realm.RealmBase.hasResourcePermission Role found:  correctUser
18 org.apache.catalina.authenticator.AuthenticatorBase.invoke Successfully passed all security constraints


Logging from Tomcat 9.0.62 (M-TLS fails, no/wrong user found)
01 org.apache.catalina.authenticator.AuthenticatorBase.invoke Security checking request POST /speer/soap/services/somefunctionality
02 org.apache.catalina.realm.RealmBase.findSecurityConstraints   Checking constraint 'SecurityConstraint[MijnvfRealm]' against POST /services/somefunctionality --> true
03 org.apache.catalina.realm.RealmBase.findSecurityConstraints   Checking constraint 'SecurityConstraint[MijnvfRealm]' against POST /services/somefunctionality --> true
04 org.apache.catalina.authenticator.AuthenticatorBase.invoke Calling hasUserDataPermission()
05 org.apache.catalina.realm.RealmBase.hasUserDataPermission   User data constraint already satisfied
06 org.apache.catalina.authenticator.AuthenticatorBase.invoke Calling authenticate()
07 org.apache.catalina.realm.CombinedRealm.authenticate Attempting to authenticate user [CN=cn, O=o, L=l, ST=st, C=c, SERIALNUMBER=00000001804415183000] with realm [org.apache.catalina.realm.UserDatabaseRealm]
08 org.apache.catalina.realm.RealmBase.authenticate Authenticating client certificate chain
09 org.apache.catalina.realm.RealmBase.authenticate  Checking validity for 'CN=cn, O=o, L=l, ST=st, C=c, SERIALNUMBER=00000001804415183000'
10 org.apache.catalina.realm.RealmBase.authenticate  Checking validity for 'CN=cnCA, O=o, C=c'
11 org.apache.catalina.realm.RealmBase.getPrincipal Got user name from X509 certificate: [CN=cn, O=o, L=l, ST=st, C=c, OID.2.5.4.5=00000001804415183000]
12 org.apache.catalina.realm.CombinedRealm.authenticate Failed to authenticate user [CN=cn, O=o, L=l, ST=st, C=c, SERIALNUMBER=00000001804415183000] with realm [org.apache.catalina.realm.UserDatabaseRealm]
13 org.apache.catalina.authenticator.AuthenticatorBase.invoke Failed authenticate() test


If you look at line 11, in both logging you can see that "OID.2.5.4.5" is used in Tomcat 9.0.62 and "SERIALNUMBER" in Tomcat 9.0.60. While in all other instances "SERIALNUMBER" is used.
Because of this correct user can not be found. 

Possible workaround: is to add the "OID.2.5.4.5" version also to the "tomcat-users.xml" file (not tested yet, but I expect id to work). 

We are running Tomcat in Docker and are using the "tomcat:9-jdk11" container as base image.
When we reverted to the container using Tomcat 9.0.60 it worked again.

Possible suspect is release 9.0.61, and the change in Coyote,
https://tomcat.apache.org/tomcat-9.0-doc/changelog.html
Comment 1 Remy Maucherat 2022-04-12 09:40:30 UTC
(In reply to Maikel from comment #0)
> Possible suspect is release 9.0.61, and the change in Coyote,
> https://tomcat.apache.org/tomcat-9.0-doc/changelog.html

I recommend you investigate this further.

The relevant change is not in the changelog I think:
https://github.com/apache/tomcat/commit/38d2c138a102a793bce630056fbca7088b7e05a3
https://github.com/apache/tomcat/commit/b21268dcebc3d470430227978caa4f168a3346d4
Is this really equivalent in all cases ?
Comment 2 Michael Osipov 2022-04-12 09:53:04 UTC
Although, I haven't analyzed recent changes, the problem you see is different representations of the ASN.1 encoded subject DN.

Here (https://github.com/apache/tomcat/blob/431f08b66e27411decb52e1333dd886cc181a854/java/org/apache/catalina/realm/RealmBase.java#L454-L455) it uses https://docs.oracle.com/javase/8/docs/api/javax/security/cert/X509Certificate.html#getIssuerDN-- which does not describe the format which is applied, but X509SubjectDnRetriever uses RFC 1779 (https://github.com/apache/tomcat/blob/431f08b66e27411decb52e1333dd886cc181a854/java/org/apache/catalina/realm/X509SubjectDnRetriever.java#L31) which is totally outdated. Moreover, depending on the X.500 Principal format you select Java maintains an internal map which OIDs can be reasonably mapped from ASN.1 to a string. Especially 2.5.4.5 is a total mess.

I have a certificate processing application at work where I apply a custom formatting to properly canonicalize RFC 2253 formatted output with all possible OIDs Java will not map by default. I assume the codebase in Tomcat needs to be analyzed and apply similar. (My custom approach bases on the way OpenSSL handles DNs)
Comment 3 Konstantin Kolinko 2022-04-12 12:57:36 UTC
(In reply to Remy Maucherat from comment #1)
> (In reply to Maikel from comment #0)
> > Possible suspect is release 9.0.61, and the change in Coyote,
> > https://tomcat.apache.org/tomcat-9.0-doc/changelog.html
> 
> I recommend you investigate this further.
> 
> The relevant change is not in the changelog I think:
> https://github.com/apache/tomcat/commit/38d2c138a102a793bce630056fbca7088b7e05a3
> https://github.com/apache/tomcat/commit/b21268dcebc3d470430227978caa4f168a3346d4
> Is this really equivalent in all cases ?

To help investigate this:

Note that an implementation of org.apache.catalina.realm.X509UsernameRetriever that was changed by those commits is configurable, with "X509UsernameRetrieverClassName" attribute on a Realm.

https://tomcat.apache.org/tomcat-9.0-doc/config/realm.html

So that you can configure your own.
Comment 4 Remy Maucherat 2022-04-13 14:50:20 UTC
Thanks for the comments. Overall I would need a test certificate to see what each different method does. I don't know why getName(X500Principal.RFC1779) was used in X509UsernameRetriever instead of getName(X500Principal.RFC2253) or simply getName() (which simply uses X500Principal.RFC2253).

Alternately, you can try to test by using X509UsernameRetrieverClassName as Konstantin said (thanks, great tip !).
Comment 5 Maikel 2022-04-13 16:01:42 UTC
Thanks for the information, I did not know I could use X509UsernameRetrieverClassName to change the behavior. We where using the certificate functionality out of the box with only some changes in the config files.

I now use the workaround by adding the "OID.2.5.4.5" version also in the  "tomcat-users.xml" file. That works.
Comment 6 Christopher Schultz 2022-04-13 16:58:40 UTC
(In reply to Remy Maucherat from comment #1)
> https://github.com/apache/tomcat/commit/
> b21268dcebc3d470430227978caa4f168a3346d4

My guess is that the above patch will fix this issue.

Can you please provide a copy of the certificate and we can double-check the behavior of getSubjectDN() vs getX500Principal().getName() vs getX500Principal().getName(X500Principal.RFC1779)?

Alternatively, we could provide you with a simple Java utility to look at the cert and print those values.
Comment 7 Christopher Schultz 2022-04-13 17:02:40 UTC
Actually, this ought to do the trick:


import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.security.auth.x500.X500Principal;


public class CertInfo {
    public static void main(String[] args) throws Exception {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        Certificate cert = cf.generateCertificate(System.in);

        if(cert instanceof X509Certificate) {
            System.out.println("Certificate is X.509");
            X509Certificate xc = (X509Certificate)cert;

            System.out.println("getSubjectDN: " + xc.getSubjectDN());
            System.out.println("getSubjectX500Principal.getName: " + xc.getSubjectX500Principal().getName());
            System.out.println("getSubjectX500Principal.getName(RFC1779): " + xc.getSubjectX500Principal().getName(X500Principal.RFC1779));
        }
    }
}
Comment 8 Remy Maucherat 2022-04-14 08:17:20 UTC
Using your test, I can see that getName(RFC1779) formatting matches getSubjectDN, and RFC2253 does not.

Now, with a certificate with more stuff inside, I get something where getSubjectDN returns EMAILADDRESS= and all the getName ones replace that with OID.1.2.840.113549.1.9.1=, which reproduces the bug. Replacing getSubjectDN with getSubjectX500Principal is simply not equivalent, so resolving the deprecation will require more effort (see comment 2 ;) ).
Comment 9 Remy Maucherat 2022-04-14 08:25:03 UTC
However getSubjectX500Principal().toString() returns the same result as getSubjectDN().getName() (I did look at the JVM code to find options ;) ), so I will revert to using that since the idea was not to changes things.
Comment 10 Remy Maucherat 2022-04-14 08:48:33 UTC
The fix will be in Tomcat 10.1.0-M15, 10.0.21, 9.0.63, 8.5.79.
Comment 11 Maikel 2022-04-14 10:55:45 UTC
Thanks for the quick reply and fixing the issue.