IT박스

OkHttp는 자체 서명 된 SSL 인증서 수락을 지원합니까?

itboxs 2020. 12. 5. 09:24
반응형

OkHttp는 자체 서명 된 SSL 인증서 수락을 지원합니까?


자체 서명 된 SSL 인증서가있는 서버를 보유한 고객을 위해 일하고 있습니다.

래핑 된 OkHttp 클라이언트를 사용하여 Retrofit + CustomClient를 사용하고 있습니다.

RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(Config.BASE_URL + Config.API_VERSION)
    .setClient(new CustomClient(new OkClient(), context))
    .build();

OkHttp는 기본적으로 자체 서명 된 SSL 인증서 서버 호출을 지원합니까?

그건 그렇고. 기본적으로 Retrofit을 사용하는 클라이언트는 무엇입니까? OkHttp라고 생각했지만 조금 더 조사했을 때 OkHttp 종속성을 가져와야한다는 것을 깨달았습니다.


네, 그렇습니다.

Retrofit을 사용하면 필요에 맞게 구성된 사용자 지정 HTTP 클라이언트를 설정할 수 있습니다.

자체 서명 된 SSL 인증서에 대한 논의가 여기에 있습니다 . 링크에는 자체 서명 된 SLL을 Android DefaultHttpClient에 추가하고이 클라이언트를 Retrofit에로드하는 코드 샘플이 포함되어 있습니다 .

OkHttpClient자체 서명 된 SSL을 수락 해야하는 경우 메서드 javax.net.ssl.SSLSocketFactory를 통해 사용자 지정 인스턴스를 전달해야합니다 setSslSocketFactory(SSLSocketFactory sslSocketFactory).

소켓 팩토리를 얻는 가장 쉬운 방법 javax.net.ssl.SSLContext여기 에서 논의한대로 소켓 팩토리를 얻는 것입니다 .

다음은 OkHttpClient를 구성하기위한 샘플입니다.

OkHttpClient client = new OkHttpClient();
KeyStore keyStore = readKeyStore(); //your method to obtain KeyStore
SSLContext sslContext = SSLContext.getInstance("SSL");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "keystore_pass".toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(),trustManagerFactory.getTrustManagers(), new SecureRandom());
client.setSslSocketFactory(sslContext.getSocketFactory());

okhttp3에 대한 업데이트 된 코드 (빌더 사용) :

    OkHttpClient client = new OkHttpClient.Builder()
            .sslSocketFactory(sslContext.getSocketFactory())
            .build();

client이곳은 지금으로부터 인증서를 사용하도록 구성되어 있습니다 KeyStore. 그러나 KeyStore시스템이 기본적으로 신뢰하더라도 사용자의 인증서 만 신뢰하고 다른 것은 신뢰하지 않습니다. (자체 서명 된 인증서 만 KeyStore있고 HTTPS를 통해 Google 메인 페이지에 연결하려고하면 SSLHandshakeException).

문서KeyStore 에서 볼 수 있듯이 파일에서 인스턴스를 얻을 수 있습니다 .

KeyStore readKeyStore() {
    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());

    // get user password and file input stream
    char[] password = getPassword();

    java.io.FileInputStream fis = null;
    try {
        fis = new java.io.FileInputStream("keyStoreName");
        ks.load(fis, password);
    } finally {
        if (fis != null) {
            fis.close();
        }
    }
    return ks;
}

Android를 사용 res/raw하는 Context경우 폴더에 넣고 다음을 사용하여 인스턴스 에서 가져올 수 있습니다.

fis = context.getResources().openRawResource(R.raw.your_keystore_filename);

키 저장소를 만드는 방법에 대한 몇 가지 논의가 있습니다. 예를 들어 여기


okhttp3.OkHttpClient 버전 com.squareup.okhttp3 : okhttp : 3.2.0의 경우 아래 코드를 사용해야합니다.

import okhttp3.Call;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

......

OkHttpClient.Builder clientBuilder = client.newBuilder().readTimeout(LOGIN_TIMEOUT_SEC, TimeUnit.SECONDS);

            boolean allowUntrusted = true;

            if (  allowUntrusted) {
                Log.w(TAG,"**** Allow untrusted SSL connection ****");
                final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        X509Certificate[] cArrr = new X509Certificate[0];
                        return cArrr;
                    }

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

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

                SSLContext sslContext = SSLContext.getInstance("SSL");

                sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
                clientBuilder.sslSocketFactory(sslContext.getSocketFactory());

                HostnameVerifier hostnameVerifier = new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        Log.d(TAG, "Trust Host :" + hostname);
                        return true;
                    }
                };
                clientBuilder.hostnameVerifier( hostnameVerifier);
            }

            final Call call = clientBuilder.build().newCall(request);

에서 키 저장소에서 자체 서명 된 인증서를 인식하는 OkHttpClient 3.0 인스턴스 를 가져 오는 두 가지 방법 (Android 프로젝트 "원시" 리소스 폴더에 준비된 pkcs12 인증서 파일 사용 ) :

private static OkHttpClient getSSLClient(Context context) throws
                              NoSuchAlgorithmException,
                              KeyStoreException,
                              KeyManagementException,
                              CertificateException,
                              IOException {

  OkHttpClient client;
  SSLContext sslContext;
  SSLSocketFactory sslSocketFactory;
  TrustManager[] trustManagers;
  TrustManagerFactory trustManagerFactory;
  X509TrustManager trustManager;

  trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
  trustManagerFactory.init(readKeyStore(context));
  trustManagers = trustManagerFactory.getTrustManagers();

  if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
    throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
  }

  trustManager = (X509TrustManager) trustManagers[0];

  sslContext = SSLContext.getInstance("TLS");

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

  sslSocketFactory = sslContext.getSocketFactory();

  client = new OkHttpClient.Builder()
      .sslSocketFactory(sslSocketFactory, trustManager)
      .build();
  return client;
}

/**
 * Get keys store. Key file should be encrypted with pkcs12 standard. It    can be done with standalone encrypting java applications like "keytool". File password is also required.
 *
 * @param context Activity or some other context.
 * @return Keys store.
 * @throws KeyStoreException
 * @throws CertificateException
 * @throws NoSuchAlgorithmException
 * @throws IOException
*/
private static KeyStore readKeyStore(Context context) throws
                          KeyStoreException,
                          CertificateException,
                          NoSuchAlgorithmException,
                          IOException {
  KeyStore keyStore;
  char[] PASSWORD = "12345678".toCharArray();
  ArrayList<InputStream> certificates;
  int certificateIndex;
  InputStream certificate;

  certificates = new ArrayList<>();
  certificates.add(context.getResources().openRawResource(R.raw.ssl_pkcs12));

keyStore = KeyStore.getInstance("pkcs12");

for (Certificate certificate : certificates) {
    try {
      keyStore.load(certificate, PASSWORD);
    } finally {
      if (certificate != null) {
        certificate.close();
      }
    }
  }
  return keyStore;
}

Retrofit 1.9에 대해 다음 전략을 사용하여 인증서를 수락 할 수있었습니다. 위험을 감수하고 사용하십시오! 인증서를 수락하는 것은 위험하므로 결과를 이해해야합니다. 일부 관련 부품은에서 org.apache.http.ssl가져 오므로 여기에서 일부 가져 오기가 필요할 수 있습니다.

// ...

    Client httpClient = getHttpClient();

    RestAdapter adapter = new RestAdapter.Builder()
        .setClient(httpClient)
        // ... the rest of your builder setup
        .build();

// ...

private Client getHttpClient() {
    try {
        // Allow self-signed (and actually any) SSL certificate to be trusted in this context
        TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;

        SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
            .loadTrustMaterial(null, acceptingTrustStrategy)
            .build();

        sslContext.getSocketFactory();

        SSLSocketFactory sf = sslContext.getSocketFactory();

        OkHttpClient client = new OkHttpClient();
        client.setSslSocketFactory(sf);

        return new OkClient(client);
    } catch (Exception e) {
        throw new RuntimeException("Failed to create new HTTP client", e);
    }
}

이 게시물이 꽤 오래되었다는 것을 알고 3.12.1있습니다. 제가 글을 쓰는 당시 버전 인 OkHttp의 최신 업데이트와 함께 저에게 도움이 된 솔루션을 공유하고 싶습니다 .

먼저 TrustManager에 추가 할 KeyStore 개체를 가져와야합니다.

/**
 *  @param context The Android context to be used for retrieving the keystore from raw resource
 * @return the KeyStore read or null on error
 */
private static KeyStore readKeyStore(Context context) {

    char[] password = "keystore_password".toCharArray();

    // for non-android usage:
    // try(FileInputStream is = new FileInputStream(keystoreName)) {

    try(InputStream is = context.getResources().openRawResource(R.raw.keystore)) {
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(is, password);
        return ks;
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    }

    return null;
}

이제 OkHttpClient키 저장소에서 자체 서명 된 인증서를 사용하여 빌드 할 수 있습니다 .

/**
 * @param context The Android context used to obtain the KeyStore
 * @return the builded OkHttpClient or null on error
 */
public static OkHttpClient getOkHttpClient(Context context) {

    try {
        TrustManagerFactory trustManagerFactory = TrustManagerFactory
                .getInstance(TrustManagerFactory.getDefaultAlgorithm());

        trustManagerFactory.init(readKeyStore(context));

        X509TrustManager trustManager = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, new TrustManager[]{trustManager}, null);

        return new OkHttpClient.Builder()
                .hostnameVerifier((hostname, session) -> {
                    HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
                    /* Never return true without verifying the hostname, otherwise you will be vulnerable
                    to man in the middle attacks. */
                    return  hv.verify("your_hostname_here", session);
                })
                .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
                .build();

    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }

    return null;
}

기억 이 매우 권장하지 않습니다 에 항상 true 반환하는 hostnameVerifier중앙 공격에 사람의 위험을 방지 할 수 있습니다.


나는 같은 문제가 있었고 다음과 같이 okhttp 클라이언트로 수정했습니다 .

1.) 다음 내용이 포함 된 certificate파일을에 추가합니다 src/main/res/raw/.

-----BEGIN CERTIFICATE-----
...=
-----END CERTIFICATE-----

2.) okHttpClient를 인스턴스화합니다.

OkHttpClient client = new OkHttpClient.Builder()
                .sslSocketFactory(getSslContext(context).getSocketFactory())
                .build();

3.) 사용 된 getSslContext(Context context)방법 은 다음과 같습니다 .

SSLContext getSslContext(Context context) throws Exception {
    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); // "BKS"
    ks.load(null, null);

    InputStream is = context.getResources().openRawResource(R.raw.certificate);
    String certificate = Converter.convertStreamToString(is);

    // generate input stream for certificate factory
    InputStream stream = IOUtils.toInputStream(certificate);

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

    ks.setCertificateEntry("av-ca", ca);

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

    // Create a SSLContext with the certificate that uses tmf (TrustManager)
    sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());

    return sslContext;
}

SslContext에 여러 인증서를 추가해야하는 경우 해결 방법은 다음과 같습니다 .


다음 코드를 사용하면 Retrofit과 함께 사용할 수있는 OkHttp 클라이언트를 만들 수 있습니다. Mailmustdie의 대답은 더 안전하다는 점에서 "더 좋음"이지만 아래 코드 조각은 구현이 더 빠릅니다.

import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.ResponseBody;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import okio.BufferedSink;
import retrofit.client.Header;
import retrofit.client.OkClient;
import retrofit.client.Request;
import retrofit.client.Response;
import retrofit.mime.TypedInput;
import retrofit.mime.TypedOutput;

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

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

public class TrustingOkClient extends OkClient {

static final int CONNECT_TIMEOUT_MILLIS = 15 * 1000; // 15s
static final int READ_TIMEOUT_MILLIS = 20 * 1000; // 20s

private static OkHttpClient generateDefaultOkHttp() {
    OkHttpClient client = new OkHttpClient();
    client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);



    final TrustManager[] certs = new TrustManager[]{new X509TrustManager() {

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

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

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

    SSLContext ctx = null;
    try {
        ctx = SSLContext.getInstance("TLS");
        ctx.init(null, certs, new SecureRandom());
    } catch (final java.security.GeneralSecurityException ex) {
    }

    try {
        final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
            @Override
            public boolean verify(final String hostname,
                                  final SSLSession session) {
                return true;
            }
        };
        client.setHostnameVerifier(hostnameVerifier);
        client.setSslSocketFactory(ctx.getSocketFactory());
    } catch (final Exception e) {
    }
    return client;
}

private final OkHttpClient client;

public TrustingOkClient() {
    this(generateDefaultOkHttp());
}

public TrustingOkClient(OkHttpClient client) {
    if (client == null) throw new NullPointerException("client == null");
    this.client = client;
}

@Override public Response execute(Request request) throws IOException {
    return parseResponse(client.newCall(createRequest(request)).execute());
}

static com.squareup.okhttp.Request createRequest(Request request) {
    com.squareup.okhttp.Request.Builder builder = new com.squareup.okhttp.Request.Builder()
            .url(request.getUrl())
            .method(request.getMethod(), createRequestBody(request.getBody()));

    List<Header> headers = request.getHeaders();
    for (int i = 0, size = headers.size(); i < size; i++) {
        Header header = headers.get(i);
        String value = header.getValue();
        if (value == null) value = "";
        builder.addHeader(header.getName(), value);
    }

    return builder.build();
}

static Response parseResponse(com.squareup.okhttp.Response response) {
    return new Response(response.request().urlString(), response.code(), response.message(),
            createHeaders(response.headers()), createResponseBody(response.body()));
}

private static RequestBody createRequestBody(final TypedOutput body) {
    if (body == null) {
        return null;
    }
    final MediaType mediaType = MediaType.parse(body.mimeType());
    return new RequestBody() {
        @Override public MediaType contentType() {
            return mediaType;
        }

        @Override public void writeTo(BufferedSink sink) throws IOException {
            body.writeTo(sink.outputStream());
        }

        @Override public long contentLength() {
            return body.length();
        }
    };
}

private static TypedInput createResponseBody(final ResponseBody body) {
    try {
        if (body.contentLength() == 0) {
            return null;
        }
        return new TypedInput() {
            @Override public String mimeType() {
                MediaType mediaType = body.contentType();
                return mediaType == null ? null : mediaType.toString();
            }

            @Override public long length() {
                try {
                    return body.contentLength();
                } catch (Exception exception) {
                    System.out.println(exception.toString());
                }
                throw new Error("createResponseBody has invalid length for its response");
            }

            @Override public InputStream in() throws IOException {
                return body.byteStream();
            }
        };
    } catch (Exception exception) {
        System.out.println(exception.toString());
    }
    throw new Error("createResponseBody has invalid content length for its response");
}

private static List<Header> createHeaders(Headers headers) {
    int size = headers.size();
    List<Header> headerList = new ArrayList<Header>(size);
    for (int i = 0; i < size; i++) {
        headerList.add(new Header(headers.name(i), headers.value(i)));
    }
    return headerList;
}

}

참고 URL : https://stackoverflow.com/questions/23103174/does-okhttp-support-accepting-self-signed-ssl-certs

반응형