7

我想創建一個連接到服務器的應用程序。該服務器使用SSL客戶端驗證。應用程序的用戶應該能夠選擇證書並允許使用它,就像它在瀏覽器應用程序中實現的一樣。Android 4.x中的SSL客戶端認證

在瀏覽器應用程序中,身份驗證按預期工作,所以我使用的證書是有效的。

當我嘗試在我的應用程序連接我得到以下錯誤:

javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: 
SSL handshake terminated: ssl=0x2a2d3b38: 
Failure in SSL library, usually a protocol error 
error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure 
(external/openssl/ssl/s3_pkt.c:1290 0x2a2df880:0x00000003) 

我試圖按照我的執行Android文檔。

這裏是我的示例活動的代碼:

public class ClientCertificateActivity extends Activity implements 
    KeyChainAliasCallback { 

protected static final String TAG = "CERT_TEST"; 
private String alias; 

@Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    choseCertificate(); 
    LinearLayout layout = new LinearLayout(this); 
    Button connectToServer = new Button(this); 
    connectToServer.setText("Try to connect to Server"); 
    connectToServer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); 
    connectToServer.setOnClickListener(new OnClickListener() { 
     @Override 
     public void onClick(View v) { 
      connectToServer(); 
     } 
    }); 
    layout.addView(connectToServer); 
    addContentView(layout, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); 
} 

protected void connectToServer() { 
    final Context ctx = this; 
    new AsyncTask<Void, Void, Boolean>() { 

     private Exception error; 

     @Override 
     protected Boolean doInBackground(Void... arg) { 
      try { 
       PrivateKey pk = KeyChain.getPrivateKey(ctx, alias); 
       X509Certificate[] chain = KeyChain.getCertificateChain(ctx, 
         alias); 

       KeyStore keyStore = KeyStore.getInstance("AndroidCAStore"); 
       TrustManagerFactory tmf = TrustManagerFactory 
         .getInstance(TrustManagerFactory 
           .getDefaultAlgorithm()); 
       tmf.init(keyStore); 

       SSLContext context = SSLContext.getInstance("TLS"); 
       context.init(null, tmf.getTrustManagers(), null); 

       URL url = new URL("https://usecert.example.com/"); 
       HttpsURLConnection urlConnection = (HttpsURLConnection) url 
         .openConnection(); 
       urlConnection.setSSLSocketFactory(context 
         .getSocketFactory()); 
       InputStream in = urlConnection.getInputStream(); 

       return true; 
      } catch (Exception e) { 
       e.printStackTrace(); 
       error = e; 
       return false; 
      } 
     } 

     @Override 
     protected void onPostExecute(Boolean valid) { 
      if (error != null) { 
       Toast.makeText(ctx, "Error: " + error.getMessage(), 
         Toast.LENGTH_LONG).show(); 
       return; 
      } 
      Toast.makeText(ctx, "Success: ", Toast.LENGTH_SHORT).show(); 
     } 
    }.execute(); 

} 

protected void choseCertificate() { 
    KeyChain.choosePrivateKeyAlias(this, this, 
      new String[] { "RSA", "DSA" }, null, "m.ergon.ch", 443, null); 
} 

@Override 
public void alias(String alias) { 
    this.alias = alias; 
} 
} 

唯一的例外是在urlConnection.getInputStream();

這裏扔在握手的捕獲在服務器和c之間lient。 Network capture of the SSL Handshake

感謝您的任何建議和tipps。

回答

4

你永遠不會用你的私鑰初始化一個KeyManager,所以客戶端認證沒有辦法把它拿起來。

你必須實現X509KeyManager來返回你的PrivateKey和一些硬編碼的別名。 以下是股票電子郵件應用程序(ICS +)的參考資料。您可能需要稍微修改它,但它應該很容易遵循:基本上它只是將字段,別名和證書鏈保存到字段中,並通過適當的方法返回它們(StubKeyManager只是針對未實現和不需要的方法拋出異常):

public static class KeyChainKeyManager extends StubKeyManager { 
    private final String mClientAlias; 
    private final X509Certificate[] mCertificateChain; 
    private final PrivateKey mPrivateKey; 

    public static KeyChainKeyManager fromAlias(Context context, String alias) 
      throws CertificateException { 
     X509Certificate[] certificateChain; 
     try { 
      certificateChain = KeyChain.getCertificateChain(context, alias); 
     } catch (KeyChainException e) { 
      logError(alias, "certificate chain", e); 
      throw new CertificateException(e); 
     } catch (InterruptedException e) { 
      logError(alias, "certificate chain", e); 
      throw new CertificateException(e); 
     } 

     PrivateKey privateKey; 
     try { 
      privateKey = KeyChain.getPrivateKey(context, alias); 
     } catch (KeyChainException e) { 
      logError(alias, "private key", e); 
      throw new CertificateException(e); 
     } catch (InterruptedException e) { 
      logError(alias, "private key", e); 
      throw new CertificateException(e); 
     } 

     if (certificateChain == null || privateKey == null) { 
      throw new CertificateException("Can't access certificate from keystore"); 
     } 

     return new KeyChainKeyManager(alias, certificateChain, privateKey); 
    } 

    private KeyChainKeyManager(
      String clientAlias, X509Certificate[] certificateChain, 
      PrivateKey privateKey) { 
     mClientAlias = clientAlias; 
     mCertificateChain = certificateChain; 
     mPrivateKey = privateKey; 
    } 


    @Override 
    public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { 
     return mClientAlias; 
    } 

    @Override 
    public X509Certificate[] getCertificateChain(String alias) { 
      return mCertificateChain; 
    } 

    @Override 
    public PrivateKey getPrivateKey(String alias) { 
      return mPrivateKey; 
    } 
} 
+0

謝謝你的提示。但是我怎樣才能用我的私鑰創建這樣一個KeyManager?此部分在[Android Doc for HttpsURLConnection]中的示例中缺失(http://developer.android.com/reference/javax/net/ssl/HttpsURLConnection.html) – surffan

+0

似乎沒有直接的方法可以做這個。你必須實現'X509KeyManager'來返回你的'PrivateKey'和一些硬編碼的別名。您不應該需要實現與服務器相關的方法。 http://developer.android.com/reference/javax/net/ssl/X509KeyManager.html –

+0

查看示例代碼的更新答案。 –