blob: 17bd5c0b7aa0381593a4f9d0424baf6dc30c1f21 [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.util.Pair;
import com.android.ike.ikev2.exceptions.IkeException;
import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
import com.android.ike.ikev2.exceptions.UnsupportedCriticalPayloadException;
import com.android.org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
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).
*/
public final class IkeMessage {
// 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;
}
/**
* Decrypt and decode encrypted IKE message body and create an instance of IkeMessage.
*
* @param header the IKE header that is decoded but not validated.
* @param inputPacket the byte array containing the whole IKE message.
* @param integrityMac the initialized Message Authentication Code (MAC) for integrity check.
* @param checksumLen the length of integrity checksum.
* @param decryptCipher the uninitialized Cipher for doing decryption.
* @param dKey the decryption key.
* @param ivLen the length of Initialization Vector.
* @return the IkeMessage instance.
* @throws IkeException if there is any protocol error.
* @throws IOException if there is any error during integrity check or decryption.
*/
public static IkeMessage decode(
IkeHeader header,
byte[] inputPacket,
Mac integrityMac,
int checksumLen,
Cipher decryptCipher,
SecretKey dKey,
int ivLen)
throws IkeException, IOException {
header.checkValidOrThrow(inputPacket.length);
Pair<IkeSkPayload, Integer> pair =
IkePayloadFactory.getIkeSkPayload(
inputPacket, integrityMac, checksumLen, decryptCipher, dKey, ivLen);
IkeSkPayload skPayload = pair.first;
int firstPayloadType = pair.second;
List<IkePayload> supportedPayloadList =
decodePayloadList(firstPayloadType, skPayload.unencryptedPayloads);
return new IkeMessage(header, supportedPayloadList);
}
/**
* Decode unencrypted IKE message body and create an instance of IkeMessage.
*
* @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 {
header.checkValidOrThrow(inputPacket.length);
byte[] unencryptedPayloads =
Arrays.copyOfRange(inputPacket, IkeHeader.IKE_HEADER_LENGTH, inputPacket.length);
List<IkePayload> supportedPayloadList =
decodePayloadList(header.nextPayloadType, unencryptedPayloads);
return new IkeMessage(header, supportedPayloadList);
}
private static List<IkePayload> decodePayloadList(
@PayloadType int firstPayloadType, 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) {
try {
Pair<IkePayload, Integer> pair =
IkePayloadFactory.getIkePayload(currentPayloadType, 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;
} catch (NegativeArraySizeException | BufferUnderflowException e) {
// TODO: b/119791832. Add length check in each payload before getting data from
// ByteBuffer.
// Invalid length error when parsing payload bodies.
throw new InvalidSyntaxException("Malformed IKE Payload");
}
}
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;
}
static Provider getSecurityProvider() {
return SECURITY_PROVIDER;
}
/**
* Encode all payloads to a byte array.
*
* @return byte array contains all encoded payloads
*/
public 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();
}
// TODO: Add a method that takes cyptographic algorithms and parameters to encrypt all payloads
// to a byte array.
/**
* Encode entire IKE message to a byte array.
*
* @param encodedIkeBody IKE message body in byte array. IKE message body is encrypted and
* integrity protected except in an IKE_SA_INIT message.
* @return the entire encoded IKE message as a byte array.
*/
public byte[] encode(byte[] encodedIkeBody) {
ByteBuffer outputBuffer =
ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH + encodedIkeBody.length);
ikeHeader.encodeToByteBuffer(outputBuffer);
outputBuffer.put(encodedIkeBody);
return outputBuffer.array();
}
}