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.net;
  
  import android.annotation.NonNull;
  import android.annotation.StringDef;
  import android.os.Build;
  import android.os.Parcel;
  import android.os.Parcelable;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.util.HexDump;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.Arrays;
  
  /**
   * This class represents a single algorithm that can be used by an {@link IpSecTransform}.
   *
   * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
   * Internet Protocol</a>
   */
  public final class IpSecAlgorithm implements Parcelable {
      private static final String TAG = "IpSecAlgorithm";
  
      /**
       * Null cipher.
       *
       * @hide
       */
      public static final String CRYPT_NULL = "ecb(cipher_null)";
  
      /**
       * AES-CBC Encryption/Ciphering Algorithm.
       *
       * <p>Valid lengths for this key are {128, 192, 256}.
       */
      public static final String CRYPT_AES_CBC = "cbc(aes)";
  
      /**
       * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
       * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
       *
       * <p>Keys for this algorithm must be 128 bits in length.
       *
       * <p>Valid truncation lengths are multiples of 8 bits from 96 to 128.
       */
      public static final String AUTH_HMAC_MD5 = "hmac(md5)";
  
      /**
       * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
       * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
       *
       * <p>Keys for this algorithm must be 160 bits in length.
       *
       * <p>Valid truncation lengths are multiples of 8 bits from 96 to 160.
       */
      public static final String AUTH_HMAC_SHA1 = "hmac(sha1)";
  
      /**
       * SHA256 HMAC Authentication/Integrity Algorithm.
       *
       * <p>Keys for this algorithm must be 256 bits in length.
       *
       * <p>Valid truncation lengths are multiples of 8 bits from 96 to 256.
       */
      public static final String AUTH_HMAC_SHA256 = "hmac(sha256)";
  
      /**
       * SHA384 HMAC Authentication/Integrity Algorithm.
       *
       * <p>Keys for this algorithm must be 384 bits in length.
       *
       * <p>Valid truncation lengths are multiples of 8 bits from 192 to 384.
       */
      public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
  
      /**
       * SHA512 HMAC Authentication/Integrity Algorithm.
       *
       * <p>Keys for this algorithm must be 512 bits in length.
       *
       * <p>Valid truncation lengths are multiples of 8 bits from 256 to 512.
       */
      public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
  
      /**
       * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
       *
       * <p>Valid lengths for keying material are {160, 224, 288}.
       *
       * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section
       * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
       * salt. RFC compliance requires that the salt must be unique per invocation with the same key.
       *
       * <p>Valid ICV (truncation) lengths are {64, 96, 128}.
       */
      public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
  
      /** @hide */
      @StringDef({
          CRYPT_AES_CBC,
          AUTH_HMAC_MD5,
          AUTH_HMAC_SHA1,
          AUTH_HMAC_SHA256,
          AUTH_HMAC_SHA384,
          AUTH_HMAC_SHA512,
          AUTH_CRYPT_AES_GCM
      })
      @Retention(RetentionPolicy.SOURCE)
      public @interface AlgorithmName {}
  
      private final String mName;
      private final byte[] mKey;
      private final int mTruncLenBits;
  
      /**
       * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
       * defined as constants in this class.
       *
       * <p>For algorithms that produce an integrity check value, the truncation length is a required
       * parameter. See {@link #IpSecAlgorithm(String algorithm, byte[] key, int truncLenBits)}
       *
       * @param algorithm name of the algorithm.
       * @param key key padded to a multiple of 8 bits.
       */
      public IpSecAlgorithm(@NonNull @AlgorithmName String algorithm, @NonNull byte[] key) {
          this(algorithm, key, 0);
      }
  
      /**
       * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
       * defined as constants in this class.
       *
       * <p>This constructor only supports algorithms that use a truncation length. i.e.
       * Authentication and Authenticated Encryption algorithms.
       *
       * @param algorithm name of the algorithm.
       * @param key key padded to a multiple of 8 bits.
       * @param truncLenBits number of bits of output hash to use.
       */
      public IpSecAlgorithm(
              @NonNull @AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) {
          mName = algorithm;
          mKey = key.clone();
          mTruncLenBits = truncLenBits;
          checkValidOrThrow(mName, mKey.length * 8, mTruncLenBits);
      }
  
      /** Get the algorithm name */
      @NonNull
      public String getName() {
          return mName;
      }
  
      /** Get the key for this algorithm */
      @NonNull
      public byte[] getKey() {
          return mKey.clone();
      }
  
      /** Get the truncation length of this algorithm, in bits */
      public int getTruncationLengthBits() {
          return mTruncLenBits;
      }
  
      /* Parcelable Implementation */
      public int describeContents() {
          return 0;
      }
  
      /** Write to parcel */
      public void writeToParcel(Parcel out, int flags) {
          out.writeString(mName);
          out.writeByteArray(mKey);
          out.writeInt(mTruncLenBits);
      }
  
      /** Parcelable Creator */
      public static final @android.annotation.NonNull Parcelable.Creator<IpSecAlgorithm> CREATOR =
              new Parcelable.Creator<IpSecAlgorithm>() {
                  public IpSecAlgorithm createFromParcel(Parcel in) {
                      final String name = in.readString();
                      final byte[] key = in.createByteArray();
                      final int truncLenBits = in.readInt();
  
                      return new IpSecAlgorithm(name, key, truncLenBits);
                  }
  
                  public IpSecAlgorithm[] newArray(int size) {
                      return new IpSecAlgorithm[size];
                  }
              };
  
      private static void checkValidOrThrow(String name, int keyLen, int truncLen) {
          boolean isValidLen = true;
          boolean isValidTruncLen = true;
  
          switch(name) {
              case CRYPT_AES_CBC:
                  isValidLen = keyLen == 128 || keyLen == 192 || keyLen == 256;
                  break;
              case AUTH_HMAC_MD5:
                  isValidLen = keyLen == 128;
                  isValidTruncLen = truncLen >= 96 && truncLen <= 128;
                  break;
              case AUTH_HMAC_SHA1:
                  isValidLen = keyLen == 160;
                  isValidTruncLen = truncLen >= 96 && truncLen <= 160;
                  break;
              case AUTH_HMAC_SHA256:
                  isValidLen = keyLen == 256;
                  isValidTruncLen = truncLen >= 96 && truncLen <= 256;
                  break;
              case AUTH_HMAC_SHA384:
                  isValidLen = keyLen == 384;
                  isValidTruncLen = truncLen >= 192 && truncLen <= 384;
                  break;
              case AUTH_HMAC_SHA512:
                  isValidLen = keyLen == 512;
                  isValidTruncLen = truncLen >= 256 && truncLen <= 512;
                  break;
              case AUTH_CRYPT_AES_GCM:
                  // The keying material for GCM is a key plus a 32-bit salt
                  isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32;
                  isValidTruncLen = truncLen == 64 || truncLen == 96 || truncLen == 128;
                  break;
              default:
                  throw new IllegalArgumentException("Couldn't find an algorithm: " + name);
          }
  
          if (!isValidLen) {
              throw new IllegalArgumentException("Invalid key material keyLength: " + keyLen);
          }
          if (!isValidTruncLen) {
              throw new IllegalArgumentException("Invalid truncation keyLength: " + truncLen);
          }
      }
  
      /** @hide */
      public boolean isAuthentication() {
          switch (getName()) {
              // Fallthrough
              case AUTH_HMAC_MD5:
              case AUTH_HMAC_SHA1:
              case AUTH_HMAC_SHA256:
              case AUTH_HMAC_SHA384:
              case AUTH_HMAC_SHA512:
                  return true;
              default:
                  return false;
          }
      }
  
      /** @hide */
      public boolean isEncryption() {
          return getName().equals(CRYPT_AES_CBC);
      }
  
      /** @hide */
      public boolean isAead() {
          return getName().equals(AUTH_CRYPT_AES_GCM);
      }
  
      // Because encryption keys are sensitive and userdebug builds are used by large user pools
      // such as beta testers, we only allow sensitive info such as keys on eng builds.
      private static boolean isUnsafeBuild() {
          return Build.IS_DEBUGGABLE && Build.IS_ENG;
      }
  
      @Override
      @NonNull
      public String toString() {
          return new StringBuilder()
                  .append("{mName=")
                  .append(mName)
                  .append(", mKey=")
                  .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>")
                  .append(", mTruncLenBits=")
                  .append(mTruncLenBits)
                  .append("}")
                  .toString();
      }
  
      /** @hide */
      @VisibleForTesting
      public static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) {
          if (lhs == null || rhs == null) return (lhs == rhs);
          return (lhs.mName.equals(rhs.mName)
                  && Arrays.equals(lhs.mKey, rhs.mKey)
                  && lhs.mTruncLenBits == rhs.mTruncLenBits);
      }
  };
  