diff --git a/opc-ua-stack/stack-core/src/main/java/org/eclipse/milo/opcua/stack/core/util/validation/CertificateValidationUtil.java b/opc-ua-stack/stack-core/src/main/java/org/eclipse/milo/opcua/stack/core/util/validation/CertificateValidationUtil.java index e022c8696..0fd98e91a 100644 --- a/opc-ua-stack/stack-core/src/main/java/org/eclipse/milo/opcua/stack/core/util/validation/CertificateValidationUtil.java +++ b/opc-ua-stack/stack-core/src/main/java/org/eclipse/milo/opcua/stack/core/util/validation/CertificateValidationUtil.java @@ -496,6 +496,17 @@ public static void checkValidity(X509Certificate certificate, boolean endEntity) } } + /** + * Creates a predicate to compare a hostname against a SubjectAltName DNSName entry in a + * certificate. The comparison is case-insensitive, following RFC 3986. + * + * @param hostname the hostname to compare against. + * @return a predicate that returns {@code true} if the hostname matches the DNSName entry. + */ + private static Predicate hostnameValidationPredicate(String hostname) { + return input -> input instanceof String && hostname.equalsIgnoreCase((String) input); + } + /** * Validate that one of {@code hostNames} matches a SubjectAltName DNSName or IPAddress entry in the certificate. * @@ -513,7 +524,7 @@ public static void checkHostnameOrIpAddress( return checkSubjectAltNameField( certificate, SUBJECT_ALT_NAME_DNS_NAME, - n::equals + hostnameValidationPredicate(n) ); } catch (Throwable t) { return false; diff --git a/opc-ua-stack/stack-core/src/test/java/org/eclipse/milo/opcua/stack/core/util/validation/CertificateValidationUtilTest.java b/opc-ua-stack/stack-core/src/test/java/org/eclipse/milo/opcua/stack/core/util/validation/CertificateValidationUtilTest.java index c14573793..2bacb4d71 100644 --- a/opc-ua-stack/stack-core/src/test/java/org/eclipse/milo/opcua/stack/core/util/validation/CertificateValidationUtilTest.java +++ b/opc-ua-stack/stack-core/src/test/java/org/eclipse/milo/opcua/stack/core/util/validation/CertificateValidationUtilTest.java @@ -43,6 +43,7 @@ import static com.google.common.collect.Sets.newHashSet; import static java.util.Collections.emptySet; import static org.eclipse.milo.opcua.stack.core.util.validation.CertificateValidationUtil.buildTrustedCertPath; +import static org.eclipse.milo.opcua.stack.core.util.validation.CertificateValidationUtil.checkHostnameOrIpAddress; import static org.eclipse.milo.opcua.stack.core.util.validation.CertificateValidationUtil.validateTrustedCertPath; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -451,6 +452,15 @@ public void testUriWithSpaces() throws Exception { ); } + @Test + public void testHostnameWithCaseInsensitive() throws Exception { + String hostname = "digitalpetri.com"; + checkHostnameOrIpAddress(createSelfSignedCertificate("digitalpetri.com"), hostname); + checkHostnameOrIpAddress(createSelfSignedCertificate("DIGITALPETRI.COM"), hostname); + checkHostnameOrIpAddress(createSelfSignedCertificate("DIGITALPETRI.com"), hostname); + checkHostnameOrIpAddress(createSelfSignedCertificate("DigitalPetri.com"), hostname); + } + private X509CRL generateCrl(X509Certificate ca, PrivateKey caPrivateKey, X509Certificate... revoked) throws Exception { X509v2CRLBuilder builder = new X509v2CRLBuilder( new X500Name(ca.getSubjectDN().getName()), @@ -487,4 +497,14 @@ private X509Certificate getCertificate(String alias) throws KeyStoreException { return certificate; } + private static X509Certificate createSelfSignedCertificate(String dnsName) throws Exception { + KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048); + + SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair) + .setApplicationUri("urn:eclipse:milo:test") + .addDnsName(dnsName); + + return builder.build(); + } + }