content: /*
   * Copyright (C) 2017 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 android.security.keystore.recovery;
  
  import android.annotation.NonNull;
  import android.annotation.SystemApi;
  import android.os.BadParcelableException;
  import android.os.Parcel;
  import android.os.Parcelable;
  
  import com.android.internal.util.Preconditions;
  
  import java.security.cert.CertPath;
  import java.security.cert.CertificateException;
  import java.util.List;
  
  /**
   * A snapshot of a version of the keystore. Two events can trigger the generation of a new snapshot:
   *
   * <ul>
   *     <li>The user's lock screen changes. (A key derived from the user's lock screen is used to
   *         protected the keychain, which is why this forces a new snapshot.)
   *     <li>A key is added to or removed from the recoverable keychain.
   * </ul>
   *
   * <p>The snapshot data is also encrypted with the remote trusted hardware's public key, so even
   * the recovery agent itself should not be able to decipher the data. The recovery agent sends an
   * instance of this to the remote trusted hardware whenever a new snapshot is generated. During a
   * recovery flow, the recovery agent retrieves a snapshot from the remote trusted hardware. It then
   * sends it to the framework, where it is decrypted using the user's lock screen from their previous
   * device.
   *
   * @hide
   */
  @SystemApi
  public final class KeyChainSnapshot implements Parcelable {
  
      // IMPORTANT! PLEASE READ!
      // -----------------------
      // If you edit this file (e.g., to add new fields), please MAKE SURE to also do the following:
      // - Update the #writeToParcel(Parcel) method below
      // - Update the #(Parcel) constructor below
      // - Update android.security.keystore.recovery.KeyChainSnapshotTest to make sure nobody
      //     accidentally breaks your fields in the Parcel in the future.
      // - Update com.android.server.locksettings.recoverablekeystore.serialization
      //     .KeyChainSnapshotSerializer to correctly serialize your new field
      // - Update com.android.server.locksettings.recoverablekeystore.serialization
      //     .KeyChainSnapshotSerializer to correctly deserialize your new field
      // - Update com.android.server.locksettings.recoverablekeystore.serialization
      //     .KeychainSnapshotSerializerTest to make sure nobody breaks serialization of your field
      //     in the future.
  
      private static final int DEFAULT_MAX_ATTEMPTS = 10;
      private static final long DEFAULT_COUNTER_ID = 1L;
  
      private int mSnapshotVersion;
      private int mMaxAttempts = DEFAULT_MAX_ATTEMPTS;
      private long mCounterId = DEFAULT_COUNTER_ID;
      private byte[] mServerParams;
      private RecoveryCertPath mCertPath;  // The cert path including necessary intermediate certs
      private List<KeyChainProtectionParams> mKeyChainProtectionParams;
      private List<WrappedApplicationKey> mEntryRecoveryData;
      private byte[] mEncryptedRecoveryKeyBlob;
  
      /**
       * Use builder to create an instance of the class.
       */
      private KeyChainSnapshot() {
  
      }
  
      /**
       * Snapshot version for given recovery agent. It is incremented when user secret or list of
       * application keys changes.
       */
      public int getSnapshotVersion() {
          return mSnapshotVersion;
      }
  
      /**
       * Number of user secret guesses allowed during KeyChain recovery.
       */
      public int getMaxAttempts() {
          return mMaxAttempts;
      }
  
      /**
       * CounterId which is rotated together with user secret.
       */
      public long getCounterId() {
          return mCounterId;
      }
  
      /**
       * Server parameters.
       */
      public @NonNull byte[] getServerParams() {
          return mServerParams;
      }
  
      /**
       * CertPath containing the public key used to encrypt {@code encryptedRecoveryKeyBlob}.
       */
      public @NonNull CertPath getTrustedHardwareCertPath() {
          try {
              return mCertPath.getCertPath();
          } catch (CertificateException e) {
              // Rethrow an unchecked exception as it should not happen. If such an issue exists,
              // an exception should have been thrown during service initialization.
              throw new BadParcelableException(e);
          }
      }
  
      /**
       * UI and key derivation parameters. Note that combination of secrets may be used.
       */
      public @NonNull List<KeyChainProtectionParams> getKeyChainProtectionParams() {
          return mKeyChainProtectionParams;
      }
  
      /**
       * List of application keys, with key material encrypted by
       * the recovery key ({@link #getEncryptedRecoveryKeyBlob}).
       */
      public @NonNull List<WrappedApplicationKey> getWrappedApplicationKeys() {
          return mEntryRecoveryData;
      }
  
      /**
       * Recovery key blob, encrypted by user secret and recovery service public key.
       */
      public @NonNull byte[] getEncryptedRecoveryKeyBlob() {
          return mEncryptedRecoveryKeyBlob;
      }
  
      public static final @NonNull Creator<KeyChainSnapshot> CREATOR =
              new Creator<KeyChainSnapshot>() {
          public KeyChainSnapshot createFromParcel(Parcel in) {
              return new KeyChainSnapshot(in);
          }
  
          public KeyChainSnapshot[] newArray(int length) {
              return new KeyChainSnapshot[length];
          }
      };
  
      /**
       * Builder for creating {@link KeyChainSnapshot}.
       * @hide
       */
      public static class Builder {
          private KeyChainSnapshot mInstance = new KeyChainSnapshot();
  
          /**
           * Snapshot version for the recovery agent.
           *
           * @param snapshotVersion The snapshot version
           * @return This builder.
           */
          public @NonNull Builder setSnapshotVersion(int snapshotVersion) {
              mInstance.mSnapshotVersion = snapshotVersion;
              return this;
          }
  
          /**
           * Sets the number of user secret guesses allowed during Keychain recovery.
           *
           * @param maxAttempts The maximum number of guesses.
           * @return This builder.
           */
          public @NonNull Builder setMaxAttempts(int maxAttempts) {
              mInstance.mMaxAttempts = maxAttempts;
              return this;
          }
  
          /**
           * Sets counter id.
           *
           * @param counterId The counter id.
           * @return This builder.
           */
          public @NonNull Builder setCounterId(long counterId) {
              mInstance.mCounterId = counterId;
              return this;
          }
  
          /**
           * Sets server parameters.
           *
           * @param serverParams The server parameters
           * @return This builder.
           */
          public @NonNull Builder setServerParams(byte[] serverParams) {
              mInstance.mServerParams = serverParams;
              return this;
          }
  
          /**
           * Sets CertPath used to validate the trusted hardware public key. The CertPath should
           * contain a certificate of the trusted hardware public key and any necessary intermediate
           * certificates.
           *
           * @param certPath The certificate path
           * @throws CertificateException if the given certificate path cannot be encoded properly
           * @return This builder.
           */
          public @NonNull Builder setTrustedHardwareCertPath(@NonNull CertPath certPath)
                  throws CertificateException {
              mInstance.mCertPath = RecoveryCertPath.createRecoveryCertPath(certPath);
              return this;
          }
  
          /**
           * Sets UI and key derivation parameters
           *
           * @param keyChainProtectionParams The UI and key derivation parameters
           * @return This builder.
           */
          public @NonNull Builder setKeyChainProtectionParams(
                  @NonNull List<KeyChainProtectionParams> keyChainProtectionParams) {
              mInstance.mKeyChainProtectionParams = keyChainProtectionParams;
              return this;
          }
  
          /**
           * List of application keys.
           *
           * @param entryRecoveryData List of application keys
           * @return This builder.
           */
          public @NonNull Builder setWrappedApplicationKeys(
                  @NonNull List<WrappedApplicationKey> entryRecoveryData) {
              mInstance.mEntryRecoveryData = entryRecoveryData;
              return this;
          }
  
          /**
           * Sets recovery key blob.
           *
           * @param encryptedRecoveryKeyBlob The recovery key blob.
           * @return This builder.
           */
          public @NonNull Builder setEncryptedRecoveryKeyBlob(
                  @NonNull byte[] encryptedRecoveryKeyBlob) {
              mInstance.mEncryptedRecoveryKeyBlob = encryptedRecoveryKeyBlob;
              return this;
          }
  
  
          /**
           * Creates a new {@link KeyChainSnapshot} instance.
           *
           * @return new instance
           * @throws NullPointerException if some of the required fields were not set.
           */
          public @NonNull KeyChainSnapshot build() {
              Preconditions.checkCollectionElementsNotNull(mInstance.mKeyChainProtectionParams,
                      "keyChainProtectionParams");
              Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData,
                      "entryRecoveryData");
              Preconditions.checkNotNull(mInstance.mEncryptedRecoveryKeyBlob);
              Preconditions.checkNotNull(mInstance.mServerParams);
              Preconditions.checkNotNull(mInstance.mCertPath);
              return mInstance;
          }
      }
  
      @Override
      public void writeToParcel(Parcel out, int flags) {
          out.writeInt(mSnapshotVersion);
          out.writeTypedList(mKeyChainProtectionParams);
          out.writeByteArray(mEncryptedRecoveryKeyBlob);
          out.writeTypedList(mEntryRecoveryData);
          out.writeInt(mMaxAttempts);
          out.writeLong(mCounterId);
          out.writeByteArray(mServerParams);
          out.writeTypedObject(mCertPath, /* no flags */ 0);
      }
  
      /**
       * @hide
       */
      protected KeyChainSnapshot(Parcel in) {
          mSnapshotVersion = in.readInt();
          mKeyChainProtectionParams = in.createTypedArrayList(KeyChainProtectionParams.CREATOR);
          mEncryptedRecoveryKeyBlob = in.createByteArray();
          mEntryRecoveryData = in.createTypedArrayList(WrappedApplicationKey.CREATOR);
          mMaxAttempts = in.readInt();
          mCounterId = in.readLong();
          mServerParams = in.createByteArray();
          mCertPath = in.readTypedObject(RecoveryCertPath.CREATOR);
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  }
  