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.ike.ikev2.message;
  
  import android.annotation.IntDef;
  import android.util.ArraySet;
  
  import com.android.ike.ikev2.exceptions.IkeException;
  import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.net.InetAddress;
  import java.nio.ByteBuffer;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.Set;
  
  /**
   * IkeNotifyPayload represents a Notify Payload.
   *
   * <p>As instructed by RFC 7296, for IKE SA concerned Notify Payload, Protocol ID and SPI Size must
   * be zero. Unrecognized notify message type must be ignored but should be logged.
   *
   * <p>Critical bit for this payload must be ignored in received packet and must not be set in
   * outbound packet.
   *
   * @see <a href="https://tools.ietf.org/html/rfc7296">RFC 7296, Internet Key Exchange Protocol
   *     Version 2 (IKEv2)</a>
   */
  public final class IkeNotifyPayload extends IkePayload {
  
      @Retention(RetentionPolicy.SOURCE)
      @IntDef({
          NOTIFY_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD,
          NOTIFY_TYPE_INVALID_MAJOR_VERSION,
          NOTIFY_TYPE_INVALID_SYNTAX,
          NOTIFY_TYPE_NO_PROPOSAL_CHOSEN,
          NOTIFY_TYPE_INVALID_SELECTORS,
          NOTIFY_TYPE_CHILD_SA_NOT_FOUND,
          NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP,
          NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP,
          NOTIFY_TYPE_REKEY_SA
      })
      public @interface NotifyType {}
  
      public static final int NOTIFY_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD = 1;
      public static final int NOTIFY_TYPE_INVALID_MAJOR_VERSION = 5;
      public static final int NOTIFY_TYPE_INVALID_SYNTAX = 7;
      public static final int NOTIFY_TYPE_NO_PROPOSAL_CHOSEN = 14;
      public static final int NOTIFY_TYPE_AUTHENTICATION_FAILED = 24;
      public static final int NOTIFY_TYPE_INVALID_SELECTORS = 39;
      public static final int NOTIFY_TYPE_CHILD_SA_NOT_FOUND = 44;
  
      public static final int NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP = 16388;
      public static final int NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP = 16389;
      public static final int NOTIFY_TYPE_REKEY_SA = 16393;
      // TODO: List all supported notify types.
  
      private static final int NOTIFY_HEADER_LEN = 4;
  
      private static final String NAT_DETECTION_DIGEST_ALGORITHM = "SHA-1";
  
      private static final Set<Integer> VALID_NOTIFY_TYPES_FOR_CHILD_SA;
  
      static {
          VALID_NOTIFY_TYPES_FOR_CHILD_SA = new ArraySet<>();
          VALID_NOTIFY_TYPES_FOR_CHILD_SA.add(NOTIFY_TYPE_INVALID_SELECTORS);
          VALID_NOTIFY_TYPES_FOR_CHILD_SA.add(NOTIFY_TYPE_CHILD_SA_NOT_FOUND);
          VALID_NOTIFY_TYPES_FOR_CHILD_SA.add(NOTIFY_TYPE_REKEY_SA);
      }
  
      public final int protocolId;
      public final byte spiSize;
      public final int notifyType;
      public final int spi;
      public final byte[] notifyData;
  
      /**
       * Construct an instance of IkeNotifyPayload in the context of IkePayloadFactory
       *
       * @param critical indicates if this payload is critical. Ignored in supported payload as
       *     instructed by the RFC 7296.
       * @param payloadBody payload body in byte array
       * @throws IkeException if there is any error
       */
      IkeNotifyPayload(boolean isCritical, byte[] payloadBody) throws IkeException {
          super(PAYLOAD_TYPE_NOTIFY, isCritical);
  
          ByteBuffer inputBuffer = ByteBuffer.wrap(payloadBody);
  
          protocolId = Byte.toUnsignedInt(inputBuffer.get());
          spiSize = inputBuffer.get();
          notifyType = Short.toUnsignedInt(inputBuffer.getShort());
  
          // Validate syntax of spiSize, protocolId and notifyType.
          // Reference: <https://tools.ietf.org/html/rfc7296#page-100>
          if (spiSize == SPI_LEN_IPSEC) {
              // For message concerning existing Child SA
              validateNotifyPayloadForExistingChildSa();
              spi = inputBuffer.getInt();
  
          } else if (spiSize == SPI_LEN_NOT_INCLUDED) {
              // For message concerning IKE SA or for new Child SA that to be negotiated.
              validateNotifyPayloadForIkeAndNewChild();
              spi = SPI_NOT_INCLUDED;
  
          } else {
              throw new InvalidSyntaxException("Invalid SPI Size: " + spiSize);
          }
  
          notifyData = new byte[payloadBody.length - NOTIFY_HEADER_LEN];
          inputBuffer.get(notifyData);
      }
  
      private void validateNotifyPayloadForExistingChildSa() throws InvalidSyntaxException {
          if (protocolId != PROTOCOL_ID_AH && protocolId != PROTOCOL_ID_ESP) {
              throw new InvalidSyntaxException(
                      "Expected Procotol ID AH(2) or ESP(3): Protocol ID is " + protocolId);
          }
  
          if (!VALID_NOTIFY_TYPES_FOR_CHILD_SA.contains(notifyType)) {
              throw new InvalidSyntaxException(
                      "Expected Notify Type for existing Child SA: Notify Type is " + notifyType);
          }
      }
  
      private void validateNotifyPayloadForIkeAndNewChild() throws InvalidSyntaxException {
          if (protocolId != PROTOCOL_ID_UNSET) {
              throw new InvalidSyntaxException(
                      "Expected Procotol ID unset: Protocol ID is " + protocolId);
          }
  
          if (notifyType == NOTIFY_TYPE_INVALID_SELECTORS
                  || notifyType == NOTIFY_TYPE_CHILD_SA_NOT_FOUND) {
              throw new InvalidSyntaxException(
                      "Expected Notify Type concerning IKE SA or new Child SA under negotiation"
                              + ": Notify Type is "
                              + notifyType);
          }
      }
  
      /**
       * Generate NAT DETECTION notification data.
       *
       * <p>This method calculates NAT DETECTION notification data which is a SHA-1 digest of the IKE
       * initiator's SPI, IKE responder's SPI, IP address and port. Source address and port should be
       * used for generating NAT_DETECTION_SOURCE_IP data. Destination address and port should be used
       * for generating NAT_DETECTION_DESTINATION_IP data.
       *
       * @param initiatorIkeSpi the SPI of IKE initiator
       * @param responderIkeSpi the SPI of IKE responder
       * @param ipAddress the IP address
       * @param port the port
       * @return the generated NAT DETECTION notification data as a byte array.
       * @throws NoSuchAlgorithmException when "SHA-1" is not supported by the security provider.
       */
      public static byte[] generateNatDetectionData(
              long initiatorIkeSpi, long responderIkeSpi, InetAddress ipAddress, int port)
              throws NoSuchAlgorithmException {
          byte[] rawIpAddr = ipAddress.getAddress();
  
          ByteBuffer byteBuffer =
                  ByteBuffer.allocate(2 * SPI_LEN_IKE + rawIpAddr.length + IP_PORT_LEN);
          byteBuffer
                  .putLong(initiatorIkeSpi)
                  .putLong(responderIkeSpi)
                  .put(rawIpAddr)
                  .putShort((short) port);
  
          MessageDigest natDetectionDataDigest =
                  MessageDigest.getInstance(
                          NAT_DETECTION_DIGEST_ALGORITHM, IkeMessage.getSecurityProvider());
          return natDetectionDataDigest.digest(byteBuffer.array());
      }
  
      /**
       * Encode Notify payload to ByteBuffer.
       *
       * @param nextPayload type of payload that follows this payload.
       * @param byteBuffer destination ByteBuffer that stores encoded payload.
       */
      @Override
      protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) {
          encodePayloadHeaderToByteBuffer(nextPayload, getPayloadLength(), byteBuffer);
          byteBuffer.put((byte) protocolId).put(spiSize).putShort((short) notifyType);
          if (spiSize == SPI_LEN_IPSEC) {
              byteBuffer.putInt(spi);
          }
          byteBuffer.put(notifyData);
      }
  
      /**
       * Get entire payload length.
       *
       * @return entire payload length.
       */
      @Override
      protected int getPayloadLength() {
          return GENERIC_HEADER_LENGTH + NOTIFY_HEADER_LEN + spiSize + notifyData.length;
      }
  
      protected IkeNotifyPayload(
              @ProtocolId int protocolId,
              byte spiSize,
              int spi,
              @NotifyType int notifyType,
              byte[] notifyData) {
          super(PAYLOAD_TYPE_NOTIFY, false);
          this.protocolId = protocolId;
          this.spiSize = spiSize;
          this.spi = spi;
          this.notifyType = notifyType;
          this.notifyData = notifyData;
      }
  
      /**
       * Construct IkeNotifyPayload concerning either an IKE SA or Child SA that is going to be
       * negotiated.
       *
       * @param notifyType the notify type concerning IKE SA
       * @param notifytData status or error data transmitted. Values for this field are notify type
       *     specific.
       */
      public IkeNotifyPayload(@NotifyType int notifyType, byte[] notifyData) {
          this(PROTOCOL_ID_UNSET, SPI_LEN_NOT_INCLUDED, SPI_NOT_INCLUDED, notifyType, notifyData);
          try {
              validateNotifyPayloadForIkeAndNewChild();
          } catch (InvalidSyntaxException e) {
              throw new IllegalArgumentException(e);
          }
      }
  
      /**
       * Construct IkeNotifyPayload concerning existing Child SA
       *
       * @param notifyType the notify type concerning Child SA
       * @param notifytData status or error data transmitted. Values for this field are notify type
       *     specific.
       */
      public IkeNotifyPayload(
              @ProtocolId int protocolId, int spi, @NotifyType int notifyType, byte[] notifyData) {
          this(protocolId, SPI_LEN_IPSEC, spi, notifyType, notifyData);
          try {
              validateNotifyPayloadForExistingChildSa();
          } catch (InvalidSyntaxException e) {
              throw new IllegalArgumentException(e);
          }
      }
  
      /**
       * Return the payload type as a String.
       *
       * @return the payload type as a String.
       */
      @Override
      public String getTypeString() {
          return "Notify Payload";
      }
  }
  