콘텐츠로 건너뛰기

자주 방문한 페이지

최근 방문한 페이지

navigation

SSL 악용을 차단하기 위해 보안 제공자 업데이트

Android에서는 보안 네트워크 통신을 제공하기 위해 보안 Provider에 의존합니다 그러나, 기본 보안 제공자에서 취약성이 수시로 발견됩니다. 이러한 취약성을 차단하기 위해 Google Play 서비스에서는 기기 보안 제공자를 자동으로 업데이트하여 알려진 악용을 차단하는 방법을 제공합니다. 여러분의 앱이 Google Play 서비스 메서드를 호출하면, 최신 업데이트가 있는 기기에서 이 앱을 실행하여 알려진 악용을 차단할 수 있습니다.

예를 들어, 어느 쪽도 모르게 보안 트래픽의 암호를 해독하는 "중간자(man-in-the-middle)" 공격에 앱을 노출시키는 취약성이 OpenSSL(CVE-2014-0224)에서 발견되었습니다. Google Play 서비스 버전 5.0에서는 수정 버전을 사용할 수 있지만, 이 수정 버전이 설치되었는지 앱이 확인해야 합니다. 여러분의 앱이 Google Play 서비스 메서드를 사용하면, 이 공격을 차단하는 안전한 기기에서 앱이 실행되도록 보장할 수 있습니다.

주의: 기기의 보안 Provider를 업데이트하더라도 android.net.SSLCertificateSocketFactory는 업데이트되지 않습니다. 앱 개발자는 이 클래스를 사용하는 대신 고수준 메서드를 사용하여 암호화와 상호작용을 수행하는 것이 좋습니다. 대부분의 앱은 사용자 지정 TrustManager를 설정하거나 SSLCertificateSocketFactory를 만들지 않고도 HttpsURLConnection과 같은 API를 사용할 수 있습니다.

ProviderInstaller로 보안 제공자에 패치 사용

기기의 보안 제공자를 업데이트하려면 ProviderInstaller 클래스를 사용합니다. 이 클래스의 installIfNeeded()(또는 installIfNeededAsync()) 메서드를 호출하여 보안 제공자가 최신인지 확인(및 필요한 경우 업데이트)할 수 있습니다.

installIfNeeded()를 호출하면 ProviderInstaller가 다음을 수행합니다.

installIfNeededAsync() 메서드도 동작은 비슷하지만, 예외를 발생시키는 대신 적절한 콜백 메서드를 호출하여 성공 또는 실패 여부를 나타냅니다.

installIfNeeded()에서 새 Provider를 설치해야 하는 경우, 이 작업은 30-50 밀리초(최신 기기의 경우) ~ 350 밀리초(구형 기기의 경우) 가량이 걸릴 수 있습니다. 보안 제공자가 이미 최신인 경우, 메서드에 걸리는 시간은 미미합니다. 사용자 환경에 영향을 미치지 않도록 하려면 다음과 같이 합니다.

경고: ProviderInstaller가 업데이트된 Provider를 설치할 수 없는 경우, 기기의 보안 제공자가 알려진 악용에 취약할 수도 있습니다. 여러분의 앱은 마치 모든 HTTP 통신이 암호화되지 않은 것처럼 동작합니다.

Provider가 업데이트되면, 보안 API(SSL API 포함)에 대한 모든 호출이 이를 통해 라우팅됩니다. (그러나, 이것은 CVE-2014-0224와 같은 악용에 취약한 android.net.SSLCertificateSocketFactory에는 적용되지 않습니다.)

동기 패치 사용

보안 제공자에 패치를 사용하는 가장 간단한 방법은 동기 메서드 installIfNeeded()를 호출하는 것입니다. 이 방법은 작업이 완료되기를 기다리는 중에 스레드 차단에 의해 사용자 환경이 영향을 받지 않는 경우에 적절한 방법입니다.

예를 들어, 다음은 보안 제공자를 업데이트하는 동기화 어댑터의 구현입니다. 동기화 어댑터는 백그라운드로 실행되므로, 보안 제공자가 업데이트되기를 기다리는 중에 스레드가 차단되더라도 문제가 없습니다. 동기화 어댑터는 보안 제공자를 업데이트하기 위해 installIfNeeded()를 호출합니다. 메서드가 정상적으로 반환하면, 동기화 어댑터는 보안 제공자가 최신 버전임을 알게 됩니다. 메서드가 예외를 발생시키면, 동기화 어댑터가 적절한 동작을 취할 수 있습니다(예: Google Play 서비스를 업데이트하도록 사용자에게 알림).

/**
 * Sample sync adapter using {@link ProviderInstaller}.
 */
public class SyncAdapter extends AbstractThreadedSyncAdapter {

  ...

  // This is called each time a sync is attempted; this is okay, since the
  // overhead is negligible if the security provider is up-to-date.
  @Override
  public void onPerformSync(Account account, Bundle extras, String authority,
      ContentProviderClient provider, SyncResult syncResult) {
    try {
      ProviderInstaller.installIfNeeded(getContext());
    } catch (GooglePlayServicesRepairableException e) {

      // Indicates that Google Play services is out of date, disabled, etc.

      // Prompt the user to install/update/enable Google Play services.
      GooglePlayServicesUtil.showErrorNotification(
          e.getConnectionStatusCode(), getContext());

      // Notify the SyncManager that a soft error occurred.
      syncResult.stats.numIOExceptions++;
      return;

    } catch (GooglePlayServicesNotAvailableException e) {
      // Indicates a non-recoverable error; the ProviderInstaller is not able
      // to install an up-to-date Provider.

      // Notify the SyncManager that a hard error occurred.
      syncResult.stats.numAuthExceptions++;
      return;
    }

    // If this is reached, you know that the provider was already up-to-date,
    // or was successfully updated.
  }
}

비동기 패치 사용

보안 제공자의 업데이트에는 최대 350밀리초가 걸릴 수 있습니다(구형 기기의 경우). 사용자 환경에 직접 영향을 미치는 스레드(예: UI 스레드)에서 업데이트를 수행 중인 경우, 작업이 완료될 때까지 앱이나 기기가 멈출 수 있으므로, 제공자를 업데이트하기 위해 동기 호출을 원치는 않을 것입니다. 그 대신, 비동기 메서드 installIfNeededAsync()를 사용해야 합니다. 이 메서드는 콜백을 호출하여 성공 또는 실패 여부를 나타냅니다.

예를 들어, 다음은 UI 스레드에 있는 한 액티비티에서 보안 제공자를 업데이트하는 코드입니다. 이 액티비티는 보안 제공자를 업데이트하기 위해 installIfNeededAsync()를 호출하고, 성공 또는 실패 알림을 수신하기 위해 스스로를 리스너로 지명합니다. 보안 제공자가 최신이거나 성공적으로 업데이트되면, 액티비티의 onProviderInstalled() 메서드가 호출되고, 이 액티비티는 통신이 안전하다는 것을 알게 됩니다. 제공자를 업데이트할 수 없는 경우, 액티비티의 onProviderInstallFailed() 메서드가 호출되고, 액티비티가 적절한 동작을 취할 수 있습니다(예: Google Play 서비스를 업데이트하도록 사용자에게 알림).

/**
 * Sample activity using {@link ProviderInstaller}.
 */
public class MainActivity extends Activity
    implements ProviderInstaller.ProviderInstallListener {

  private static final int ERROR_DIALOG_REQUEST_CODE = 1;

  private boolean mRetryProviderInstall;

  //Update the security provider when the activity is created.
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ProviderInstaller.installIfNeededAsync(this, this);
  }

  /**
   * This method is only called if the provider is successfully updated
   * (or is already up-to-date).
   */
  @Override
  protected void onProviderInstalled() {
    // Provider is up-to-date, app can make secure network calls.
  }

  /**
   * This method is called if updating fails; the error code indicates
   * whether the error is recoverable.
   */
  @Override
  protected void onProviderInstallFailed(int errorCode, Intent recoveryIntent) {
    if (GooglePlayServicesUtil.isUserRecoverableError(errorCode)) {
      // Recoverable error. Show a dialog prompting the user to
      // install/update/enable Google Play services.
      GooglePlayServicesUtil.showErrorDialogFragment(
          errorCode,
          this,
          ERROR_DIALOG_REQUEST_CODE,
          new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
              // The user chose not to take the recovery action
              onProviderInstallerNotAvailable();
            }
          });
    } else {
      // Google Play services is not available.
      onProviderInstallerNotAvailable();
    }
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode,
      Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == ERROR_DIALOG_REQUEST_CODE) {
      // Adding a fragment via GooglePlayServicesUtil.showErrorDialogFragment
      // before the instance state is restored throws an error. So instead,
      // set a flag here, which will cause the fragment to delay until
      // onPostResume.
      mRetryProviderInstall = true;
    }
  }

  /**
   * On resume, check to see if we flagged that we need to reinstall the
   * provider.
   */
  @Override
  protected void onPostResume() {
    super.onPostResult();
    if (mRetryProviderInstall) {
      // We can now safely retry installation.
      ProviderInstall.installIfNeededAsync(this, this);
    }
    mRetryProviderInstall = false;
  }

  private void onProviderInstallerNotAvailable() {
    // This is reached if the provider cannot be updated for some reason.
    // App should consider all HTTP communication to be vulnerable, and take
    // appropriate action.
  }
}
이 사이트는 쿠키를 사용하여 사이트별 언어 및 표시 옵션에 대한 환경설정을 저장합니다.

Google Play에서 성공을 거두는 데 도움이 되는 최신 Android 개발자 뉴스 및 도움말을 받아 보세요.

* 필수 입력란

완료되었습니다.

WeChat에서 Google Developers 팔로우하기

이 사이트를 (으)로 탐색할까요?

페이지를 요청했지만 이 사이트의 언어 환경설정은 입니다.

언어 환경설정을 변경하고 이 사이트를 (으)로 탐색할까요? 언어 환경설정을 나중에 변경하려면 각 페이지 하단의 언어 메뉴를 사용하세요.

이 클래스를 사용하려면 API 수준 이상이 필요합니다.

문서에 대해 선택한 API 수준이 이므로 이 문서가 표시되지 않습니다. 왼쪽 탐색 메뉴의 선택기로 문서 API 수준을 변경할 수 있습니다.

앱에 필요한 API 수준 지정에 관한 자세한 내용은 다양한 플랫폼 버전 지원을 참조하세요.

Take a short survey?
Help us improve the Android developer experience. (April 2018 — Developer Survey)