blob: c4f215ca2fe10d62af241fb4cdc64fc08f8af783 [file] [log] [blame]
/*
* 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);
}
}