java client访问https server(客户端代码、服务器端配置)

openssl配置证书时直接配置crt、key即可
而jsse通过keystore来存储证书、证书链信息,java客户端通过jsse来使用ssl
具体openssl和keytool各种证书类型之间的转换可以参考“收藏”中一些文章

cfca颁发的pfx个人证书需要转换成keystore格式才能使用
从cfca获得如下测试证书:
个人证书:cfca_client.pfx
服务器证书:cfca_server.crt、cfca_server.key
根证书:CFCA_RCA.cer(一级根证书)、cfca_root.crt(二级根证书)

以下以上述cfca的证书为例,其实也可以自己用openssl生成自己的ca自己的服务器、客户端证书进行测试
服务器证书和个人证书都从同一个ca签发,生成keystore时只需要分别把根证书、服务器证书生成服务器端的keystore文件;根证书、客户端个人证书生成客户端的keystore文件,即可完成证书配置,而不需要将服务器端证书加入客户端的keystore文件,也不需要将客户端证书加入服务器端的keystore文件,他们之间通过相同的根证书来建立互相信任,这样客户端、服务器端的keyStore和trustStore都分别指向一个jks文件即可
但是,如果客户端、服务器端根证书不一致,就需要在客户端新建一个可信任的keystore文件信任服务器端的根证书,在服务器端另建一个可信任的keystore文件信任客户端的根证书,配置时分别陪在trustStore中,而自己的证书作为keyStrore配置

一、服务器端配置
1、如果通过apache做负载均衡,只需要配置在apache上即可
apache ssl的配置参考文章《rhel5+apache+jboss+ssl》
将上述证书文件复制到服务器目录下,修改$APACHE_HOME/conf/extra/httpd-ssl.conf

SSLCertificateFile "/opt/apache/conf/cfca_server.crt"
#SSLCertificateFile "/opt/apache/conf/server.crt"
#SSLCertificateFile "/opt/apache/conf/server-dsa.crt"

# Server Private Key:
# If the key is not combined with the certificate, use this
# directive to point at the key file. Keep in mind that if
# you've both a RSA and a DSA private key you can configure
# both in parallel (to also allow the use of DSA ciphers, etc.)
SSLCertificateKeyFile "/opt/apache/conf/cfca_server.key"
#SSLCertificateKeyFile "/opt/apache/conf/server.key"
#SSLCertificateKeyFile "/opt/apache/conf/server-dsa.key"

# Server Certificate Chain:
# Point SSLCertificateChainFile at a file containing the
# concatenation of PEM encoded CA certificates which form the
# certificate chain for the server certificate. Alternatively
# the referenced file can be the same as SSLCertificateFile
# when the CA certificates are directly appended to the server
# certificate for convinience.
#SSLCertificateChainFile "/opt/apache/conf/CFCA_RCA.cer

# Certificate Authority (CA):
# Set the CA certificate verification path where to find CA
# certificates for client authentication or alternatively one
# huge file containing all of them (file must be PEM encoded)
# Note: Inside SSLCACertificatePath you need hash symlinks
# to point to the certificate files. Use the provided
# Makefile to update the hash symlinks after changes.
SSLCACertificatePath "/opt/apache/conf"
#SSLCACertificateFile "/opt/apache/conf/ca.crt"
SSLCACertificateFile "/opt/apache/conf/cfca_root.crt"

2、如果不通过apache,有后端的应用服务器暴露ssl端口,那就需要在应用服务器上进行配置,以tomcat为例:
2.1 修改$TOMCAT_HOME/conf/server.xml,tomcat支持jsse模式即配置keystore:

maxThreads="150" scheme="https" secure="true"
clientAuth="true" sslProtocol="TLS"
keystoreFile="/home/cherubim/cert/cfca/cfcakeystore_server.jks" keystorePass="xxxxx"
truststoreFile="/home/cherubim/cert/cfca/cfcakeystore_server.jks" truststorePass="xxxxx"/>

tcServerKeystore.jks文件时根据上述cfca证书生成的,具体参考后面内容

2.2 tomcat也支持APR connector,按照openssl的方式直接配置证书(类似apache上的配置):

port="8443" maxThreads="200"
scheme="https" secure="true" SSLEnabled="true"
SSLCertificateFile="/home/cherubim/cert/cfca/cfca_server.crt"
SSLCertificateKeyFile=/home/cherubim/cert/cfca/cfca_server.key"
clientAuth="optional" SSLProtocol="TLSv1"/>

tomcat如何启用APR,参考tomcat文档:[url]http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html[/url]

二、将pfx、crt、key等证书、密钥文件转换为keystore文件
需要用到openssl(linux下需要安装openssl包)
还要用到keytool(在$JAVA_HOME/jre/bin下)
1、客户端:将pfx证书转换为keystore
1.1 cfca的二级根证书文件时ascii格式的需要转换(可以用文本编辑器打开,是两段加密字符串):
用以下命令将ca根证书转换成DER格式
openssl x509 -in cfca_root.crt -out cfca_root.der -outform DER


1.2 用以下命令将DER格式的根证书导入keystore

keytool -import -alias cfca_rca -keystore cfcakeystore_client.jks -import -trustcacerts -file CFCA_RCA.cer
keytool -import -alias cfca_root -keystore cfcakeystore_client.jks -import -trustcacerts -file cfca_root.der

[color=red]
注:一级根证书、二级根证书以及后续的pfx个人证书导入顺序似乎没有要求,但最好还是按照先一级、再二级等顺序来导吧,生成keystore时会要求输入密码,这个密钥要记住,后续编码要用到
[/color]
(如果不行在第一条命令中将格式改为PEM:...cfca_root.pem -outform PEM)

1.3 用以下命令将个人证书导入keystore
keytool -importkeystore -v  -srckeystore cfca_client.pfx -srcstoretype pkcs12 -srcstorepass 123456 -destkeystore cfcakeystore_client.jks -deststoretype jks 

[color=red]
注:pfx个人证书一般带有保护密码,如上述的123456,那么在将pfx导入到keystore中时,kestore的保护密码(第二步中第一次生成keystore时会要求输入密码)必须与这个密码一致,否则在后续客户端代码从pfx中获取个人证书时会报“can not recover key”的异常(这样就限死了keystore的密码必须与pfx的初始密码一致)
如果想自己定义keystore的密码,可以先将pfx文件导入到ie(导入时选择允许导出私钥),再从ie中导出(选择导出私钥),再选择pfx格式,其他不要选,导出时定义一个自己的密码,如“xxxxx”,那么在生成自己的keystore时就可以继续用这个密码
在ie中导出pfx时,有个选项“如果可能,则数据包包含证书路径的所有证书”,如果选了这个pfx中将包含根证书信息,但如果直接将这个pfx导成keystore文件,keystore文件中格式似乎不对,根证书在这个jks文件中不是一个独立的entry,用程序去访问也会报错!最好的方式还是按照上述步骤一步步生成keystore文件
[/color]
keystore文件已生成,可以用工具查看其中的内容:
>keytool -list -v -keystore d:\cfcakeystore_all.jks

可以看到3个entry,两个“trustedCertEntry”和一个“PrivateKeyEntry”

1.4 将上述jks文件作为客户端的keystore和trustedstore试试

2、服务器端:如果要配置tomcat使用jsse的keystore格式,需要将服务器证书、key文件合并生成pfx文件,再按上述步骤生成keystore文件

openssl pkcs12 -export -in cfca_server.crt -inkey cfca_server.key -out cfca_server.p12 -name cfca_server
openssl x509 -in cfca_root.crt -out cfca_root.der -outform DER
keytool -import -alias cfca_rca -keystore cfcakeystore_server.jks -import -trustcacerts -file CFCA_RCA.cer
keytool -import -alias cfca_root -keystore cfcakeystore_server.jks -import -trustcacerts -file cfca_root.der
keytool -importkeystore -v -srckeystore cfca_server.p12 -srcstoretype pkcs12 -srcstorepass xxxxx -destkeystore cfcakeystore_server.jks -deststoretype jks


三、客户端测试代码


package test;

/**
*

Title:


*

Description:


*

Copyright: Copyright (c) 2005


*

Company:


* @author not attributable
* @version 1.0
*/

import java.net.*;
import java.io.InputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.Iterator;
import java.io.*;

import javax.net.ssl.HttpsURLConnection;
public class TestHttp {
public TestHttp() {
}
public static void main(String[] args) {
//TestHttp testHttp1 = new TestHttp();
try{
// System.setProperty("ssl.provider", "com.sun.net.ssl.internal.ssl.Provider ");
// System.setProperty("ssl.pkgs", "com.sun.net.ssl.internal.www.protocol");
System.setProperty("javax.net.ssl.keyStore","d:\\cfcakeystore_client.jks");
System.setProperty("javax.net.ssl.keyStorePassword","xxxxx");
System.setProperty("javax.net.ssl.trustStore","d:\\cfcakeystore_client.jks");
System.setProperty("javax.net.ssl.trustStorePassword","xxxxx");
URL url = new URL("https://xx.xxx.com:8443/docs");
// URL url = new URL("http://localhost:7001/test/newpageflow1/Newpageflow1Controller.jpf");
HttpsURLConnection uc=(HttpsURLConnection)url.openConnection();

System.out.println(url.toExternalForm());
//BufferedReader reader=new BufferedReader(new InputStreamReader(url.openStream()));
System.out.println("length"+uc.getContentLength());
System.out.println("/////////////"+uc.getContentType());
Map map=uc.getHeaderFields();
Iterator it=map.entrySet().iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("========"+uc.getHeaderField("Content-disposition"));
System.out.println(uc.getContent());
File file=new File("c:/xx.dat");
FileOutputStream fo=new FileOutputStream(file);
InputStream input=uc.getInputStream();
// ���������������
BufferedInputStream bis=null;
BufferedOutputStream bos=null;
try {
// ���������������
bis=new BufferedInputStream(uc.getInputStream());
bos=new BufferedOutputStream(fo);
// ���� Buffer
byte[] buff=new byte[1024];
int bytesRead;
int total=0;
PrintWriter pw=new PrintWriter(System.out);
// ��/дѭ����
while(-1!=(bytesRead=bis.read(buff,0,buff.length))) {
bos.write(buff,0,bytesRead);
bos.flush();
total+=bytesRead;

//pw.write(total+" bytes readed");
pw.flush();
}
} catch(IOException e){
// logger.debug("IOException. \n"+e.getMessage());
throw e;
} finally{
if(bis!=null) bis.close();
if(bos!=null) bos.close();
}
//
// do{
// tempStr=reader.readLine();
// System.out.println(tempStr);
// }while(tempStr!=null);
}catch(Exception e){
e.printStackTrace();
}
}

}



package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;

public class TestHttps {

/**
* @param args
*/
public static void main(String[] args) {
DefaultHttpClient httpclient = new DefaultHttpClient();
FileInputStream instream = null;
FileInputStream instream2 = null;
KeyStore trustStore;
KeyStore keyStore;
try {
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
instream = new FileInputStream(new File("d:\\cfcakeystore_client.jks"));
instream2 = new FileInputStream(new File("d:\\cfcakeystore_client.jks"));
trustStore.load(instream, "xxxxx".toCharArray());
keyStore.load(instream2, "xxxxx".toCharArray());
SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore,
"xxxxx", trustStore);
Scheme sch = new Scheme("https", socketFactory, 8443);
httpclient.getConnectionManager().getSchemeRegistry().register(sch);

HttpGet httpget = new HttpGet("https://xx.xxx.com:8443/docs");

System.out.println("executing request" + httpget.getRequestLine());

HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();

System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
if (entity != null) {
System.out.println("Response content length: "
+ entity.getContentLength());
// entity.getContent().
entity.consumeContent();
}
httpclient.getConnectionManager().shutdown();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
instream.close();
instream2.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}
}



四、客户端代码troubleshooting
1、程序中如果keystore密码输错,将会出现异常
2、如果生成keystore时,keystore的保护密码和被导入的pfx的保护密码不一致将出现异常
3、如果服务器端的证书的cn与服务器的dns或者ip不一致,程序将报错:
java.security.cert.CertificateException: No subject alternative names present

可以修改windows\system32\drivers\etc\hosts文件,如:
172.17.249.48 xx.xxx.com

xx.xxx.com与服务器证书的cn一致

客户端代码程序访问时,url也必须写成:
URL url = new URL("https://xx.xxx.com:8443/docs");

而不能用ip地址替代xx.xxx.com,但是在第二段代码中ip和域名都可以,为什么会这样懒得深究了!
4、如果服务器的证书过期,可以修改客户端机器的时间

[color=red][b]老革命碰到新问题,在另一台机器上测试出现以下异常(用第一段代码,使用jdk自带的HttpsURLConnection访问,用apache的httpclient没试过):[/b]
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:136)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1657)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:932)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:746)
at com.sun.net.ssl.internal.ssl.AppInputStream.read(AppInputStream.java:75)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:258)
at java.io.BufferedInputStream.read(BufferedInputStream.java:317)
at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:687)
at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:632)
at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:652)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1000)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:373)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:318)
at com.stf.action.TestCert.main(TestCert.java:34)
[/color]

在网上找了一下,原来是TLS的Renegotiation 的问题:
参考:[url]http://www.oracle.com/technetwork/java/javase/documentation/tlsreadme2-176330.html[/url]
[url]http://httpd.apache.org/docs/trunk/mod/mod_ssl.html#sslinsecurerenegotiation[/url]

有两种解决方案(都测试通过)
1、修改apache的配置加上:SSLInsecureRenegotiation on
但这样似乎不安全:were vulnerable to a Man-in-the-Middle attack (CVE-2009-3555) during a renegotiation

2、将java端的jre升级,参考oralce的文档jdk1.6的话要升级到update22以上

你可能感兴趣的:(安全)