| /* |
| * 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 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 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 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 |
| |
| int fragFirstInnerPayload = |
| i == 0 ? firstInnerPayload : IkePayload.PAYLOAD_TYPE_NO_NEXT; |
| IkeSkfPayload skfPayload = |
| new IkeSkfPayload( |
| skfHeader, |
| fragFirstInnerPayload, |
| unencryptedData, |
| integrityMac, |
| encryptCipher, |
| integrityKey, |
| encryptionKey, |
| fragNum, |
| totalFragments); |
| |
| packetList[i] = encodeHeaderAndBody(skfHeader, skfPayload, fragFirstInnerPayload); |
| getIkeLog() |
| .d( |
| "IkeMessage", |
| "Build an IKE fragment (" |
| + (i + 1) |
| + "/" |
| + totalFragments |
| + "): " |
| + getIkeLog().pii(packetList[i])); |
| } |
| |
| 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; |
| } |
| } |