No subject alternative names那些事

最近做了一個項目,需要java調用WCF服務,其中爲了安全性,使用了https的basic身份認證來做這件事情

而關鍵的WCF配置如下所示:

      <basicHttpBinding>
        <binding name="test1">
          <!--當前綁定的安全認證模式-->
          <security mode="Transport" >
            <!--定義消息級安全性要求的類型,爲證書-->
            <transport clientCredentialType="Basic"/>
            <!--<message clientCredentialType="UserName" />-->
          </security>
        </binding>

         <serviceBehaviors>
            <behavior name="httpsBasicBindings">
          <!--makecert -sr LocalMachine -ss My -n CN=ejiyuan -sky exchange -pe -r-->
          <serviceCredentials >
            <!--指定一個 X.509 證書,用戶對認證中的用戶名密碼加密解密-->
            <serviceCertificate  x509FindType="FindByThumbprint" findValue="6404232d69dbb8eb85ca4dca6fd44cde345d5731" storeLocation="LocalMachine" storeName="My"/>
            <clientCertificate>
              <!--自定義對客戶端進行證書認證方式 這裏爲 None-->
              <authentication certificateValidationMode="None"/>
            </clientCertificate>
            <!--自定義用戶名和密碼驗證的設置-->
            <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="<繼承System.IdentityModel.Selectors.UserNamePasswordValidator的類名>" />
          </serviceCredentials>

其中FindByThumbprint表示按照證書的指紋(哈希值)查找證書storeLocation在本地計算機LocalMachine而不是本用戶(LocalUser),

這裏的證書只用於對用戶名密碼的加密而不會用於https連接,

在Win7等現代一點的Windows版本中,使用

netsh http sslcert ipport=0.0.0.0:9988 certhash=<證書指紋> appid=<應用的GUID>
來在本地計算機的個人證書目錄中使用指定的證書作爲https傳輸用服務器端證書,而查看本地計算機已經安裝的證書可以通過mmc命令行打開的窗體的菜單->添加刪除管理單元來查看和管理本計算機和本用戶的證書

證書可以用sdk工具makecert生成,其中參數-n "CN=xxx.xxx.xxx“爲此https服務的域名

然後使用apachede CXF生成java soap代碼,爲了避免證書信任問題,可以通過瀏覽器導出此https的證書,然後通過keytool工具導入jre/jdk的信任庫:

%JAVA_HOME%\bin>keytool -import -file "瀏覽器導出的.cer文件全路徑" -keystore "C:\Program %JAVA_HOME%/JRE/LIB/SECURITY/CACERTS"

其中默認的信任庫密碼爲changeit

之後,只要CXF生成的soap代碼是在此jdk、jre上運行,就不會報證書信任錯誤

或者,在程序運行之前,插入如下代碼:

//先定義個trust定製類
static class miTM implements javax.net.ssl.TrustManager,
	 javax.net.ssl.X509TrustManager {
	 public java.security.cert.X509Certificate[] getAcceptedIssuers() {
	 return null;
	 }

	 public boolean isServerTrusted(
	 java.security.cert.X509Certificate[] certs) {
	 return true;
	 }

	 public boolean isClientTrusted(
	 java.security.cert.X509Certificate[] certs) {
	 return true;
	 }

	 public void checkServerTrusted(
	 java.security.cert.X509Certificate[] certs, String authType,SSLEngine e)
	 throws java.security.cert.CertificateException {
	 return;
	 }

	 public void checkClientTrusted(
	 java.security.cert.X509Certificate[] certs, String authType)
	 throws java.security.cert.CertificateException {
	 return;
	 }

	public void checkServerTrusted(X509Certificate[] arg0, String arg1)
			throws CertificateException {
		return;
		
	}
}

//在soap代碼運行之前執行如下代碼
javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
			 javax.net.ssl.TrustManager tm = new miTM();
			 trustAllCerts[0] = tm;
			 javax.net.ssl.SSLContext sc;
			try {
				sc = javax.net.ssl.SSLContext
				 .getInstance("SSL");
				sc.init(null, trustAllCerts, null);
				 javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc
						 .getSocketFactory());
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

這樣,證書信任問題就解決了。


本來到這裏就完了,接下來,閒着無聊,把soap的地址改成ip地址,運行代碼,於是出現了那個著名的No subject alternative names錯誤

Got java.security.cert.CertificateException: No subject alternative names matching IP address xx.x.xxx.xxx found while opening stream from https://xx.x.xxx.xxx :9988/?wsdl.
at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.tryWithMex(Unknown Source)
at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(Unknown Source)
at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(Unknown Source)
at com.sun.xml.internal.ws.client.WSServiceDelegate.parseWSDL(Unknown Source)
at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(Unknown Source)
at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(Unknown Source)
at com.sun.xml.internal.ws.spi.ProviderImpl.createServiceDelegate(Unknown Source)
at javax.xml.ws.Service.<init>(Unknown Source)
at Test.SqlServerService.<init>(SqlServerService.java:47)
at Test.Test.main(Test.java:148)
Caused by: java.io.IOException: Got java.security.cert.CertificateException: No subject alternative names matching IP address xx.x.xxx.xxx  found while opening stream from https://10.0.194.206:9988/?wsdl
at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.createReader(Unknown Source)
at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.resolveWSDL(Unknown Source)
... 9 more
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names matching IP address xx.x.xxx.xxx  found
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
at sun.security.ssl.Handshaker.fatalSE(Unknown Source)

...

...

於是上網搜索,發現可以通過繞過java的hostvalidator來解決這個問題,同樣在程序運行之前,執行如下代碼:

		   javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier()  
	        {        



				public boolean verify(String hostname,
						SSLSession sslsession) {
					return true;
				}  
	        });

上面的方法簡單直接,粗暴,也能解決問題,但是人家說了,這只能在開發的時候使用,正式環境上不推薦,


於是就研究在正式環境上,如何在https服務上使用ip地址而不是使用域名訪問服務的方法

首先這個問題是由於在證書中沒有【IP Address】這個擴展屬性引起的,而微軟的makecert多地址解決方案與java的不同,微軟是通過多CN用逗號隔開的方式來支持多地址的,而sun卻是通過使用x509證書v3中的擴展屬性來指明證書的多地址的,找了好半天,也沒有發現怎樣通過makecert來生成x509證書的擴展屬性,於是只能求助於openssl.


首先安裝windows的openssl,我安裝的是win32的,另外還需要用到C++ 2008再發布包在安裝openssl的時候會出來這個提示,接着需要修改openssl的openssl.cfg文件

如有人寫的這樣,不過這位兄弟增加的是DNS,而這裏需要增加DNS(域名)和IP(服務IP):

http://colinzhouyj.blog.51cto.com/2265679/1566438

文章中修改方法

[ alt_names ]
DNS.1 = abc.example.com
DNS.2 = dfe.example.org
DNS.3 = ex.abcexpale.net

需要改成

[ alt_names ]
IP.1 = xx.xxx.xxx.xxx
DNS.1 = xxx.xxx.com

其中DNS.1爲機器名(如果沒有域,也不會包含.com,只有單獨機器名)


 

然後照着文章那樣生成ca.crt,server.key,server.crt,同時,可以通過window證書管理器把ca.crt導入到計算機的[受信任的根證書頒發機構]

此時點擊server.crt查看,可以看到


此時,證書的擴展屬性有了[Ip Address]這項,

接下來,如果把這證書導入本地計算機,然後用netsh綁定到某個端口,就一定會出現如下錯誤:

未能添加 SSL 證書,錯誤: 1312
指定的登錄會話不存在。可能已被終止。

 

如果發生這種錯誤,還可能是生成證書的電腦與要安裝此證書的電腦不是一臺電腦


同時如果在證書管理器中查看此證書,會發現少了紅框中這一塊:



只有把這個server.key和server.crt結合生成.p12個人證書:

openssl  pkcs12 -export -inkeyserver.key -in server.crt -out  server.p12

然後把這個server.p12證書通過mmc導入到系統中。

而之前通過 certutil -store My 查看證書,可以發現導入的證書有如下的消息:



而導入了p12個人證書之後:




此時使用IE通過IP瀏覽此https站點:


而用chrome:


可以看出IE是不認識證書中Ip Address這個擴展名的,而chrome卻認識

但不管怎麼說,現在在java soap程序中,把那個hostvalidator自定義驗證去掉,

則程序直接能運行成功了,而之前,卻是報No subject alternative names錯誤的



另外,如果在生成證書的時候報”

the xxxName field needed to be the same

則可以參考http://xiuxian1.iteye.com/blog/719668說的把cfg文件中的policy_match相關字段改成optional

如果報”TXT_DB error number 2“,則可以找到demoCA文件夾下的index.txt,把他刪掉,然後把index.txt.old拷貝一份其中一個重命名爲index.txt

則證書數據庫恢復成初始安裝狀態


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章