获得免费证书的网站:https://wosign.com/
示例:https://freessl.wosign.com/
介绍:http://www.wosign.com/DVSSL/DV_KuaiSSL_Free.htm
申请地址:https://buy.wosign.com/free/
Nginx SSL 证书部署指南:http://www.wosign.com/Docdownload/index.htm
http://www.wosign.com/Docdownload/Nginx%20SSL%E8%AF%81%E4%B9%A6%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97.pdf
另一个可以免费申请证书的站点(可以试下):https://www.startssl.com/
https://globalsign.ssllabs.com/analyze.html 这个网址可以去分析网址的SSL配置健康状况
我用本机绑定hosts:127.0.0.1 caiya.me,使用wosign获取免费证书进行测试。
nginx配置(80端口跳转到443端口):
server { listen 80; server_name caiya.me; rewrite ^(.*) https://$server_name$1 permanent; } server { listen 443; server_name caiya.me; ssl on; ssl_certificate sslkey/1_caiya.me_bundle.crt;#(证书公钥) ssl_certificate_key sslkey/2_caiya.me.key;#(证书私钥) ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ALL:!DH:!EXPORT:!RC4:+HIGH:+MEDIUM:-LOW:!aNULL:!eNULL; ssl_prefer_server_ciphers on; location / { root html; index index.html index.htm; } }
在我使用免费证书之前先自签了一个无效证书,使用wget获取数据流的时候异常如下:
$ wget https://caiya.me --2016-02-16 16:29:33-- https://caiya.me Resolving caiya.me... 127.0.0.1 Connecting to caiya.me|127.0.0.1|:443... connected. ERROR: cannot verify caiya.me's certificate, issued by '[email protected],CN=caiya.me,OU=AAA Tech,O=BBB Tech,L=Hangzhou,ST=Zhejiang,C=CN': Self-signed certificate encountered. To connect to caiya.me insecurely, use `--no-check-certificate'.
那根据提示加个参数--no-check-certificate
wget --no-check-certificate https://caiya.me
就跳过验证了。。
这种异常情况还适用于证书的不完整性,中间证书丢失!!从浏览器地址栏看不出异样,还是绿色,但是wget或其他形式的请求就有问题了。
使用免费证书签名后,用java模拟http(s)请求时又发生如下异常(jdk版本1.7):
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1904) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:279) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:273) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1446) at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:209) at sun.security.ssl.Handshaker.processLoop(Handshaker.java:901) at sun.security.ssl.Handshaker.process_record(Handshaker.java:837) at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1023) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1332) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1359) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1343) at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563) at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185) at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:153) at com.maijia.order.web.utils.HttpRequestUtils.getResponseContent(HttpRequestUtils.java:84) at com.maijia.trade.AppTest.testHttps(AppTest.java:60) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at junit.framework.TestCase.runTest(TestCase.java:168) at junit.framework.TestCase.runBare(TestCase.java:134) at junit.framework.TestResult$1.protect(TestResult.java:110) at junit.framework.TestResult.runProtected(TestResult.java:128) at junit.framework.TestResult.run(TestResult.java:113) at junit.framework.TestCase.run(TestCase.java:124) at junit.framework.TestSuite.runTest(TestSuite.java:243) at junit.framework.TestSuite.run(TestSuite.java:238) at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:83) at org.junit.runner.JUnitCore.run(JUnitCore.java:157) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68) Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385) at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292) at sun.security.validator.Validator.validate(Validator.java:260) at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326) at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231) at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1428) ... 29 more Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:196) at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:268) at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380) ... 35 more
而后发现jdk自带的证书中没有要请求的域名的认证信息时就报错,而同样的方式请求taobao、baidu则不会出现问题。
不同的jdk版本自带证书不完全相同,而且自带的一般都是高级证书。
于是有以下几种方式解决此问题:
1、绕过证书验证(需要指明,不同的请求方式、版本等可能需要不同的实现方式)
/** * 发送HTTP_POST_SSL请求 * @see 1)该方法会自动关闭连接,释放资源 * @see 2)该方法亦可处理普通的HTTP_POST请求 * @see 3)当处理HTTP_POST_SSL请求时,默认请求的是对方443端口,除非reqURL参数中指明了SSL端口 * @see 4)方法内设置了连接和读取超时时间,单位为毫秒,超时或发生其它异常时方法会自动返回"通信失败"字符串 * @see 5)请求参数含中文等特殊字符时,可直接传入本方法,并指明其编码字符集encodeCharset参数,方法内部会自动对其转码 * @see 6)方法内部会自动注册443作为SSL端口,若实际使用中reqURL指定的SSL端口非443,可自行尝试更改方法内部注册的SSL端口 * @see 7)该方法在解码响应报文时所采用的编码,取自响应消息头中的[Content-Type:text/html; charset=GBK]的charset值 // * @see 若响应消息头中未指定Content-Type属性,则会使用HttpClient内部默认的ISO-8859-1 * @param reqURL 请求地址 * @param params 请求参数 * @param encodeCharset 编码字符集,编码请求数据时用之,当其为null时,则取HttpClient内部默认的ISO-8859-1编码请求参数 * @return 远程主机响应正文 */ public static String sendPostSSLRequest(String reqURL, Map<String, String> params, String encodeCharset){ String responseContent = "通信失败"; HttpClient httpClient = new DefaultHttpClient(); httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000); httpClient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 20000); //创建TrustManager() //用于解决javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated X509TrustManager trustManager = new X509TrustManager(){ @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {} @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {} @Override public X509Certificate[] getAcceptedIssuers() {return null;} }; //创建HostnameVerifier //用于解决javax.net.ssl.SSLException: hostname in certificate didn't match: <123.125.97.66> != <123.125.97.241> X509HostnameVerifier hostnameVerifier = new X509HostnameVerifier(){ @Override public void verify(String host, SSLSocket ssl) throws IOException {} @Override public void verify(String host, X509Certificate cert) throws SSLException {} @Override public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {} @Override public boolean verify(String arg0, SSLSession arg1) {return true;} }; try { //TLS1.0与SSL3.0基本上没有太大的差别,可粗略理解为TLS是SSL的继承者,但它们使用的是相同的SSLContext SSLContext sslContext = SSLContext.getInstance(SSLSocketFactory.TLS); //使用TrustManager来初始化该上下文,TrustManager只是被SSL的Socket所使用 sslContext.init(null, new TrustManager[]{trustManager}, null); //创建SSLSocketFactory SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, hostnameVerifier); //通过SchemeRegistry将SSLSocketFactory注册到HttpClient上 httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory)); //创建HttpPost // HttpPost httpPost = new HttpPost(reqURL); HttpGet httpGet = new HttpGet(reqURL); //由于下面使用的是new UrlEncodedFormEntity(....),所以这里不需要手工指定CONTENT_TYPE为application/x-www-form-urlencoded //因为在查看了HttpClient的源码后发现,UrlEncodedFormEntity所采用的默认CONTENT_TYPE就是application/x-www-form-urlencoded //httpPost.setHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded; charset=" + encodeCharset); //构建POST请求的表单参数 if(null != params){ List<NameValuePair> formParams = new ArrayList<NameValuePair>(); for(Map.Entry<String,String> entry : params.entrySet()){ formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } // httpPost.setEntity(new UrlEncodedFormEntity(formParams, encodeCharset)); } HttpResponse response = httpClient.execute(httpGet); HttpEntity entity = response.getEntity(); if (null != entity) { responseContent = EntityUtils.toString(entity, ContentType.getOrDefault(entity).getCharset()); EntityUtils.consume(entity); } } catch (ConnectTimeoutException cte){ logger.error("请求通信[" + reqURL + "]时连接超时,堆栈轨迹如下", cte); } catch (SocketTimeoutException ste){ logger.error("请求通信[" + reqURL + "]时读取超时,堆栈轨迹如下", ste); } catch (Exception e) { logger.error("请求通信[" + reqURL + "]时偶遇异常,堆栈轨迹如下", e); } finally { httpClient.getConnectionManager().shutdown(); } return responseContent; }
2、添加要请求的域名的证书到jdk
首先,从浏览器导出证书(一般火狐比较方便);
然后使用jdk自带的工具(/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/bin/keytool)导入证书(密钥库口令默认为changeit):
keytool -import -alias caiya.me -file /Users/caiya/Downloads/caiya.me.crt -keystore $JAVA_HOME/jre/lib/security/cacerts -trustcacerts -storepass changeit
可在导入证书前后执行命令:
keytool -list -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit
显示如下:
密钥库类型: JKS 密钥库提供方: SUN 您的密钥库包含 92 个条目 digicertassuredidrootca, 2008-4-16, trustedCertEntry, 证书指纹 (SHA1): 05:63:B8:63:0D:62:D7:5A:BB:C8:AB:1E:4B:DF:B5:A8:99:B2:4D:43 trustcenterclass2caii, 2008-4-29, trustedCertEntry, ... ...
以对比证书密钥库的变化;
另外,删除证书命令如下:
keytool -delete -alias caiya.me -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit
证书导入完成之后,再次请求caiya.me,有正常结果返回!
这时debug,于是找到了我添加的证书:
To: Thu Feb 15 17:11:57 CST 2018]\n Issuer: CN=CA 沃通免费SSL证书 G2, O=WoSign CA Limited, C=CN\n SerialNumber: [ 2f5f91a8 cd0e8e3f b0c806 45 = {HashMap$Entry@1600} "[\n[\n Version: V3\n Subject: OU=Equifax Secure Certificate Authority, O=Equifax, C=US\n Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5\n\n Key: Sun RSA public key, 1024 bits\n modulus: 135786214035069526348186531221551781468391756233528066061569654028671100866720352830303278016129003918213826297308054231261658522889438712013757624116391437358730449661353175673177742307421061340003741057138887918110217006515773038453829253517076741780039735595086881329494037450587568122088113584549069375417\n public exponent: 65537\n Validity: [From: Sun Aug 23 00:41:51 CST 1998,\n To: Thu Aug 23 00:41:51 CST 2018]\n Issuer: OU=Equifax Secure Certificate Authority, O=Equifax, C=US\n SerialNumber: [ 35def4cf]\n\nCertificate Extensions: 7\n[1]: ObjectId: 1.2.840.113533.7.65.0 Criticality=false\nExtension unknown: DER encoded OCTET string =\n0000: 04 0D 30 0B 1B 05 56 33 2E 30 63 03 02 06 C0 ..0...V3.0c....\n\n\n[2]: ObjectId: 2.5.29.35 Criticality=false\nAuthorityKeyIdentifier