Androidのhttps通信セキュリティについて

21121 ワード

テキストリンク:http://pingguohe.net/2016/02/26/Android-App-secure-ssl.html
の原因となる
この間、同僚はコードを持って安全にスキャンしたバグを持って問い合わせに来ました.私はhttps通信時のデジタル証明書検査の抜け穴だったのを見て、考えてみると大体分かりました.実はこの問題は2年前から大規模な暴露があり、各メーカーのAppも次々と中招し、こんなに長い間猫クライアントにこのような穴が残っていたとは思わなかった.それから抜け穴のあるコードの断片をよく研究して、もとは新浪微博がsdkの内部を分かち合うので、このsdkはソースコードが引用したので、ずっと更新していないで、年を取って修理していないので、スキャンされました.そのため、解決策は次のとおりです.
まず最新のsdkを取得して、その内部が解決したかどうかを見て、解決したらsdkバージョンをアップグレードすればいいです.
第1歩は通じなくて、それでは自分で検証論理を書いて、猫客のグローバル通信は基本的にhttps通信を使って、参考にしてもう一度検証論理を書いても問題ではありません;
その後、ネット上の情報を調べてみると、2014年10月には黒い雲のプラットフォームで天猫という抜け穴が暴露されていた.
この問題はsdkのアップグレードによって解決されたが、この問題は開発者自身の不注意によるものである.特に初級開発者にとって、異常を解決するために、検証ロジックを遮断した可能性がある.だから私はやはり暇を見つけてこの抜け穴をreviewして、関連情報を整理しました.
脆弱性の説明
デジタル証明書に関する概念については、Androidでhttps通信コードは復唱せず、直接問題を話します.適切なセキュリティチェックが欠けていると、仲介者が攻撃しやすくなりますが、脆弱性の形式は主に以下の3種類あります.
カスタムX 509 TrustManager
HttpsURLConnectionを使用してHTTPSリクエストを開始すると、カスタムX 509 TrustManagerが提供され、セキュリティ検証ロジックは実現されず、次のセグメントが当時の新浪微博sdk内部のコードセグメントである.カスタムX 509 TrustManagerが提供されていない場合、コードが実行されると異常が報告される可能性があります(原因は後述します).初心者は、カスタムX 509 TrustManagerを真相不明のまま提供しやすくなりますが、適切な方法を正しく実現することを忘れてしまいます.本稿では,このようなシーンの扱い方に重点を置いて紹介する.
TrustManager tm = new X509TrustManager() {
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
              //do nothing,         
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
              //do nothing,         
    }

    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
};

sslContext.init(null, new TrustManager[] { tm }, null);

カスタムHostnameVerifier
握手中にURLのホスト名とサーバの識別ホスト名が一致しない場合、認証メカニズムは、このインタフェースのインプリメンテーションをコールバックして、接続を許可すべきかどうかを決定します.コールバックが適切でない場合、デフォルトではすべてのドメイン名が受け入れられます.セキュリティリスクがあります.コードの例.
HostnameVerifier hnv = new HostnameVerifier() {
  @Override
  public boolean verify(String hostname, SSLSession session) {
    // Always return true,         
    return true;
  }
};
HttpsURLConnection.setDefaultHostnameVerifier(hnv);

すべてのホスト名を信頼
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

修復シナリオ
別々に、異なる脆弱性点についてそれぞれ説明します.ここで説明する修復案は、主に非ブラウザApp、非ブラウザAppのサービス側通信オブジェクトが固定されており、一般的には自宅サーバであり、多くの特定のシーンのカスタマイズ検証を行うことができます.ブラウザAppの場合、検証ポリシーはより一般的です.
X 509 TrustManagerをカスタマイズします.前述したように、HTTPSリクエストが開始されると、次のコードを例に挙げると、例外が投げ出される可能性があります(公式ドキュメントから).
try {
    URL url = new URL("https://certs.cac.washington.edu/CAtest/");
    URLConnection urlConnection = url.openConnection();
    InputStream in = urlConnection.getInputStream();
    copyInputStreamToOutputStream(in, System.out);
} catch (MalformedURLException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
private void copyInputStreamToOutputStream(InputStream in, PrintStream out) throws IOException {
    byte[] buffer = new byte[1024];
    int c = 0;
    while ((c = in.read(buffer)) != -1) {
        out.write(buffer, 0, c);
    }
}

SSLhandshakeExceptionの異常を放出します.
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:322)
    at com.android.okhttp.Connection.upgradeToTls(Connection.java:201)
    at com.android.okhttp.Connection.connect(Connection.java:155)
    at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:276)
    at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:211)
    at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:382)
    at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:332)
    at com.android.okhttp.internal.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:199)
    at com.android.okhttp.internal.http.DelegatingHttpsURLConnection.getInputStream(DelegatingHttpsURLConnection.java:210)
    at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:25)
    at me.longerian.abcandroid.datetimepicker.TestDateTimePickerActivity$1.run(TestDateTimePickerActivity.java:236)
Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:318)
    at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:219)
    at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:114)
    at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:550)
    at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
    at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:318)
 ... 10 more
Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
 ... 16 more

Android携帯電話には、ターゲットURLサーバから発行された証明書が信頼されている証明書のリストに含まれていない場合、またはその証明書が自己署名であり、権威機関から発行されていない場合、異常が発生します.ブラウザ以外のappでは、インストール証明書をダウンロードするようユーザーに促すと、奇妙に見えるかもしれません.幸いなことに、カスタム検証メカニズムで証明書を検証することもできます.検証の考え方は2つあります.
シナリオ1
権威機関が発行した証明書であれ、自己署名であれ、assetに保存するなど、appの内部にパッケージ化されます.この内蔵証明書でKeyStoreを初期化し、生成されたTrustManagerを起動して検証を行います.具体的なコードは次のとおりです.
try {
  CertificateFactory cf = CertificateFactory.getInstance("X.509");
  // uwca.crt     asset  ,      https://itconnect.uw.edu/security/securing-computer/install/safari-os-x/  
  InputStream caInput = new BufferedInputStream(getAssets().open("uwca.crt"));
  Certificate ca;
  try {
      ca = cf.generateCertificate(caInput);
      Log.i("Longer", "ca=" + ((X509Certificate) ca).getSubjectDN());
      Log.i("Longer", "key=" + ((X509Certificate) ca).getPublicKey();
  } finally {
      caInput.close();
  }

  // Create a KeyStore containing our trusted CAs
  String keyStoreType = KeyStore.getDefaultType();
  KeyStore keyStore = KeyStore.getInstance(keyStoreType);
  keyStore.load(null, null);
  keyStore.setCertificateEntry("ca", ca);

  // Create a TrustManager that trusts the CAs in our KeyStore
  String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
  TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
  tmf.init(keyStore);

  // Create an SSLContext that uses our TrustManager
  SSLContext context = SSLContext.getInstance("TLSv1","AndroidOpenSSL");
  context.init(null, tmf.getTrustManagers(), null);

  URL url = new URL("https://certs.cac.washington.edu/CAtest/");
  HttpsURLConnection urlConnection =
          (HttpsURLConnection)url.openConnection();
  urlConnection.setSSLSocketFactory(context.getSocketFactory());
  InputStream in = urlConnection.getInputStream();
  copyInputStreamToOutputStream(in, System.out);
} catch (CertificateException e) {
  e.printStackTrace();
} catch (IOException e) {
  e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
  e.printStackTrace();
} catch (KeyStoreException e) {
  e.printStackTrace();
} catch (KeyManagementException e) {
  e.printStackTrace();
} catch (NoSuchProviderException e) {
  e.printStackTrace();
}

これにより、正しい出力内容が得られます.
<html>
<head>
<title>UW Services CA Test Page</title>
</head>
<body>
<h2>
UW Services CA test page
</h2>
<p>
<b>QUESTION</b>:
Did you arrive here without any security alerts or warnings?</p>
<ul>
<p>
<li>
<b>YES</b> - This test page uses a certificate issued by the
UW Services Certificate Authority. If you reached this page
without any alerts or warnings from your browser, you
have successfully installed the UW Services CA Certificate
into your browser.
<p>
<li>
<b>NO</b> - If your browser warned you about the validity of this
test page's security certificate, or the certificate
authority is unrecognized, you may not have successfully
installed the UW Services CA Certificate.
<p>
</ul>
<form action="https://www.washington.edu/computing/ca/" method=get>
<input type=submit value="Return to the Install Page">
</form>
</body>
</html>

上記と同じコードでアクセスするとhttps://www.taobao.com/またはhttps://www.baidu.com/を選択すると、そのSSLhandshakeException例外が投げ出されます.つまり、特定の証明書で生成されたTrustManagerでは、特定のサーバとのセキュリティリンクのみが検証され、セキュリティが向上します.前述したように、ブラウザ以外のappでは受け入れられます.
シナリオ2
同シナリオ1では、証明書をapp内部にパッケージ化するが、KeyStoreで生成されたTrustManagerを起動するのではなく、TrustManagerを直接カスタマイズし、自分で検証ロジックを実現する.検証ロジックには、次のものが含まれます.
•サーバ証明書の有効期限が切れているかどうか•証明書署名が正当かどうか
try {
  CertificateFactory cf = CertificateFactory.getInstance("X.509");
  // uwca.crt     asset  ,      https://itconnect.uw.edu/security/securing-computer/install/safari-os-x/  
  InputStream caInput = new BufferedInputStream(getAssets().open("uwca.crt"));
  final Certificate ca;
  try {
      ca = cf.generateCertificate(caInput);
      Log.i("Longer", "ca=" + ((X509Certificate) ca).getSubjectDN());
      Log.i("Longer", "key=" + ((X509Certificate) ca).getPublicKey());
  } finally {
      caInput.close();
  }
  // Create an SSLContext that uses our TrustManager
  SSLContext context = SSLContext.getInstance("TLSv1","AndroidOpenSSL");
  context.init(null, new TrustManager[]{
          new X509TrustManager() {
              @Override
              public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

              }

              @Override
              public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                  for (X509Certificate cert : chain) {

                      // Make sure that it hasn't expired.
                      cert.checkValidity();

                      // Verify the certificate's public key chain.
                      try {
                          cert.verify(((X509Certificate) ca).getPublicKey());
                      } catch (NoSuchAlgorithmException e) {
                          e.printStackTrace();
                      } catch (InvalidKeyException e) {
                          e.printStackTrace();
                      } catch (NoSuchProviderException e) {
                          e.printStackTrace();
                      } catch (SignatureException e) {
                          e.printStackTrace();
                      }
                  }
              }

              @Override
              public X509Certificate[] getAcceptedIssuers() {
                  return new X509Certificate[0];
              }
          }
  }, null);

  URL url = new URL("https://certs.cac.washington.edu/CAtest/");
  HttpsURLConnection urlConnection =
          (HttpsURLConnection)url.openConnection();
  urlConnection.setSSLSocketFactory(context.getSocketFactory());
  InputStream in = urlConnection.getInputStream();
  copyInputStreamToOutputStream(in, System.out);
} catch (CertificateException e) {
  e.printStackTrace();
} catch (IOException e) {
  e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
  e.printStackTrace();
} catch (KeyManagementException e) {
  e.printStackTrace();
} catch (NoSuchProviderException e) {
  e.printStackTrace();
}

上記のコードはcerts.cac.washington.edu関連ドメイン名アドレスにのみアクセスできます.https://www.taobao.com/またはhttps://www.baidu.com/、cert.verify((X 509 Certificate)ca).getPublicKey();ドロップが異常で、接続に失敗しました.
•カスタムHostnameVerifierは、簡単にはドメイン名に基づいて文字列マッチングチェックを行います.業務が複雑であれば、配置センター、ホワイトリスト、ブラックリスト、正則マッチングなどのマルチレベルのダイナミックチェックを組み合わせることもできます.全体的に論理は比較的簡単で、どうせその方法を正しく実現すればいい.
HostnameVerifier hnv = new HostnameVerifier() {
  @Override
  public boolean verify(String hostname, SSLSession session) {
    //  
    if("yourhostname".equals(hostname)){  
      return true;  
    } else {  
      HostnameVerifier hv =
            HttpsURLConnection.getDefaultHostnameVerifier();
      return hv.verify(hostname, session);
    }
  }
};

•ホスト名検証ポリシーを厳格なモードに変更
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);

文/ユンスター
テキストリンク:http://www.jianshu.com/p/7c1bc2daef8d
著作権は作者の所有で、転載は作者に連絡して授権を得て、そして“簡書の作者”を表示してください.