2013-01-18 54 views
2

我試圖在使用Java的網站上自動執行一些任務。我的網站有一個有效的客戶端(當我使用firefox登錄時),但是當我嘗試使用http客戶端登錄時,我一直收到403錯誤。請注意,我希望我的信任商店能夠信任任何東西(我知道這是不安全的,但此時我並不擔心)。使用有效的客戶端證書時出現HttpClient 403錯誤

這裏是我的代碼:

KeyStore keystore = getKeyStore();//Implemented somewhere else and working ok 
    String password = "changeme"; 

    SSLContext context = SSLContext.getInstance("SSL"); 
    KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 
    kmfactory.init(keystore, password.toCharArray()); 
    context.init(kmfactory.getKeyManagers(), new TrustManager[] { new X509TrustManager() { 
     @Override 
     public void checkClientTrusted(java.security.cert.X509Certificate[] arg0, String arg1) 
       throws CertificateException { 
     } 
     @Override 
     public void checkServerTrusted(java.security.cert.X509Certificate[] arg0, String arg1) 
       throws CertificateException { 
     } 

     @Override 
     public java.security.cert.X509Certificate[] getAcceptedIssuers() { 
      return null; 
     } 
    } }, new SecureRandom()); 
    SSLSocketFactory sf = new SSLSocketFactory(context); 

    Scheme httpsScheme = new Scheme("https", 443, sf); 
    SchemeRegistry schemeRegistry = new SchemeRegistry(); 
    schemeRegistry.register(httpsScheme); 
    ClientConnectionManager cm = new SingleClientConnManager(schemeRegistry); 
    HttpClient client = new DefaultHttpClient(cm); 

    HttpGet get = new HttpGet("https://theurl.com"); 
    HttpResponse response = client.execute(get); 
    System.out.println(EntityUtils.toString(response.getEntity(), "UTF-8")); 

最後一條語句打印出一個403錯誤。我在這裏錯過了什麼?

回答

5

想通了自己的。出現403錯誤是因爲Java SSL未選擇我的客戶端證書。

我調試了SSL握手,發現服務器要求客戶端證書由一系列機構頒發,並且我的客戶端證書的頒發者不在該列表中。所以Java SSL根本無法在我的密鑰庫中找到合適的證書。它看起來像Web瀏覽器和Java實現SSL有點不同,因爲我的瀏覽器實際上詢問我使用哪個證書,無論服務器證書在客戶端證書頒發者方面要求什麼。

在這種情況下,服務器證書是責任。它是自簽名的,它通知可接受的發行人名單不完整。這與Java SSL實現不兼容。但服務器不是我的,除了抱怨巴西政府(他們的服務器),我無能爲力。如果沒有進一步的因,這裏是我的解決方法:

首先,我用的是信任任何一個的TrustManager(就像我在我的問題一樣):

public class MyTrustManager implements X509TrustManager { 
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 
    } 
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 
    } 
    public X509Certificate[] getAcceptedIssuers() { 
     return null; 
    } 
} 

然後我實現它總是使用密鑰我一個密鑰管理器從PKCS12(.PFX)證書要:

public class MyKeyManager extends X509ExtendedKeyManager { 

KeyStore keystore = null; 
String password = null; 
public MyKeyManager(KeyStore keystore, String password) { 
     this.keystore = keystore; 
     this.password = password; 
} 

@Override 
public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) { 
    return "";//can't be null 
} 

@Override 
public String chooseServerAlias(String arg0, Principal[] arg1, Socket arg2) { 
    return null; 
} 

@Override 
public X509Certificate[] getCertificateChain(String arg0) { 
    try { 
     X509Certificate[] result = new X509Certificate[keystore.getCertificateChain(keystore.aliases().nextElement()).length]; 
     for (int i=0; i < result.length; i++){ 
      result[i] = (X509Certificate) keystore.getCertificateChain(keystore.aliases().nextElement())[i]; 
     } 
     return result ; 
    } catch (Exception e) { 
    } 
    return null; 
} 

@Override 
public String[] getClientAliases(String arg0, Principal[] arg1) { 
    try { 
     return new String[] { keystore.aliases().nextElement() }; 
    } catch (Exception e) { 
     return null; 
    } 
} 

@Override 
public PrivateKey getPrivateKey(String arg0) { 
    try { 
     return ((KeyStore.PrivateKeyEntry) keystore.getEntry(keystore.aliases().nextElement(), 
       new KeyStore.PasswordProtection(password.toCharArray()))).getPrivateKey(); 
    } catch (Exception e) { 
    } 
    return null; 
} 

@Override 
public String[] getServerAliases(String arg0, Principal[] arg1) { 
    return null; 
} 

} 

想,如果工作我PFX也包含在其頒發者證書。但它不(耶!)。所以當我像上面那樣使用密鑰管理器的時候,我得到了一個SSL握手錯誤(對端沒有通過身份驗證)。如果客戶端發送服務器信任的證書鏈,則服務器僅對客戶端進行身份驗證。由於我的證書(由巴西代理機構簽發)不包含其頒發者,因此其證書鏈僅包含其自身。服務器不喜歡這種情況,並拒絕認證客戶端。解決方法是手動創建的證書鏈:

... 
@Override 
    //The order matters, your certificate should be the first one in the chain, its issuer the second, its issuer's issuer the third and so on. 
public X509Certificate[] getCertificateChain(String arg0) { 
      X509Certificate[] result = new X509Certificate[2]; 
      //The certificate chain contains only one entry in my case 
      result[0] = (X509Certificate) keystore.getCertificateChain(keystore.aliases().nextElement())[0]; 
      //Implement getMyCertificateIssuer() according to your needs. In my case, I read it from a JKS keystore from my database 
      result[1] = getMyCertificateIssuer(); 
      return result; 
} 
... 

之後,它只是一個把我的自定義鍵和信任管理器很好地利用的事:

  InputStream keystoreContents = null;//Read it from a file, a byte array or whatever floats your boat 
      KeyStore keystore = KeyStore.getInstance("PKCS12"); 
      keystore.load(keystoreContetns, "changeme".toCharArray()); 
      SSLContext context = SSLContext.getInstance("TLSv1"); 
      context.init(new KeyManager[] { new MyKeyManager(keystore, "changeme") }, 
          new TrustManager[] { new MyTrustManager() }, new SecureRandom()); 
      SSLSocketFactory sf = new SSLSocketFactory(context); 
      Scheme httpsScheme = new Scheme("https", 443, sf); 
      SchemeRegistry schemeRegistry = new SchemeRegistry(); 
      schemeRegistry.register(httpsScheme); 
      ClientConnectionManager cm = new SingleClientConnManager(schemeRegistry); 
      HttpClient client = new DefaultHttpClient(cm); 
      HttpPost post = new HttpPost("https://www.someserver.com"); 
+0

我想知道你是如何調試ssl手抖動過程來弄清楚這一點? –

+0

@AlbertCheng我啓用了SSL調試,以查看來回發送的內容。除此之外,還涉及到很多試驗和錯誤。 – Andre

+0

救了我一命大男人。但是,嘿,我期待着問題,這是巴西。乾杯,夥伴 –

1

首先,無論您選擇要解決您的問題,請不要使用信任任何信任管理器。從客戶角度來看,它與即將使用的客戶端證書無關,但它確實打開了與潛在的MITM攻擊的連接(儘管MITM攻擊者仍然有冒充合法客戶端的問題)。

對於客戶端證書方面,您不必使用您自己的密鑰管理器,每次以定製的方式重新構建鏈,您可以修復您的密鑰庫以設置您的客戶端證書條目以使用完整鏈in this answer。由於您首先使用PFX文件(PKCS#12),因此可能會更好地將密鑰庫轉換爲JKS,其格式爲keytool -importkeystore,如in this answer所述。

+0

就信任管理者而言,我用一個「混雜」的原型,並保持我的問題/答案更短。我不會在生產中使用它。 客戶端證書方面有點奇怪我的應用程序(你不知道,可以嗎?)。我希望我的用戶能夠自己管理證書並將他們的pfx文件上傳到服務器。我將這些證書保存在我的數據庫中,並且用戶可以選擇在任務需要連接到政府時使用哪個證書。 感謝您的意見! – Andre

+0

爲什麼不把鏈存儲在PFX文件中呢? (順便說一下,關於信任管理器,將測試中使用的特定證書導入到您的測試機器的信任庫中,這將使問題更加簡短,並進行更真實的測試。) – Bruno

+0

pfx文件由驗證者給出。我不想改變它。 – Andre