diff options
Diffstat (limited to 'src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java')
-rw-r--r-- | src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java | 274 |
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"; + } +} |