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;
  
  import com.android.ike.ikev2.message.IkeMessage;
  import com.android.ike.ikev2.message.IkePayload;
  import com.android.internal.annotations.VisibleForTesting;
  
  import java.nio.ByteBuffer;
  import java.security.InvalidKeyException;
  import java.security.NoSuchAlgorithmException;
  import java.util.List;
  
  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;
  
  /**
   * SaRecord represents common information of an IKE SA and a Child SA.
   *
   * <p>When doing rekey, there can be multiple SAs in the same IkeSessionStateMachine or
   * ChildSessionStateMachine, where they use same cryptographic algorithms but with different keys.
   * We store cryptographic algorithms and unchanged SA configurations in IkeSessionOptions or
   * ChildSessionOptions and store changed information including keys, SPIs, and nonces in SaRecord.
   */
  public abstract class SaRecord {
  
      private static ISaRecordHelper sSaRecordHelper = new SaRecordHelper();
  
      public final byte[] nonceInitiator;
      public final byte[] nonceResponder;
  
      /** Package private */
      SaRecord(byte[] nonceInit, byte[] nonceResp) {
          nonceInitiator = nonceInit;
          nonceResponder = nonceResp;
      }
  
      /**
       * SaRecordHelper implements methods for constructing SaRecord.
       *
       * <p>Package private
       */
      static class SaRecordHelper implements ISaRecordHelper {
          @Override
          public IkeSaRecord makeFirstIkeSaRecord(IkeMessage initRequest, IkeMessage initResponse) {
              // TODO: Generate keying materials
              return null;
          }
  
          @Override
          public IkeSaRecord makeNewIkeSaRecord(
                  IkeSaRecord oldSaRecord, IkeMessage rekeyRequest, IkeMessage rekeyResponse) {
              // TODO: Generate keying materials based on old SK_d
              return null;
          }
  
          @Override
          public ChildSaRecord makeChildSaRecord(
                  List<IkePayload> reqPayloads, List<IkePayload> respPayloads) {
              // TODO: Calculate keys and build IpSecTransform.
              return null;
          }
      }
  
      /** Package private */
      static void setSaRecordHelper(ISaRecordHelper helper) {
          sSaRecordHelper = helper;
      }
  
      /** IkeSaRecord represents an IKE SA. */
      public static class IkeSaRecord extends SaRecord implements Comparable<IkeSaRecord> {
  
          /** SPI of IKE SA initiator */
          public final long initiatorSpi;
          /** SPI of IKE SA responder */
          public final long responderSpi;
          /** Flag indicates if this IKE SA is locally initiated */
          public final boolean isLocalInit;
  
          /** Package private */
          IkeSaRecord(
                  long initSpi, long respSpi, boolean localInit, byte[] nonceInit, byte[] nonceResp) {
              super(nonceInit, nonceResp);
              initiatorSpi = initSpi;
              responderSpi = respSpi;
              isLocalInit = localInit;
              // TODO: Impement constructor. There will be more input parameters.
          }
  
          /** Package private */
          static IkeSaRecord makeFirstIkeSaRecord(IkeMessage initRequest, IkeMessage initResponse) {
              return sSaRecordHelper.makeFirstIkeSaRecord(initRequest, initResponse);
          }
  
          /** Package private */
          static IkeSaRecord makeNewIkeSaRecord(
                  IkeSaRecord oldSaRecord, IkeMessage rekeyRequest, IkeMessage rekeyResponse) {
              return sSaRecordHelper.makeNewIkeSaRecord(oldSaRecord, rekeyRequest, rekeyResponse);
          }
  
          /** Package private */
          long getRemoteSpi() {
              if (isLocalInit) {
                  return responderSpi;
              } else {
                  return initiatorSpi;
              }
          }
  
          /**
           * Compare with a specific IkeSaRecord
           *
           * @param record IkeSaRecord to be compared.
           * @return a negative integer if input IkeSaRecord contains lowest nonce; a positive integer
           *     if this IkeSaRecord has lowest nonce; return zero if lowest nonces of two
           *     IkeSaRecords match.
           */
          public int compareTo(IkeSaRecord record) {
              // TODO: Implement it b/122924815.
              return 1;
          }
      }
  
      /** ChildSaRecord represents an Child SA. */
      public static class ChildSaRecord extends SaRecord implements Comparable<ChildSaRecord> {
  
          /** Locally generated SPI for receiving IPsec Packet. */
          public final int inboundSpi;
          /** Remotely generated SPI for sending IPsec Packet. */
          public final int outboundSpi;
  
          /** Package private */
          ChildSaRecord(int inSpi, int outSpi, byte[] nonceInit, byte[] nonceResp) {
              super(nonceInit, nonceResp);
              inboundSpi = inSpi;
              outboundSpi = outSpi;
              // TODO: Impement constructor. Will be more input parameters.
          }
  
          /** Package private */
          static ChildSaRecord makeChildSaRecord(
                  List<IkePayload> reqPayloads, List<IkePayload> respPayloads) {
              return sSaRecordHelper.makeChildSaRecord(reqPayloads, respPayloads);
          }
  
          /**
           * Compare with a specific ChildSaRecord
           *
           * @param record ChildSaRecord to be compared.
           * @return a negative integer if input ChildSaRecord contains lowest nonce; a positive
           *     integer if this ChildSaRecord has lowest nonce; return zero if lowest nonces of two
           *     ChildSaRecord match.
           */
          public int compareTo(ChildSaRecord record) {
              // TODO: Implement it b/122924815
              return 1;
          }
      }
  
      /**
       * ISaRecordHelper provides a package private interface for constructing SaRecord.
       *
       * <p>ISaRecordHelper exists so that the interface is injectable for testing.
       */
      interface ISaRecordHelper {
          /**
           * Construct IkeSaRecord as results of IKE initial exchange.
           *
           * @param initRequest IKE_INIT request.
           * @param initResponse IKE_INIT request.
           * @return ikeSaRecord for initial IKE SA.
           */
          IkeSaRecord makeFirstIkeSaRecord(IkeMessage initRequest, IkeMessage initResponse);
  
          /**
           * Construct new IkeSaRecord when doing rekey.
           *
           * @param oldSaRecord old IKE SA
           * @param rekeyRequest Rekey IKE request.
           * @param rekeyResponse Rekey IKE response.
           * @return ikeSaRecord for new IKE SA.
           */
          IkeSaRecord makeNewIkeSaRecord(
                  IkeSaRecord oldSaRecord, IkeMessage rekeyRequest, IkeMessage rekeyResponse);
  
          /**
           * Construct ChildSaRecord and generate IpSecTransform pairs.
           *
           * @param reqPayloads payload list in request.
           * @param respPayloads payload list in response.
           * @return new Child SA.
           */
          ChildSaRecord makeChildSaRecord(
                  List<IkePayload> reqPayloads, List<IkePayload> respPayloads);
      }
  
      /** Generate SKEYSEED using negotiated PRF. */
      @VisibleForTesting
      static byte[] generateSKeySeed(
              String prfAlgorithm, byte[] nonceInit, byte[] nonceResp, byte[] sharedDhKey) {
          try {
              ByteBuffer keyBuffer = ByteBuffer.allocate(nonceInit.length + nonceResp.length);
              keyBuffer.put(nonceInit).put(nonceResp);
              SecretKeySpec prfKeySpec = new SecretKeySpec(keyBuffer.array(), prfAlgorithm);
  
              Mac prfMac = Mac.getInstance(prfAlgorithm, IkeMessage.getSecurityProvider());
              prfMac.init(prfKeySpec);
  
              ByteBuffer sharedKeyBuffer = ByteBuffer.wrap(sharedDhKey);
              prfMac.update(sharedKeyBuffer);
  
              return prfMac.doFinal();
          } catch (InvalidKeyException | NoSuchAlgorithmException e) {
              throw new IllegalArgumentException("Failed to generate SKEYSEED", e);
          }
      }
  
      /**
       * Derives key materials using negotiated PRF.
       *
       * <p>prf+(K, S) outputs a pseudorandom stream by using negotiated PRF iteratively. In this way
       * it can generate long enough keying material containing all the keys for this IKE/Child SA.
       *
       * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.13">RFC 7296 nternet Key Exchange
       *     Protocol Version 2 (IKEv2) 2.13. Generating Keying Material </a>
       */
      @VisibleForTesting
      static byte[] generateKeyMat(
              String prfAlgorithm, byte[] prfKey, byte[] dataToSign, int keyMaterialLen)
              throws InvalidKeyException {
          try {
              SecretKeySpec prfKeySpec = new SecretKeySpec(prfKey, prfAlgorithm);
              Mac prfMac = Mac.getInstance(prfAlgorithm, IkeMessage.getSecurityProvider());
  
              ByteBuffer keyMatBuffer = ByteBuffer.allocate(keyMaterialLen);
  
              byte[] previousMac = new byte[0];
              final int padLen = 1;
              byte padValue = 1;
  
              while (keyMatBuffer.remaining() > 0) {
                  prfMac.init(prfKeySpec);
  
                  ByteBuffer dataToSignBuffer =
                          ByteBuffer.allocate(previousMac.length + dataToSign.length + padLen);
                  dataToSignBuffer.put(previousMac).put(dataToSign).put(padValue);
                  dataToSignBuffer.rewind();
  
                  prfMac.update(dataToSignBuffer);
  
                  previousMac = prfMac.doFinal();
                  keyMatBuffer.put(
                          previousMac, 0, Math.min(previousMac.length, keyMatBuffer.remaining()));
  
                  padValue++;
              }
  
              return keyMatBuffer.array();
          } catch (InvalidKeyException | NoSuchAlgorithmException e) {
              throw new IllegalArgumentException("Failed to generate keying material", e);
          }
      }
  }
  