content: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.backup.encryption.keys;
  
  import static com.android.internal.util.Preconditions.checkNotNull;
  
  import android.annotation.IntDef;
  import android.content.Context;
  import android.security.keystore.recovery.InternalRecoveryServiceException;
  import android.security.keystore.recovery.RecoveryController;
  import android.util.Slog;
  
  import javax.crypto.SecretKey;
  
  /**
   * Wraps a {@link RecoveryController}'s {@link SecretKey}. These are kept in "AndroidKeyStore" (a
   * provider for {@link java.security.KeyStore} and {@link javax.crypto.KeyGenerator}. They are also
   * synced with the recoverable key store, wrapped by the primary key. This allows them to be
   * recovered on a user's subsequent device through providing their lock screen secret.
   */
  public class RecoverableKeyStoreSecondaryKey {
      private static final String TAG = "RecoverableKeyStoreSecondaryKey";
  
      private final String mAlias;
      private final SecretKey mSecretKey;
  
      /**
       * A new instance.
       *
       * @param alias The alias. It is keyed with this in AndroidKeyStore and the recoverable key
       *     store.
       * @param secretKey The key.
       */
      public RecoverableKeyStoreSecondaryKey(String alias, SecretKey secretKey) {
          mAlias = checkNotNull(alias);
          mSecretKey = checkNotNull(secretKey);
      }
  
      /**
       * The ID, as stored in the recoverable {@link java.security.KeyStore}, and as used to identify
       * wrapped tertiary keys on the backup server.
       */
      public String getAlias() {
          return mAlias;
      }
  
      /** The secret key, to be used to wrap tertiary keys. */
      public SecretKey getSecretKey() {
          return mSecretKey;
      }
  
      /**
       * The status of the key. i.e., whether it's been synced to remote trusted hardware.
       *
       * @param context The application context.
       * @return One of {@link Status#SYNCED}, {@link Status#NOT_SYNCED} or {@link Status#DESTROYED}.
       */
      public @Status int getStatus(Context context) {
          try {
              return getStatusInternal(context);
          } catch (InternalRecoveryServiceException e) {
              Slog.wtf(TAG, "Internal error getting recovery status", e);
              // Return NOT_SYNCED by default, as we do not want the backups to fail or to repeatedly
              // attempt to reinitialize.
              return Status.NOT_SYNCED;
          }
      }
  
      private @Status int getStatusInternal(Context context) throws InternalRecoveryServiceException {
          int status = RecoveryController.getInstance(context).getRecoveryStatus(mAlias);
          switch (status) {
              case RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE:
                  return Status.DESTROYED;
              case RecoveryController.RECOVERY_STATUS_SYNCED:
                  return Status.SYNCED;
              case RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS:
                  return Status.NOT_SYNCED;
              default:
                  // Throw an exception if we encounter a status that doesn't match any of the above.
                  throw new InternalRecoveryServiceException(
                          "Unexpected status from getRecoveryStatus: " + status);
          }
      }
  
      /** Status of a key in the recoverable key store. */
      @IntDef({Status.NOT_SYNCED, Status.SYNCED, Status.DESTROYED})
      public @interface Status {
          /**
           * The key has not yet been synced to remote trusted hardware. This may be because the user
           * has not yet unlocked their device.
           */
          int NOT_SYNCED = 1;
  
          /**
           * The key has been synced with remote trusted hardware. It should now be recoverable on
           * another device.
           */
          int SYNCED = 2;
  
          /** The key has been lost forever. This can occur if the user disables their lock screen. */
          int DESTROYED = 3;
      }
  }
  