|
64 | 64 | import java.util.logging.Level; |
65 | 65 | import java.util.logging.Logger; |
66 | 66 |
|
| 67 | +import javax.naming.InvalidNameException; |
| 68 | +import javax.naming.ldap.LdapName; |
| 69 | +import javax.naming.ldap.Rdn; |
67 | 70 | import javax.net.SocketFactory; |
68 | 71 | import javax.net.ssl.KeyManager; |
69 | 72 | import javax.net.ssl.SSLContext; |
@@ -1524,38 +1527,34 @@ private final class HostNameOverrideX509TrustManager implements X509TrustManager |
1524 | 1527 | this.hostName = hostName.toLowerCase(Locale.ENGLISH); |
1525 | 1528 | } |
1526 | 1529 |
|
1527 | | - // Parse name in RFC 2253 format |
1528 | | - // Returns the common name if successful, null if failed to find the common name. |
1529 | | - // The parser tuned to be safe than sorry so if it sees something it cant parse correctly it returns null |
1530 | | - private String parseCommonName(String distinguishedName) { |
1531 | | - int index; |
1532 | | - // canonical name converts entire name to lowercase |
1533 | | - index = distinguishedName.indexOf("cn="); |
1534 | | - if (index == -1) { |
1535 | | - return null; |
1536 | | - } |
1537 | | - distinguishedName = distinguishedName.substring(index + 3); |
1538 | | - // Parse until a comma or end is reached |
1539 | | - // Note the parser will handle gracefully (essentially will return empty string) , inside the quotes (e.g |
1540 | | - // cn="Foo, bar") however |
1541 | | - // RFC 952 says that the hostName cant have commas however the parser should not (and will not) crash if it |
1542 | | - // sees a , within quotes. |
1543 | | - for (index = 0; index < distinguishedName.length(); index++) { |
1544 | | - if (distinguishedName.charAt(index) == ',') { |
1545 | | - break; |
| 1530 | + /** |
| 1531 | + * Securely parse the Common Name from an X.509 certificate using RFC2253 format. |
| 1532 | + * This method prevents DN injection attacks by using LdapName/Rdn parsing. |
| 1533 | + * |
| 1534 | + * @param cert X.509 certificate |
| 1535 | + * @return Common Name from the certificate subject, or null if not found |
| 1536 | + */ |
| 1537 | + private String parseCommonNameSecure(X509Certificate cert) { |
| 1538 | + try { |
| 1539 | + String subjectDN = cert.getSubjectX500Principal().getName(); // RFC2253 format |
| 1540 | + LdapName ldapName = new LdapName(subjectDN); |
| 1541 | + |
| 1542 | + // Iterate through RDNs to find CN |
| 1543 | + for (Rdn rdn : ldapName.getRdns()) { |
| 1544 | + if ("CN".equalsIgnoreCase(rdn.getType())) { |
| 1545 | + return rdn.getValue().toString(); |
| 1546 | + } |
1546 | 1547 | } |
1547 | | - } |
1548 | | - String commonName = distinguishedName.substring(0, index); |
1549 | | - // strip any quotes |
1550 | | - if (commonName.length() > 1 && ('\"' == commonName.charAt(0))) { |
1551 | | - if ('\"' == commonName.charAt(commonName.length() - 1)) |
1552 | | - commonName = commonName.substring(1, commonName.length() - 1); |
1553 | | - else { |
1554 | | - // Be safe the name is not ended in " return null so the common Name wont match |
1555 | | - commonName = null; |
| 1548 | + if (logger.isLoggable(Level.FINER)) { |
| 1549 | + logger.finer(logContext + " No CN found in certificate subject"); |
1556 | 1550 | } |
| 1551 | + return null; |
| 1552 | + } catch (Exception e) { |
| 1553 | + if (logger.isLoggable(Level.WARNING)) { |
| 1554 | + logger.warning(logContext + " Error parsing certificate: " + e.getMessage()); |
| 1555 | + } |
| 1556 | + return null; |
1557 | 1557 | } |
1558 | | - return commonName; |
1559 | 1558 | } |
1560 | 1559 |
|
1561 | 1560 | private boolean validateServerName(String nameInCert) { |
@@ -1645,17 +1644,21 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) throws |
1645 | 1644 | } |
1646 | 1645 |
|
1647 | 1646 | private void validateServerNameInCertificate(X509Certificate cert) throws CertificateException { |
1648 | | - String nameInCertDN = cert.getSubjectX500Principal().getName("canonical"); |
1649 | 1647 | if (logger.isLoggable(Level.FINER)) { |
1650 | 1648 | logger.finer(logContext + " Validating the server name:" + hostName); |
1651 | | - logger.finer(logContext + " The DN name in certificate:" + nameInCertDN); |
1652 | 1649 | } |
1653 | 1650 |
|
1654 | 1651 | boolean isServerNameValidated; |
1655 | 1652 | String dnsNameInSANCert = ""; |
1656 | 1653 |
|
1657 | | - // the name in cert is in RFC2253 format parse it to get the actual subject name |
1658 | | - String subjectCN = parseCommonName(nameInCertDN); |
| 1654 | + // Use secure RFC2253 parsing to prevent DN injection attacks |
| 1655 | + String subjectCN = parseCommonNameSecure(cert); |
| 1656 | + // X.509 certificate standard requires domain names to be in ASCII. |
| 1657 | + // Even IDN (Unicode) names will be represented here in Punycode (ASCII). |
| 1658 | + // Normalize case for comparison using English to avoid case issues like Turkish i. |
| 1659 | + if (subjectCN != null) { |
| 1660 | + subjectCN = subjectCN.toLowerCase(Locale.ENGLISH); |
| 1661 | + } |
1659 | 1662 |
|
1660 | 1663 | isServerNameValidated = validateServerName(subjectCN); |
1661 | 1664 |
|
|
0 commit comments