=== modified file 'src/main/java/org/mariadb/jdbc/internal/mysql/MySQLProtocol.java'
--- src/main/java/org/mariadb/jdbc/internal/mysql/MySQLProtocol.java	2013-06-22 19:53:04 +0000
+++ src/main/java/org/mariadb/jdbc/internal/mysql/MySQLProtocol.java	2013-06-22 20:38:54 +0000
@@ -75,6 +75,8 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.net.URL;
@@ -170,21 +172,86 @@
      */
     int secondsBeforeRetryMaster =  30;
 
-    private SSLSocketFactory getSSLSocketFactory(boolean trustServerCertificate)  throws QueryException
-    {
-        if (!trustServerCertificate)
-            return (SSLSocketFactory)SSLSocketFactory.getDefault();
-
-        try {
-            SSLContext sslContext = SSLContext.getInstance("TLS");
-            X509TrustManager[] m = {new DummyX509TrustManager()};
-            sslContext.init(null, m ,null);
+    private SSLSocketFactory getSSLSocketFactory(X509TrustManager trustManager) throws QueryException {
+      try {
+        SSLContext sslContext = SSLContext.getInstance("TLS");
+        X509TrustManager[] m = { trustManager };
+        sslContext.init(null, m, null);
         return sslContext.getSocketFactory();
-        } catch (Exception e) {
-            throw new QueryException(e.getMessage());
+      } catch (Exception e) {
+        throw new QueryException(e.getMessage());
+      }
+    }
+
+    /**
+     * Create an SSLSocketFactory based upon the java.util.Properties parameter.
+     *
+     * This method first checks if the "trustServerCertificate" property is set. If so then a non-validating socket factory is created.
+     * 
+     * It not, it then checks if an explicit SSLSocketFactory class has been specified via the property "sslFactory". If so then it tries to
+     * retrieve the class at runtime and look for a valid constructor. It first looks for a constructor that takes a java.util.Properties file
+     * as a parameter. If it finds it then the class is instantiated with the MySQL JDBC connection properties passed as a parameter. If not then
+     * it tries to instantiate the class using the default no-arg constructor.
+     *
+     * If an explicit socket factory is not defined then the method returns the default SSLSocketFactory presumably using the default JVM TrustManager.
+     *
+     * Any non-RuntimeExceptions thrown during the creation of the SSLSocketFactory are re-thrown as QueryExceptions with the main exception message.
+     *
+     * Examples:
+     *   1) To accept connections to *any* SSL server without performing any validation a client need only set:
+     *         "useSSL" = "true"
+     *         "trustServerCertificate" = "true"
+     *   2) To accept connections to a self-signed SSL server that you already have the certificate stored in a String a client needs to set:
+     *         "useSSL" = "true"
+     *         "sslFactory" = "org.mariadb.jdbc.ssl.SingleCertSocketFactory"
+     *         "serverSslCert" = "-----BEGIN CERTIFICATE----- ... [ Server's Certificate Goes Here] ...."
+     *   3) To accept connections to a self-signed SSL server that you already have the certificate stored in a file a client needs to set:
+     *         "useSSL" = "true"
+     *         "sslFactory" = "org.mariadb.jdbc.ssl.SingleCertSocketFactory"
+     *         "serverSslCertFile" = "classpath:mysql/server.crt"
+     *   4) To accept connections to a server using the default SSL factory a client needs to set:
+     *         "useSSL" = "true"
+     */
+    private SSLSocketFactory getSSLSocketFactory(Properties info) throws QueryException {
+      try {
+        boolean trustServerCertificate = info.getProperty("trustServerCertificate") != null;
+        String socketFactoryClassName = info.getProperty("sslFactory");
+        if (trustServerCertificate) {
+          // use non-validating factory:
+          return getSSLSocketFactory(new DummyX509TrustManager());
+        } else if (socketFactoryClassName != null) {
+          // use user defined factory:
+          Class<? extends SSLSocketFactory> clazz = Class.forName(socketFactoryClassName).asSubclass(SSLSocketFactory.class);
+          try {
+            // First look for a constructor that takes a java.util.Properties as a parameter:
+            Constructor<? extends SSLSocketFactory> constr = clazz.getConstructor(Properties.class);
+            return constr.newInstance(info);
+          } catch (NoSuchMethodException e) {
+            // ignore this and try to find a default constructor instead
+          }
+          try {
+            // Second look for a default no-arg constructor
+            Constructor<? extends SSLSocketFactory> constr = clazz.getConstructor();
+            return constr.newInstance();
+          } catch (NoSuchMethodException e) {
+            // if we don't one here then throw an exception
+            throw new QueryException("No valid constructor found on sslFactory class: " + socketFactoryClassName);
+          }
+        } else {
+          // use default factory:
+          return (SSLSocketFactory) SSLSocketFactory.getDefault();
         }
+      } catch (InvocationTargetException e) {
+        throw new QueryException(e.getTargetException().getMessage());
+      } catch (QueryException e) {
+        throw e;
+      } catch (RuntimeException e) {
+        throw e;
+      } catch (Exception e) {
+        throw new QueryException(e.getMessage());
+      }
+    }
 
-    }
     /**
      * Get a protocol instance
      * @param url connection URL
@@ -365,9 +432,7 @@
                AbbreviatedMySQLClientAuthPacket amcap = new AbbreviatedMySQLClientAuthPacket(capabilities);
                amcap.send(writer);
 
-               boolean trustServerCertificate  =  info.getProperty("trustServerCertificate") != null;
-
-               SSLSocketFactory f = getSSLSocketFactory(trustServerCertificate);
+               SSLSocketFactory f = getSSLSocketFactory(info);
                SSLSocket sslSocket = (SSLSocket)f.createSocket(socket,
                        socket.getInetAddress().getHostAddress(),  socket.getPort(),  false);
 

=== added directory 'src/main/java/org/mariadb/jdbc/ssl'
=== added file 'src/main/java/org/mariadb/jdbc/ssl/SingleCertSocketFactory.java'
--- src/main/java/org/mariadb/jdbc/ssl/SingleCertSocketFactory.java	1970-01-01 00:00:00 +0000
+++ src/main/java/org/mariadb/jdbc/ssl/SingleCertSocketFactory.java	2013-06-22 20:57:25 +0000
@@ -0,0 +1,132 @@
+package org.mariadb.jdbc.ssl;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Properties;
+import java.util.UUID;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * An SSLSocketFactory that validates the server against a single predefined certificate.
+ * <p>
+ * The constructor takes as a parameter the MySQL JDBC Properties used to create a new connection.
+ * <p>
+ * It looks for two properties for the server certificate:
+ * 
+ * <ul>
+ *  <li>
+ *    <b>serverSslCert</b> : The String value of this property is read as the certificate. It's expected to be in PEM format. This property has precedance if both are defined.
+ *  </li>
+ *  <li>
+ *   <b>serverSslCertFile</b> : The path to a file containing the certificate. The value of this can be prefixed with "classpath:" to have a classpath relative search done via the current thread's classloader.
+ *  </li>
+ * </ul>
+ */
+public class SingleCertSocketFactory extends WrappedFactory {
+	private static final String CLASSPATH_PREFIX = "classpath:";
+
+	public SingleCertSocketFactory(Properties info) throws GeneralSecurityException, IOException {
+		String serverCertPem = info.getProperty("serverSslCert");
+		String serverCertPemFile = info.getProperty("serverSslCertFile");
+
+		InputStream serverCert = null;
+		if (serverCertPem != null) {
+			// Load server certificate from String property
+			serverCert = new ByteArrayInputStream(serverCertPem.getBytes());
+		} else if (serverCertPemFile != null) {
+			// Load server certificate from a file
+			if (serverCertPemFile.startsWith(CLASSPATH_PREFIX)) {
+				// Load it from a classpath relative file
+				String classpathFile = serverCertPemFile.substring(CLASSPATH_PREFIX.length());
+				serverCert = new BufferedInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream(classpathFile));
+			} else {
+				// Load it from an explicit file path
+				serverCert = new BufferedInputStream(new FileInputStream(serverCertPemFile));
+			}
+		} else {
+			throw new IllegalArgumentException("serverSslCert or serverSslCertFile must be defined");
+		}
+
+		SSLContext sslContext = SSLContext.getInstance("TLS");
+		X509TrustManager trustManager = new SingleCertTrustManager(serverCert);
+		X509TrustManager[] m = { trustManager };
+		sslContext.init(null, m, null);
+		_factory = sslContext.getSocketFactory();
+	}
+
+	/**
+	 * An X509TrustManager that validates against a single predefined certificate
+	 * 
+	 */
+	static class SingleCertTrustManager implements X509TrustManager {
+		X509TrustManager trustManager;
+		X509Certificate cert;
+
+		public SingleCertTrustManager(InputStream in) {
+			init(in);
+		}
+
+		private void init(InputStream in) {
+			try {
+				KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+				try {
+					// Note: KeyStore requires it be loaded even if you don't load anything into it:
+					ks.load(null);
+				} catch (Exception e) {
+				}
+				CertificateFactory cf = CertificateFactory.getInstance("X509");
+				cert = (X509Certificate) cf.generateCertificate(in);
+				ks.setCertificateEntry(UUID.randomUUID().toString(), cert);
+				TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+				tmf.init(ks);
+				for (TrustManager tm : tmf.getTrustManagers()) {
+					if (tm instanceof X509TrustManager) {
+						trustManager = (X509TrustManager) tm;
+						break;
+					}
+				}
+				if (trustManager == null) {
+					throw new RuntimeException("No X509TrustManager found");
+				}
+			} catch (RuntimeException e) {
+				throw e;
+			} catch (Exception e) {
+				throw new RuntimeException(e);
+			} finally {
+				if (in != null) {
+					try {
+						in.close();
+					} catch (Exception e) {
+					}
+				}
+			}
+		}
+
+		@Override
+		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+			trustManager.checkClientTrusted(chain, authType);
+		}
+
+		@Override
+		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+			trustManager.checkServerTrusted(chain, authType);
+		}
+
+		@Override
+		public X509Certificate[] getAcceptedIssuers() {
+			return new X509Certificate[] { cert };
+		}
+	}
+}

=== added file 'src/main/java/org/mariadb/jdbc/ssl/WrappedFactory.java'
--- src/main/java/org/mariadb/jdbc/ssl/WrappedFactory.java	1970-01-01 00:00:00 +0000
+++ src/main/java/org/mariadb/jdbc/ssl/WrappedFactory.java	2013-06-22 21:05:05 +0000
@@ -0,0 +1,39 @@
+package org.mariadb.jdbc.ssl;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import javax.net.ssl.SSLSocketFactory;
+
+public abstract class WrappedFactory extends SSLSocketFactory {
+	protected SSLSocketFactory _factory;
+
+	public Socket createSocket(InetAddress host, int port) throws IOException {
+		return _factory.createSocket(host, port);
+	}
+
+	public Socket createSocket(String host, int port) throws IOException {
+		return _factory.createSocket(host, port);
+	}
+
+	public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
+		return _factory.createSocket(host, port, localHost, localPort);
+	}
+
+	public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
+		return _factory.createSocket(address, port, localAddress, localPort);
+	}
+
+	public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
+		return _factory.createSocket(socket, host, port, autoClose);
+	}
+
+	public String[] getDefaultCipherSuites() {
+		return _factory.getDefaultCipherSuites();
+	}
+
+	public String[] getSupportedCipherSuites() {
+		return _factory.getSupportedCipherSuites();
+	}
+}

=== added file 'src/test/java/org/mariadb/jdbc/SSLValidationTest.java'
--- src/test/java/org/mariadb/jdbc/SSLValidationTest.java	1970-01-01 00:00:00 +0000
+++ src/test/java/org/mariadb/jdbc/SSLValidationTest.java	2013-06-23 21:32:49 +0000
@@ -0,0 +1,180 @@
+package org.mariadb.jdbc;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLNonTransientConnectionException;
+import java.sql.Statement;
+import java.util.Properties;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mariadb.jdbc.utils.JdbcUtils;
+
+public class SSLValidationTest {
+	String host = "localhost";
+	int port = 3306;
+	String username = "root";
+	String password = "";
+	String database = "test";
+	String serverCertificatePath = "/etc/mysql/server-cert.pem";
+
+	private String getServerCertificate() {
+		BufferedReader br = null;
+		try {
+			br = new BufferedReader(new InputStreamReader(new FileInputStream(serverCertificatePath)));
+			StringBuilder sb = new StringBuilder();
+			String line = null;
+			while ((line = br.readLine()) != null) {
+				sb.append(line);
+				sb.append("\n");
+			}
+			return sb.toString();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			if (br != null) {
+				try {
+					br.close();
+				} catch (Exception e) {
+				}
+			}
+		}
+	}
+
+	private void saveToFile(String path, String contents) {
+		PrintWriter out = null;
+		try {
+			out = new PrintWriter(new FileOutputStream(path));
+			out.print(contents);
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			if (out != null) {
+				try {
+					out.close();
+				} catch (Exception e) {
+				}
+			}
+		}
+	}
+
+	private Connection createConnection(Properties info) throws SQLException {
+		String jdbcUrl = "jdbc:mysql://" + host + ":" + port + "/" + database;
+		Properties connProps = new Properties(info);
+		connProps.setProperty("user", username);
+		connProps.setProperty("password", password);
+		return DriverManager.getConnection(jdbcUrl, connProps);
+	}
+
+	interface ConnectionCallback {
+		void doWithConn(Connection conn);
+	}
+
+	public void testConnect(Properties info, boolean sslExpected) throws SQLException {
+		Connection conn = null;
+		Statement stmt = null;
+		try {
+			conn = createConnection(info);
+			// First do a basic select test:
+			stmt = conn.createStatement();
+			ResultSet rs = stmt.executeQuery("SELECT 1");
+			rs.next();
+			Assert.assertTrue(rs.getInt(1) == 1);
+			rs.close();
+
+			// Then check if SSL matches what is expected
+			rs = stmt.executeQuery("SHOW STATUS LIKE 'Ssl_cipher'");
+			rs.next();
+			String sslCipher = rs.getString(2);
+			boolean sslActual = sslCipher != null && sslCipher.length() > 0;
+			Assert.assertEquals("sslExpected does not match", sslExpected, sslActual);
+		} finally {
+			JdbcUtils.close(stmt);
+			JdbcUtils.close(conn);
+		}
+	}
+
+	@Test
+	public void testConnectNonSSL() throws SQLException {
+		Properties info = new Properties();
+		testConnect(info, false);
+	}
+
+	@Test
+	public void testTrustedServer() throws SQLException {
+		Properties info = new Properties();
+		info.setProperty("useSSL", "true");
+		info.setProperty("trustServerCertificate", "true");
+		testConnect(info, true);
+	}
+
+	@Test(expected = SQLNonTransientConnectionException.class)
+	public void testNonTrustedServer() throws SQLException {
+		Properties info = new Properties();
+		info.setProperty("useSSL", "true");
+		testConnect(info, false);
+	}
+
+	@Test(expected = SQLException.class)
+	public void testMissingCert() throws SQLException {
+		Properties info = new Properties();
+		info.setProperty("useSSL", "true");
+		info.setProperty("sslFactory", "org.mariadb.jdbc.ssl.SingleCertSocketFactory");
+		testConnect(info, true);
+	}
+
+	@Test
+	public void testServerCertString() throws SQLException {
+		Properties info = new Properties();
+		info.setProperty("useSSL", "true");
+		info.setProperty("sslFactory", "org.mariadb.jdbc.ssl.SingleCertSocketFactory");
+		info.setProperty("serverSslCert", getServerCertificate());
+		testConnect(info, true);
+	}
+
+	@Test(expected = SQLException.class)
+	public void testBadServerCertString() throws SQLException {
+		Properties info = new Properties();
+		info.setProperty("useSSL", "true");
+		info.setProperty("sslFactory", "org.mariadb.jdbc.ssl.SingleCertSocketFactory");
+		info.setProperty("serverSslCert", "foobar");
+		testConnect(info, true);
+	}
+
+	@Test
+	public void testServerCertFile() throws SQLException {
+		Properties info = new Properties();
+		info.setProperty("useSSL", "true");
+		info.setProperty("sslFactory", "org.mariadb.jdbc.ssl.SingleCertSocketFactory");
+		info.setProperty("serverSslCertFile", serverCertificatePath);
+		testConnect(info, true);
+	}
+
+	@Test
+	public void testServerCertClasspathFile() throws SQLException {
+		Properties info = new Properties();
+		info.setProperty("useSSL", "true");
+		info.setProperty("sslFactory", "org.mariadb.jdbc.ssl.SingleCertSocketFactory");
+		// Copy the valid server certificate to a known location on the classpath:
+		String classpathFilename = "server-ssl-cert.pem";
+		saveToFile("target/classes/" + classpathFilename, getServerCertificate());
+		info.setProperty("serverSslCertFile", "classpath:" + classpathFilename);
+		testConnect(info, true);
+	}
+
+	@Test(expected = SQLException.class)
+	public void testWrongServerCert() throws Throwable {
+		Properties info = new Properties();
+		info.setProperty("useSSL", "true");
+		info.setProperty("sslFactory", "org.mariadb.jdbc.ssl.SingleCertSocketFactory");
+		info.setProperty("serverSslCertFile", "classpath:ssl/wrong-server.crt");
+		testConnect(info, true);
+	}
+}

=== added directory 'src/test/java/org/mariadb/jdbc/utils'
=== added file 'src/test/java/org/mariadb/jdbc/utils/JdbcUtils.java'
--- src/test/java/org/mariadb/jdbc/utils/JdbcUtils.java	1970-01-01 00:00:00 +0000
+++ src/test/java/org/mariadb/jdbc/utils/JdbcUtils.java	2013-06-23 20:50:44 +0000
@@ -0,0 +1,37 @@
+package org.mariadb.jdbc.utils;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+public class JdbcUtils {
+	private JdbcUtils() {
+	}
+
+	public static void close(Connection conn) {
+		if (conn != null) {
+			try {
+				conn.close();
+			} catch (Exception e) {
+			}
+		}
+	}
+
+	public static void close(Statement stmt) {
+		if (stmt != null) {
+			try {
+				stmt.close();
+			} catch (Exception e) {
+			}
+		}
+	}
+
+	public static void close(ResultSet rs) {
+		if (rs != null) {
+			try {
+				rs.close();
+			} catch (Exception e) {
+			}
+		}
+	}
+}

=== added directory 'src/test/resources'
=== added directory 'src/test/resources/ssl'
=== added file 'src/test/resources/ssl/README'
--- src/test/resources/ssl/README	1970-01-01 00:00:00 +0000
+++ src/test/resources/ssl/README	2013-06-23 21:19:34 +0000
@@ -0,0 +1,16 @@
+# The file wrong-server.crt is a valid PEM file for testing using the 
+# the wrong certificate to connect to a server. To generate a new
+# certificate for testing run the following commands and copy the
+# resulting server.crt file to "wrong-server.crt"
+
+# Create certificate (will prompt for password):
+openssl genrsa -des3 -out server.key.secure 2048
+
+# Create password-less cert:
+openssl rsa -in server.key.secure -out server.key
+
+# Create signing request:
+openssl req -new -key server.key -out server.csr
+
+# Create self signed cert:
+openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt

=== added file 'src/test/resources/ssl/wrong-server.crt'
--- src/test/resources/ssl/wrong-server.crt	1970-01-01 00:00:00 +0000
+++ src/test/resources/ssl/wrong-server.crt	2013-06-23 21:05:37 +0000
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDdjCCAl4CCQDLKi+rmQABYjANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJB
+VTETMBEGA1UECAwKU29tZS1TdGF0ZTE7MDkGA1UECgwyU2FtcGxlIENlcnRpZmlj
+YXRlIGZvciBNYXJpYURCIEphdmEgQ2xpZW50IFRlc3RpbmcxHDAaBgNVBAMME21h
+cmlhZGIuZXhhbXBsZS5vcmcwHhcNMTMwNjIzMjEwNTM3WhcNMjMwNjIxMjEwNTM3
+WjB9MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTE7MDkGA1UECgwy
+U2FtcGxlIENlcnRpZmljYXRlIGZvciBNYXJpYURCIEphdmEgQ2xpZW50IFRlc3Rp
+bmcxHDAaBgNVBAMME21hcmlhZGIuZXhhbXBsZS5vcmcwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDbs2HMKguBwp4iNjYX0rKxYeTyUzW1JqQGBKL1LUhM
+EHDW9yOYpoNsrOpoH58mQC+h+FfwEdZr3O4DLmUQnryohOhzfdc6a/4mXgx+7Cnm
+Y1AgA14cxIRK7ziNJawURyHvLyEmHuoi3e9bJWPRt7b8OIsoftuxpyQ3CIvhah7o
+ZP2EY5DQWeieWuE9H27vYyimWpweAFB7SQ9Mg42yM/a3qiubmtLVoV2vcli62tck
+/hLxbsSOzM4vVTf7qTPui0wWsqWImz1QSN2SwRh6icPxs5GKqFcNFGyRkXrxj4lE
+sMfcFPDDLnpmT/PKdPLA38qSAe9M/YJHXw+oz73dB0XJAgMBAAEwDQYJKoZIhvcN
+AQEFBQADggEBAA8fo4b409d0s3pFbqNmx/LoA1RHG7/rbYaVW7mh327MDNN6BAZr
+2pg1PoldAgKP6BZPKb/iwLkebv7axya8XE2h4p6aC5PPw4REvClW6/SVVfc3vhAs
+4yzIG8x7LYkmCDRDcLseov+GpsqeKOEhIyWHEYVDuXLhQkSenL16Fv4c4IYR0xyX
+6cvA9v/wpQLU4vRAQw8gEJWj3xAVnhWNm4MUCxrVewJtw2HK/2YVFysWw5r65+oZ
+PT/nVjCkkrQZsXnoLVRmVNG480ajLCKAYDD6DVd345njkVdDyWJa+9noq+542MEv
+GpAkl0yrTcd/AXgdYNyzNec0ZBmWZv6B/MY=
+-----END CERTIFICATE-----

