diff --git a/docs/release-notes.html b/docs/release-notes.html index 31a8e0706..bc63543cd 100644 --- a/docs/release-notes.html +++ b/docs/release-notes.html @@ -35,6 +35,13 @@

Version 4.0.13



+
  • + Fixed an issue in the prompt trust manager that could cause it to display an + incorrect warning for some certificates with a basic constraints extension with + a path length constraint. +

    +
  • +
  • Fixed an issue in the encoding and decoding of X.509 certificates that included an authority key extension that included the optional diff --git a/messages/unboundid-ldapsdk-ssl.properties b/messages/unboundid-ldapsdk-ssl.properties index e3d9f8e3f..160b959df 100644 --- a/messages/unboundid-ldapsdk-ssl.properties +++ b/messages/unboundid-ldapsdk-ssl.properties @@ -149,10 +149,15 @@ WARN_PROMPT_PROCESSOR_NO_BC_EXTENSION=WARNING: Issuer certificate ''{0}'' \ WARN_PROMPT_PROCESSOR_BC_NOT_CA=WARNING: Issuer certificate ''{0}'' has a \ basic constraints extension that indicates the certificate should not be \ trusted as an issuer certificate. -WARN_PROMPT_PROCESSOR_BC_PATH_LENGTH_EXCEEDED=WARNING: Issuer certificate \ - ''{0}'' has a basic constraints extension that indicates a valid \ - certificate chain should contain more than {1,number,0} certificates, but \ - the presented certificate chain has {2,number,0} certificates. +WARN_PROMPT_PROCESSOR_BC_DISALLOWED_INTERMEDIATE=WARNING: Issuer certificate \ + ''{0}'' has a basic constraints extension that indicates there should not \ + be any intermediate CA certificates between it and the end entity \ + certificate, but one or more intermediate CA certificates were found ahead \ + of it in the chain. +WARN_PROMPT_PROCESSOR_BC_TOO_MANY_INTERMEDIATES=WARNING: Issuer certificate \ + ''{0}'' has a basic constraints extension that indicates there should be at \ + most {1,number,0} intermediate CA certificate(s) between it and the end \ + entity certificate, but {2,number,0} intermediate CA certificates were found. WARN_PROMPT_PROCESSOR_KU_NO_KEY_CERT_SIGN=WARNING: Issuer certificate \ ''{0}'' has a key usage extension that does not include the keyCertSign \ usage. This indicates that the certificate should not be trusted as an \ diff --git a/src/com/unboundid/util/ssl/PromptTrustManagerProcessor.java b/src/com/unboundid/util/ssl/PromptTrustManagerProcessor.java index 50bbcf4f4..edf4c9224 100644 --- a/src/com/unboundid/util/ssl/PromptTrustManagerProcessor.java +++ b/src/com/unboundid/util/ssl/PromptTrustManagerProcessor.java @@ -279,11 +279,21 @@ else if (! bc.isCA()) chain[i].getSubjectDN())); } else if ((bc.getPathLengthConstraint() != null) && - (bc.getPathLengthConstraint() < chain.length)) + ((i-1) > bc.getPathLengthConstraint())) { - warningMessages.add(WARN_PROMPT_PROCESSOR_BC_PATH_LENGTH_EXCEEDED.get( - chain[i].getSubjectDN(), bc.getPathLengthConstraint(), - chain.length)); + if (bc.getPathLengthConstraint() == 0) + { + warningMessages.add( + WARN_PROMPT_PROCESSOR_BC_DISALLOWED_INTERMEDIATE.get( + chain[i].getSubjectDN())); + } + else + { + warningMessages.add( + WARN_PROMPT_PROCESSOR_BC_TOO_MANY_INTERMEDIATES.get( + chain[i].getSubjectDN(), bc.getPathLengthConstraint(), + (i-1))); + } } if ((ku != null) && (! ku.isKeyCertSignBitSet())) diff --git a/tests/unit/src/com/unboundid/util/ssl/PromptTrustManagerProcessorTestCase.java b/tests/unit/src/com/unboundid/util/ssl/PromptTrustManagerProcessorTestCase.java index a651684c2..ca290b1ed 100644 --- a/tests/unit/src/com/unboundid/util/ssl/PromptTrustManagerProcessorTestCase.java +++ b/tests/unit/src/com/unboundid/util/ssl/PromptTrustManagerProcessorTestCase.java @@ -887,11 +887,20 @@ public void testIssuerBasicConstraintsPathLengthExceeded() // during testing. final File tempDir = createTempDir(); - final String caCertificateAlias = "ca-cert"; - final String caKeyStorePath = new File(tempDir, - caCertificateAlias + "-keystore.jks").getAbsolutePath(); - final String caCertificatePath = - new File(tempDir, caCertificateAlias + ".cert").getAbsolutePath(); + final String rootCACertificateAlias = "root-ca-cert"; + final String rootCAKeyStorePath = new File(tempDir, + rootCACertificateAlias + "-keystore.jks").getAbsolutePath(); + final String rootCACertificatePath = + new File(tempDir, rootCACertificateAlias + ".cert").getAbsolutePath(); + + final String intermediateCACertificateAlias = "intermediate-ca-cert"; + final String intermediateCAKeyStorePath = new File(tempDir, + intermediateCACertificateAlias + "-keystore.jks").getAbsolutePath(); + final String intermediateCACSRPath = new File(tempDir, + intermediateCACertificateAlias + ".csr").getAbsolutePath(); + final String intermediateCACertificatePath = + new File(tempDir, intermediateCACertificateAlias + ".cert"). + getAbsolutePath(); final String serverCertificateAlias = "server-cert"; final String serverKeyStorePath = new File(tempDir, @@ -901,13 +910,13 @@ public void testIssuerBasicConstraintsPathLengthExceeded() final String serverCertificatePath = new File(tempDir, serverCertificateAlias + ".cert").getAbsolutePath(); - // Create a JKS keystore with just a CA certificate. + // Create a JKS keystore with just a root CA certificate. manageCertificates( "generate-self-signed-certificate", - "--keystore", caKeyStorePath, + "--keystore", rootCAKeyStorePath, "--keystore-password", "password", "--keystore-type", "JKS", - "--alias", caCertificateAlias, + "--alias", rootCACertificateAlias, "--subject-dn", "CN=Example Root CA,O=Example Corporation,C=US", "--days-valid", "7300", "--key-algorithm", "RSA", @@ -915,20 +924,21 @@ public void testIssuerBasicConstraintsPathLengthExceeded() "--signature-algorithm", "SHA256withRSA", "--subject-alternative-name-email-address", "ca@example.com", "--basic-constraints-is-ca", "true", - "--basic-constraints-maximum-path-length", "1", + "--basic-constraints-maximum-path-length", "0", "--key-usage", "key-cert-sign", "--display-keytool-command"); manageCertificates( "export-certificate", - "--keystore", caKeyStorePath, + "--keystore", rootCAKeyStorePath, "--keystore-password", "password", - "--alias", caCertificateAlias, + "--alias", rootCACertificateAlias, "--output-format", "PEM", - "--output-file", caCertificatePath, + "--output-file", rootCACertificatePath, "--display-keytool-command"); - // Create a JKS keystore with a server certificate that is signed by the CA. + // Create a JKS keystore with a server certificate that is signed by the + // root CA. manageCertificates( "generate-certificate-signing-request", "--output-file", serverCSRPath, @@ -955,9 +965,9 @@ public void testIssuerBasicConstraintsPathLengthExceeded() "--request-input-file", serverCSRPath, "--certificate-output-file", serverCertificatePath, "--output-format", "PEM", - "--keystore", caKeyStorePath, + "--keystore", rootCAKeyStorePath, "--keystore-password", "password", - "--signing-certificate-alias", caCertificateAlias, + "--signing-certificate-alias", rootCACertificateAlias, "--days-valid", "365", "--include-requested-extensions", "--no-prompt", @@ -965,7 +975,7 @@ public void testIssuerBasicConstraintsPathLengthExceeded() manageCertificates( "import-certificate", "--certificate-file", serverCertificatePath, - "--certificate-file", caCertificatePath, + "--certificate-file", rootCACertificatePath, "--keystore", serverKeyStorePath, "--keystore-password", "password", "--alias", serverCertificateAlias, @@ -974,20 +984,19 @@ public void testIssuerBasicConstraintsPathLengthExceeded() // Load the keystore and get the certificate chain. - final KeyStore keystore = KeyStore.getInstance("JKS"); + KeyStore keystore = KeyStore.getInstance("JKS"); try (FileInputStream inputStream = new FileInputStream(serverKeyStorePath)) { keystore.load(inputStream, "password".toCharArray()); } - final Certificate[] javaChain = + Certificate[] javaChain = keystore.getCertificateChain(serverCertificateAlias); - final X509Certificate[] ldapSDKChain = - PromptTrustManager.convertChain(javaChain); + X509Certificate[] ldapSDKChain = PromptTrustManager.convertChain(javaChain); // Invoke the shouldPrompt method and examine the result. - final ObjectPair> promptResult = + ObjectPair> promptResult = PromptTrustManagerProcessor.shouldPrompt( PromptTrustManager.getCacheKey(javaChain[0]), ldapSDKChain, true, true, Collections.emptyMap(), @@ -996,6 +1005,129 @@ public void testIssuerBasicConstraintsPathLengthExceeded() assertNotNull(promptResult.getFirst()); assertEquals(promptResult.getFirst(), Boolean.TRUE); + assertNotNull(promptResult.getSecond()); + assertTrue(promptResult.getSecond().isEmpty()); + + + // Create a JKS keystore with an intermediate CA certificate that is signed + // by the root CA. + manageCertificates( + "generate-certificate-signing-request", + "--output-file", intermediateCACSRPath, + "--keystore", intermediateCAKeyStorePath, + "--keystore-password", "password", + "--keystore-type", "JKS", + "--alias", intermediateCACertificateAlias, + "--subject-dn", + "CN=Example Intermediate CA,O=Example Corporation,C=US", + "--key-algorithm", "RSA", + "--key-size-bits", "2048", + "--signature-algorithm", "SHA256withRSA", + "--basic-constraints-is-ca", "true", + "--basic-constraints-maximum-path-length", "0", + "--subject-alternative-name-dns", "ldap.example.com", + "--subject-alternative-name-dns", "ldap", + "--subject-alternative-name-dns", "ds.example.com", + "--subject-alternative-name-dns", "ds", + "--subject-alternative-name-dns", "localhost", + "--subject-alternative-name-ip-address", "127.0.0.1", + "--subject-alternative-name-ip-address", "::1", + "--extended-key-usage", "server-auth", + "--extended-key-usage", "client-auth", + "--display-keytool-command"); + manageCertificates( + "sign-certificate-signing-request", + "--request-input-file", intermediateCACSRPath, + "--certificate-output-file", intermediateCACertificatePath, + "--output-format", "PEM", + "--keystore", rootCAKeyStorePath, + "--keystore-password", "password", + "--signing-certificate-alias", rootCACertificateAlias, + "--days-valid", "365", + "--include-requested-extensions", + "--no-prompt", + "--display-keytool-command"); + manageCertificates( + "import-certificate", + "--certificate-file", intermediateCACertificatePath, + "--certificate-file", rootCACertificatePath, + "--keystore", intermediateCAKeyStorePath, + "--keystore-password", "password", + "--alias", intermediateCACertificateAlias, + "--no-prompt", + "--display-keytool-command"); + + + // Delete the server certificate keystore and recreate it with a server + // certificate that is signed by the intermediate CA. + assertTrue(new File(serverKeyStorePath).delete()); + assertTrue(new File(serverCertificatePath).delete()); + assertTrue(new File(serverCSRPath).delete()); + manageCertificates( + "generate-certificate-signing-request", + "--output-file", serverCSRPath, + "--keystore", serverKeyStorePath, + "--keystore-password", "password", + "--keystore-type", "JKS", + "--alias", serverCertificateAlias, + "--subject-dn", "CN=ldap.example.com,O=Example Corporation,C=US", + "--key-algorithm", "RSA", + "--key-size-bits", "2048", + "--signature-algorithm", "SHA256withRSA", + "--subject-alternative-name-dns", "ldap.example.com", + "--subject-alternative-name-dns", "ldap", + "--subject-alternative-name-dns", "ds.example.com", + "--subject-alternative-name-dns", "ds", + "--subject-alternative-name-dns", "localhost", + "--subject-alternative-name-ip-address", "127.0.0.1", + "--subject-alternative-name-ip-address", "::1", + "--extended-key-usage", "server-auth", + "--extended-key-usage", "client-auth", + "--display-keytool-command"); + manageCertificates( + "sign-certificate-signing-request", + "--request-input-file", serverCSRPath, + "--certificate-output-file", serverCertificatePath, + "--output-format", "PEM", + "--keystore", intermediateCAKeyStorePath, + "--keystore-password", "password", + "--signing-certificate-alias", intermediateCACertificateAlias, + "--days-valid", "365", + "--include-requested-extensions", + "--no-prompt", + "--display-keytool-command"); + manageCertificates( + "import-certificate", + "--certificate-file", serverCertificatePath, + "--certificate-file", intermediateCACertificatePath, + "--certificate-file", rootCACertificatePath, + "--keystore", serverKeyStorePath, + "--keystore-password", "password", + "--alias", serverCertificateAlias, + "--no-prompt", + "--display-keytool-command"); + + + // Load the keystore and get the certificate chain. + keystore = KeyStore.getInstance("JKS"); + try (FileInputStream inputStream = new FileInputStream(serverKeyStorePath)) + { + keystore.load(inputStream, "password".toCharArray()); + } + + javaChain = keystore.getCertificateChain(serverCertificateAlias); + ldapSDKChain = PromptTrustManager.convertChain(javaChain); + + + // Invoke the shouldPrompt method and examine the result. + promptResult = PromptTrustManagerProcessor.shouldPrompt( + PromptTrustManager.getCacheKey(javaChain[0]), + ldapSDKChain, true, true, Collections.emptyMap(), + Collections.singletonList("ldap.example.com")); + + assertNotNull(promptResult.getFirst()); + assertEquals(promptResult.getFirst(), Boolean.TRUE); + assertNotNull(promptResult.getSecond()); assertFalse(promptResult.getSecond().isEmpty()); assertEquals(promptResult.getSecond().size(), 1);