blob: 1594967448289c47bada61922fdf8da6622a2702 [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 android.util.Pair;
import com.android.ike.ikev2.IkeSessionOptions;
import com.android.ike.ikev2.SaRecord.IkeSaRecord;
import com.android.ike.ikev2.exceptions.IkeException;
import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
import com.android.ike.ikev2.exceptions.UnsupportedCriticalPayloadException;
import com.android.internal.annotations.VisibleForTesting;
import com.android.org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.Provider;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
/**
* IkeMessage represents an IKE message.
*
* <p>It contains all attributes and provides methods for encoding, decoding, encrypting and
* decrypting.
*
* @see <a href="https://tools.ietf.org/html/rfc7296#section-3">RFC 7296, Internet Key Exchange
* Protocol Version 2 (IKEv2)</a>
*/
public final class IkeMessage {
@Retention(RetentionPolicy.SOURCE)
@IntDef({
MESSAGE_TYPE_IKE_INIT_RESP,
MESSAGE_TYPE_IKE_AUTH_RESP,
MESSAGE_TYPE_DELETE_IKE_REQ,
MESSAGE_TYPE_DELETE_IKE_RESP,
MESSAGE_TYPE_REKEY_IKE_REQ,
MESSAGE_TYPE_REKEY_IKE_RESP,
MESSAGE_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD,
MESSAGE_TYPE_INVALID_MAJOR_VERSION,
MESSAGE_TYPE_INVALID_SYNTAX
})
public @interface MessageType {}
// Message type for decoded IkeMessage.
public static final int PROCEDURE_TYPE_BASE = 0;
public static final int MESSAGE_TYPE_IKE_INIT_RESP = PROCEDURE_TYPE_BASE + 1;
public static final int MESSAGE_TYPE_IKE_AUTH_RESP = PROCEDURE_TYPE_BASE + 2;
public static final int MESSAGE_TYPE_DELETE_IKE_REQ = PROCEDURE_TYPE_BASE + 3;
public static final int MESSAGE_TYPE_DELETE_IKE_RESP = PROCEDURE_TYPE_BASE + 4;
public static final int MESSAGE_TYPE_REKEY_IKE_REQ = PROCEDURE_TYPE_BASE + 5;
public static final int MESSAGE_TYPE_REKEY_IKE_RESP = PROCEDURE_TYPE_BASE + 6;
public static final int NOTIFICATION_TYPE_BASE = PROCEDURE_TYPE_BASE + 100;
public static final int MESSAGE_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD =
NOTIFICATION_TYPE_BASE + IkeNotifyPayload.NOTIFY_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD;
public static final int MESSAGE_TYPE_INVALID_MAJOR_VERSION =
NOTIFICATION_TYPE_BASE + IkeNotifyPayload.NOTIFY_TYPE_INVALID_MAJOR_VERSION;
public static final int MESSAGE_TYPE_INVALID_SYNTAX =
NOTIFICATION_TYPE_BASE + IkeNotifyPayload.NOTIFY_TYPE_INVALID_SYNTAX;
private static IIkeMessageHelper sIkeMessageHelper = new IkeMessageHelper();
// Currently use Bouncy Castle as crypto security provider
static final Provider SECURITY_PROVIDER = new BouncyCastleProvider();
public final IkeHeader ikeHeader;
public final List<IkePayload> ikePayloadList;
/**
* Conctruct an instance of IkeMessage. It is called by decode or for building outbound message.
*
* @param header the header of this IKE message
* @param payloadList the list of decoded IKE payloads in this IKE message
*/
public IkeMessage(IkeHeader header, List<IkePayload> payloadList) {
ikeHeader = header;
ikePayloadList = payloadList;
}
static Provider getSecurityProvider() {
return SECURITY_PROVIDER;
}
/**
* Decode unencrypted IKE message body and create an instance of IkeMessage.
*
* <p>This method catches all RuntimeException during decoding incoming IKE packet.
*
* @param header the IKE header that is decoded but not validated.
* @param inputPacket the byte array contains the whole IKE message.
* @return the IkeMessage instance.
* @throws IkeException if there is any protocol error.
*/
public static IkeMessage decode(IkeHeader header, byte[] inputPacket) throws IkeException {
return sIkeMessageHelper.decode(header, inputPacket);
}
/**
* Decrypt and decode encrypted IKE message body and create an instance of IkeMessage.
*
* @param ikeSessionOptions IkeSessionOptions that contains cryptographic algorithm set.
* @param ikeSaRecord ikeSaRecord where this packet is sent on.
* @param ikeHeader header of IKE packet.
* @param packet IKE packet as a byte array.
* @return decoded IKE message.
* @throws IkeException for decoding errors.
* @throws GeneralSecurityException if there is any error during integrity check or decryption.
*/
public static IkeMessage decode(
IkeSessionOptions ikeSessionOptions,
IkeSaRecord ikeSaRecord,
IkeHeader ikeHeader,
byte[] packet)
throws IkeException, GeneralSecurityException {
return sIkeMessageHelper.decode(ikeSessionOptions, ikeSaRecord, ikeHeader, packet);
}
private static List<IkePayload> decodePayloadList(
@PayloadType int firstPayloadType, boolean isResp, byte[] unencryptedPayloads)
throws IkeException {
ByteBuffer inputBuffer = ByteBuffer.wrap(unencryptedPayloads);
int currentPayloadType = firstPayloadType;
// For supported payload
List<IkePayload> supportedPayloadList = new LinkedList<>();
// For unsupported critical payload
List<Integer> unsupportedCriticalPayloadList = new LinkedList<>();
while (currentPayloadType != IkePayload.PAYLOAD_TYPE_NO_NEXT) {
Pair<IkePayload, Integer> pair =
IkePayloadFactory.getIkePayload(currentPayloadType, isResp, inputBuffer);
IkePayload payload = pair.first;
if (!(payload instanceof IkeUnsupportedPayload)) {
supportedPayloadList.add(payload);
} else if (payload.isCritical) {
unsupportedCriticalPayloadList.add(payload.payloadType);
}
// Simply ignore unsupported uncritical payload.
currentPayloadType = pair.second;
}
if (inputBuffer.remaining() > 0) {
throw new InvalidSyntaxException(
"Malformed IKE Payload: Unexpected bytes at the end of packet.");
}
if (unsupportedCriticalPayloadList.size() > 0) {
throw new UnsupportedCriticalPayloadException(unsupportedCriticalPayloadList);
}
return supportedPayloadList;
}
/**
* Encode unencrypted IKE message.
*
* @return encoded IKE message in byte array.
*/
public byte[] encode() {
return sIkeMessageHelper.encode(this);
}
/**
* Encrypt and encode packet.
*
* @param ikeSessionOptions IkeSessionOptions that contains cryptographic algorithm set.
* @param ikeSaRecord ikeSaRecord where this packet is sent on.
* @return encoded IKE message in byte array.
*/
public byte[] encode(IkeSessionOptions ikeSessionOptions, IkeSaRecord ikeSaRecord) {
return sIkeMessageHelper.encode(ikeSessionOptions, ikeSaRecord, this);
}
/**
* Encode all payloads to a byte array.
*
* @return byte array contains all encoded payloads
*/
private byte[] encodePayloads() {
if (ikePayloadList.isEmpty()) {
return new byte[0];
}
int payloadLengthSum = 0;
for (IkePayload payload : ikePayloadList) {
payloadLengthSum += payload.getPayloadLength();
}
ByteBuffer byteBuffer = ByteBuffer.allocate(payloadLengthSum);
for (int i = 0; i < ikePayloadList.size() - 1; i++) {
ikePayloadList
.get(i)
.encodeToByteBuffer(ikePayloadList.get(i + 1).payloadType, byteBuffer);
}
ikePayloadList
.get(ikePayloadList.size() - 1)
.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, byteBuffer);
return byteBuffer.array();
}
/** Package */
@VisibleForTesting
byte[] attachEncodedHeader(byte[] encodedIkeBody) {
ByteBuffer outputBuffer =
ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH + encodedIkeBody.length);
ikeHeader.encodeToByteBuffer(outputBuffer);
outputBuffer.put(encodedIkeBody);
return outputBuffer.array();
}
@MessageType
public int getMessageType() {
return sIkeMessageHelper.getMessageType(this);
}
/**
* IIkeMessageHelper provides interface for decoding, encoding and processing IKE packet.
*
* <p>IkeMessageHelper exists so that the interface is injectable for testing.
*/
@VisibleForTesting
public interface IIkeMessageHelper {
/**
* Check message type of decoded IKE message.
*
* @param ikeMessage IKE message to be checked.
* @return message type.
*/
@MessageType
int getMessageType(IkeMessage ikeMessage);
/**
* Encode IKE message.
*
* @param ikeMessage message need to be encoded.
* @return encoded IKE message in byte array.
*/
byte[] encode(IkeMessage ikeMessage);
/**
* Encrypt and encode IKE message.
*
* @param ikeSessionOptions ikeSessionOptions that contains cryptographic algorithm set.
* @param ikeSaRecord ikeSaRecord where this packet is sent on.
* @param ikeMessage message need to be encoded.
* @return encoded IKE message in byte array.
*/
byte[] encode(
IkeSessionOptions ikeSessionOptions,
IkeSaRecord ikeSaRecord,
IkeMessage ikeMessage);
/**
* Decode unencrypted packet.
*
* @param ikeHeader header of IKE packet.
* @param packet IKE packet as a byte array.
* @return decoded IKE message.
* @throws IkeException for decoding errors.
*/
IkeMessage decode(IkeHeader ikeHeader, byte[] packet) throws IkeException;
/**
* Decrypt and decode packet.
*
* @param ikeSessionOptions ikeSessionOptions that contains cryptographic algorithm set.
* @param ikeSaRecord ikeSaRecord where this packet is sent on.
* @param ikeHeader header of IKE packet.
* @param packet IKE packet as a byte array.
* @return decoded IKE message.
* @throws IkeException for decoding errors.
*/
IkeMessage decode(
IkeSessionOptions ikeSessionOptions,
IkeSaRecord ikeSaRecord,
IkeHeader ikeHeader,
byte[] packet)
throws IkeException, GeneralSecurityException;
}
/** IkeMessageHelper provides methods for decoding, encoding and processing IKE packet. */
public static final class IkeMessageHelper implements IIkeMessageHelper {
@Override
public byte[] encode(IkeMessage ikeMessage) {
byte[] encodedIkeBody = ikeMessage.encodePayloads();
return ikeMessage.attachEncodedHeader(encodedIkeBody);
}
@Override
public byte[] encode(
IkeSessionOptions ikeSessionOptions,
IkeSaRecord ikeSaRecord,
IkeMessage ikeMessage) {
// TODO: Implement it.
return null;
}
@Override
public IkeMessage decode(IkeHeader header, byte[] inputPacket) throws IkeException {
header.checkValidOrThrow(inputPacket.length);
byte[] unencryptedPayloads =
Arrays.copyOfRange(
inputPacket, IkeHeader.IKE_HEADER_LENGTH, inputPacket.length);
try {
List<IkePayload> supportedPayloadList =
decodePayloadList(
header.nextPayloadType, header.isResponseMsg, unencryptedPayloads);
return new IkeMessage(header, supportedPayloadList);
} catch (NegativeArraySizeException | BufferUnderflowException e) {
// Invalid length error when parsing payload bodies.
throw new InvalidSyntaxException("Malformed IKE Payload");
}
}
@Override
public IkeMessage decode(
IkeSessionOptions ikeSessionOptions,
IkeSaRecord ikeSaRecord,
IkeHeader ikeHeader,
byte[] packet)
throws IkeException, GeneralSecurityException {
// TODO: Extract crypto params and call private decode method.
return null;
}
private IkeMessage decode(
IkeHeader header,
byte[] inputPacket,
Mac integrityMac,
int checksumLen,
Cipher decryptCipher,
SecretKey dKey)
throws IkeException, GeneralSecurityException {
header.checkValidOrThrow(inputPacket.length);
if (header.nextPayloadType != IkePayload.PAYLOAD_TYPE_SK) {
// TODO: b/123372339 Handle message containing unprotected payloads.
throw new UnsupportedOperationException("Message contains unprotected payloads");
}
try {
Pair<IkeSkPayload, Integer> pair =
IkePayloadFactory.getIkeSkPayload(
inputPacket, integrityMac, checksumLen, decryptCipher, dKey);
IkeSkPayload skPayload = pair.first;
int firstPayloadType = pair.second;
List<IkePayload> supportedPayloadList =
decodePayloadList(
firstPayloadType,
header.isResponseMsg,
skPayload.getUnencryptedPayloads());
return new IkeMessage(header, supportedPayloadList);
} catch (NegativeArraySizeException | BufferUnderflowException e) {
// Invalid length error when parsing payload bodies.
throw new InvalidSyntaxException("Malformed IKE Payload");
}
}
@Override
@MessageType
public int getMessageType(IkeMessage ikeMessage) {
// TODO: Implement it.
return 0;
}
}
/**
* For setting mocked IIkeMessageHelper for testing
*
* @param helper the mocked IIkeMessageHelper
*/
public static void setIkeMessageHelper(IIkeMessageHelper helper) {
sIkeMessageHelper = helper;
}
}