diff options
Diffstat (limited to 'src/java/com/android/ike/ikev2/message/IkeHeader.java')
-rw-r--r-- | src/java/com/android/ike/ikev2/message/IkeHeader.java | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/src/java/com/android/ike/ikev2/message/IkeHeader.java b/src/java/com/android/ike/ikev2/message/IkeHeader.java new file mode 100644 index 00000000..c4f215ca --- /dev/null +++ b/src/java/com/android/ike/ikev2/message/IkeHeader.java @@ -0,0 +1,199 @@ +/* + * 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 static com.android.ike.ikev2.message.IkePayload.PayloadType; + +import android.annotation.IntDef; + +import com.android.ike.ikev2.exceptions.IkeException; +import com.android.ike.ikev2.exceptions.InvalidMajorVersionException; +import com.android.ike.ikev2.exceptions.InvalidSyntaxException; +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.ByteBuffer; + +/** + * IkeHeader represents an IKE message header. It contains all header attributes and provide methods + * for encoding and decoding it. + * + * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.1">RFC 7296, Internet Key Exchange + * Protocol Version 2 (IKEv2)</a> + */ +public final class IkeHeader { + // TODO: b/122838549 Change IkeHeader to static inner class of IkeMessage. + private static final byte IKE_HEADER_VERSION_INFO = (byte) 0x20; + + // Indicate whether this message is a response message + private static final byte IKE_HEADER_FLAG_IS_RESP_MSG = (byte) 0x20; + // Indicate whether this message is sent from the original IKE initiator + private static final byte IKE_HEADER_FLAG_FROM_IKE_INITIATOR = (byte) 0x08; + + public static final int IKE_HEADER_LENGTH = 28; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + EXCHANGE_TYPE_IKE_SA_INIT, + EXCHANGE_TYPE_IKE_AUTH, + EXCHANGE_TYPE_CREATE_CHILD_SA, + EXCHANGE_TYPE_INFORMATIONAL + }) + public @interface ExchangeType {} + + public static final int EXCHANGE_TYPE_IKE_SA_INIT = 34; + public static final int EXCHANGE_TYPE_IKE_AUTH = 35; + public static final int EXCHANGE_TYPE_CREATE_CHILD_SA = 36; + public static final int EXCHANGE_TYPE_INFORMATIONAL = 37; + + public final long ikeInitiatorSpi; + public final long ikeResponderSpi; + @PayloadType public final int nextPayloadType; + public final byte majorVersion; + public final byte minorVersion; + @ExchangeType public final int exchangeType; + public final boolean isResponseMsg; + public final boolean fromIkeInitiator; + public final int messageId; + + // Cannot assign encoded message length value for an outbound IKE message before it's encoded. + private static final int ENCODED_MESSAGE_LEN_UNAVAILABLE = -1; + + // mEncodedMessageLength is only set for an inbound IkeMessage. When building an outbound + // IkeMessage, message length is not set because message body length is unknown until it gets + // encrypted and encoded. + private final int mEncodedMessageLength; + + /** + * Construct an instance of IkeHeader. It is only called in the process of building outbound + * message. + * + * @param iSpi the SPI of IKE initiator + * @param rSpi the SPI of IKE responder + * @param nextPType the first payload's type + * @param eType the type of IKE exchange being used + * @param isResp indicates if this message is a response or a request + * @param fromInit indictaes if this message is sent from the IKE initiator or the IKE responder + * @param msgId the message identifier + */ + public IkeHeader( + long iSpi, + long rSpi, + @PayloadType int nextPType, + @ExchangeType int eType, + boolean isResp, + boolean fromInit, + int msgId) { + ikeInitiatorSpi = iSpi; + ikeResponderSpi = rSpi; + nextPayloadType = nextPType; + exchangeType = eType; + isResponseMsg = isResp; + fromIkeInitiator = fromInit; + messageId = msgId; + + mEncodedMessageLength = ENCODED_MESSAGE_LEN_UNAVAILABLE; + + // Major version of IKE protocol in use; it must be set to 2 when building an IKEv2 message. + majorVersion = 2; + // Minor version of IKE protocol in use; it must be set to 0 when building an IKEv2 message. + minorVersion = 0; + } + + /** + * Decode IKE header from a byte array and construct an IkeHeader instance. + * + * @param packet the raw byte array of the whole IKE message + */ + public IkeHeader(byte[] packet) throws IkeException { + if (packet.length <= IKE_HEADER_LENGTH) { + throw new InvalidSyntaxException("IKE message is too short to contain a header"); + } + + ByteBuffer buffer = ByteBuffer.wrap(packet); + + ikeInitiatorSpi = buffer.getLong(); + ikeResponderSpi = buffer.getLong(); + nextPayloadType = Byte.toUnsignedInt(buffer.get()); + + byte versionByte = buffer.get(); + majorVersion = (byte) ((versionByte >> 4) & 0x0F); + minorVersion = (byte) (versionByte & 0x0F); + + exchangeType = Byte.toUnsignedInt(buffer.get()); + + byte flagsByte = buffer.get(); + isResponseMsg = ((flagsByte & 0x20) != 0); + fromIkeInitiator = ((flagsByte & 0x08) != 0); + + messageId = buffer.getInt(); + mEncodedMessageLength = buffer.getInt(); + } + + /*Package private*/ + @VisibleForTesting + int getInboundMessageLength() { + if (mEncodedMessageLength == ENCODED_MESSAGE_LEN_UNAVAILABLE) { + throw new UnsupportedOperationException( + "It is not supported to get encoded message length from an outbound message."); + } + return mEncodedMessageLength; + } + + /** Validate syntax and major version of inbound IKE header. */ + public void checkInboundValidOrThrow(int packetLength) throws IkeException { + if (majorVersion > 2) { + // Receive higher version of protocol. Stop parsing. + throw new InvalidMajorVersionException(majorVersion); + } + if (majorVersion < 2) { + // There is no specific instruction for dealing with this error case. + // Since IKE library only supports IKEv2 and not allowed to check if message + // sender supports higher version, it is proper to treat this error as an invalid syntax + // error. + throw new InvalidSyntaxException("Major version is smaller than 2."); + } + if (exchangeType < EXCHANGE_TYPE_IKE_SA_INIT + || exchangeType > EXCHANGE_TYPE_INFORMATIONAL) { + throw new InvalidSyntaxException("Invalid IKE Exchange Type."); + } + if (mEncodedMessageLength != packetLength) { + throw new InvalidSyntaxException("Invalid IKE Message Length."); + } + } + + /** Encode IKE header to ByteBuffer */ + public void encodeToByteBuffer(ByteBuffer byteBuffer, int encodedMessageBodyLen) { + byteBuffer + .putLong(ikeInitiatorSpi) + .putLong(ikeResponderSpi) + .put((byte) nextPayloadType) + .put(IKE_HEADER_VERSION_INFO) + .put((byte) exchangeType); + + byte flag = 0; + if (isResponseMsg) { + flag |= IKE_HEADER_FLAG_IS_RESP_MSG; + } + if (fromIkeInitiator) { + flag |= IKE_HEADER_FLAG_FROM_IKE_INITIATOR; + } + + byteBuffer.put(flag).putInt(messageId).putInt(IKE_HEADER_LENGTH + encodedMessageBodyLen); + } +} |