4

Java を使用して Web サイトでいくつかのタスクを自動化しようとしています。その Web サイトに有効なクライアントがあります (firefox を使用してログインすると機能します) が、http クライアントを使用してログインしようとすると 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 エラーを出力します。ここで何が欠けていますか?

4

2 に答える 2

7

それは自分自身のものであると考えました。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");
于 2013-01-19T16:26:56.007 に答える
1

まず、問題を解決することを選択したとしても、何も信頼しないトラスト マネージャーを使用しないでください。クライアントの観点からは、使用されるクライアント証明書とは何の関係もありませんが、潜在的な MITM 攻撃への接続が開かれます (ただし、MITM 攻撃者は正当なクライアントになりすますことにまだ問題があります)。

クライアント証明書の側面については、この回答で説明されているように、独自のキー マネージャーを使用してチェーンを毎回再構築する代わりに、キーストアを修正してクライアント証明書エントリを設定し、完全なチェーンを使用することができます。PFX ファイル (PKCS#12) から始めているため、この回答でkeytool -importkeystore説明されているように、最初に を使用してキーストアを JKS に変換することをお勧めします。

于 2013-01-19T18:11:29.383 に答える