Skip to content

Commit

Permalink
[CONJ-511] Add legacy SSL certificate Hostname verification with CN e…
Browse files Browse the repository at this point in the history
…ven when SAN are set

Improve error message
  • Loading branch information
rusher committed Aug 22, 2017
1 parent 533626d commit d4ae2a2
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 83 deletions.
4 changes: 3 additions & 1 deletion src/main/java/org/mariadb/jdbc/BasePrepareStatement.java
Expand Up @@ -117,8 +117,10 @@ public abstract class BasePrepareStatement extends MariaDbStatement implements P
* @param autoGeneratedKeys a flag indicating whether auto-generated keys should be returned; one of
* <code>Statement.RETURN_GENERATED_KEYS</code>
* or <code>Statement.NO_GENERATED_KEYS</code>
* @throws SQLException if cannot retrieve auto increment value
*/
public BasePrepareStatement(MariaDbConnection connection, int resultSetScrollType, int resultSetConcurrency, int autoGeneratedKeys) {
public BasePrepareStatement(MariaDbConnection connection, int resultSetScrollType, int resultSetConcurrency, int autoGeneratedKeys)
throws SQLException {
super(connection, resultSetScrollType, resultSetConcurrency);
this.noBackslashEscapes = protocol.noBackslashEscapes();
this.useFractionalSeconds = options.useFractionalSeconds;
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/org/mariadb/jdbc/MariaDbStatement.java
Expand Up @@ -120,8 +120,9 @@ public class MariaDbStatement implements Statement, Cloneable {
* <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
* @param resultSetConcurrency a concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
* <code>ResultSet.CONCUR_UPDATABLE</code>
* @throws SQLException if cannot retrieve auto increment value
*/
public MariaDbStatement(MariaDbConnection connection, int resultSetScrollType, int resultSetConcurrency) {
public MariaDbStatement(MariaDbConnection connection, int resultSetScrollType, int resultSetConcurrency) throws SQLException {
this.protocol = connection.getProtocol();
this.connection = connection;
this.canUseServerTimeout = connection.canUseServerTimeout();
Expand Down Expand Up @@ -152,8 +153,12 @@ public MariaDbStatement clone(MariaDbConnection connection) throws CloneNotSuppo
clone.protocol = connection.getProtocol();
clone.timerTaskFuture = null;
clone.batchQueries = new ArrayList<>();
clone.results = new Results(clone, clone.protocol.getAutoIncrementIncrement(),
clone.resultSetScrollType, clone.resultSetConcurrency);
try {
clone.results = new Results(clone, clone.protocol.getAutoIncrementIncrement(),
clone.resultSetScrollType, clone.resultSetConcurrency);
} catch (SQLException sqle) {
//eat exception
}
clone.closed = false;
clone.warningsCleared = true;
clone.fetchSize = 0;
Expand Down
Expand Up @@ -269,7 +269,7 @@ private List<String> getCurrentEndpointIdentifiers(Protocol protocol) throws SQL
} catch (SQLException qe) {
log.log(Level.WARNING, "SQL exception occurred: " + qe.getMessage());
if (protocol.getProxy().hasToHandleFailover(qe)) {
if (masterProtocol.equals(protocol)) {
if (masterProtocol == null || masterProtocol.equals(protocol)) {
setMasterHostFail();
} else if (secondaryProtocol.equals(protocol)) {
setSecondaryHostFail();
Expand Down
Expand Up @@ -704,7 +704,7 @@ private void handleConnectionPhases(String host) throws SQLException {
X509Certificate cert = (X509Certificate) certs[0];
hostnameVerifier.verify(host, cert);
} catch (SSLException ex) {
throw new SQLNonTransientConnectionException(ex.getMessage()
throw new SQLNonTransientConnectionException("SSL hostname verification failed : " + ex.getMessage()
+ "\nThis verification can be disable using the option \"disableSslHostnameVerification\" "
+ "but won't prevent man-in-the-middle attacks anymore", "08006");
}
Expand Down
Expand Up @@ -1395,8 +1395,9 @@ private void handleStateChange(Buffer buf, Results results) throws SQLException
* Get current auto increment increment.
*
* @return auto increment increment.
* @throws SQLException if cannot retrieve auto increment value
*/
public int getAutoIncrementIncrement() {
public int getAutoIncrementIncrement() throws SQLException {
if (autoIncrementIncrement == 0) {
try {
Results results = new Results();
Expand All @@ -1405,7 +1406,8 @@ public int getAutoIncrementIncrement() {
ResultSet rs = results.getResultSet();
rs.next();
autoIncrementIncrement = rs.getInt(1);
} catch (Exception e) {
} catch (SQLException e) {
if (e.getSQLState().startsWith("08")) throw e;
autoIncrementIncrement = 1;
}
}
Expand Down
Expand Up @@ -265,7 +265,7 @@ void resetStateAfterFailover(long maxRows, int transactionIsolationLevel, String

boolean isEofDeprecated();

int getAutoIncrementIncrement();
int getAutoIncrementIncrement() throws SQLException;

boolean sessionStateAware();

Expand Down
Expand Up @@ -47,7 +47,7 @@ private static boolean matchDns(String hostname, String tlsDnsPattern) throws SS
if (!matchWildCards(hostIsIp, hostnameSt.nextToken(), templateSt.nextToken())) return false;
}
} catch (SSLException exception) {
throw new SSLException("host \"" + hostname + "\" doesn't correspond to certificate CN \"" + tlsDnsPattern
throw new SSLException(normalizedHostMsg(hostname) + " doesn't correspond to certificate CN \"" + tlsDnsPattern
+ "\" : wildcards not possible for IPs");
}
return true;
Expand Down Expand Up @@ -157,12 +157,18 @@ public boolean verify(String host, SSLSession session) {
* @throws SSLException exception
*/
public void verify(String host, X509Certificate cert) throws SSLException {
String normalizedHost = host.toLowerCase(Locale.ROOT);
try {
//***********************************************************
// RFC 6125 : check Subject Alternative Name (SAN)
//***********************************************************
String altNameError = "";

SubjectAltNames subjectAltNames = getSubjectAltNames(cert);
if (!subjectAltNames.isEmpty()) {

//***********************************************************
// Host is IPv4 : Check corresponding entries in alternative subject names
// Host is IPv4 : Check corresponding entries in subject alternative names
//***********************************************************
if (Utils.isIPv4(host)) {
for (GeneralName entry : subjectAltNames.getGeneralNames()) {
Expand All @@ -176,13 +182,10 @@ public void verify(String host, X509Certificate cert) throws SSLException {
if (host.equals(entry.value)) return;
}
}
throw new SSLException("No IPv4 corresponding to host \"" + host + "\" in certificate alt-names " + subjectAltNames.toString());
}

//***********************************************************
// Host is IPv6 : Check corresponding entries in alternative subject names
//***********************************************************
if (Utils.isIPv6(host)) {
} else if (Utils.isIPv6(host)) {
//***********************************************************
// Host is IPv6 : Check corresponding entries in subject alternative names
//***********************************************************
String normalisedHost = normaliseAddress(host);
for (GeneralName entry : subjectAltNames.getGeneralNames()) {
if (logger.isTraceEnabled()) {
Expand All @@ -199,46 +202,69 @@ public void verify(String host, X509Certificate cert) throws SSLException {
}
}
}
throw new SSLException("No IPv6 corresponding to host \"" + host + "\" in certificate alt-names " + subjectAltNames.toString());
}

//***********************************************************
// Host is not IP = DNS : Check corresponding entries in alternative subject names
//***********************************************************
String normalizedHost = host.toLowerCase(Locale.ROOT);
for (GeneralName entry : subjectAltNames.getGeneralNames()) {
if (logger.isTraceEnabled()) {
logger.trace("DNS verification of hostname : type=" + entry.extension
+ " value=" + entry.value
+ " to " + host);
}
if (entry.extension == Extension.DNS) { //IP
String normalizedSubjectAlt = entry.value.toLowerCase(Locale.ROOT);
if (matchDns(normalizedHost, normalizedSubjectAlt)) {
return;
} else {
//***********************************************************
// Host is not IP = DNS : Check corresponding entries in alternative subject names
//***********************************************************
for (GeneralName entry : subjectAltNames.getGeneralNames()) {
if (logger.isTraceEnabled()) {
logger.trace("DNS verification of hostname : type=" + entry.extension
+ " value=" + entry.value
+ " to " + host);
}
if (entry.extension == Extension.DNS) { //IP
String normalizedSubjectAlt = entry.value.toLowerCase(Locale.ROOT);
if (matchDns(normalizedHost, normalizedSubjectAlt)) {
return;
}
}
}
}
throw new SSLException("DNS host \"" + host + "\" not found in certificate alt-names " + subjectAltNames.toString());
}

//***********************************************************
// RFC 2818 : legacy fallback using CN (recommendation is using alt-names)
//***********************************************************
X500Principal subjectPrincipal = cert.getSubjectX500Principal();
String cn = extractCommonName(subjectPrincipal.getName(X500Principal.RFC2253));

if (cn == null) {
if (subjectAltNames.isEmpty()) {
throw new SSLException("CN not found in certificate principal \"" + subjectPrincipal
+ "\" and certificate doesn't contain SAN");
} else {
throw new SSLException("CN not found in certificate principal \"" + subjectPrincipal
+ "\" and " + normalizedHostMsg(normalizedHost) + " doesn't correspond to " + subjectAltNames.toString());
}
}

String normalizedCn = cn.toLowerCase(Locale.ROOT);

if (!matchDns(normalizedHost, normalizedCn)) {
String errorMsg = normalizedHostMsg(normalizedHost) + " doesn't correspond to certificate CN \"" + normalizedCn + "\"";
if (!subjectAltNames.isEmpty()) errorMsg += " and " + subjectAltNames.toString();
throw new SSLException(errorMsg);
}

return;


} catch (CertificateParsingException cpe) {
// ignore error
throw new SSLException("certificate parsing error : " + cpe.getMessage());
}
}

//***********************************************************
// no alternative subject names, check using CN
//***********************************************************
X500Principal subjectPrincipal = cert.getSubjectX500Principal();
String cn = extractCommonName(subjectPrincipal.getName(X500Principal.RFC2253));
if (cn == null) {
throw new SSLException("CN not found in certificate principal \"" + subjectPrincipal + "\"");
private static String normalizedHostMsg(String normalizedHost) {
StringBuilder msg = new StringBuilder();
if (Utils.isIPv4(normalizedHost)) {
msg.append("IPv4 host \"");
} else if (Utils.isIPv6(normalizedHost)) {
msg.append("IPv6 host \"");
} else {
msg.append("DNS host \"");
}
String normalizedHost = host.toLowerCase(Locale.ROOT);
String normalizedCn = cn.toLowerCase(Locale.ROOT);
if (!matchDns(normalizedHost, normalizedCn)) {
throw new SSLException("host \"" + normalizedHost + "\" doesn't correspond to certificate CN \"" + normalizedCn + "\"");
}

msg.append(normalizedHost).append("\"");
return msg.toString();
}

private enum Extension {
Expand All @@ -256,7 +282,7 @@ public GeneralName(String value, Extension extension) {

@Override
public String toString() {
return "{\"" + value + "\"|" + extension + "}";
return "{" + extension + ":\"" + value + "\"}";
}
}

Expand All @@ -265,7 +291,9 @@ private class SubjectAltNames {

@Override
public String toString() {
StringBuilder sb = new StringBuilder("certificate SubjectAltNames[");
if (isEmpty()) return "SAN[-empty-]";

StringBuilder sb = new StringBuilder("SAN[");
boolean first = true;

for (GeneralName generalName : generalNames) {
Expand Down

0 comments on commit d4ae2a2

Please sign in to comment.