postgresql 数据库用证书验证,看了一下源码,但是ssl证书验证有几点不懂的地方.
下面是pgjdbc获取完数据库的连接后, 开始启用ssl证书验证的流程.
我的问题: 过程我都看明白了, 但是不知道为什么它要这么做, 还是说这是个标准, 有人可以帮我讲解一下,或者给点资料参考也行, 谢谢
为了保持代码的简洁性,代码有删减.
private PGStream enableSSL(PGStream pgStream, SslMode sslMode, Properties info,
int connectTimeout)
throws IOException, PSQLException {
// 发送SSL请求包
pgStream.sendInteger4(8);//发送一个4字节的整数到后端。
pgStream.sendInteger2(1234);//发送一个2字节整数(短)到后端。
pgStream.sendInteger2(5679);
pgStream.flush();//将任何挂起的输出刷新到后端。
// Now get the response from the backend, one of N, E, S. 现在从后端(N, E, S)中获取响应。
int beresp = pgStream.receiveChar();//从后端接收单个字符。
switch (beresp) {
case 'S':
// ssl 服务器支持ssl
**org.herodbsql.ssl.MakeSSL.convert(pgStream, info);**
return pgStream;
}
}
上面可以看到,jdbc给数据库发了几个字节的整数, 然后获取响应,走不同的switch,但是不知道为什么这么做,接着进去看,convert方法
public static void convert(PGStream stream, Properties info)
throws PSQLException, IOException {
LOGGER.log(Level.FINE, "converting regular socket connection to ssl");
**SSLSocketFactory factory = SocketFactoryFactory.getSslSocketFactory(info);**
SSLSocket newConnection;
// 将常规套接字连接转换为ssl
newConnection = (SSLSocket) factory.createSocket(stream.getSocket(), stream.getHostSpec().getHost(), stream.getHostSpec().getPort(), true);
// 我们必须手动调用,否则将隐藏异常
newConnection.setUseClientMode(true);// 设置使用客户端模式
newConnection.startHandshake();// 开始握手
stream.changeSocket(newConnection);
}
在看SocketFactoryFactory.getSslSocketFactory(info);
public static SSLSocketFactory getSslSocketFactory(Properties info) throws PSQLException {//获取Ssl套接字工厂
String classname = PGProperty.SSL_FACTORY.get(info);// 获取要使用的SSL工厂的类名
if (classname == null
|| "org.herodbsql.ssl.jdbc4.LibPQFactory".equals(classname)
|| "org.herodbsql.ssl.LibPQFactory".equals(classname)) {
return new LibPQFactory(info);
}
}
接着看 new LibPQFactory(info);
这个方法应该就是加载证书的地方
public LibPQFactory(Properties info) throws PSQLException {
try {
SSLContext ctx = SSLContext.getInstance("TLS"); // or "SSL" ?
// 确定默认文件位置
String pathsep = System.getProperty("file.separator");// 获取windows或者linux的文件夹分隔符
String defaultdir;
boolean defaultfile = false;
if (System.getProperty("os.name").toLowerCase().contains("windows")) { // It is Windows
defaultdir = System.getenv("APPDATA") + pathsep + "herodbsql" + pathsep; // 获取指定环境变量的值。环境变量是一个依赖于系统的外部命名值。
} else {
defaultdir = System.getProperty("user.home") + pathsep + ".herodbsql" + pathsep;
}
// Load the client's certificate and key 加载客户机的证书和密钥
String sslcertfile = PGProperty.SSL_CERT.get(info);
if (sslcertfile == null) { // Fall back to default 退回到默认状态
defaultfile = true;
sslcertfile = defaultdir + "herodbsql.crt";
}
String sslkeyfile = PGProperty.SSL_KEY.get(info);
if (sslkeyfile == null) { // Fall back to default
defaultfile = true;
sslkeyfile = defaultdir + "herodbsql.pk8";
}
// Determine the callback handler 确定回调处理程序
CallbackHandler cbh;
String sslpasswordcallback = PGProperty.SSL_PASSWORD_CALLBACK.get(info);
if (sslpasswordcallback != null) {
try {
cbh = (CallbackHandler) ObjectFactory.instantiate(sslpasswordcallback, info, false, null);
} catch (Exception e) {
throw new PSQLException(
GT.tr("The password callback class provided {0} could not be instantiated.",
sslpasswordcallback),
PSQLState.CONNECTION_FAILURE, e);
}
} else {
cbh = new ConsoleCallbackHandler(PGProperty.SSL_PASSWORD.get(info));// 获取 sslkey的加密密码
}
// If the properties are empty, give null to prevent client key selection 如果属性为空,则为null,以防止选择客户机密钥
km = new LazyKeyManager(("".equals(sslcertfile) ? null : sslcertfile),
("".equals(sslkeyfile) ? null : sslkeyfile), cbh, defaultfile);//创建lazykeymanagerduixinag
TrustManager[] tm;
SslMode sslMode = SslMode.of(info);
if (!sslMode.verifyCertificate()) {
// server validation is not required 不需要服务器验证
tm = new TrustManager[]{new NonValidatingTM()};
} else {
// Load the server certificate 加载服务器证书
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");// 创建证书信任管理器工厂
KeyStore ks;
try {
ks = KeyStore.getInstance("jks");// 读取秘钥是所需要用到的工具类
} catch (KeyStoreException e) {
// this should never happen
throw new NoSuchAlgorithmException("jks KeyStore not available");
}
String sslrootcertfile = PGProperty.SSL_ROOT_CERT.get(info);//获取根证书
if (sslrootcertfile == null) { // Fall back to default
sslrootcertfile = defaultdir + "root.crt";
}
FileInputStream fis;
try {
fis = new FileInputStream(sslrootcertfile); // 获取根证书ca的文件流
} catch (FileNotFoundException ex) {
throw new PSQLException(
GT.tr("Could not open SSL root certificate file {0}.", sslrootcertfile),
PSQLState.CONNECTION_FAILURE, ex);
}
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");// // 获取X.509工厂实例
// Certificate[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{}); //Does
// not work in java 1.4
Object[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{});//生成ca证书的数组视图
ks.load(null, null);
for (int i = 0; i < certs.length; i++) {
ks.setCertificateEntry("cert" + i, (Certificate) certs[i]);//设置证书条目 将给定的可信证书分配给给定的别名。
}
tmf.init(ks);
} catch (IOException ioex) {
throw new PSQLException(
GT.tr("Could not read SSL root certificate file {0}.", sslrootcertfile),
PSQLState.CONNECTION_FAILURE, ioex);
} catch (GeneralSecurityException gsex) {
throw new PSQLException(
GT.tr("Loading the SSL root certificate {0} into a TrustManager failed.",
sslrootcertfile),
PSQLState.CONNECTION_FAILURE, gsex);
} finally {
try {
fis.close();
} catch (IOException e) {
/* ignore */
}
}
tm = tmf.getTrustManagers();
}
// finally we can initialize the context 最后,我们可以初始化上下文
try {
ctx.init(new KeyManager[]{km}, tm, null);
} catch (KeyManagementException ex) {
throw new PSQLException(GT.tr("Could not initialize SSL context."),
PSQLState.CONNECTION_FAILURE, ex);
}
factory = ctx.getSocketFactory();
} catch (NoSuchAlgorithmException ex) {
throw new PSQLException(GT.tr("Could not find a java cryptographic algorithm: {0}.",
ex.getMessage()), PSQLState.CONNECTION_FAILURE, ex);
}
}