diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:19:18 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:19:18 +0000 |
commit | db74d7937f3a9503f0e2590ec24c3bcfbf259ce0 (patch) | |
tree | b39c3e794945072d38b97c963c5a6f10753514e1 /src/java/com/android/internal/net/ipsec/ike/message/IkeMessage.java | |
parent | 7ffef5cae6d3c93c93b4055fd22517d44f77acda (diff) | |
parent | eb4c77d7228f956f928c7d3500a220339ee78388 (diff) | |
download | ike-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.java | 981 |
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; + } +} |