ยอมรับการเชื่อมต่อ HTTPS ด้วยใบรับรองที่ลงนามเอง


153

ฉันกำลังพยายามเชื่อมต่อ HTTPS โดยใช้HttpClientlib แต่ปัญหาคือว่าเนื่องจากใบรับรองไม่ได้ลงนามโดย Certificate Authority (CA) ที่รู้จักเช่นVerisign , GlobalSIgnเป็นต้นแสดงอยู่ในชุดใบรับรองที่เชื่อถือได้ของ Android javax.net.ssl.SSLException: Not trusted server certificateฉันให้ได้รับ

ฉันเห็นโซลูชันที่คุณเพียงแค่ยอมรับใบรับรองทั้งหมด แต่ถ้าฉันต้องการถามผู้ใช้

ฉันต้องการไดอะล็อกที่คล้ายกับเบราว์เซอร์เพื่อให้ผู้ใช้ตัดสินใจดำเนินการต่อหรือไม่ โดยเฉพาะอย่างยิ่งฉันต้องการใช้ใบรับรองเดียวกันกับเบราว์เซอร์ ความคิดใด ๆ


โซลูชันที่ได้รับการยอมรับนี้ใช้ได้กับฉัน - stackoverflow.com/questions/2642777/…
Venkatesh

คำตอบ:


171

สิ่งแรกที่คุณต้องทำคือการกำหนดระดับการตรวจสอบ ระดับดังกล่าวไม่มาก:

  • ALLOW_ALL_HOSTNAME_VERIFIER
  • BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
  • STRICT_HOSTNAME_VERIFIER

แม้ว่าเมธอด setHostnameVerifier () จะล้าสมัยสำหรับ apache ไลบรารีใหม่ แต่สำหรับเวอร์ชันใน Android SDK เป็นเรื่องปกติ และเพื่อให้เราใช้เวลาและตั้งโรงงานในวิธีการALLOW_ALL_HOSTNAME_VERIFIERSSLSocketFactory.setHostnameVerifier()

ต่อไปคุณต้องตั้งค่าโรงงานของเราเพื่อให้โปรโตคอลเป็น https ในการทำเช่นนี้ให้เรียกSchemeRegistry.register()วิธีการนั้น

แล้วคุณจะต้องสร้างด้วยDefaultHttpClient SingleClientConnManagerนอกจากนี้ในรหัสด้านล่างคุณจะเห็นว่าตามค่าเริ่มต้นจะใช้การตั้งค่าสถานะของเรา ( ALLOW_ALL_HOSTNAME_VERIFIER) ตามวิธีการHttpsURLConnection.setDefaultHostnameVerifier()

รหัสด้านล่างใช้งานได้สำหรับฉัน:

HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;

DefaultHttpClient client = new DefaultHttpClient();

SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("https", socketFactory, 443));
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams());

// Set verifier     
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);

// Example send http request
final String url = "https://encrypted.google.com/";
HttpPost httpPost = new HttpPost(url);
HttpResponse response = httpClient.execute(httpPost);

6
ฉันไม่สามารถทำให้รหัสนี้ทำงานได้ แต่ฉันยังได้รับ "ใบรับรองเซิร์ฟเวอร์ที่เชื่อถือไม่ได้" มีสิทธิ์พิเศษใด ๆ ที่ฉันต้องตั้งค่าเพื่อให้ทำงานได้หรือไม่
Juriy

1
รหัสนี้ไม่เพียงแค่ยอมรับใบรับรองทั้งหมดหรือไม่ ฉันต้องการป๊อปอัพที่จะยอมรับมัน
มอร์เทน

3
ฉันใช้org.apache.http.conn.ssl.SSLSocketFactoryทำไมฉันถึงต้องการใช้javax.net.ssl.HttpsURLConnection?
คนอยู่ที่ไหนสักแห่ง

9
คุณช่วยอธิบายได้อย่างไรว่ารหัสนี้ดีกว่าการปิดใช้งานการตรวจสอบใบรับรองทั้งหมดหรือไม่ ฉันไม่คุ้นเคยกับ ssl API ของ Android แต่อย่างรวดเร็วดูเหมือนว่าจะไม่ปลอดภัยอย่างสมบูรณ์ต่อผู้โจมตีที่ใช้งานอยู่
CodesInChaos

3
ฉันอยากจะแนะนำให้ใช้ ThreadSafeClientConnManager แทน SingleClientConnManager
ฟาร์ม

124

ขั้นตอนหลักต่อไปนี้จะต้องมีการเชื่อมต่อที่ปลอดภัยจากผู้ออกใบรับรองซึ่งไม่ได้รับการพิจารณาว่าเชื่อถือได้โดยแพลตฟอร์ม Android

ตามที่ผู้ใช้หลายคนร้องขอฉันได้ทำการสะท้อนส่วนที่สำคัญที่สุดจากบทความบล็อกของฉันที่นี่:

  1. รับใบรับรองที่จำเป็นทั้งหมด (รูทและ CA ระดับกลางใด ๆ )
  2. สร้างที่เก็บคีย์ด้วย keytool และผู้ให้บริการBouncyCastleและอิมพอร์ต certs
  3. โหลดที่เก็บคีย์ในแอป android ของคุณและใช้สำหรับการเชื่อมต่อที่ปลอดภัย (ฉันแนะนำให้ใช้Apache HttpClientแทนมาตรฐานjava.net.ssl.HttpsURLConnection(เข้าใจง่ายกว่ามีประสิทธิภาพมากกว่า)

คว้า certs

คุณต้องได้รับใบรับรองทั้งหมดที่สร้างสายโซ่จากใบรับรองจุดสิ้นสุดจนถึงรูต CA ซึ่งหมายความว่าใบรับรอง CA ระดับกลาง (ถ้ามี) ใด ๆ และใบรับรอง Root CA คุณไม่จำเป็นต้องได้รับใบรับรองปลายทาง

สร้างที่เก็บคีย์

ดาวน์โหลดผู้ให้บริการ BouncyCastleและเก็บไว้ในตำแหน่งที่ทราบ ตรวจสอบให้แน่ใจว่าคุณสามารถเรียกใช้คำสั่ง keytool (โดยปกติจะอยู่ใต้โฟลเดอร์ bin ของการติดตั้ง JRE ของคุณ)

ตอนนี้นำเข้าใบรับรองที่ได้รับ (อย่านำเข้าใบรับรองปลายทาง) ไปยังที่เก็บคีย์ที่จัดรูปแบบ BouncyCastle

ฉันไม่ได้ทดสอบ แต่ฉันคิดว่าลำดับการนำเข้าใบรับรองเป็นสิ่งสำคัญ ซึ่งหมายความว่านำเข้าใบรับรอง CA ระดับต่ำสุดมาก่อนจากนั้นขึ้นไปจนถึงใบรับรอง Root CA

ด้วยคำสั่งต่อไปนี้ที่เก็บคีย์ใหม่ (ถ้าไม่มีอยู่) พร้อมรหัสผ่านmysecretจะถูกสร้างขึ้นและใบรับรอง Intermediate CA จะถูกนำเข้า ฉันยังกำหนดผู้ให้บริการ BouncyCastle ซึ่งสามารถพบได้ในระบบไฟล์ของฉันและรูปแบบที่เก็บคีย์ ดำเนินการคำสั่งนี้สำหรับแต่ละใบรับรองในสายโซ่

keytool -importcert -v -trustcacerts -file "path_to_cert/interm_ca.cer" -alias IntermediateCA -keystore "res/raw/mykeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

ตรวจสอบว่าใบรับรองถูกอิมพอร์ตอย่างถูกต้องไปยังที่เก็บคีย์:

keytool -list -keystore "res/raw/mykeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

ควรส่งออกโซ่ทั้งหมด:

RootCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 24:77:D9:A8:91:D1:3B:FA:88:2D:C2:FF:F8:CD:33:93
IntermediateCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 98:0F:C3:F8:39:F7:D8:05:07:02:0D:E3:14:5B:29:43

ตอนนี้คุณสามารถคัดลอก keystore เป็นทรัพยากรดิบในแอพ android ของคุณภายใต้ res/raw/

ใช้ที่เก็บคีย์ในแอปของคุณ

ก่อนอื่นเราต้องสร้าง Apache HttpClient ที่กำหนดเองซึ่งใช้ที่เก็บคีย์ของเราสำหรับการเชื่อมต่อ HTTPS:

import org.apache.http.*

public class MyHttpClient extends DefaultHttpClient {

    final Context context;

    public MyHttpClient(Context context) {
        this.context = context;
    }

    @Override
    protected ClientConnectionManager createClientConnectionManager() {
        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        // Register for port 443 our SSLSocketFactory with our keystore
        // to the ConnectionManager
        registry.register(new Scheme("https", newSslSocketFactory(), 443));
        return new SingleClientConnManager(getParams(), registry);
    }

    private SSLSocketFactory newSslSocketFactory() {
        try {
            // Get an instance of the Bouncy Castle KeyStore format
            KeyStore trusted = KeyStore.getInstance("BKS");
            // Get the raw resource, which contains the keystore with
            // your trusted certificates (root and any intermediate certs)
            InputStream in = context.getResources().openRawResource(R.raw.mykeystore);
            try {
                // Initialize the keystore with the provided trusted certificates
                // Also provide the password of the keystore
                trusted.load(in, "mysecret".toCharArray());
            } finally {
                in.close();
            }
            // Pass the keystore to the SSLSocketFactory. The factory is responsible
            // for the verification of the server certificate.
            SSLSocketFactory sf = new SSLSocketFactory(trusted);
            // Hostname verification from certificate
            // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
            sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
            return sf;
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }
}

เราได้สร้าง HttpClient ที่กำหนดเองตอนนี้เราสามารถใช้มันเพื่อการเชื่อมต่อที่ปลอดภัย ตัวอย่างเช่นเมื่อเราทำการเรียก GET ไปยังทรัพยากร REST:

// Instantiate the custom HttpClient
DefaultHttpClient client = new MyHttpClient(getApplicationContext());
HttpGet get = new HttpGet("https://www.mydomain.ch/rest/contacts/23");
// Execute the GET call and obtain the response
HttpResponse getResponse = client.execute(get);
HttpEntity responseEntity = getResponse.getEntity();

แค่นั้นแหละ ;)


8
สิ่งนี้มีประโยชน์สำหรับการรับใบรับรองก่อนส่งใบสมัครของคุณ ไม่ได้ช่วยให้ผู้ใช้ยอมรับใบรับรองของตนเอง สำหรับการสมัครของคุณ
Fuzzy

สวัสดีทุกคนสามารถบอกขั้นตอนการตรวจสอบสำหรับที่เก็บคีย์กับ truststore สำหรับการใช้งานข้างต้นได้ไหม ขอบคุณล่วงหน้า ..
andriod_testing

ทำงานได้ดี แต่ตอนนี้ฉันกำลังประสบปัญหาเมื่อฉัน rekey ใบรับรองบนเซิร์ฟเวอร์ ดูเหมือนว่าทุกครั้งที่ฉันอัปเดตใบรับรองบนเซิร์ฟเวอร์ของฉันร้านค้าฝั่งไคลเอ็นต์ก็ควรได้รับการอัปเดตด้วย จะต้องมีวิธีที่ดีกว่า: |
bpn

คำตอบที่ Gr8 ฉันขอแนะนำให้ใช้ ThreadSafeClientConnManager แทน SingleClientConnManager
ฟาร์ม

ฉันได้เพิ่ม/res/raw/mykeystore.bksแล้ว แต่ไม่สามารถแก้ไขการอ้างอิงได้ วิธีแก้ปัญหานี้
uniruddh

16

หากคุณมีใบรับรองแบบกำหนดเอง / ลงนามเองบนเซิร์ฟเวอร์ที่ไม่มีอยู่ในอุปกรณ์คุณสามารถใช้คลาสด้านล่างเพื่อโหลดและใช้งานในฝั่งไคลเอ็นต์ใน Android:

วาง*.crtไฟล์ใบรับรองไว้/res/rawเพื่อให้พร้อมใช้งานR.raw.*

ใช้คลาสด้านล่างเพื่อรับHTTPClientหรือHttpsURLConnectionจะมีโรงงานซ็อกเก็ตโดยใช้ใบรับรองนั้น:

package com.example.customssl;

import android.content.Context;
import org.apache.http.client.HttpClient;
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.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

public class CustomCAHttpsProvider {

    /**
     * Creates a {@link org.apache.http.client.HttpClient} which is configured to work with a custom authority
     * certificate.
     *
     * @param context       Application Context
     * @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
     * @param allowAllHosts If true then client will not check server against host names of certificate.
     * @return Http Client.
     * @throws Exception If there is an error initializing the client.
     */
    public static HttpClient getHttpClient(Context context, int certRawResId, boolean allowAllHosts) throws Exception {


        // build key store with ca certificate
        KeyStore keyStore = buildKeyStore(context, certRawResId);

        // init ssl socket factory with key store
        SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore);

        // skip hostname security check if specified
        if (allowAllHosts) {
            sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier());
        }

        // basic http params for client
        HttpParams params = new BasicHttpParams();

        // normal scheme registry with our ssl socket factory for "https"
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));

        // create connection manager
        ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);

        // create http client
        return new DefaultHttpClient(cm, params);
    }

    /**
     * Creates a {@link javax.net.ssl.HttpsURLConnection} which is configured to work with a custom authority
     * certificate.
     *
     * @param urlString     remote url string.
     * @param context       Application Context
     * @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
     * @param allowAllHosts If true then client will not check server against host names of certificate.
     * @return Http url connection.
     * @throws Exception If there is an error initializing the connection.
     */
    public static HttpsURLConnection getHttpsUrlConnection(String urlString, Context context, int certRawResId,
                                                           boolean allowAllHosts) throws Exception {

        // build key store with ca certificate
        KeyStore keyStore = buildKeyStore(context, certRawResId);

        // 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 sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);

        // Create a connection from url
        URL url = new URL(urlString);
        HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
        urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());

        // skip hostname security check if specified
        if (allowAllHosts) {
            urlConnection.setHostnameVerifier(new AllowAllHostnameVerifier());
        }

        return urlConnection;
    }

    private static KeyStore buildKeyStore(Context context, int certRawResId) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
        // init a default key store
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);

        // read and add certificate authority
        Certificate cert = readCert(context, certRawResId);
        keyStore.setCertificateEntry("ca", cert);

        return keyStore;
    }

    private static Certificate readCert(Context context, int certResourceId) throws CertificateException, IOException {

        // read certificate resource
        InputStream caInput = context.getResources().openRawResource(certResourceId);

        Certificate ca;
        try {
            // generate a certificate
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            ca = cf.generateCertificate(caInput);
        } finally {
            caInput.close();
        }

        return ca;
    }

}

ประเด็นสำคัญ:

  1. Certificateวัตถุถูกสร้างขึ้นจาก.crtไฟล์
  2. ค่าเริ่มต้นKeyStoreถูกสร้างขึ้น
  3. keyStore.setCertificateEntry("ca", cert)กำลังเพิ่มใบรับรองในที่จัดเก็บคีย์ภายใต้นามแฝง "ca" คุณปรับเปลี่ยนรหัสเพื่อเพิ่มใบรับรองเพิ่มเติม (CA ระดับกลาง ฯลฯ )
  4. วัตถุประสงค์หลักคือการสร้างSSLSocketFactoryซึ่งสามารถนำมาใช้โดยหรือHTTPClientHttpsURLConnection
  5. SSLSocketFactory สามารถกำหนดค่าเพิ่มเติมได้เช่นข้ามการตรวจสอบชื่อโฮสต์เป็นต้น

ข้อมูลเพิ่มเติมได้ที่: http://developer.android.com/training/articles/security-ssl.html


ฉันจะหา.crtไฟล์ได้ที่ไหนดาวน์โหลดจากเซิร์ฟเวอร์?
zionpi

@zionpi ไฟล์ใบรับรองจะเหมือนกับเซิร์ฟเวอร์ที่เปิดใช้งาน TLS ที่คุณกำลังเชื่อมต่อ
SD

ขอบคุณ! มันง่ายมาก!
kapil thadani

@SD ฉันจะใช้ไฟล์. P12 แทนที่จะเป็น. crt ได้อย่างไร
Rakesh R Nair

ฉันมีข้อสงสัยคล้าย ๆ กับหวัดคุณโปรดช่วยstackoverflow.com/questions/57389622/…
StezPet

8

ฉันผิดหวังที่พยายามเชื่อมต่อแอพ Android ของฉันกับบริการ RESTful โดยใช้ https อีกทั้งฉันยังรู้สึกรำคาญเล็กน้อยกับคำตอบทั้งหมดที่แนะนำให้ปิดใช้งานการตรวจสอบใบรับรองโดยสิ้นเชิง หากคุณทำเช่นนั้นจุดประสงค์ของ https คืออะไร

หลังจาก googled เกี่ยวกับหัวข้อในขณะที่ในที่สุดฉันก็พบวิธีแก้ปัญหานี้ที่ขวดภายนอกไม่จำเป็นต้องใช้เพียง API Android ขอบคุณ Andrew Smith ที่โพสต์เมื่อกรกฎาคม 2014

 /**
 * Set up a connection to myservice.domain using HTTPS. An entire function
 * is needed to do this because myservice.domain has a self-signed certificate.
 * 
 * The caller of the function would do something like:
 * HttpsURLConnection urlConnection = setUpHttpsConnection("https://littlesvr.ca");
 * InputStream in = urlConnection.getInputStream();
 * And read from that "in" as usual in Java
 * 
 * Based on code from:
 * https://developer.android.com/training/articles/security-ssl.html#SelfSigned
 */
public static HttpsURLConnection setUpHttpsConnection(String urlString)
{
    try
    {
        // Load CAs from an InputStream
        // (could be from a resource or ByteArrayInputStream or ...)
        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        // My CRT file that I put in the assets folder
        // I got this file by following these steps:
        // * Go to https://littlesvr.ca using Firefox
        // * Click the padlock/More/Security/View Certificate/Details/Export
        // * Saved the file as littlesvr.crt (type X.509 Certificate (PEM))
        // The MainActivity.context is declared as:
        // public static Context context;
        // And initialized in MainActivity.onCreate() as:
        // MainActivity.context = getApplicationContext();
        InputStream caInput = new BufferedInputStream(MainActivity.context.getAssets().open("littlesvr.crt"));
        Certificate ca = cf.generateCertificate(caInput);
        System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());

        // 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("TLS");
        context.init(null, tmf.getTrustManagers(), null);

        // Tell the URLConnection to use a SocketFactory from our SSLContext
        URL url = new URL(urlString);
        HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
        urlConnection.setSSLSocketFactory(context.getSocketFactory());

        return urlConnection;
    }
    catch (Exception ex)
    {
        Log.e(TAG, "Failed to establish SSL connection to server: " + ex.toString());
        return null;
    }
}

มันใช้งานได้ดีสำหรับแอปจำลองของฉัน


X509 ใบรับรองฉันควรนำเข้า java หรือ javax ใด
Siddharth

ฉันนำเข้าแล้วimport java.security.cert.X509Certificate;
Gonzalo Fernández

ขอบคุณสำหรับ.
it ใช้

6

คำตอบยอดนิยมไม่ทำงานสำหรับฉัน หลังจากการตรวจสอบฉันพบข้อมูลที่จำเป็นใน "นักพัฒนา Android": https://developer.android.com/training/articles/security-ssl.html#SelfSigned

การสร้างการใช้งานที่ว่างเปล่าของ X509TrustManager ได้ทำเคล็ดลับ:

private static class MyTrustManager implements X509TrustManager
{

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType)
         throws CertificateException
    {
    }

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

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

}

...

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
try
{
    // Create an SSLContext that uses our TrustManager
    SSLContext context = SSLContext.getInstance("TLS");
    TrustManager[] tmlist = {new MyTrustManager()};
    context.init(null, tmlist, null);
    conn.setSSLSocketFactory(context.getSocketFactory());
}
catch (NoSuchAlgorithmException e)
{
    throw new IOException(e);
} catch (KeyManagementException e)
{
    throw new IOException(e);
}
conn.setRequestMethod("GET");
int rcode = conn.getResponseCode();

โปรดทราบว่าการใช้ TustManager ที่ว่างเปล่านี้เป็นเพียงตัวอย่างและการใช้ในสภาพแวดล้อมที่มีประสิทธิผลจะทำให้เกิดภัยคุกคามด้านความปลอดภัยที่รุนแรง!


1
เพียงแค่ fyi - idk ถ้าเป็นเช่นนี้ในเวลานั้น แต่พวกเขาดูเหมือนจะกีดกันวิธีการนี้ในตอนนี้ (ดูหมายเหตุ)
Saik Caskey

6

Google แนะนำให้ใช้งานAndroid Volley สำหรับการเชื่อมต่อ HTTP / HTTPSเนื่องจากHttpClientไม่ได้รับการสนับสนุน ดังนั้นคุณรู้ทางเลือกที่ถูกต้อง :)

และไม่ควรใช้ใบรับรอง SSL ของ NUKE (ไม่เคย !!!)

เพื่อ nuke ใบรับรอง SSL คือโดยสิ้นเชิงกับวัตถุประสงค์ของ SSL ซึ่งคือการส่งเสริมการรักษาความปลอดภัย ไม่มีความหมายของการใช้ SSL หากคุณวางแผนที่จะวางระเบิดใบรับรอง SSL ทั้งหมดที่มา ทางออกที่ดีกว่าคือไม่ใช้ SSL หรือวิธีแก้ปัญหาที่ดีกว่าคือการสร้าง Custom TrustManagerบน App + ของคุณโดยใช้ Android Volley สำหรับการเชื่อมต่อ HTTP / HTTPS

นี่คือส่วนสำคัญที่ฉันสร้างขึ้นด้วย LoginApp พื้นฐานที่ดำเนินการเชื่อมต่อ HTTPS โดยใช้ใบรับรองที่ลงชื่อด้วยตนเองทางฝั่งเซิร์ฟเวอร์ยอมรับในแอป

นี่คืออีกส่วนสำคัญที่อาจช่วยในการสร้างใบรับรอง SSL ที่ลงชื่อด้วยตนเองเพื่อตั้งค่าบนเซิร์ฟเวอร์ของคุณและใช้ใบรับรองในแอปของคุณ สำคัญมาก:คุณต้องคัดลอกไฟล์. crt ที่สร้างโดยสคริปต์ข้างต้นไปยังไดเรกทอรี "raw" จากโครงการ Android ของคุณ


สวัสดี Ivan ฉันไม่เคยทำงานกับใบรับรอง SSL คุณสนใจที่จะอธิบายรายละเอียดเล็กน้อยฉันจะรับไฟล์. crt ได้อย่างไร
jlively

สวัสดี Jively! ฉันเห็น. ใช่แน่นอน แต่ก่อนอื่นคุณจะลองดู Gist ที่สองที่ฉันพูดถึงข้างต้นได้ไหม? ฉันใส่สองไฟล์ลงในส่วนสำคัญนี้: หนึ่งคือไฟล์ที่ใช้โดยสคริปต์และอีกหนึ่งเป็นสคริปต์ตัวเองที่ใช้ "openssl" ไบนารีเพื่ออ่านไฟล์แล้วสร้างไฟล์ที่มีใบรับรอง SSL ( .crt) แจ้งให้เราทราบหากคุณจัดการเพื่อเข้าใจสิ่งทั้งหมด ความนับถือ :).
ivanleoncz

อืมใช่ฉันได้ดู 2 จิสต์เหล่านั้นแล้ว แต่ฉันไม่เข้าใจจริงๆว่าฉันจะใช้ได้อย่างไร
jlively

4

นี่คือวิธีที่คุณสามารถเพิ่มใบรับรองเพิ่มเติมใน KeyStore ของคุณเพื่อหลีกเลี่ยงปัญหานี้: การเชื่อถือใบรับรองทั้งหมดโดยใช้ HttpClient ผ่าน HTTPS

มันจะไม่พรอมต์ผู้ใช้อย่างที่คุณถาม แต่จะทำให้มีโอกาสน้อยลงที่ผู้ใช้จะพบข้อผิดพลาด "ใบรับรองเซิร์ฟเวอร์ไม่น่าเชื่อถือ"


เพียงเพื่อจุดประสงค์ในการทดสอบคุณไม่สามารถเผยแพร่แอพใน Play Store ด้วยเล่ห์เหลี่ยมนี้เพราะมันจะถูกปฏิเสธ
ariel

3

วิธีที่ง่ายที่สุดสำหรับการสร้างใบรับรอง SSL

เปิด Firefox (ฉันคิดว่ามันเป็นไปได้ด้วย Chrome แต่มันง่ายสำหรับฉันด้วย FF)

เยี่ยมชมไซต์พัฒนาของคุณด้วยใบรับรอง SSL ที่ลงชื่อด้วยตนเอง

คลิกที่ใบรับรอง (ถัดจากชื่อไซต์)

คลิกที่ "ข้อมูลเพิ่มเติม"

คลิกที่ "ดูใบรับรอง"

คลิกที่ "รายละเอียด"

คลิกที่ "ส่งออก ... "

เลือก "ใบรับรอง X.509 ที่มีห่วงโซ่ (PEM)" เลือกโฟลเดอร์และชื่อที่จะบันทึกและคลิก "บันทึก"

ไปที่บรรทัดรับคำสั่งไปยังไดเรกทอรีที่คุณดาวน์โหลดไฟล์ pem และเรียกใช้งาน "openssl x509 -inform PEM -outform DM -in .pem -out .crt"

คัดลอกไฟล์. crt ไปยังรูทของโฟลเดอร์ / sdcard ภายในอุปกรณ์ Android ของคุณภายในอุปกรณ์ Android ของคุณการตั้งค่า> ความปลอดภัย> ติดตั้งจากที่เก็บข้อมูล

ควรตรวจสอบใบรับรองและให้คุณเพิ่มลงในอุปกรณ์เรียกดูไปยังไซต์การพัฒนาของคุณ

ครั้งแรกควรให้คุณยืนยันข้อยกเว้นด้านความปลอดภัย นั่นคือทั้งหมดที่

ใบรับรองควรทำงานกับเบราว์เซอร์ที่ติดตั้งบน Android ของคุณ (เบราว์เซอร์, Chrome, Opera, Dolphin ... )

โปรดจำไว้ว่าหากคุณให้บริการไฟล์คงที่ของคุณจากโดเมนอื่น (เราทุกคนเป็นผู้ใช้ความเร็วหน้า) คุณต้องเพิ่มใบรับรองสำหรับโดเมนนั้น


2

ฉันเขียนไลบรารี่ขนาดเล็กssl-utils-androidเพื่อเชื่อถือใบรับรองเฉพาะบน Android

คุณสามารถโหลดใบรับรองใด ๆ โดยให้ชื่อไฟล์จากไดเรกทอรีสินทรัพย์

การใช้งาน:

OkHttpClient client = new OkHttpClient();
SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context, "BPClass2RootCA-sha2.cer");
client.setSslSocketFactory(sslContext.getSocketFactory());

1

การแก้ไขเหล่านี้ไม่เหมาะกับการพัฒนาแพลตฟอร์มที่กำหนดเป้าหมายเป็น SDK 16, Release 4.1.2 ดังนั้นฉันจึงพบวิธีแก้ปัญหา

แอพของฉันเก็บข้อมูลบนเซิร์ฟเวอร์โดยใช้ " http://www.example.com/page.php?data=somedata "

เมื่อเร็ว ๆ นี้ page.php ถูกย้ายไปที่ " https://www.secure-example.com/page.php " และฉันยังได้รับ "javax.net.ssl.SSLException: ไม่ได้รับใบรับรองเซิร์ฟเวอร์ที่เชื่อถือได้"

แทนที่จะยอมรับใบรับรองทั้งหมดเพียงหน้าเดียวเริ่มต้นด้วยคำแนะนำนี้ฉันแก้ไขปัญหาของฉันในการเขียน page.php ของฉันเองที่เผยแพร่ใน " http://www.example.com/page.php "

<?php

caronte ("https://www.secure-example.com/page.php");

function caronte($url) {
    // build curl request
    $ch = curl_init();
    foreach ($_POST as $a => $b) {
        $post[htmlentities($a)]=htmlentities($b);
    }
    curl_setopt($ch, CURLOPT_URL,$url);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($post));

    // receive server response ...
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $server_output = curl_exec ($ch);
    curl_close ($ch);

    echo $server_output;
}

?>

1

19 มกราคม 2563 ใบรับรองที่ลงนามด้วยตนเองการแก้ไขปัญหา:

ในการเล่นวิดีโอรูปภาพการโทร webservice สำหรับใบรับรองที่ลงชื่อด้วยตนเองหรือเชื่อมต่อกับ url ที่ไม่ปลอดภัยเพียงแค่เรียกวิธีนี้ก่อนดำเนินการใด ๆ มันจะแก้ไขปัญหาของคุณเกี่ยวกับปัญหาใบรับรอง:

รหัส KOTLIN

  private fun disableSSLCertificateChecking() {
        val hostnameVerifier = object: HostnameVerifier {
            override fun verify(s:String, sslSession: SSLSession):Boolean {
                return true
            }
        }
        val trustAllCerts = arrayOf<TrustManager>(object: X509TrustManager {
            override fun getAcceptedIssuers(): Array<X509Certificate> {
                TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
            }

            //val acceptedIssuers:Array<X509Certificate> = null
            @Throws(CertificateException::class)
            override fun checkClientTrusted(arg0:Array<X509Certificate>, arg1:String) {// Not implemented
            }
            @Throws(CertificateException::class)
            override fun checkServerTrusted(arg0:Array<X509Certificate>, arg1:String) {// Not implemented
            }
        })
        try
        {
            val sc = SSLContext.getInstance("TLS")
            sc.init(null, trustAllCerts, java.security.SecureRandom())
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory())
            HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier)
        }
        catch (e: KeyManagementException) {
            e.printStackTrace()
        }
        catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        }
    }

0

อาจเป็นประโยชน์ ... มันทำงานบนไคลเอนต์ java โดยใช้ใบรับรองที่ลงนามเอง (ไม่มีการตรวจสอบใบรับรอง) ระวังและใช้สำหรับกรณีการพัฒนาเท่านั้นเพราะมันไม่ปลอดภัยเลย !!

วิธีเพิกเฉยข้อผิดพลาดใบรับรอง SSL ใน Apache HttpClient 4.0

หวังว่ามันจะทำงานบน Android เพียงแค่เพิ่มห้องสมุด HttpClient ... ขอให้โชคดี !!


1
ไม่มันไม่ทำงานบน Android เนื่องจากมันอาศัยวิธีการที่ไม่ได้มีอยู่ในตัวแปร Android :-(
kellyfj

0

ปัญหานี้เกิดจากการขาดการสนับสนุน SNI (การระบุชื่อเซิร์ฟเวอร์) ใน A, ndroid 2.x ฉันกำลังดิ้นรนกับปัญหานี้เป็นเวลาหนึ่งสัปดาห์จนกระทั่งฉันได้พบกับคำถามต่อไปนี้ซึ่งไม่เพียง แต่ให้พื้นฐานที่ดีของปัญหา แต่ยังให้วิธีการทำงานที่มีประสิทธิภาพและไร้ปัญหาด้านความปลอดภัย

ข้อผิดพลาด 'ไม่มีใบรับรองเพื่อน' ใน Android 2.3 แต่ไม่ใช่ใน 4

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.