- 在 Linux Server 上安装好了 FTPS server, ( vsftpd 工具 )
- 在Server上生成 SSL 证书, 签证的时候我是 自签名的 self-signed。(当然,可以花钱由CA中心签发电子证书)
如何 生成 self-signed certificate 见我前面 叙述的文章.
假定我们现在生成的 证书 为 server.cer
- 然后在 Client 上根据 server.cer 证书生成客户端的 trustStore
如果 是self-signed 的证书,一定要在客户端生成相应的trustStore, 因为
Man-In-Middle-Attact, 就是说,在Server返回Certificate的时候,第三者在中间截断,然后生成一个伪冒的证书发送给Client,这样,Client就会用这张伪冒的证书里面的Public Key去加密生成的发送给Server的Secret Key. 这样Client发送的任何东西,第三方均可以获取.
所以,我们需要实现建立好trustStore,并用它去验证Server证书的合法性.
keytool -import -alias ca -file server.cer -keystore jassecacerts
这里需要密码来 import,可以随便自己定义,但得牢记
这样我们根据服务器端的 self-signed certificate 生成了 自己的trustStore 叫 jassecacerts
- Client 创建FPTS实例链接 Server. 验证过程如何?
根据官方文档,我们可以有两种选择
1. 把 jassecacerts 部署到 %JAVA_HOME%/lib/security 目录中
2. 或者用程序 System.setProperty( "javax.net.ssl.trustStore", ".../jssecacerts"); 导入
然后当 Client 链接 Server (hand shakes) 的时候, jsse 会根据 设置的 TrustStore 去自动校验 certificate的合法性. 具体步骤是这样的, 先在 %JAVA_HOME%/lib/security里面查找 jssecacerts里面有没有,然后再找cacerts里面有没有. 这样去验证 合法性...
事实真是这样的吗???? 经过我具体的测试,JSSE会去找,但是合法性验证了没,这里,我用的是FTPS做的实验,我打包票的说,没有.... 那JSSE只做了些什么呢? 就是 去找了下有没有..... 如果没有,就直接加入.
看如下的验证过程:
我先导入Gmail的证书, 用这个证书作为我自己Server证书的一个fake版本的.
Gmail证书如何生成的,很简单,需要用到 Openssl,
1. Get the certificate from Gmail
openssl s_client -showcerts -connect gmail.google.com:443
2. 然后将 -----BEGIN CERTIFICATE----- 和 -----END CERTIFICATE----- 之间,也包括这两行 直接拷贝到 gmail.cer
3. keytool -import -alias fake gmail.cert -keystore fakestore
这样,我们就生成了一个 针对我自己server的一个假冒的truststore
然后我把这个假冒的trust store 导入到 %JAVA_HOME%/lib/security中,当然得重命名为 jssecacerts,然后我运行下面的这段测试代码:
FTPS _ftps = new FTPSClient();
_ftps.connect( _myserveraddr, 21 );
System.out.println( "Reply Code:" +_ftps.getReplyCode + "; Reply String: "+ _ftps.getReplyString() );
String[] enabledProtocols = _ftps.getEnabledProtocols();
for( String p : enabledProtocols ){
System.out.println("Enabled protocol for this connection: "+p);
}
呵呵,居然返回 234 Proceed with negotiation. 链接成功............
Enabled protocol for this connection: TLS
Enabled protocol for this connection: TLSv1
这里我用了一个假的证书放到我的truststore里面,而真的证书根本就没放,它也连接成功。
然后我用 Djavax
java -Djavax.net.debug=SSL,handshake,data,trustmanager MyApp
从控制台里面发现,似乎 证书被加载到 truststore里面了..
所以试想,如果这里真的有第三者,充当 Man-In-Middle-attact 的角色,
当服务器 将它自己的self-signed证书给客户端的时候,被截取,而截取者将证书改成自己的假冒证书(类似于上面的gmail.cer),里面有自己的public key,
这样,如果当shake hands结束的时候,客户端 exchange的private key就会被 截取这截获了..
所以,我们不能用 JSSE 默认的验证方法.
- 所以,我们应该怎样去验证证书的合法性呢?我们需要手动创建TrustManager去验证.
来吧,我们自己去验证合法性... 通过创建一个自己的 TrustManager, 根据我们从服务器上的cer导入的 trustStore
看下面的代码
下面是我的核心代码片段.
_true_cert 是我真正的非仿冒的 truststore
_true_cert_pwd 是根据server.cer导入truststore时用的密码
注意,我们创建 一个keystore实例,然后倒入truely证书的truststore,然后根据导入了的keystore生成我们的TrustManager,然后将这个trustmanger set 到我们的FTPSClient中... OK.. 这样我们的客户端在connect的时候,会根据这个TrustManager去做验证了,如果从Server上得到的self-singed certificate和我本地Truststore里面放的certificate不同,就会出错...
有兴趣可以将_true_cert换成我上面的那个假的证书_fake_cert,一验证,发现通过不了.....
javax.net.ssl.SSLHandshakeException: com.ibm.jsse2.util.h: No trusted certificate found
呵呵....................... 仔细看下面的代码吧.. 原来真理都那么简单,可是找的过程却如此复杂
// Set the trust store by System Property
System.setProperty( "com.ibm.ssl.trustStore", new File( _true_cert ).getAbsolutePath() );
System.setProperty( "javax.net.ssl.trustStore", new File( _true_cert ).getAbsolutePath() );
FTPSClient _ftps = null;
try{
_ftps = new FTPSClient();
KeyStore keyStore = KeyStore.getInstance( (KeyStore.getDefaultType()) );
keyStore.load(new FileInputStream( _true_cert ), _true_cert_pwd.toCharArray());
java.security.cert.Certificate ca = keyStore.getCertificate( "ca" );
// To check the certificate is really a fake one.
System.out.println(ca.toString());
// We need the trustStore to verify the server's certificate manual if it's a self-signed certificate.
// Set the keyStore for performance enhance when connecting. Optional step for the key manager.
KeyManagerFactory kmf = KeyManagerFactory.getInstance( "IbmX509" );
kmf.init( keyStore, _true_cert_pwd.toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
_ftps.setKeyManager( kms[0] );
// Create the trustStore, It's must for verify the self-signed certificate
TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
tmf.init(keyStore);
TrustManager[] tms = tmf.getTrustManagers();
_ftps.setTrustManager( tms[0] ); // -- very important step to verify the self-signed certificate
//Otherwise, if not set the trust manager to verify the self-signed certificate
//The FTPS client is not so smart to know whether it's a fake or not but just to loaded the certificate into its trustStore
/*
* Shake hands starting
*/
_ftps.connect( "prtdevmq.pok.ibm.com", 21);
Assert.assertEquals("Connect sucessfull", true, true );
System.out.println( "Connect sucessfull, Reply Code:" + _ftps.getReplyCode() +"; Reply String:"+ _ftps.getReplyString() );
String[] enabledProtocols = _ftps.getEnabledProtocols();
for( String p : enabledProtocols ){
System.out.println("Enabled protocol for this connection: "+p);
}