From d6466459d165381f41954d587cb9549fc68267d6 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 22 Oct 2026 15:43:42 +0200 Subject: [PATCH] Replace custom parsing of CNs with jdk methods --- src/main/java/module-info.java | 1 + .../org/cryptomator/common/X509Helper.java | 60 +++++-------------- .../cryptomator/common/X509HelperTest.java | 23 +++++++ 3 files changed, 38 insertions(+), 46 deletions(-) create mode 100644 src/test/java/org/cryptomator/common/X509HelperTest.java diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 2671bcae1..303b1d961 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -35,6 +35,7 @@ open module org.cryptomator.desktop { requires org.cryptomator.integrations.api; // jdk: requires java.desktop; + requires java.naming; requires java.net.http; requires javafx.base; requires javafx.graphics; diff --git a/src/main/java/org/cryptomator/common/X509Helper.java b/src/main/java/org/cryptomator/common/X509Helper.java index 5dff5864e..f1eafa4b2 100644 --- a/src/main/java/org/cryptomator/common/X509Helper.java +++ b/src/main/java/org/cryptomator/common/X509Helper.java @@ -2,6 +2,10 @@ package org.cryptomator.common; import com.google.common.io.BaseEncoding; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; import javax.security.auth.x500.X500Principal; import java.io.ByteArrayInputStream; import java.security.GeneralSecurityException; @@ -60,54 +64,18 @@ final class X509Helper { } static Optional extractCommonName(X509Certificate cert) { - String rfc2253Name = cert.getSubjectX500Principal().getName(X500Principal.RFC2253); - for (String rdn : splitRdns(rfc2253Name)) { - int equalsPos = rdn.indexOf('='); - if (equalsPos > 0 && "CN".equalsIgnoreCase(rdn.substring(0, equalsPos).trim())) { - return Optional.of(unescapeRdnValue(rdn.substring(equalsPos + 1).trim())); + try { + var name = new LdapName(cert.getSubjectX500Principal().getName(X500Principal.RFC2253)); + for (Rdn rdn : name.getRdns().reversed()) { // LdapName lists RDNs right to left + Attribute cn = rdn.toAttributes().get("CN"); + if (cn != null && cn.get() instanceof String value) { + return Optional.of(value); + } } + return Optional.empty(); + } catch (NamingException e) { + return Optional.empty(); } - return Optional.empty(); - } - - private static List splitRdns(String distinguishedName) { - List rdns = new ArrayList<>(); - StringBuilder current = new StringBuilder(); - boolean escaped = false; - for (int i = 0; i < distinguishedName.length(); i++) { - char c = distinguishedName.charAt(i); - if (escaped) { - current.append(c); - escaped = false; - } else if (c == '\\') { - current.append(c); - escaped = true; - } else if (c == ',') { - rdns.add(current.toString()); - current.setLength(0); - } else { - current.append(c); - } - } - rdns.add(current.toString()); - return rdns; - } - - private static String unescapeRdnValue(String value) { - StringBuilder unescaped = new StringBuilder(value.length()); - boolean escaped = false; - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (escaped) { - unescaped.append(c); - escaped = false; - } else if (c == '\\') { - escaped = true; - } else { - unescaped.append(c); - } - } - return unescaped.toString(); } private static X509Certificate parseDerCertificate(byte[] derEncodedCertificate) { diff --git a/src/test/java/org/cryptomator/common/X509HelperTest.java b/src/test/java/org/cryptomator/common/X509HelperTest.java new file mode 100644 index 000000000..6f0a0b74b --- /dev/null +++ b/src/test/java/org/cryptomator/common/X509HelperTest.java @@ -0,0 +1,23 @@ +package org.cryptomator.common; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.security.auth.x500.X500Principal; +import java.security.cert.X509Certificate; +import java.util.Optional; + +public class X509HelperTest { + + @Test + public void testExtractCommonNameFromMultiValuedRdn() { + var cert = Mockito.mock(X509Certificate.class); + Mockito.doReturn(new X500Principal("OU=Licensing+CN=License Issuer CA (Test), O=Skymatic GmbH, C=DE")).when(cert).getSubjectX500Principal(); + + Optional cn = X509Helper.extractCommonName(cert); + + Assertions.assertEquals(Optional.of("License Issuer CA (Test)"), cn); + } + +}