diff options
author | evitayan <evitayan@google.com> | 2018-10-23 13:49:29 -0700 |
---|---|---|
committer | evitayan <evitayan@google.com> | 2018-11-28 14:57:48 -0800 |
commit | 8dfc82cc11c5d8e19357d741a925fdca5861e9f2 (patch) | |
tree | ed5259ece77258793ef8c7952e4b3e18c01aa5af /src/java/com/android/ike | |
parent | 6ee9837e14cf1ba51ac94a8c83e962ab435300b9 (diff) | |
download | ike-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')
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"; + } +} |