content: /*
   * Copyright (C) 2019 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.ike.ikev2.message;
  
  import com.android.ike.ikev2.exceptions.IkeException;
  import com.android.internal.annotations.VisibleForTesting;
  
  import java.nio.ByteBuffer;
  import java.security.GeneralSecurityException;
  import java.security.SecureRandom;
  import java.util.Arrays;
  
  import javax.crypto.Cipher;
  import javax.crypto.Mac;
  import javax.crypto.SecretKey;
  import javax.crypto.spec.IvParameterSpec;
  
  /**
   * IkeEncryptedPayloadBody is a package private class that represents an IKE payload substructure
   * that contains initialization vector, encrypted content, padding, pad length and integrity
   * checksum.
   *
   * <p>Both an Encrypted Payload (IkeSkPayload) and an EncryptedFragmentPayload (IkeSkfPayload)
   * consists of an IkeEncryptedPayloadBody instance.
   *
   * @see <a href="https://tools.ietf.org/html/rfc7296#page-105">RFC 7296, Internet Key Exchange
   *     Protocol Version 2 (IKEv2)</a>
   * @see <a href="https://tools.ietf.org/html/rfc7383#page-6">RFC 7383, Internet Key Exchange
   *     Protocol Version 2 (IKEv2) Message Fragmentation</a>
   */
  final class IkeEncryptedPayloadBody {
      // Length of pad length field.
      private static final int PAD_LEN_LEN = 1;
  
      private final byte[] mUnencryptedData;
      private final byte[] mEncryptedAndPaddedData;
      private final byte[] mIv;
      private final byte[] mIntegrityChecksum;
  
      /**
       * Package private constructor for constructing an instance of IkeEncryptedPayloadBody from
       * decrypting an incoming packet.
       */
      IkeEncryptedPayloadBody(
              byte[] message, Mac integrityMac, int checksumLen, Cipher decryptCipher, SecretKey dKey)
              throws IkeException, GeneralSecurityException {
          ByteBuffer inputBuffer = ByteBuffer.wrap(message);
  
          // Skip IKE header and SK payload header
          byte[] tempArray = new byte[IkeHeader.IKE_HEADER_LENGTH + IkePayload.GENERIC_HEADER_LENGTH];
          inputBuffer.get(tempArray);
  
          // Extract bytes for authentication and decryption.
          int expectedIvLen = decryptCipher.getBlockSize();
          mIv = new byte[expectedIvLen];
  
          int encryptedDataLen =
                  message.length
                          - (IkeHeader.IKE_HEADER_LENGTH
                                  + IkePayload.GENERIC_HEADER_LENGTH
                                  + expectedIvLen
                                  + checksumLen);
          // IkeMessage will catch exception if encryptedDataLen is negative.
          mEncryptedAndPaddedData = new byte[encryptedDataLen];
  
          mIntegrityChecksum = new byte[checksumLen];
          inputBuffer.get(mIv).get(mEncryptedAndPaddedData).get(mIntegrityChecksum);
  
          // Authenticate and decrypt.
          byte[] dataToAuthenticate = Arrays.copyOfRange(message, 0, message.length - checksumLen);
          validateChecksumOrThrow(dataToAuthenticate, integrityMac, mIntegrityChecksum);
          mUnencryptedData = decrypt(mEncryptedAndPaddedData, decryptCipher, dKey, mIv);
      }
  
      /**
       * Package private constructor for constructing an instance of IkeEncryptedPayloadBody for
       * building an outbound packet.
       */
      IkeEncryptedPayloadBody(
              IkeHeader ikeHeader,
              @IkePayload.PayloadType int firstPayloadType,
              byte[] unencryptedPayloads,
              Mac integrityMac,
              int checksumLen,
              Cipher encryptCipher,
              SecretKey eKey) {
          this(
                  ikeHeader,
                  firstPayloadType,
                  unencryptedPayloads,
                  integrityMac,
                  checksumLen,
                  encryptCipher,
                  eKey,
                  encryptCipher.getIV(),
                  calculatePadding(unencryptedPayloads.length, encryptCipher.getBlockSize()));
      }
  
      /** Package private constructor only for testing. */
      @VisibleForTesting
      IkeEncryptedPayloadBody(
              IkeHeader ikeHeader,
              @IkePayload.PayloadType int firstPayloadType,
              byte[] unencryptedPayloads,
              Mac integrityMac,
              int checksumLen,
              Cipher encryptCipher,
              SecretKey eKey,
              byte[] iv,
              byte[] padding) {
          mUnencryptedData = unencryptedPayloads;
  
          // Encrypt data
          mIv = iv;
          mEncryptedAndPaddedData = encrypt(unencryptedPayloads, encryptCipher, eKey, iv, padding);
  
          // Build authenticated section using ByteBuffer. Authenticated section includes bytes from
          // beginning of IKE header to the pad length, which are concatenation of IKE header, current
          // payload header, iv and encrypted and padded data.
          int dataToAuthenticateLength =
                  IkeHeader.IKE_HEADER_LENGTH
                          + IkePayload.GENERIC_HEADER_LENGTH
                          + iv.length
                          + mEncryptedAndPaddedData.length;
          ByteBuffer authenticatedSectionBuffer = ByteBuffer.allocate(dataToAuthenticateLength);
  
          // Encode IKE header
          int encryptedPayloadLength =
                  IkePayload.GENERIC_HEADER_LENGTH
                          + iv.length
                          + mEncryptedAndPaddedData.length
                          + checksumLen;
          ikeHeader.encodeToByteBuffer(authenticatedSectionBuffer, encryptedPayloadLength);
  
          // Encode payload header. The next payload type field indicates the first payload nested in
          // this SkPayload/SkfPayload.
          int payloadLength =
                  IkePayload.GENERIC_HEADER_LENGTH
                          + iv.length
                          + mEncryptedAndPaddedData.length
                          + checksumLen;
          IkePayload.encodePayloadHeaderToByteBuffer(
                  firstPayloadType, payloadLength, authenticatedSectionBuffer);
  
          // Encode iv and padded encrypted data.
          authenticatedSectionBuffer.put(iv).put(mEncryptedAndPaddedData);
  
          // Calculate checksum
          mIntegrityChecksum =
                  calculateChecksum(authenticatedSectionBuffer.array(), integrityMac, checksumLen);
      }
  
      // TODO: Add another constructor for AEAD protected payload.
  
      // TODO: Add constructors that initiate IkeEncryptedPayloadBody for an outbound packet
  
      /** Package private for testing */
      @VisibleForTesting
      static byte[] calculateChecksum(byte[] dataToAuthenticate, Mac integrityMac, int checksumLen) {
          ByteBuffer inputBuffer = ByteBuffer.wrap(dataToAuthenticate);
          integrityMac.update(inputBuffer);
          byte[] calculatedChecksum = Arrays.copyOfRange(integrityMac.doFinal(), 0, checksumLen);
          return calculatedChecksum;
      }
  
      /** Package private for testing */
      @VisibleForTesting
      static void validateChecksumOrThrow(
              byte[] dataToAuthenticate, Mac integrityMac, byte[] integrityChecksum)
              throws GeneralSecurityException {
          // TODO: Make it package private and add test.
          int checkSumLen = integrityChecksum.length;
          byte[] calculatedChecksum =
                  calculateChecksum(dataToAuthenticate, integrityMac, checkSumLen);
  
          if (!Arrays.equals(integrityChecksum, calculatedChecksum)) {
              throw new GeneralSecurityException("Message authentication failed.");
          }
      }
  
      /** Package private for testing */
      @VisibleForTesting
      static byte[] encrypt(
              byte[] dataToEncrypt, Cipher encryptCipher, SecretKey eKey, byte[] iv, byte[] padding) {
          int padLength = padding.length;
          int paddedDataLength = dataToEncrypt.length + padLength + PAD_LEN_LEN;
          ByteBuffer inputBuffer = ByteBuffer.allocate(paddedDataLength);
          inputBuffer.put(dataToEncrypt).put(padding).put((byte) padLength);
          inputBuffer.rewind();
  
          try {
              // Encrypt data.
              ByteBuffer outputBuffer = ByteBuffer.allocate(paddedDataLength);
              encryptCipher.init(Cipher.ENCRYPT_MODE, eKey, new IvParameterSpec(iv));
              encryptCipher.doFinal(inputBuffer, outputBuffer);
              return outputBuffer.array();
          } catch (GeneralSecurityException e) {
              throw new IllegalArgumentException("Fail to encrypt IKE message. ", e);
          }
      }
  
      /** Package private for testing */
      @VisibleForTesting
      static byte[] decrypt(byte[] encryptedData, Cipher decryptCipher, SecretKey dKey, byte[] iv)
              throws GeneralSecurityException {
          // TODO: Make it package private and add test.
          decryptCipher.init(Cipher.DECRYPT_MODE, dKey, new IvParameterSpec(iv));
  
          ByteBuffer inputBuffer = ByteBuffer.wrap(encryptedData);
          ByteBuffer outputBuffer = ByteBuffer.allocate(encryptedData.length);
          decryptCipher.doFinal(inputBuffer, outputBuffer);
  
          // Remove padding
          outputBuffer.rewind();
          int padLength = Byte.toUnsignedInt(outputBuffer.get(encryptedData.length - PAD_LEN_LEN));
          byte[] decryptedData = new byte[encryptedData.length - padLength - PAD_LEN_LEN];
  
          outputBuffer.get(decryptedData);
          return decryptedData;
      }
  
      /** Package private for testing */
      @VisibleForTesting
      static byte[] calculatePadding(int dataToEncryptLength, int blockSize) {
          // Sum of dataToEncryptLength, PAD_LEN_LEN and padLength should be aligned with block size.
          int unpaddedLen = dataToEncryptLength + PAD_LEN_LEN;
          int padLength = (unpaddedLen + blockSize - 1) / blockSize * blockSize - unpaddedLen;
          byte[] padding = new byte[padLength];
  
          // According to RFC 7296, "Padding MAY contain any value".
          new SecureRandom().nextBytes(padding);
  
          return padding;
      }
  
      /** Package private */
      byte[] getUnencryptedData() {
          return mUnencryptedData;
      }
  
      /** Package private */
      int getLength() {
          return (mIv.length + mEncryptedAndPaddedData.length + mIntegrityChecksum.length);
      }
  
      /** Package private */
      byte[] encode() {
          ByteBuffer buffer = ByteBuffer.allocate(getLength());
          buffer.put(mIv).put(mEncryptedAndPaddedData).put(mIntegrityChecksum);
          return buffer.array();
      }
  }
  