aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
diff options
context:
space:
mode:
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, 0 insertions, 981 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
deleted file mode 100644
index 27fb9651..00000000
--- a/src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java
+++ /dev/null
@@ -1,981 +0,0 @@
-/*
- * 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;
- }
-}