aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2019-11-11 21:19:18 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2019-11-11 21:19:18 +0000
commitdb74d7937f3a9503f0e2590ec24c3bcfbf259ce0 (patch)
treeb39c3e794945072d38b97c963c5a6f10753514e1 /src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
parent7ffef5cae6d3c93c93b4055fd22517d44f77acda (diff)
parenteb4c77d7228f956f928c7d3500a220339ee78388 (diff)
downloadike-db74d7937f3a9503f0e2590ec24c3bcfbf259ce0.tar.gz
Snap for 6001391 from eb4c77d7228f956f928c7d3500a220339ee78388 to qt-aml-tzdata-release
Change-Id: Ic6fc3b152696e0096d00036f460262a8b0319394
Diffstat (limited to 'src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java')
-rw-r--r--src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java981
1 files changed, 981 insertions, 0 deletions
diff --git a/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java b/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
new file mode 100644
index 00000000..27fb9651
--- /dev/null
+++ b/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
@@ -0,0 +1,981 @@
+/*
+ * 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.internal.net.ipsec.ike.message;
+
+import static android.net.ipsec.ike.IkeManager.getIkeLog;
+
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PayloadType;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeInternalException;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecord;
+import com.android.internal.net.ipsec.ike.crypto.IkeCipher;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidMessageIdException;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
+import com.android.internal.net.ipsec.ike.exceptions.UnsupportedCriticalPayloadException;
+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.security.Security;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 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 {
+ private static final String TAG = "IkeMessage";
+
+ private static IIkeMessageHelper sIkeMessageHelper = new IkeMessageHelper();
+
+ // Currently use Bouncy Castle as crypto security provider
+ static final Provider SECURITY_PROVIDER = new BouncyCastleProvider();
+
+ // TODO: b/142070035 Use Conscrypt as default security provider instead of BC
+
+ // Currently use HarmonyJSSE as TrustManager provider
+ static final Provider TRUST_MANAGER_PROVIDER = Security.getProvider("HarmonyJSSE");
+
+ // Payload types in this set may be included multiple times within an IKE message. All other
+ // payload types can be included at most once.
+ private static final Set<Integer> REPEATABLE_PAYLOAD_TYPES = new HashSet<>();
+
+ static {
+ REPEATABLE_PAYLOAD_TYPES.add(IkePayload.PAYLOAD_TYPE_CERT);
+ REPEATABLE_PAYLOAD_TYPES.add(IkePayload.PAYLOAD_TYPE_CERT_REQUEST);
+ REPEATABLE_PAYLOAD_TYPES.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
+ REPEATABLE_PAYLOAD_TYPES.add(IkePayload.PAYLOAD_TYPE_DELETE);
+ REPEATABLE_PAYLOAD_TYPES.add(IkePayload.PAYLOAD_TYPE_VENDOR);
+ }
+
+ 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;
+ }
+
+ /**
+ * Get security provider for IKE library
+ *
+ * <p>Use BouncyCastleProvider as the default security provider.
+ *
+ * @return the security provider of IKE library.
+ */
+ public static Provider getSecurityProvider() {
+ // TODO: Move this getter out of IKE message package since not only this package uses it.
+ return SECURITY_PROVIDER;
+ }
+
+ /**
+ * Get security provider for X509TrustManager to do certificate validation.
+ *
+ * <p>Use JSSEProvdier as the default security provider.
+ *
+ * @return the provider for X509TrustManager
+ */
+ public static Provider getTrustManagerProvider() {
+ return TRUST_MANAGER_PROVIDER;
+ }
+
+ /**
+ * Decode unencrypted IKE message body and create an instance of IkeMessage.
+ *
+ * <p>This method catches all RuntimeException during decoding incoming IKE packet.
+ *
+ * @param expectedMsgId the expected message ID to validate against.
+ * @param header the IKE header that is decoded but not validated.
+ * @param inputPacket the byte array contains the whole IKE message.
+ * @return the decoding result.
+ */
+ public static DecodeResult decode(int expectedMsgId, IkeHeader header, byte[] inputPacket) {
+ return sIkeMessageHelper.decode(expectedMsgId, header, inputPacket);
+ }
+
+ /**
+ * Decrypt and decode encrypted IKE message body and create an instance of IkeMessage.
+ *
+ * @param expectedMsgId the expected message ID to validate against.
+ * @param integrityMac the negotiated integrity algorithm.
+ * @param decryptCipher the negotiated encryption algorithm.
+ * @param ikeSaRecord ikeSaRecord where this packet is sent on.
+ * @param ikeHeader header of IKE packet.
+ * @param packet IKE packet as a byte array.
+ * @param collectedFragments previously received IKE fragments.
+ * @return the decoding result.
+ */
+ public static DecodeResult decode(
+ int expectedMsgId,
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher decryptCipher,
+ IkeSaRecord ikeSaRecord,
+ IkeHeader ikeHeader,
+ byte[] packet,
+ DecodeResultPartial collectedFragments) {
+ return sIkeMessageHelper.decode(
+ expectedMsgId,
+ integrityMac,
+ decryptCipher,
+ ikeSaRecord,
+ ikeHeader,
+ packet,
+ collectedFragments);
+ }
+
+ private static List<IkePayload> decodePayloadList(
+ @PayloadType int firstPayloadType, boolean isResp, byte[] unencryptedPayloads)
+ throws IkeProtocolException {
+ 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<>();
+
+ // For marking the existence of supported payloads in this message.
+ HashSet<Integer> supportedTypesFoundSet = new HashSet<>();
+
+ StringBuilder logPayloadsSb = new StringBuilder();
+ logPayloadsSb.append("Decoded payloads [ ");
+
+ while (currentPayloadType != IkePayload.PAYLOAD_TYPE_NO_NEXT) {
+ Pair<IkePayload, Integer> pair =
+ IkePayloadFactory.getIkePayload(currentPayloadType, isResp, inputBuffer);
+ IkePayload payload = pair.first;
+ logPayloadsSb.append(payload.getTypeString()).append(" ");
+
+ if (!(payload instanceof IkeUnsupportedPayload)) {
+ int type = payload.payloadType;
+ if (!supportedTypesFoundSet.add(type) && !REPEATABLE_PAYLOAD_TYPES.contains(type)) {
+ throw new InvalidSyntaxException(
+ "It is not allowed to have multiple payloads with payload type: "
+ + type);
+ }
+
+ supportedPayloadList.add(payload);
+ } else if (payload.isCritical) {
+ unsupportedCriticalPayloadList.add(payload.payloadType);
+ }
+ // Simply ignore unsupported uncritical payload.
+
+ currentPayloadType = pair.second;
+ }
+
+ logPayloadsSb.append("]");
+ getIkeLog().d("IkeMessage", logPayloadsSb.toString());
+
+ 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);
+ }
+
+ // TODO: Verify that for all status notification payloads, only
+ // NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP and NOTIFY_TYPE_IPCOMP_SUPPORTED can be included
+ // multiple times in a request message. There is not a clear number restriction for
+ // error notification payloads.
+
+ 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 integrityMac the negotiated integrity algorithm.
+ * @param encryptCipher the negotiated encryption algortihm.
+ * @param ikeSaRecord the ikeSaRecord where this packet is sent on.
+ * @param supportFragment if IKE fragmentation is supported
+ * @param fragSize the maximum size of IKE fragment
+ * @return encoded IKE message in byte array.
+ */
+ public byte[][] encryptAndEncode(
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher encryptCipher,
+ IkeSaRecord ikeSaRecord,
+ boolean supportFragment,
+ int fragSize) {
+ return sIkeMessageHelper.encryptAndEncode(
+ integrityMac, encryptCipher, ikeSaRecord, this, supportFragment, fragSize);
+ }
+
+ /**
+ * Encode all payloads to a byte array.
+ *
+ * @return byte array contains all encoded payloads
+ */
+ private byte[] encodePayloads() {
+ StringBuilder logPayloadsSb = new StringBuilder();
+ logPayloadsSb.append("Generating payloads [ ");
+
+ int payloadLengthSum = 0;
+ for (IkePayload payload : ikePayloadList) {
+ payloadLengthSum += payload.getPayloadLength();
+ logPayloadsSb.append(payload.getTypeString()).append(" ");
+ }
+ logPayloadsSb.append("]");
+ getIkeLog().d("IkeMessage", logPayloadsSb.toString());
+
+ if (ikePayloadList.isEmpty()) return new byte[0];
+
+ 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, encodedIkeBody.length);
+ outputBuffer.put(encodedIkeBody);
+ return outputBuffer.array();
+ }
+
+ /**
+ * Obtain all payloads with input payload type.
+ *
+ * <p>This method can be only applied to the payload types that can be included multiple times
+ * within an IKE message.
+ *
+ * @param payloadType the payloadType to look for.
+ * @param payloadClass the class of the desired payloads.
+ * @return a list of IkePayloads with the payloadType.
+ */
+ public <T extends IkePayload> List<T> getPayloadListForType(
+ @IkePayload.PayloadType int payloadType, Class<T> payloadClass) {
+ // STOPSHIP: b/130190639 Notify user the error and close IKE session.
+ if (!REPEATABLE_PAYLOAD_TYPES.contains(payloadType)) {
+ throw new IllegalArgumentException(
+ "Received unexpected payloadType: "
+ + payloadType
+ + " that can be included at most once within an IKE message.");
+ }
+
+ return IkePayload.getPayloadListForTypeInProvidedList(
+ payloadType, payloadClass, ikePayloadList);
+ }
+
+ /**
+ * Obtain the payload with the input payload type.
+ *
+ * <p>This method can be only applied to the payload type that can be included at most once
+ * within an IKE message.
+ *
+ * @param payloadType the payloadType to look for.
+ * @param payloadClass the class of the desired payload.
+ * @return the IkePayload with the payloadType.
+ */
+ public <T extends IkePayload> T getPayloadForType(
+ @IkePayload.PayloadType int payloadType, Class<T> payloadClass) {
+ // STOPSHIP: b/130190639 Notify user the error and close IKE session.
+ if (REPEATABLE_PAYLOAD_TYPES.contains(payloadType)) {
+ throw new IllegalArgumentException(
+ "Received unexpected payloadType: "
+ + payloadType
+ + " that may be included multiple times within an IKE message.");
+ }
+
+ return IkePayload.getPayloadForTypeInProvidedList(
+ payloadType, payloadClass, ikePayloadList);
+ }
+
+ /**
+ * Checks if this Request IkeMessage was a DPD message
+ *
+ * <p>An IKE message is a DPD request iff the message was encrypted (has a SK payload) and there
+ * were no payloads within the SK payload (or outside the SK payload).
+ */
+ public boolean isDpdRequest() {
+ return !ikeHeader.isResponseMsg
+ && ikeHeader.exchangeType == IkeHeader.EXCHANGE_TYPE_INFORMATIONAL
+ && ikePayloadList.isEmpty()
+ && ikeHeader.nextPayloadType == IkePayload.PAYLOAD_TYPE_SK;
+ }
+
+ /**
+ * 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 {
+ /**
+ * 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 integrityMac the negotiated integrity algorithm.
+ * @param encryptCipher the negotiated encryption algortihm.
+ * @param ikeSaRecord the ikeSaRecord where this packet is sent on.
+ * @param ikeMessage message need to be encoded. * @param supportFragment if IKE
+ * fragmentation is supported.
+ * @param fragSize the maximum size of IKE fragment.
+ * @return encoded IKE message in byte array.
+ */
+ byte[][] encryptAndEncode(
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher encryptCipher,
+ IkeSaRecord ikeSaRecord,
+ IkeMessage ikeMessage,
+ boolean supportFragment,
+ int fragSize);
+
+ // TODO: Return DecodeResult when decoding unencrypted message
+ /**
+ * Decode unencrypted packet.
+ *
+ * @param expectedMsgId the expected message ID to validate against.
+ * @param ikeHeader header of IKE packet.
+ * @param packet IKE packet as a byte array.
+ * @return the decoding result.
+ */
+ DecodeResult decode(int expectedMsgId, IkeHeader ikeHeader, byte[] packet);
+
+ /**
+ * Decrypt and decode packet.
+ *
+ * @param expectedMsgId the expected message ID to validate against.
+ * @param integrityMac the negotiated integrity algorithm.
+ * @param decryptCipher the negotiated encryption algorithm.
+ * @param ikeSaRecord ikeSaRecord where this packet is sent on.
+ * @param ikeHeader header of IKE packet.
+ * @param packet IKE packet as a byte array.
+ * @param collectedFragments previously received IKE fragments.
+ * @return the decoding result.
+ */
+ DecodeResult decode(
+ int expectedMsgId,
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher decryptCipher,
+ IkeSaRecord ikeSaRecord,
+ IkeHeader ikeHeader,
+ byte[] packet,
+ DecodeResultPartial collectedFragments);
+ }
+
+ /** IkeMessageHelper provides methods for decoding, encoding and processing IKE packet. */
+ public static final class IkeMessageHelper implements IIkeMessageHelper {
+ @Override
+ public byte[] encode(IkeMessage ikeMessage) {
+ getIkeLog().d("IkeMessage", "Generating " + ikeMessage.ikeHeader.getBasicInfoString());
+
+ byte[] encodedIkeBody = ikeMessage.encodePayloads();
+ byte[] packet = ikeMessage.attachEncodedHeader(encodedIkeBody);
+ getIkeLog().d("IkeMessage", "Build a complete IKE message: " + getIkeLog().pii(packet));
+ return packet;
+ }
+
+ @Override
+ public byte[][] encryptAndEncode(
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher encryptCipher,
+ IkeSaRecord ikeSaRecord,
+ IkeMessage ikeMessage,
+ boolean supportFragment,
+ int fragSize) {
+ getIkeLog().d("IkeMessage", "Generating " + ikeMessage.ikeHeader.getBasicInfoString());
+
+ return encryptAndEncode(
+ ikeMessage.ikeHeader,
+ ikeMessage.ikePayloadList.isEmpty()
+ ? IkePayload.PAYLOAD_TYPE_NO_NEXT
+ : ikeMessage.ikePayloadList.get(0).payloadType,
+ ikeMessage.encodePayloads(),
+ integrityMac,
+ encryptCipher,
+ ikeSaRecord.getOutboundIntegrityKey(),
+ ikeSaRecord.getOutboundEncryptionKey(),
+ supportFragment,
+ fragSize);
+ }
+
+ @VisibleForTesting
+ byte[][] encryptAndEncode(
+ IkeHeader ikeHeader,
+ @PayloadType int firstInnerPayload,
+ byte[] unencryptedPayloads,
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher encryptCipher,
+ byte[] integrityKey,
+ byte[] encryptionKey,
+ boolean supportFragment,
+ int fragSize) {
+
+ IkeSkPayload skPayload =
+ new IkeSkPayload(
+ ikeHeader,
+ firstInnerPayload,
+ unencryptedPayloads,
+ integrityMac,
+ encryptCipher,
+ integrityKey,
+ encryptionKey);
+ int msgLen = IkeHeader.IKE_HEADER_LENGTH + skPayload.getPayloadLength();
+
+ // Build complete IKE message
+ if (!supportFragment || msgLen <= fragSize) {
+ byte[][] packetList = new byte[1][];
+ packetList[0] = encodeHeaderAndBody(ikeHeader, skPayload, firstInnerPayload);
+
+ getIkeLog()
+ .d(
+ "IkeMessage",
+ "Build a complete IKE message: " + getIkeLog().pii(packetList[0]));
+ return packetList;
+ }
+
+ // Build IKE fragments
+ int dataLenPerPacket =
+ fragSize
+ - IkeHeader.IKE_HEADER_LENGTH
+ - IkePayload.GENERIC_HEADER_LENGTH
+ - IkeSkfPayload.SKF_HEADER_LEN
+ - encryptCipher.getIvLen()
+ - integrityMac.getChecksumLen()
+ - encryptCipher.getBlockSize();
+
+ // Caller of this method MUST validate fragSize is valid.
+ if (dataLenPerPacket <= 0) {
+ throw new IllegalArgumentException(
+ "Max fragment size is too small for an IKE fragment.");
+ }
+
+ int totalFragments =
+ (unencryptedPayloads.length + dataLenPerPacket - 1) / dataLenPerPacket;
+ IkeHeader skfHeader = ikeHeader.makeSkfHeaderFromSkHeader();
+ byte[][] packetList = new byte[totalFragments][];
+
+ ByteBuffer unencryptedDataBuffer = ByteBuffer.wrap(unencryptedPayloads);
+ for (int i = 0; i < totalFragments; i++) {
+ byte[] unencryptedData =
+ new byte[Math.min(dataLenPerPacket, unencryptedDataBuffer.remaining())];
+ unencryptedDataBuffer.get(unencryptedData);
+
+ int fragNum = i + 1; // 1-based
+
+ IkeSkfPayload skfPayload =
+ new IkeSkfPayload(
+ ikeHeader,
+ firstInnerPayload,
+ unencryptedData,
+ integrityMac,
+ encryptCipher,
+ integrityKey,
+ encryptionKey,
+ fragNum,
+ totalFragments);
+
+ packetList[i] =
+ encodeHeaderAndBody(
+ skfHeader,
+ skfPayload,
+ i == 0 ? firstInnerPayload : IkePayload.PAYLOAD_TYPE_NO_NEXT);
+ getIkeLog()
+ .d(
+ "IkeMessage",
+ "Build an IKE fragment ("
+ + (i + 1)
+ + "/"
+ + totalFragments
+ + "): "
+ + getIkeLog().pii(packetList[0]));
+ }
+
+ return packetList;
+ }
+
+ private byte[] encodeHeaderAndBody(
+ IkeHeader ikeHeader, IkeSkPayload skPayload, @PayloadType int firstInnerPayload) {
+ ByteBuffer outputBuffer =
+ ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH + skPayload.getPayloadLength());
+ ikeHeader.encodeToByteBuffer(outputBuffer, skPayload.getPayloadLength());
+ skPayload.encodeToByteBuffer(firstInnerPayload, outputBuffer);
+ return outputBuffer.array();
+ }
+
+ @Override
+ public DecodeResult decode(int expectedMsgId, IkeHeader header, byte[] inputPacket) {
+ try {
+ if (header.messageId != expectedMsgId) {
+ throw new InvalidMessageIdException(header.messageId);
+ }
+
+ header.validateMajorVersion();
+ header.validateInboundHeader(inputPacket.length);
+
+ byte[] unencryptedPayloads =
+ Arrays.copyOfRange(
+ inputPacket, IkeHeader.IKE_HEADER_LENGTH, inputPacket.length);
+ List<IkePayload> supportedPayloadList =
+ decodePayloadList(
+ header.nextPayloadType, header.isResponseMsg, unencryptedPayloads);
+ return new DecodeResultOk(
+ new IkeMessage(header, supportedPayloadList), inputPacket);
+ } catch (NegativeArraySizeException | BufferUnderflowException e) {
+ // Invalid length error when parsing payload bodies.
+ return new DecodeResultUnprotectedError(
+ new InvalidSyntaxException("Malformed IKE Payload"));
+ } catch (IkeProtocolException e) {
+ return new DecodeResultUnprotectedError(e);
+ }
+ }
+
+ @Override
+ public DecodeResult decode(
+ int expectedMsgId,
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher decryptCipher,
+ IkeSaRecord ikeSaRecord,
+ IkeHeader ikeHeader,
+ byte[] packet,
+ DecodeResultPartial collectedFragments) {
+ return decode(
+ expectedMsgId,
+ ikeHeader,
+ packet,
+ integrityMac,
+ decryptCipher,
+ ikeSaRecord.getInboundIntegrityKey(),
+ ikeSaRecord.getInboundDecryptionKey(),
+ collectedFragments);
+ }
+
+ private DecodeResult decode(
+ int expectedMsgId,
+ IkeHeader header,
+ byte[] inputPacket,
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher decryptCipher,
+ byte[] integrityKey,
+ byte[] decryptionKey,
+ DecodeResultPartial collectedFragments) {
+ if (header.nextPayloadType != IkePayload.PAYLOAD_TYPE_SK
+ && header.nextPayloadType != IkePayload.PAYLOAD_TYPE_SKF) {
+ // TODO: b/123372339 Handle message containing unprotected payloads.
+ throw new UnsupportedOperationException("Message contains unprotected payloads");
+ }
+
+ // Decrypt message and do authentication
+ Pair<IkeSkPayload, Integer> pair;
+ try {
+ pair =
+ decryptAndAuthenticate(
+ expectedMsgId,
+ header,
+ inputPacket,
+ integrityMac,
+ decryptCipher,
+ integrityKey,
+ decryptionKey);
+ } catch (IkeException e) {
+ if (collectedFragments == null) {
+ return new DecodeResultUnprotectedError(e);
+ } else {
+ getIkeLog()
+ .i(
+ TAG,
+ "Message authentication or decryption failed on received"
+ + " message. Discard it ",
+ e);
+ return collectedFragments;
+ }
+ }
+
+ // Handle IKE fragment
+ boolean isFragment = (header.nextPayloadType == IkePayload.PAYLOAD_TYPE_SKF);
+ boolean fragReassemblyStarted = (collectedFragments != null);
+
+ if (isFragment) {
+ getIkeLog()
+ .d(
+ TAG,
+ "Received an IKE fragment ("
+ + ((IkeSkfPayload) pair.first).fragmentNum
+ + "/"
+ + ((IkeSkfPayload) pair.first).totalFragments
+ + ")");
+ }
+
+ // IKE fragment reassembly has started but a complete message was received.
+ if (!isFragment && fragReassemblyStarted) {
+ getIkeLog()
+ .w(
+ TAG,
+ "Received a complete IKE message while doing IKE fragment"
+ + " reassembly. Discard the newly received message.");
+ return collectedFragments;
+ }
+
+ byte[] firstPacket = inputPacket;
+ byte[] decryptedBytes = pair.first.getUnencryptedData();
+ int firstPayloadType = pair.second;
+
+ // Received an IKE fragment
+ if (isFragment) {
+ validateFragmentHeader(header, inputPacket.length, collectedFragments);
+
+ // Add the recently received fragment to the reassembly queue.
+ DecodeResultPartial DecodeResultPartial =
+ processIkeFragment(
+ header,
+ inputPacket,
+ (IkeSkfPayload) (pair.first),
+ pair.second,
+ collectedFragments);
+
+ if (!DecodeResultPartial.isAllFragmentsReceived()) return DecodeResultPartial;
+
+ firstPayloadType = DecodeResultPartial.firstPayloadType;
+ decryptedBytes = DecodeResultPartial.reassembleAllFrags();
+ firstPacket = DecodeResultPartial.firstFragBytes;
+ }
+
+ // Received or has reassembled a complete IKE message. Check if there is protocol error.
+ try {
+ // TODO: Log IKE header information and payload types
+
+ List<IkePayload> supportedPayloadList =
+ decodePayloadList(firstPayloadType, header.isResponseMsg, decryptedBytes);
+
+ header.validateInboundHeader(inputPacket.length);
+ return new DecodeResultOk(
+ new IkeMessage(header, supportedPayloadList), firstPacket);
+ } catch (NegativeArraySizeException | BufferUnderflowException e) {
+ // Invalid length error when parsing payload bodies.
+ return new DecodeResultProtectedError(
+ new InvalidSyntaxException("Malformed IKE Payload", e), firstPacket);
+ } catch (IkeProtocolException e) {
+ return new DecodeResultProtectedError(e, firstPacket);
+ }
+ }
+
+ private Pair<IkeSkPayload, Integer> decryptAndAuthenticate(
+ int expectedMsgId,
+ IkeHeader header,
+ byte[] inputPacket,
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher decryptCipher,
+ byte[] integrityKey,
+ byte[] decryptionKey)
+ throws IkeException {
+
+ try {
+ if (header.messageId != expectedMsgId) {
+ throw new InvalidMessageIdException(header.messageId);
+ }
+
+ header.validateMajorVersion();
+
+ boolean isSkf = header.nextPayloadType == IkePayload.PAYLOAD_TYPE_SKF;
+ return IkePayloadFactory.getIkeSkPayload(
+ isSkf,
+ inputPacket,
+ integrityMac,
+ decryptCipher,
+ integrityKey,
+ decryptionKey);
+ } catch (NegativeArraySizeException | BufferUnderflowException e) {
+ throw new InvalidSyntaxException("Malformed IKE Payload", e);
+ } catch (GeneralSecurityException e) {
+ throw new IkeInternalException(e);
+ }
+ }
+
+ private void validateFragmentHeader(
+ IkeHeader fragIkeHeader, int packetLen, DecodeResultPartial collectedFragments) {
+ try {
+ fragIkeHeader.validateInboundHeader(packetLen);
+ } catch (IkeProtocolException e) {
+ getIkeLog()
+ .e(
+ TAG,
+ "Received an IKE fragment with invalid header. Will be handled when"
+ + " reassembly is done.",
+ e);
+ }
+
+ if (collectedFragments == null) return;
+ if (fragIkeHeader.exchangeType != collectedFragments.ikeHeader.exchangeType) {
+ getIkeLog()
+ .e(
+ TAG,
+ "Received an IKE fragment with different exchange type from"
+ + " previously collected fragments. Ignore it.");
+ }
+ }
+
+ private DecodeResultPartial processIkeFragment(
+ IkeHeader header,
+ byte[] inputPacket,
+ IkeSkfPayload skf,
+ int nextPayloadType,
+ @Nullable DecodeResultPartial collectedFragments) {
+ if (collectedFragments == null) {
+ return new DecodeResultPartial(
+ header, inputPacket, skf, nextPayloadType, collectedFragments);
+ }
+
+ if (skf.totalFragments > collectedFragments.collectedFragsList.length) {
+ getIkeLog()
+ .i(
+ TAG,
+ "Received IKE fragment has larger total fragments number. Discard"
+ + " all previously collected fragments");
+ return new DecodeResultPartial(
+ header, inputPacket, skf, nextPayloadType, null /*collectedFragments*/);
+ }
+
+ if (skf.totalFragments < collectedFragments.collectedFragsList.length) {
+ getIkeLog()
+ .i(
+ TAG,
+ "Received IKE fragment has smaller total fragments number. Discard"
+ + " it.");
+ return collectedFragments;
+ }
+
+ if (collectedFragments.collectedFragsList[skf.fragmentNum - 1] != null) {
+ getIkeLog().i(TAG, "Received IKE fragment is a replay.");
+ return collectedFragments;
+ }
+
+ return new DecodeResultPartial(
+ header, inputPacket, skf, nextPayloadType, collectedFragments);
+ }
+ }
+
+ /** Status to describe the result of decoding an inbound IKE message. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DECODE_STATUS_OK,
+ DECODE_STATUS_PARTIAL,
+ DECODE_STATUS_PROTECTED_ERROR,
+ DECODE_STATUS_UNPROTECTED_ERROR,
+ })
+ public @interface DecodeStatus {}
+
+ /**
+ * Represents a message that has been successfully (decrypted and) decoded or reassembled from
+ * IKE fragments
+ */
+ public static final int DECODE_STATUS_OK = 0;
+ /** Represents that reassembly process of IKE fragments has started but has not finished */
+ public static final int DECODE_STATUS_PARTIAL = 1;
+ /** Represents a crypto protected message with correct message ID but has parsing error. */
+ public static final int DECODE_STATUS_PROTECTED_ERROR = 2;
+ /**
+ * Represents an unencrypted message with parsing error, an encrypted message with
+ * authentication or decryption error, or any message with wrong message ID.
+ */
+ public static final int DECODE_STATUS_UNPROTECTED_ERROR = 3;
+
+ /** This class represents common decoding result of an IKE message. */
+ public abstract static class DecodeResult {
+ public final int status;
+
+ /** Construct an instance of DecodeResult. */
+ protected DecodeResult(int status) {
+ this.status = status;
+ }
+ }
+
+ /** This class represents an IKE message has been successfully (decrypted and) decoded. */
+ public static class DecodeResultOk extends DecodeResult {
+ public final IkeMessage ikeMessage;
+ public final byte[] firstPacket;
+
+ public DecodeResultOk(IkeMessage ikeMessage, byte[] firstPacket) {
+ super(DECODE_STATUS_OK);
+ this.ikeMessage = ikeMessage;
+ this.firstPacket = firstPacket;
+ }
+ }
+
+ /**
+ * This class represents IKE fragments are being reassembled to build a complete IKE message.
+ *
+ * <p>All IKE fragments should have the same IKE headers, except for the message length. This
+ * class only stores the IKE header of the first arrived IKE fragment to represent the IKE
+ * header of the complete IKE message. In this way we can verify all subsequent fragments'
+ * headers against it.
+ *
+ * <p>The first payload type is only stored in the first fragment, as indicated in RFC 7383. So
+ * this class only stores the next payload type field taken from the first fragment.
+ */
+ public static class DecodeResultPartial extends DecodeResult {
+ public final int firstPayloadType;
+ public final byte[] firstFragBytes;
+ public final IkeHeader ikeHeader;
+ public final byte[][] collectedFragsList;
+
+ /**
+ * Construct an instance of DecodeResultPartial with collected fragments and the newly
+ * received fragment.
+ *
+ * <p>The newly received fragment has been validated against collected fragments during
+ * decoding that all fragments have the same total fragments number and the newly received
+ * fragment is not a replay.
+ */
+ public DecodeResultPartial(
+ IkeHeader ikeHeader,
+ byte[] inputPacket,
+ IkeSkfPayload skfPayload,
+ int nextPayloadType,
+ @Nullable DecodeResultPartial collectedFragments) {
+ super(DECODE_STATUS_PARTIAL);
+
+ boolean isFirstFragment = 1 == skfPayload.fragmentNum;
+ if (collectedFragments == null) {
+ // First arrived IKE fragment
+ this.ikeHeader = ikeHeader;
+ this.firstPayloadType =
+ isFirstFragment ? nextPayloadType : IkePayload.PAYLOAD_TYPE_NO_NEXT;
+ this.firstFragBytes = isFirstFragment ? inputPacket : null;
+ this.collectedFragsList = new byte[skfPayload.totalFragments][];
+ } else {
+ this.ikeHeader = collectedFragments.ikeHeader;
+ this.firstPayloadType =
+ isFirstFragment ? nextPayloadType : collectedFragments.firstPayloadType;
+ this.firstFragBytes =
+ isFirstFragment ? inputPacket : collectedFragments.firstFragBytes;
+ this.collectedFragsList = collectedFragments.collectedFragsList;
+ }
+
+ this.collectedFragsList[skfPayload.fragmentNum - 1] = skfPayload.getUnencryptedData();
+ }
+
+ /** Return if all IKE fragments have been collected */
+ public boolean isAllFragmentsReceived() {
+ for (byte[] frag : collectedFragsList) {
+ if (frag == null) return false;
+ }
+ return true;
+ }
+
+ /** Reassemble all IKE fragments and return the unencrypted message body in byte array. */
+ public byte[] reassembleAllFrags() {
+ if (!isAllFragmentsReceived()) {
+ throw new IllegalStateException("Not all fragments have been received");
+ }
+
+ int len = 0;
+ for (byte[] frag : collectedFragsList) {
+ len += frag.length;
+ }
+
+ ByteBuffer buffer = ByteBuffer.allocate(len);
+ for (byte[] frag : collectedFragsList) {
+ buffer.put(frag);
+ }
+
+ return buffer.array();
+ }
+ }
+
+ /**
+ * This class represents common information of error cases in decrypting and decoding message.
+ */
+ public abstract static class DecodeResultError extends DecodeResult {
+ public final IkeException ikeException;
+
+ protected DecodeResultError(int status, IkeException ikeException) {
+ super(status);
+ this.ikeException = ikeException;
+ }
+ }
+ /**
+ * This class represents that decoding errors have been found after the IKE message is
+ * authenticated and decrypted.
+ */
+ public static class DecodeResultProtectedError extends DecodeResultError {
+ public final byte[] firstPacket;
+
+ public DecodeResultProtectedError(IkeException ikeException, byte[] firstPacket) {
+ super(DECODE_STATUS_PROTECTED_ERROR, ikeException);
+ this.firstPacket = firstPacket;
+ }
+ }
+ /** This class represents errors have been found during message authentication or decryption. */
+ public static class DecodeResultUnprotectedError extends DecodeResultError {
+ public DecodeResultUnprotectedError(IkeException ikeException) {
+ super(DECODE_STATUS_UNPROTECTED_ERROR, ikeException);
+ }
+ }
+
+ /**
+ * For setting mocked IIkeMessageHelper for testing
+ *
+ * @param helper the mocked IIkeMessageHelper
+ */
+ public static void setIkeMessageHelper(IIkeMessageHelper helper) {
+ sIkeMessageHelper = helper;
+ }
+}