سؤال

كيف يمكنني قبول شهادة موقعة ذاتيًا في Java على نظام Android؟

ستكون عينة التعليمات البرمجية مثالية.

لقد بحثت في كل مكان على الإنترنت، وبينما يدعي بعض الأشخاص أنهم عثروا على الحل، فهو إما لا يعمل أو لا يوجد نموذج تعليمة برمجية لدعمه.

هل كانت مفيدة؟

المحلول

ولدي هذه الوظيفة في exchangeIt، الذي يربط صرف مايكروسوفت عبر WebDAV. وإليك بعض التعليمات البرمجية لإنشاء HttpClient وإصلاحه الذي سيربط إلى موقعة ذاتيا في سيرت عبر SSL:

SchemeRegistry schemeRegistry = new SchemeRegistry();
// http scheme
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// https scheme
schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443));

HttpParams params = new BasicHttpParams();
params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);

ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);

ووEasySSLSocketFactory هو <وأ href = "http://code.google.com/p/exchangeit/source/browse/trunk/src/com/byarger/exchangeit/EasySSLSocketFactory.java؟spec=svn23&r=23" يختلط = "نوفولو noreferrer"> هنا ، وEasyX509TrustManager هو <وأ href = "http://code.google.com/p/exchangeit/source/browse/trunk/src/com/byarger/exchangeit/EasyX509TrustManager . JAVA؟ المواصفات = svn23 وص = 23 "يختلط =" نوفولو noreferrer "> هنا .

ورمز لexchangeIt هو المصدر المفتوح، واستضافتها على googlecode هنا ، إذا كان لديك أي قضايا. أنا لا تعمل بنشاط على ذلك بعد الآن، ولكن يجب أن تعمل التعليمات البرمجية.

ملاحظة أنه منذ الروبوت 2.2 عملية قد تغير قليلا، حتى تحقق <لأ href = "https://stackoverflow.com/questions/2899079/custom-ssl-handling-stopped-working-on-android-2- 2 ل Froyo "> هذا لجعل رمز أعلاه العمل.

نصائح أخرى

كما علق EJP بشكل صحيح، "يجب على القراء ملاحظة أن هذه التقنية غير آمنة بشكل جذري.SSL غير آمن ما لم تتم مصادقة نظير واحد على الأقل.انظر RFC 2246."

بعد قولي هذا، إليك طريقة أخرى، دون أي فئات إضافية:

import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;

private void trustEveryone() {
    try {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }});
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, new X509TrustManager[]{new X509TrustManager(){
            public void checkClientTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {}
            public void checkServerTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {}
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }}}, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(
                context.getSocketFactory());
    } catch (Exception e) { // should never happen
        e.printStackTrace();
    }
}

لقد واجهت هذه المشكلة بالأمس، أثناء ترحيل RESTful API لشركتنا إلى HTTPS، ولكن باستخدام شهادات SSL موقعة ذاتيًا.

لقد بحثت في كل مكان، ولكن جميع الإجابات "الصحيحة" التي وجدتها تتألف من تعطيل التحقق من صحة الشهادة، مما يتجاوز بوضوح كل معنى SSL.

وأخيراً توصلت إلى الحل:

  1. إنشاء متجر مفاتيح محلي

    لتمكين تطبيقك من التحقق من صحة الشهادات الموقعة ذاتيًا، تحتاج إلى توفير مخزن مفاتيح مخصص مع الشهادات بطريقة يمكن لنظام Android أن يثق بها في نقطة النهاية الخاصة بك.

تنسيق مخازن المفاتيح المخصصة هذه هو "BKS" من نطاط القلعة, ، لذا فأنت بحاجة إلى الإصدار 1.46 من BouncyCastleProvider الذي يمكنك تنزيله هنا.

أنت أيضًا بحاجة إلى شهادتك الموقعة ذاتيًا، وأفترض أنها تحمل اسمًا self_cert.pem.

الآن الأمر الخاص بإنشاء مخزن المفاتيح الخاص بك هو:

<!-- language: lang-sh -->

    $ keytool -import -v -trustcacerts -alias 0 \
    -file *PATH_TO_SELF_CERT.PEM* \
    -keystore *PATH_TO_KEYSTORE* \
    -storetype BKS \
    -provider org.bouncycastle.jce.provider.BouncyCastleProvider \
    -providerpath *PATH_TO_bcprov-jdk15on-146.jar* \
    -storepass *STOREPASS*

PATH_TO_KEYSTORE يشير إلى ملف حيث سيتم إنشاء ملف تخزين المفاتيح الخاص بك.هو - هي يجب ألا يكون موجودا.

PATH_TO_bcprov-jdk15on-146.jar.JAR هو المسار إلى مكتبة .jar التي تم تنزيلها.

STOREPASS هي كلمة مرور مخزن المفاتيح التي تم إنشاؤها حديثًا.

  1. قم بتضمين KeyStore في التطبيق الخاص بك

انسخ ملف تخزين المفاتيح الذي تم إنشاؤه حديثًا من PATH_TO_KEYSTORE ل res/raw/certs.bks (certs.bks هو مجرد اسم الملف.يمكنك استخدام أي اسم تريد)

إنشاء مفتاح في res/values/strings.xml مع

<!-- language: lang-xml -->

    <resources>
    ...
        <string name="store_pass">*STOREPASS*</string>
    ...
    </resources>
  1. قم بإنشاء هذه الفئة التي ترث DefaultHttpClient

    import android.content.Context;
    import android.util.Log;
    import org.apache.http.conn.scheme.PlainSocketFactory;
    import org.apache.http.conn.scheme.Scheme;
    import org.apache.http.conn.scheme.SchemeRegistry;
    import org.apache.http.conn.ssl.SSLSocketFactory;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.params.HttpParams;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.security.*;
    
    public class MyHttpClient extends DefaultHttpClient {
    
        private static Context appContext = null;
        private static HttpParams params = null;
        private static SchemeRegistry schmReg = null;
        private static Scheme httpsScheme = null;
        private static Scheme httpScheme = null;
        private static String TAG = "MyHttpClient";
    
        public MyHttpClient(Context myContext) {
    
            appContext = myContext;
    
            if (httpScheme == null || httpsScheme == null) {
                httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
                httpsScheme = new Scheme("https", mySSLSocketFactory(), 443);
            }
    
            getConnectionManager().getSchemeRegistry().register(httpScheme);
            getConnectionManager().getSchemeRegistry().register(httpsScheme);
    
        }
    
        private SSLSocketFactory mySSLSocketFactory() {
            SSLSocketFactory ret = null;
            try {
                final KeyStore ks = KeyStore.getInstance("BKS");
    
                final InputStream inputStream = appContext.getResources().openRawResource(R.raw.certs);
    
                ks.load(inputStream, appContext.getString(R.string.store_pass).toCharArray());
                inputStream.close();
    
                ret = new SSLSocketFactory(ks);
            } catch (UnrecoverableKeyException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (KeyStoreException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (KeyManagementException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (NoSuchAlgorithmException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (IOException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (Exception ex) {
                Log.d(TAG, ex.getMessage());
            } finally {
                return ret;
            }
        }
    }
    

الآن ببساطة استخدم مثيلاً لـ **MyHttpClient** كما تفعل مع **DefaultHttpClient** لإجراء استعلامات HTTPS الخاصة بك، وسوف يستخدم ويتحقق من صحة شهادات SSL الموقعة ذاتيًا بشكل صحيح.

HttpResponse httpResponse;

HttpPost httpQuery = new HttpPost("https://yourserver.com");
... set up your query ...

MyHttpClient myClient = new MyHttpClient(myContext);

try{

    httpResponse = myClient.(peticionHttp);

    // Check for 200 OK code
    if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) {
        ... do whatever you want with your response ...
    }

}catch (Exception ex){
    Log.d("httpError", ex.getMessage());
}

ما لم فاتني شيء ما، فإن الإجابات الأخرى في هذه الصفحة خطيرة، وهي تعادل وظيفيًا عدم استخدام SSL على الإطلاق.إذا كنت تثق في الشهادات الموقعة ذاتيًا دون إجراء المزيد من عمليات التحقق للتأكد من أن الشهادات هي الشهادات التي تتوقعها، فيمكن لأي شخص إنشاء شهادة موقعة ذاتيًا ويمكنه التظاهر بأنه الخادم الخاص بك.في تلك المرحلة، ليس لديك أي أمان حقيقي.

ال فقط الطريقة المشروعة للقيام بذلك (دون كتابة مكدس SSL كامل) هي إضافة مرساة موثوقة إضافية يمكن الوثوق بها أثناء عملية التحقق من الشهادة.يتضمن كلاهما تشفير شهادة الارتساء الموثوق بها في تطبيقك وإضافتها إلى أي نقاط ارتساء موثوقة يوفرها نظام التشغيل (وإلا فلن تتمكن من الاتصال بموقعك إذا حصلت على شهادة حقيقية).

أنا على دراية بطريقتين للقيام بذلك:

  1. قم بإنشاء مخزن ثقة مخصص كما هو موضح في http://www.ibm.com/developerworks/Java/library/j-customssl/#8

  2. قم بإنشاء مثيل مخصص لـ X509TrustManager وتجاوز أسلوب getAcceptedIssuers لإرجاع مصفوفة تحتوي على شهادتك:

    public X509Certificate[] getAcceptedIssuers()
    {
        X509Certificate[] trustedAnchors =
            super.getAcceptedIssuers();
    
        /* Create a new array with room for an additional trusted certificate. */
        X509Certificate[] myTrustedAnchors = new X509Certificate[trustedAnchors.length + 1];
        System.arraycopy(trustedAnchors, 0, myTrustedAnchors, 0, trustedAnchors.length);  
    
        /* Load your certificate.
    
           Thanks to http://stackoverflow.com/questions/11857417/x509trustmanager-override-without-allowing-all-certs
           for this bit.
         */
        InputStream inStream = new FileInputStream("fileName-of-cert");
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
        inStream.close();
    
        /* Add your anchor cert as the last item in the array. */
        myTrustedAnchors[trustedAnchors.length] = cert;
    
        return myTrustedAnchors;
    }
    

لاحظ أن هذا الكود لم يتم اختباره بالكامل وربما لم يتم تجميعه، ولكن يجب على الأقل أن يوجهك في الاتجاه الصحيح.

والجواب براين Yarger وتعمل في الروبوت 2.2، وكذلك إذا قمت بتعديل أكبر طريقة createSocket الزائد على النحو التالي. استغرق الأمر مني بعض الوقت للحصول على SSLs موقعة ذاتيا تعمل.

public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
    return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}

في الروبوت، HttpProtocolParams يقبل ProtocolVersion بدلا من HttpVersion.

ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1);
HttpProtocolParams.setVersion(params, pv);

و @كريس - نشر هذا كجواب لأنني لا يمكن إضافة تعليقات (حتى الآن). أنا أتساءل عما اذا كان يفترض نهجكم للعمل عند استخدام عرض ويب. لا أستطيع الحصول عليه القيام بذلك على أندرويد 2.3 - بدلا من ذلك أنا مجرد الحصول على شاشة بيضاء

وبعد بعض مزيد من البحث، جئت عبر هذا <وأ href = "http://r3gis.fr/blog/index.php؟post/2009/11/17/Android-WebView-and-ssl-self-signed -certificates "يختلط =" نوفولو "> تحديد بسيط للتعامل مع أخطاء SSL في عرض ويب حيث عملت مثل السحر بالنسبة لي.

في معالج I تحقق لمعرفة ما إذا أنا في وضع ديف خاص، وندعو handler.proceed ()، وإلا أدعو handler.cancel (). هذا يتيح لي الفرصة لقيام تطوير ضد سيرت موقعة ذاتيا على موقع على شبكة الانترنت المحلي.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top