aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java')
-rw-r--r--src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java274
1 files changed, 274 insertions, 0 deletions
diff --git a/src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java b/src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java
new file mode 100644
index 00000000..ee6fe47a
--- /dev/null
+++ b/src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java
@@ -0,0 +1,274 @@
+/*
+ * 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";
+ }
+}