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) 

내가 내 구현을위한 안드로이드 문서를 따라하려고 :

나는 다음과 같은 오류가 내 응용 프로그램에 연결을 시도

. 핸드 쉐이크의 캡처

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();

던져됩니다 : 여기

답변

4

KeyManager를 개인 키로 초기화하지 않으므로 클라이언트 인증에서이를 가져올 수 없습니다.

X509KeyManager를 구현하여 PrivateKey 및 일부 하드 코딩 된 별칭을 반환해야합니다. 참고로 전자 메일 응용 프로그램 (ICS +)에서 가져온 것입니다. 약간 수정해야 할 수도 있지만 따르기 쉽습니다. 기본적으로 키, 별칭 및 인증서 체인을 필드에 저장하고 적절한 메서드를 통해 반환합니다 (StubKeyManager은 구현되지 않은 메서드와 불필요한 메서드의 예외를 throw합니다) :

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

직접적인 방법은없는 것처럼 보입니다. 이. 'PrivateKey'와 일부 하드 코드 된 별칭을 반환하려면'X509KeyManager'를 구현해야합니다. 서버 관련 메소드를 구현할 필요는 없습니다. http://developer.android.com/reference/javax/net/ssl/X509KeyManager.html –

+0

샘플 코드에 대한 업데이트 된 답변보기 –