/* * Copyright (c) 2004, PostgreSQL Global Development Group * See the LICENSE file in the project root for more information. */ package org.postgresql.ssl; import org.postgresql.util.GT; 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.UUID; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; /** * Provides a SSLSocketFactory that authenticates the remote server against an explicit pre-shared * SSL certificate. This is more secure than using the NonValidatingFactory as it prevents "man in * the middle" attacks. It is also more secure than relying on a central CA signing your server's * certificate as it pins the server's certificate. * * <p> * This class requires a single String parameter specified by setting the connection property * <code>sslfactoryarg</code>. The value of this property is the PEM-encoded remote server's SSL * certificate. * </p> * <p> * Where the certificate is loaded from is based upon the prefix of the * * <pre> * <code>sslfactoryarg</code> * </pre> * * property. The following table lists the valid set of prefixes. * <table border="1" summary="Valid prefixes for sslfactoryarg"> * <tr> * <th>Prefix</th> * <th>Example</th> * <th>Explanation</th> * </tr> * <tr> * <td> * * <pre> * <code>classpath:</code> * </pre> * * </td> * <td> * * <pre> * <code>classpath:ssl/server.crt</code> * </pre> * * </td> * <td>Loaded from the classpath.</td> * </tr> * <tr> * <td> * * <pre> * <code>file:</code> * </pre> * * </td> * <td> * * <pre> * <code>file:/foo/bar/server.crt</code> * </pre> * * </td> * <td>Loaded from the filesystem.</td> * </tr> * <tr> * <td> * * <pre> * <code>env:</code> * </pre> * * </td> * <td> * * <pre> * <code>env:mydb_cert</code> * </pre> * * </td> * <td>Loaded from string value of the * * <pre> * <code>mydb_cert</code> * </pre> * * environment variable.</td> * </tr> * <tr> * <td> * * <pre> * <code>sys:</code> * </pre> * * </td> * <td> * * <pre> * <code>sys:mydb_cert</code> * </pre> * * </td> * <td>Loaded from string value of the * * <pre> * <code>mydb_cert</code> * </pre> * * system property.</td> * </tr> * <tr> * <td> * * <pre> * -----BEGIN CERTIFICATE------ * </pre> * * </td> * <td> * * <pre> * -----BEGIN CERTIFICATE----- * MIIDQzCCAqygAwIBAgIJAOd1tlfiGoEoMA0GCSqGSIb3DQEBBQUAMHUxCzAJBgNV * [... truncated ...] * UCmmYqgiVkAGWRETVo+byOSDZ4swb10= * -----END CERTIFICATE----- * </pre> * * </td> * <td>Loaded from string value of the argument.</td> * </tr> * </table> */ public class SingleCertValidatingFactory extends WrappedFactory { private static final String FILE_PREFIX = "file:"; private static final String CLASSPATH_PREFIX = "classpath:"; private static final String ENV_PREFIX = "env:"; private static final String SYS_PROP_PREFIX = "sys:"; public SingleCertValidatingFactory(String sslFactoryArg) throws GeneralSecurityException { if (sslFactoryArg == null || sslFactoryArg.equals("")) { throw new GeneralSecurityException(GT.tr("The sslfactoryarg property may not be empty.")); } InputStream in = null; try { if (sslFactoryArg.startsWith(FILE_PREFIX)) { String path = sslFactoryArg.substring(FILE_PREFIX.length()); in = new BufferedInputStream(new FileInputStream(path)); } else if (sslFactoryArg.startsWith(CLASSPATH_PREFIX)) { String path = sslFactoryArg.substring(CLASSPATH_PREFIX.length()); in = new BufferedInputStream( Thread.currentThread().getContextClassLoader().getResourceAsStream(path)); } else if (sslFactoryArg.startsWith(ENV_PREFIX)) { String name = sslFactoryArg.substring(ENV_PREFIX.length()); String cert = System.getenv(name); if (cert == null || "".equals(cert)) { throw new GeneralSecurityException(GT.tr( "The environment variable containing the server's SSL certificate must not be empty.")); } in = new ByteArrayInputStream(cert.getBytes("UTF-8")); } else if (sslFactoryArg.startsWith(SYS_PROP_PREFIX)) { String name = sslFactoryArg.substring(SYS_PROP_PREFIX.length()); String cert = System.getProperty(name); if (cert == null || "".equals(cert)) { throw new GeneralSecurityException(GT.tr( "The system property containing the server's SSL certificate must not be empty.")); } in = new ByteArrayInputStream(cert.getBytes("UTF-8")); } else if (sslFactoryArg.startsWith("-----BEGIN CERTIFICATE-----")) { in = new ByteArrayInputStream(sslFactoryArg.getBytes("UTF-8")); } else { throw new GeneralSecurityException(GT.tr( "The sslfactoryarg property must start with the prefix file:, classpath:, env:, sys:, or -----BEGIN CERTIFICATE-----.")); } SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, new TrustManager[]{new SingleCertTrustManager(in)}, null); _factory = ctx.getSocketFactory(); } catch (RuntimeException e) { throw e; } catch (Exception e) { if (e instanceof GeneralSecurityException) { throw (GeneralSecurityException) e; } throw new GeneralSecurityException(GT.tr("An error occurred reading the certificate"), e); } finally { if (in != null) { try { in.close(); } catch (Exception e2) { // ignore } } } } public class SingleCertTrustManager implements X509TrustManager { X509Certificate cert; X509TrustManager trustManager; public SingleCertTrustManager(InputStream in) throws IOException, GeneralSecurityException { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); // Note: KeyStore requires it be loaded even if you don't load anything into it: ks.load(null); 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 GeneralSecurityException(GT.tr("No X509TrustManager found")); } } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { trustManager.checkServerTrusted(chain, authType); } public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{cert}; } } }