aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/ike
diff options
context:
space:
mode:
authorevitayan <evitayan@google.com>2018-10-23 13:49:29 -0700
committerevitayan <evitayan@google.com>2018-11-28 14:57:48 -0800
commit8dfc82cc11c5d8e19357d741a925fdca5861e9f2 (patch)
treeed5259ece77258793ef8c7952e4b3e18c01aa5af /src/java/com/android/ike
parent6ee9837e14cf1ba51ac94a8c83e962ab435300b9 (diff)
downloadike-8dfc82cc11c5d8e19357d741a925fdca5861e9f2.tar.gz
Decrypt IKE message
This commit: - Create SkPayload - Add decode method in IkeMessage for decoding encrypted message - Add check for payload length field in generic payload header Bug: 112041656 Test: FrameworksIkeTests Change-Id: I25658f5988c00212931f76563e4040cf14567bd2
Diffstat (limited to 'src/java/com/android/ike')
-rw-r--r--src/java/com/android/ike/ikev2/message/IkeHeader.java5
-rw-r--r--src/java/com/android/ike/ikev2/message/IkeMessage.java83
-rw-r--r--src/java/com/android/ike/ikev2/message/IkePayloadFactory.java81
-rw-r--r--src/java/com/android/ike/ikev2/message/IkeSkPayload.java101
4 files changed, 255 insertions, 15 deletions
diff --git a/src/java/com/android/ike/ikev2/message/IkeHeader.java b/src/java/com/android/ike/ikev2/message/IkeHeader.java
index 851442b9..d2277cef 100644
--- a/src/java/com/android/ike/ikev2/message/IkeHeader.java
+++ b/src/java/com/android/ike/ikev2/message/IkeHeader.java
@@ -138,7 +138,7 @@ public final class IkeHeader {
}
/** Validate syntax and major version. */
- public void validate() throws IkeException {
+ public void checkValidOrThrow(int packetLength) throws IkeException {
if (majorVersion > 2) {
// Receive higher version of protocol. Stop parsing.
throw new InvalidMajorVersionException(majorVersion);
@@ -154,6 +154,9 @@ public final class IkeHeader {
|| exchangeType > EXCHANGE_TYPE_INFORMATIONAL) {
throw new InvalidSyntaxException("Invalid IKE Exchange Type.");
}
+ if (messageLength != packetLength) {
+ throw new InvalidSyntaxException("Invalid IKE Message Length.");
+ }
}
/** Encode IKE header to ByteBuffer */
diff --git a/src/java/com/android/ike/ikev2/message/IkeMessage.java b/src/java/com/android/ike/ikev2/message/IkeMessage.java
index 0261ec2b..17bd5c0b 100644
--- a/src/java/com/android/ike/ikev2/message/IkeMessage.java
+++ b/src/java/com/android/ike/ikev2/message/IkeMessage.java
@@ -25,12 +25,18 @@ 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.
*
@@ -60,22 +66,66 @@ public final class IkeMessage {
}
/**
- * Decode unenrypted IKE message body and create an instance of IkeMessage.
+ * 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
- * @throws IkeException if there is any error
+ * @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.validate();
+ header.checkValidOrThrow(inputPacket.length);
+
+ byte[] unencryptedPayloads =
+ Arrays.copyOfRange(inputPacket, IkeHeader.IKE_HEADER_LENGTH, inputPacket.length);
- ByteBuffer inputBuffer =
- ByteBuffer.wrap(
- inputPacket,
- IkeHeader.IKE_HEADER_LENGTH,
- inputPacket.length - IkeHeader.IKE_HEADER_LENGTH);
- @PayloadType int currentPayloadType = header.nextPayloadType;
+ 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
@@ -96,14 +146,23 @@ public final class IkeMessage {
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 new IkeMessage(header, supportedPayloadList);
+ return supportedPayloadList;
}
static Provider getSecurityProvider() {
diff --git a/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java b/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java
index 86edb585..ced76d1b 100644
--- a/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java
+++ b/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java
@@ -19,10 +19,16 @@ package com.android.ike.ikev2.message;
import android.util.Pair;
import com.android.ike.ikev2.exceptions.IkeException;
+import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
import com.android.internal.annotations.VisibleForTesting;
+import java.io.IOException;
import java.nio.ByteBuffer;
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+
/**
* IkePayloadFactory is used for creating IkePayload according to is type.
*
@@ -31,6 +37,14 @@ import java.nio.ByteBuffer;
*/
final class IkePayloadFactory {
+ // Critical bit is set and following reserved 7 bits are unset.
+ private static final byte PAYLOAD_HEADER_CRITICAL_BIT_SET = (byte) 0x80;
+
+ private static boolean isCriticalPayload(byte flagByte) {
+ // Reserved 7 bits following critical bit must be ignore on receipt.
+ return (flagByte & PAYLOAD_HEADER_CRITICAL_BIT_SET) == PAYLOAD_HEADER_CRITICAL_BIT_SET;
+ }
+
/** Default instance used for constructing IkePayload */
@VisibleForTesting
static IkePayloadDecoder sDecoderInstance =
@@ -68,15 +82,25 @@ final class IkePayloadFactory {
* IkePayload.PayloadType}
* @param input the encoded IKE message body containing all payloads. Position of it will
* increment.
+ * @return a Pair including IkePayload and next payload type.
*/
- static Pair<IkePayload, Integer> getIkePayload(int payloadType, ByteBuffer input)
+ protected static Pair<IkePayload, Integer> getIkePayload(int payloadType, ByteBuffer input)
throws IkeException {
int nextPayloadType = (int) input.get();
// read critical bit
- boolean isCritical = ((input.get() & 0x80) == 0x80);
+ boolean isCritical = isCriticalPayload(input.get());
int payloadLength = Short.toUnsignedInt(input.getShort());
+ if (payloadLength <= IkePayload.GENERIC_HEADER_LENGTH) {
+ throw new InvalidSyntaxException(
+ "Invalid Payload Length: Payload length is too short.");
+ }
int bodyLength = payloadLength - IkePayload.GENERIC_HEADER_LENGTH;
+ if (bodyLength > input.remaining()) {
+ // It is not clear whether previous payloads or current payload has invalid payload
+ // length.
+ throw new InvalidSyntaxException("Invalid Payload Length: Payload length is too long.");
+ }
byte[] payloadBody = new byte[bodyLength];
input.get(payloadBody);
@@ -85,6 +109,59 @@ final class IkePayloadFactory {
return new Pair(payload, nextPayloadType);
}
+ /**
+ * Construct an instance of IkeSkPayload by decrypting the received message.
+ *
+ * @param message the byte array contains the whole IKE message.
+ * @param integrityMac the initialized 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 a pair including IkePayload and next payload type.
+ * @throws IOException
+ */
+ protected static Pair<IkeSkPayload, Integer> getIkeSkPayload(
+ byte[] message,
+ Mac integrityMac,
+ int checksumLen,
+ Cipher decryptCipher,
+ SecretKey dKey,
+ int ivLen)
+ throws IkeException {
+ ByteBuffer input =
+ ByteBuffer.wrap(
+ message,
+ IkeHeader.IKE_HEADER_LENGTH,
+ message.length - IkeHeader.IKE_HEADER_LENGTH);
+
+ int nextPayloadType = (int) input.get();
+ // read critical bit
+ boolean isCritical = isCriticalPayload(input.get());
+
+ int payloadLength = Short.toUnsignedInt(input.getShort());
+
+ int bodyLength = message.length - IkeHeader.IKE_HEADER_LENGTH;
+ if (bodyLength < payloadLength) {
+ throw new InvalidSyntaxException(
+ "Invalid length of SK Payload: Payload length is too long.");
+ } else if (bodyLength > payloadLength) {
+ // According to RFC 7296, SK Payload must be the last payload and for CREATE_CHILD_SA,
+ // IKE_AUTH and INFORMATIONAL exchanges, message following the header is encrypted. Thus
+ // this implementaion only accepts that SK Payload to be the only payload. Any IKE
+ // packet violating this format will be treated as invalid. A request violating this
+ // format will be rejected and replied with an error notification.
+ throw new InvalidSyntaxException(
+ "Invalid length of SK Payload: Payload length is too short"
+ + " or SK Payload is not the only payload.");
+ }
+
+ IkeSkPayload payload =
+ new IkeSkPayload(
+ isCritical, message, integrityMac, checksumLen, decryptCipher, dKey, ivLen);
+ return new Pair(payload, nextPayloadType);
+ }
+
@VisibleForTesting
interface IkePayloadDecoder {
IkePayload decodeIkePayload(int payloadType, boolean isCritical, byte[] payloadBody)
diff --git a/src/java/com/android/ike/ikev2/message/IkeSkPayload.java b/src/java/com/android/ike/ikev2/message/IkeSkPayload.java
new file mode 100644
index 00000000..65c021a7
--- /dev/null
+++ b/src/java/com/android/ike/ikev2/message/IkeSkPayload.java
@@ -0,0 +1,101 @@
+/*
+ * 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 com.android.ike.ikev2.message.IkePayload.PayloadType;
+
+import java.nio.ByteBuffer;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+
+/**
+ * IkeSkPayload represents a Encrypted Payload.
+ *
+ * <p>It contains other payloads in encrypted form. It is must be the last payload in the message.
+ * It should be the only payload in this implementation.
+ *
+ * <p>Critical bit must be ignored when doing decoding.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc7296#page-105">RFC 7296, Internet Key Exchange
+ * Protocol Version 2 (IKEv2).
+ */
+public final class IkeSkPayload extends IkePayload {
+
+ public final byte[] unencryptedPayloads;
+
+ /**
+ * Construct an instance of IkeSkPayload in the context of {@link IkePayloadFactory}.
+ *
+ * @param critical indicates if it is a critical payload.
+ * @param message the byte array contains the whole IKE message.
+ * @param integrityMac the initialized 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.
+ */
+ IkeSkPayload(
+ boolean critical,
+ byte[] message,
+ Mac integrityMac,
+ int checksumLen,
+ Cipher decryptCipher,
+ SecretKey dKey,
+ int ivLen) {
+ super(PAYLOAD_TYPE_SK, critical);
+ // TODO:Check integrity and decrypt SkPayload body.
+ throw new UnsupportedOperationException("It is not supported to construct a SkPayload.");
+ }
+
+ //TODO: Add another constructor for AEAD protected payload.
+
+ /**
+ * Throw an Exception when trying to encode this payload.
+ *
+ * @throws UnsupportedOperationException for this payload.
+ */
+ @Override
+ protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) {
+ // TODO: Implement thie method
+ throw new UnsupportedOperationException(
+ "It is not supported to encode a " + getTypeString());
+ }
+
+ /**
+ * Get entire payload length.
+ *
+ * @return entire payload length.
+ */
+ @Override
+ protected int getPayloadLength() {
+ // TODO: Implement thie method
+ throw new UnsupportedOperationException(
+ "It is not supported to get length of a " + getTypeString());
+ }
+
+ /**
+ * Return the payload type as a String.
+ *
+ * @return the payload type as a String.
+ */
+ @Override
+ public String getTypeString() {
+ return "Encrypted and Authenticated Payload";
+ }
+}