Merge "Add end-to-end testing for EAP-AKA."
diff --git a/src/java/com/android/ike/eap/message/simaka/EapAkaTypeData.java b/src/java/com/android/ike/eap/message/simaka/EapAkaTypeData.java
index 381c538..c486485 100644
--- a/src/java/com/android/ike/eap/message/simaka/EapAkaTypeData.java
+++ b/src/java/com/android/ike/eap/message/simaka/EapAkaTypeData.java
@@ -91,7 +91,7 @@
}
/**
- * EapSimTypeDataDecoder will be used for decoding {@link EapAkaTypeData} objects.
+ * EapAkaTypeDataDecoder will be used for decoding {@link EapAkaTypeData} objects.
*/
public static class EapAkaTypeDataDecoder extends EapSimAkaTypeDataDecoder<EapAkaTypeData> {
private static final String TAG = EapAkaTypeDataDecoder.class.getSimpleName();
@@ -109,7 +109,7 @@
/**
* Decodes the given byte-array into a DecodeResult object.
*
- * @param typeData the byte-encoding of the EapSimTypeData to be parsed
+ * @param typeData the byte-encoding of the EapAkaTypeData to be parsed
* @return a DecodeResult object. If the decoding is successful, this will encapsulate an
* EapAkaTypeData instance representing the data stored in typeData. Otherwise, it
* will contain the relevant AtClientErrorCode for the decoding error.
diff --git a/src/java/com/android/ike/ikev2/IkeManager.java b/src/java/com/android/ike/ikev2/IkeManager.java
index ab37429..61a67de 100644
--- a/src/java/com/android/ike/ikev2/IkeManager.java
+++ b/src/java/com/android/ike/ikev2/IkeManager.java
@@ -66,8 +66,8 @@
firstChildSessionCallback);
}
- // Package private
- static Log getIkeLog() {
+ /** Returns IKE logger. */
+ public static Log getIkeLog() {
return sIkeLog;
}
diff --git a/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java b/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
index 10180f1..c191037 100644
--- a/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
+++ b/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
@@ -22,8 +22,10 @@
import static com.android.ike.ikev2.exceptions.IkeProtocolException.ErrorType;
import static com.android.ike.ikev2.message.IkeHeader.EXCHANGE_TYPE_INFORMATIONAL;
import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_OK;
-import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_PROTECTED_ERROR_MESSAGE;
-import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE;
+import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_PARTIAL;
+import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_PROTECTED_ERROR;
+import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR;
+import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED;
import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP;
import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP;
import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_REKEY_SA;
@@ -76,6 +78,9 @@
import com.android.ike.ikev2.message.IkeKePayload;
import com.android.ike.ikev2.message.IkeMessage;
import com.android.ike.ikev2.message.IkeMessage.DecodeResult;
+import com.android.ike.ikev2.message.IkeMessage.DecodeResultError;
+import com.android.ike.ikev2.message.IkeMessage.DecodeResultOk;
+import com.android.ike.ikev2.message.IkeMessage.DecodeResultPartial;
import com.android.ike.ikev2.message.IkeNoncePayload;
import com.android.ike.ikev2.message.IkeNotifyPayload;
import com.android.ike.ikev2.message.IkePayload;
@@ -133,6 +138,11 @@
private static final String TAG = "IkeSessionStateMachine";
+ // TODO: b/140579254 Allow users to configure fragment size.
+
+ // Default fragment size in bytes.
+ @VisibleForTesting static final int DEFAULT_FRAGMENT_SIZE = 1280;
+
// TODO: Add SA_HARD_LIFETIME_MS
// Time after which IKE SA needs to be rekeyed
@@ -265,8 +275,11 @@
final HashMap<IChildSessionCallback, ChildSessionStateMachine> mChildCbToSessions =
new HashMap<>();
- @VisibleForTesting byte[] mLastReceivedIkeReq;
- @VisibleForTesting byte[] mLastSentIkeResp;
+ // TODO: Store them in IkeSaRecord
+ // If received request is identical in byte with last received request or the first fragment of
+ // last received request, retransmit the cached response.
+ @VisibleForTesting byte[] mLastReceivedIkeReqFirstPacket;
+ @VisibleForTesting byte[][] mLastSentIkeResp;
/**
* Package private socket that sends and receives encoded IKE message. Initialized in Initial
@@ -286,6 +299,9 @@
/** Indicates if remote node is behind a NAT. */
@VisibleForTesting boolean mIsRemoteBehindNat;
+ /** Indicates if both sides support fragmentation. Set in IKE INIT */
+ @VisibleForTesting boolean mSupportFragment;
+
/** Package private SaProposal that represents the negotiated IKE SA proposal. */
@VisibleForTesting SaProposal mSaProposal;
@@ -1148,10 +1164,17 @@
// Sends the provided IkeMessage using the provided IKE SA record
@VisibleForTesting
void sendEncryptedIkeMessage(IkeSaRecord ikeSaRecord, IkeMessage msg) {
- byte[] bytes = msg.encryptAndEncode(mIkeIntegrity, mIkeCipher, ikeSaRecord);
- mIkeSocket.sendIkePacket(bytes, mRemoteAddress);
-
- if (msg.ikeHeader.isResponseMsg) mLastSentIkeResp = bytes;
+ byte[][] packetList =
+ msg.encryptAndEncode(
+ mIkeIntegrity,
+ mIkeCipher,
+ ikeSaRecord,
+ mSupportFragment,
+ DEFAULT_FRAGMENT_SIZE);
+ for (byte[] packet : packetList) {
+ mIkeSocket.sendIkePacket(packet, mRemoteAddress);
+ }
+ if (msg.ikeHeader.isResponseMsg) mLastSentIkeResp = packetList;
}
// Builds and sends IKE-level error notification response on the provided IKE SA record
@@ -1362,36 +1385,45 @@
mIkeCipher,
ikeSaRecord,
ikeHeader,
- ikePacketBytes);
+ ikePacketBytes,
+ ikeSaRecord.getCollectedFragments(true /*isResp*/));
switch (decodeResult.status) {
case DECODE_STATUS_OK:
ikeSaRecord.incrementLocalRequestMessageId();
+ ikeSaRecord.resetCollectedFragments(true /*isResp*/);
- if (isTempFailure(decodeResult.ikeMessage)) {
+ DecodeResultOk resultOk = (DecodeResultOk) decodeResult;
+ if (isTempFailure(resultOk.ikeMessage)) {
handleTempFailure();
} else {
mTempFailHandler.reset();
}
- handleResponseIkeMessage(decodeResult.ikeMessage);
+ handleResponseIkeMessage(resultOk.ikeMessage);
break;
- case DECODE_STATUS_PROTECTED_ERROR_MESSAGE:
- loge(methodTag + "Protected error", decodeResult.ikeException);
+ case DECODE_STATUS_PARTIAL:
+ ikeSaRecord.updateCollectedFragments(
+ (DecodeResultPartial) decodeResult, true /*isResp*/);
+ break;
+ case DECODE_STATUS_PROTECTED_ERROR:
+ IkeException ikeException = ((DecodeResultError) decodeResult).ikeException;
+ logi(methodTag + "Protected error", ikeException);
ikeSaRecord.incrementLocalRequestMessageId();
+ ikeSaRecord.resetCollectedFragments(true /*isResp*/);
handleResponseGenericProcessError(
ikeSaRecord,
new InvalidSyntaxException(
"Generic processing error in the received response",
- decodeResult.ikeException));
+ ikeException));
break;
- case DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE:
+ case DECODE_STATUS_UNPROTECTED_ERROR:
logi(
methodTag
+ "Message authentication or decryption failed on received"
+ " response. Discard it",
- decodeResult.ikeException);
+ ((DecodeResultError) decodeResult).ikeException);
break;
default:
cleanUpAndQuit(
@@ -1402,10 +1434,12 @@
} else {
int expectedMsgId = ikeSaRecord.getRemoteRequestMessageId();
if (expectedMsgId - 1 == ikeHeader.messageId) {
- if (Arrays.equals(mLastReceivedIkeReq, ikePacketBytes)) {
+ if (Arrays.equals(mLastReceivedIkeReqFirstPacket, ikePacketBytes)) {
logd("Received re-transmitted request. Retransmitting response");
- mIkeSocket.sendIkePacket(mLastSentIkeResp, mRemoteAddress);
+ for (byte[] packet : mLastSentIkeResp) {
+ mIkeSocket.sendIkePacket(packet, mRemoteAddress);
+ }
// Notify state if it is listening for retransmitted request.
handleRetransmittedReq();
@@ -1421,12 +1455,16 @@
mIkeCipher,
ikeSaRecord,
ikeHeader,
- ikePacketBytes);
+ ikePacketBytes,
+ ikeSaRecord.getCollectedFragments(false /*isResp*/));
switch (decodeResult.status) {
case DECODE_STATUS_OK:
ikeSaRecord.incrementRemoteRequestMessageId();
- mLastReceivedIkeReq = ikePacketBytes;
- IkeMessage ikeMessage = decodeResult.ikeMessage;
+ ikeSaRecord.resetCollectedFragments(false /*isResp*/);
+
+ // TODO:b/140429499 Only store the first fragment
+ mLastReceivedIkeReqFirstPacket = ikePacketBytes;
+ IkeMessage ikeMessage = ((DecodeResultOk) decodeResult).ikeMessage;
// Handle DPD here.
if (ikeMessage.isDpdRequest()) {
@@ -1468,24 +1506,32 @@
}
handleRequestIkeMessage(ikeMessage, ikeExchangeSubType, message);
break;
- case DECODE_STATUS_PROTECTED_ERROR_MESSAGE:
- loge(methodTag + "Protected error", decodeResult.ikeException);
+ case DECODE_STATUS_PARTIAL:
+ ikeSaRecord.updateCollectedFragments(
+ (DecodeResultPartial) decodeResult, false /*isResp*/);
+ break;
+ case DECODE_STATUS_PROTECTED_ERROR:
+ IkeException ikeException =
+ ((DecodeResultError) decodeResult).ikeException;
+ logi(methodTag + "Protected error", ikeException);
ikeSaRecord.incrementRemoteRequestMessageId();
- mLastReceivedIkeReq = ikePacketBytes;
+ ikeSaRecord.resetCollectedFragments(false /*isResp*/);
+
+ mLastReceivedIkeReqFirstPacket = ikePacketBytes;
// IkeException MUST be already wrapped into an IkeProtocolException
handleRequestGenericProcessError(
ikeSaRecord,
ikeHeader.messageId,
- (IkeProtocolException) decodeResult.ikeException);
+ (IkeProtocolException) ikeException);
break;
- case DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE:
+ case DECODE_STATUS_UNPROTECTED_ERROR:
logi(
methodTag
+ "Message authentication or decryption failed on"
+ " received request. Discard it",
- decodeResult.ikeException);
+ ((DecodeResultError) decodeResult).ikeException);
break;
default:
cleanUpAndQuit(
@@ -2279,6 +2325,8 @@
private IkeSecurityParameterIndex mRemoteIkeSpiResource;
private Retransmitter mRetransmitter;
+ // TODO: Support negotiating IKE fragmentation
+
@Override
public void enterState() {
try {
@@ -2336,7 +2384,7 @@
switch (decodeResult.status) {
case DECODE_STATUS_OK:
- handleResponseIkeMessage(decodeResult.ikeMessage);
+ handleResponseIkeMessage(((DecodeResultOk) decodeResult).ikeMessage);
mIkeInitResponseBytes = ikePacketBytes;
// SA negotiation failed
@@ -2344,15 +2392,20 @@
mCurrentIkeSaRecord.incrementLocalRequestMessageId();
break;
- case DECODE_STATUS_PROTECTED_ERROR_MESSAGE:
+ case DECODE_STATUS_PARTIAL:
+ // Fall through. We don't support IKE fragmentation here. We should never
+ // get this status.
+ case DECODE_STATUS_PROTECTED_ERROR:
// IKE INIT response is not protected. So we should never get this status
cleanUpAndQuit(
new IllegalStateException(
"Unexpected decoding status: " + decodeResult.status));
- case DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE:
+ break;
+ case DECODE_STATUS_UNPROTECTED_ERROR:
logi(
"Discard unencrypted response with syntax error",
- decodeResult.ikeException);
+ ((DecodeResultError) decodeResult).ikeException);
+ break;
default:
cleanUpAndQuit(
new IllegalStateException(
@@ -2422,6 +2475,9 @@
mRemoteAddress,
mLocalPort,
IkeSocket.IKE_SERVER_PORT);
+ payloadList.add(
+ new IkeNotifyPayload(
+ IkeNotifyPayload.NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED));
// TODO: Add Notification Payloads according to user configurations.
@@ -2505,6 +2561,9 @@
}
natDestPayload = notifyPayload;
break;
+ case NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED:
+ mSupportFragment = true;
+ break;
default:
// Unknown and unexpected status notifications are ignored as per
// RFC7296.
@@ -3628,10 +3687,12 @@
mIkeCipher,
ikeSaRecord,
ikeHeader,
- receivedIkePacket.ikePacketBytes);
+ receivedIkePacket.ikePacketBytes,
+ ikeSaRecord.getCollectedFragments(ikeHeader.isResponseMsg));
isMessageOnNewSa =
- (decodeResult.status == DECODE_STATUS_PROTECTED_ERROR_MESSAGE)
- || (decodeResult.status == DECODE_STATUS_OK);
+ (decodeResult.status == DECODE_STATUS_PROTECTED_ERROR)
+ || (decodeResult.status == DECODE_STATUS_OK)
+ || (decodeResult.status == DECODE_STATUS_PARTIAL);
}
// Authenticated request received on the new/surviving SA; treat it as
diff --git a/src/java/com/android/ike/ikev2/SaRecord.java b/src/java/com/android/ike/ikev2/SaRecord.java
index 5a02b2b..9edd77d 100644
--- a/src/java/com/android/ike/ikev2/SaRecord.java
+++ b/src/java/com/android/ike/ikev2/SaRecord.java
@@ -33,6 +33,7 @@
import com.android.ike.ikev2.crypto.IkeMacPrf;
import com.android.ike.ikev2.message.IkeKePayload;
import com.android.ike.ikev2.message.IkeMessage;
+import com.android.ike.ikev2.message.IkeMessage.DecodeResultPartial;
import com.android.ike.ikev2.message.IkeNoncePayload;
import com.android.ike.ikev2.message.IkePayload;
import com.android.internal.annotations.VisibleForTesting;
@@ -563,6 +564,9 @@
private int mLocalRequestMessageId;
private int mRemoteRequestMessageId;
+ private DecodeResultPartial mCollectedReqFragments;
+ private DecodeResultPartial mCollectedRespFragments;
+
/** Package private */
IkeSaRecord(
IkeSecurityParameterIndex initSpi,
@@ -589,6 +593,9 @@
mLocalRequestMessageId = 0;
mRemoteRequestMessageId = 0;
+
+ mCollectedReqFragments = null;
+ mCollectedRespFragments = null;
}
/**
@@ -748,6 +755,33 @@
mRemoteRequestMessageId++;
}
+ /** Return all collected IKE fragments that have been collected. */
+ public DecodeResultPartial getCollectedFragments(boolean isResp) {
+ return isResp ? mCollectedRespFragments : mCollectedReqFragments;
+ }
+
+ /**
+ * Update collected IKE fragments when receiving new IKE fragment.
+ *
+ * <p>TODO: b/140264067 Investigate if we need to support reassembling timeout. It is safe
+ * to do not support it because as an initiator, we will re-transmit the request anyway. As
+ * a responder, caching these fragments until getting a complete message won't affect
+ * anything.
+ */
+ public void updateCollectedFragments(
+ DecodeResultPartial updatedFragments, boolean isResp) {
+ if (isResp) {
+ mCollectedRespFragments = updatedFragments;
+ } else {
+ mCollectedReqFragments = updatedFragments;
+ }
+ }
+
+ /** Reset collected IKE fragemnts */
+ public void resetCollectedFragments(boolean isResp) {
+ updateCollectedFragments(null, isResp);
+ }
+
/** Release IKE SPI resource. */
@Override
public void close() {
diff --git a/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java b/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java
index 6967efd..2286ac3 100644
--- a/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java
+++ b/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java
@@ -60,7 +60,7 @@
IkeMacIntegrity integrityMac,
IkeCipher decryptCipher,
byte[] integrityKey,
- byte[] decryptKey)
+ byte[] decryptionKey)
throws IkeProtocolException, GeneralSecurityException {
ByteBuffer inputBuffer = ByteBuffer.wrap(message);
@@ -82,7 +82,7 @@
// Authenticate and decrypt.
byte[] dataToAuthenticate = Arrays.copyOfRange(message, 0, message.length - checksumLen);
validateChecksumOrThrow(dataToAuthenticate, integrityMac, integrityKey, mIntegrityChecksum);
- mUnencryptedData = decrypt(mEncryptedAndPaddedData, decryptCipher, decryptKey, mIv);
+ mUnencryptedData = decrypt(mEncryptedAndPaddedData, decryptCipher, decryptionKey, mIv);
}
/**
@@ -96,7 +96,7 @@
IkeMacIntegrity integrityMac,
IkeCipher encryptCipher,
byte[] integrityKey,
- byte[] encryptKey) {
+ byte[] encryptionKey) {
this(
ikeHeader,
firstPayloadType,
@@ -104,7 +104,7 @@
integrityMac,
encryptCipher,
integrityKey,
- encryptKey,
+ encryptionKey,
encryptCipher.generateIv(),
calculatePadding(unencryptedPayloads.length, encryptCipher.getBlockSize()));
}
@@ -118,7 +118,7 @@
IkeMacIntegrity integrityMac,
IkeCipher encryptCipher,
byte[] integrityKey,
- byte[] encryptKey,
+ byte[] encryptionKey,
byte[] iv,
byte[] padding) {
mUnencryptedData = unencryptedPayloads;
@@ -126,7 +126,7 @@
// Encrypt data
mIv = iv;
mEncryptedAndPaddedData =
- encrypt(unencryptedPayloads, encryptCipher, encryptKey, iv, padding);
+ encrypt(unencryptedPayloads, encryptCipher, encryptionKey, iv, padding);
// Build authenticated section using ByteBuffer. Authenticated section includes bytes from
// beginning of IKE header to the pad length, which are concatenation of IKE header, current
@@ -189,7 +189,7 @@
static byte[] encrypt(
byte[] dataToEncrypt,
IkeCipher encryptCipher,
- byte[] encryptKey,
+ byte[] encryptionKey,
byte[] iv,
byte[] padding) {
int padLength = padding.length;
@@ -198,15 +198,15 @@
inputBuffer.put(dataToEncrypt).put(padding).put((byte) padLength);
// Encrypt data.
- return encryptCipher.encrypt(inputBuffer.array(), encryptKey, iv);
+ return encryptCipher.encrypt(inputBuffer.array(), encryptionKey, iv);
}
/** Package private for testing */
@VisibleForTesting
static byte[] decrypt(
- byte[] encryptedData, IkeCipher decryptCipher, byte[] decryptKey, byte[] iv)
+ byte[] encryptedData, IkeCipher decryptCipher, byte[] decryptionKey, byte[] iv)
throws IllegalBlockSizeException {
- byte[] decryptedPaddedData = decryptCipher.decrypt(encryptedData, decryptKey, iv);
+ byte[] decryptedPaddedData = decryptCipher.decrypt(encryptedData, decryptionKey, iv);
// Remove padding. Pad length value is the last byte of the padded unencrypted data.
int padLength = Byte.toUnsignedInt(decryptedPaddedData[encryptedData.length - 1]);
diff --git a/src/java/com/android/ike/ikev2/message/IkeHeader.java b/src/java/com/android/ike/ikev2/message/IkeHeader.java
index 188a8b0..a4d739c 100644
--- a/src/java/com/android/ike/ikev2/message/IkeHeader.java
+++ b/src/java/com/android/ike/ikev2/message/IkeHeader.java
@@ -145,6 +145,21 @@
mEncodedMessageLength = buffer.getInt();
}
+ /** Packet private method to build header of an IKE fragemnt from current IKE header. */
+ IkeHeader makeSkfHeaderFromSkHeader() {
+ if (nextPayloadType != IkePayload.PAYLOAD_TYPE_SK) {
+ throw new IllegalArgumentException("Next payload type is not SK.");
+ }
+ return new IkeHeader(
+ ikeInitiatorSpi,
+ ikeResponderSpi,
+ IkePayload.PAYLOAD_TYPE_SKF,
+ exchangeType,
+ isResponseMsg,
+ fromIkeInitiator,
+ messageId);
+ }
+
/*Package private*/
@VisibleForTesting
int getInboundMessageLength() {
@@ -155,8 +170,8 @@
return mEncodedMessageLength;
}
- /** Validate syntax and major version of inbound IKE header. */
- public void checkInboundValidOrThrow(int packetLength) throws IkeProtocolException {
+ /** Validate major version of inbound IKE header. */
+ public void validateMajorVersion() throws IkeProtocolException {
if (majorVersion > 2) {
// Receive higher version of protocol. Stop parsing.
throw new InvalidMajorVersionException(majorVersion);
@@ -168,6 +183,15 @@
// error.
throw new InvalidSyntaxException("Major version is smaller than 2.");
}
+ }
+
+ /**
+ * Validate syntax of inbound IKE header.
+ *
+ * <p>It MUST only be used for an inbound IKE header because we don't know the outbound message
+ * length before we encode it.
+ */
+ public void validateInboundHeader(int packetLength) throws IkeProtocolException {
if (exchangeType < EXCHANGE_TYPE_IKE_SA_INIT
|| exchangeType > EXCHANGE_TYPE_INFORMATIONAL) {
throw new InvalidSyntaxException("Invalid IKE Exchange Type.");
diff --git a/src/java/com/android/ike/ikev2/message/IkeMessage.java b/src/java/com/android/ike/ikev2/message/IkeMessage.java
index 38366d8..88d760e 100644
--- a/src/java/com/android/ike/ikev2/message/IkeMessage.java
+++ b/src/java/com/android/ike/ikev2/message/IkeMessage.java
@@ -16,6 +16,7 @@
package com.android.ike.ikev2.message;
+import static com.android.ike.ikev2.IkeManager.getIkeLog;
import static com.android.ike.ikev2.message.IkePayload.PayloadType;
import android.annotation.IntDef;
@@ -56,6 +57,8 @@
* 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();
@@ -120,6 +123,7 @@
* @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(
@@ -128,9 +132,16 @@
IkeCipher decryptCipher,
IkeSaRecord ikeSaRecord,
IkeHeader ikeHeader,
- byte[] packet) {
+ byte[] packet,
+ DecodeResultPartial collectedFragments) {
return sIkeMessageHelper.decode(
- expectedMsgId, integrityMac, decryptCipher, ikeSaRecord, ikeHeader, packet);
+ expectedMsgId,
+ integrityMac,
+ decryptCipher,
+ ikeSaRecord,
+ ikeHeader,
+ packet,
+ collectedFragments);
}
private static List<IkePayload> decodePayloadList(
@@ -200,13 +211,18 @@
* @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(
+ public byte[][] encryptAndEncode(
@Nullable IkeMacIntegrity integrityMac,
IkeCipher encryptCipher,
- IkeSaRecord ikeSaRecord) {
- return sIkeMessageHelper.encryptAndEncode(integrityMac, encryptCipher, ikeSaRecord, this);
+ IkeSaRecord ikeSaRecord,
+ boolean supportFragment,
+ int fragSize) {
+ return sIkeMessageHelper.encryptAndEncode(
+ integrityMac, encryptCipher, ikeSaRecord, this, supportFragment, fragSize);
}
/**
@@ -330,14 +346,18 @@
* @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 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(
+ byte[][] encryptAndEncode(
@Nullable IkeMacIntegrity integrityMac,
IkeCipher encryptCipher,
IkeSaRecord ikeSaRecord,
- IkeMessage ikeMessage);
+ IkeMessage ikeMessage,
+ boolean supportFragment,
+ int fragSize);
// TODO: Return DecodeResult when decoding unencrypted message
/**
@@ -359,6 +379,7 @@
* @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(
@@ -367,7 +388,8 @@
IkeCipher decryptCipher,
IkeSaRecord ikeSaRecord,
IkeHeader ikeHeader,
- byte[] packet);
+ byte[] packet,
+ DecodeResultPartial collectedFragments);
}
/** IkeMessageHelper provides methods for decoding, encoding and processing IKE packet. */
@@ -379,11 +401,13 @@
}
@Override
- public byte[] encryptAndEncode(
+ public byte[][] encryptAndEncode(
@Nullable IkeMacIntegrity integrityMac,
IkeCipher encryptCipher,
IkeSaRecord ikeSaRecord,
- IkeMessage ikeMessage) {
+ IkeMessage ikeMessage,
+ boolean supportFragment,
+ int fragSize) {
return encryptAndEncode(
ikeMessage.ikeHeader,
ikeMessage.ikePayloadList.isEmpty()
@@ -393,32 +417,112 @@
integrityMac,
encryptCipher,
ikeSaRecord.getOutboundIntegrityKey(),
- ikeSaRecord.getOutboundEncryptionKey());
+ ikeSaRecord.getOutboundEncryptionKey(),
+ supportFragment,
+ fragSize);
}
- private byte[] encryptAndEncode(
+ @VisibleForTesting
+ byte[][] encryptAndEncode(
IkeHeader ikeHeader,
- @PayloadType int firstPayload,
+ @PayloadType int firstInnerPayload,
byte[] unencryptedPayloads,
@Nullable IkeMacIntegrity integrityMac,
IkeCipher encryptCipher,
byte[] integrityKey,
- byte[] encryptKey) {
+ byte[] encryptionKey,
+ boolean supportFragment,
+ int fragSize) {
+
IkeSkPayload skPayload =
new IkeSkPayload(
ikeHeader,
- firstPayload,
+ firstInnerPayload,
unencryptedPayloads,
integrityMac,
encryptCipher,
integrityKey,
- encryptKey);
+ 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(firstPayload, outputBuffer);
-
+ skPayload.encodeToByteBuffer(firstInnerPayload, outputBuffer);
return outputBuffer.array();
}
@@ -429,7 +533,8 @@
throw new InvalidMessageIdException(header.messageId);
}
- header.checkInboundValidOrThrow(inputPacket.length);
+ header.validateMajorVersion();
+ header.validateInboundHeader(inputPacket.length);
byte[] unencryptedPayloads =
Arrays.copyOfRange(
@@ -437,19 +542,14 @@
List<IkePayload> supportedPayloadList =
decodePayloadList(
header.nextPayloadType, header.isResponseMsg, unencryptedPayloads);
- return new DecodeResult(
- DECODE_STATUS_OK,
- new IkeMessage(header, supportedPayloadList),
- null /*ikeException*/);
+ return new DecodeResultOk(new IkeMessage(header, supportedPayloadList));
} catch (NegativeArraySizeException | BufferUnderflowException e) {
// Invalid length error when parsing payload bodies.
- return new DecodeResult(
- DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE,
- null /*ikeMessage*/,
+ return new DecodeResultError(
+ DECODE_STATUS_UNPROTECTED_ERROR,
new InvalidSyntaxException("Malformed IKE Payload"));
} catch (IkeProtocolException e) {
- return new DecodeResult(
- DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE, null /*ikeMessage*/, e);
+ return new DecodeResultError(DECODE_STATUS_UNPROTECTED_ERROR, e);
}
}
@@ -460,7 +560,8 @@
IkeCipher decryptCipher,
IkeSaRecord ikeSaRecord,
IkeHeader ikeHeader,
- byte[] packet) {
+ byte[] packet,
+ DecodeResultPartial collectedFragments) {
return decode(
expectedMsgId,
ikeHeader,
@@ -468,7 +569,8 @@
integrityMac,
decryptCipher,
ikeSaRecord.getInboundIntegrityKey(),
- ikeSaRecord.getInboundDecryptionKey());
+ ikeSaRecord.getInboundDecryptionKey(),
+ collectedFragments);
}
private DecodeResult decode(
@@ -478,104 +580,324 @@
@Nullable IkeMacIntegrity integrityMac,
IkeCipher decryptCipher,
byte[] integrityKey,
- byte[] decryptKey) {
-
- if (header.nextPayloadType != IkePayload.PAYLOAD_TYPE_SK) {
+ 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");
}
- // Validate security parameters.
+ // Decrypt message and do authentication
Pair<IkeSkPayload, Integer> pair;
try {
- if (header.messageId != expectedMsgId) {
- throw new InvalidMessageIdException(header.messageId);
- }
pair =
- IkePayloadFactory.getIkeSkPayload(
- false /*isSkf*/,
+ decryptAndAuthenticate(
+ expectedMsgId,
+ header,
inputPacket,
integrityMac,
decryptCipher,
integrityKey,
- decryptKey);
-
- // TODO: Support decoding IkeSkfPayload
- } catch (NegativeArraySizeException | BufferUnderflowException e) {
- return new DecodeResult(
- DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE,
- null /*ikeMessage*/,
- new InvalidSyntaxException("Malformed IKE Payload"));
- } catch (GeneralSecurityException e) {
- return new DecodeResult(
- DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE,
- null /*ikeMessage*/,
- new IkeInternalException(e));
+ decryptionKey);
} catch (IkeException e) {
- return new DecodeResult(
- DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE, null /*ikeMessage*/, e);
+ if (collectedFragments == null) {
+ return new DecodeResultError(DECODE_STATUS_UNPROTECTED_ERROR, e);
+ } else {
+ getIkeLog()
+ .i(
+ TAG,
+ "Message authentication or decryption failed on received"
+ + " message. Discard it ",
+ e);
+ return collectedFragments;
+ }
}
- // Check is there is protocol error in this IKE message.
+ // 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[] 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,
+ (IkeSkfPayload) (pair.first),
+ pair.second,
+ collectedFragments);
+
+ if (!DecodeResultPartial.isAllFragmentsReceived()) return DecodeResultPartial;
+
+ firstPayloadType = DecodeResultPartial.firstPayloadType;
+ decryptedBytes = DecodeResultPartial.reassembleAllFrags();
+ }
+
+ // Received or has reassembled a complete IKE message. Check if there is protocol error.
try {
- IkeSkPayload skPayload = pair.first;
- int firstPayloadType = pair.second;
+ // TODO: Log IKE header information and payload types
List<IkePayload> supportedPayloadList =
- decodePayloadList(
- firstPayloadType,
- header.isResponseMsg,
- skPayload.getUnencryptedData());
+ decodePayloadList(firstPayloadType, header.isResponseMsg, decryptedBytes);
- header.checkInboundValidOrThrow(inputPacket.length);
- return new DecodeResult(
- DECODE_STATUS_OK,
- new IkeMessage(header, supportedPayloadList),
- null /*ikeException*/);
+ header.validateInboundHeader(inputPacket.length);
+ return new DecodeResultOk(new IkeMessage(header, supportedPayloadList));
} catch (NegativeArraySizeException | BufferUnderflowException e) {
// Invalid length error when parsing payload bodies.
- return new DecodeResult(
- DECODE_STATUS_PROTECTED_ERROR_MESSAGE,
- null /*ikeMessage*/,
- new InvalidSyntaxException("Malformed IKE Payload"));
+ return new DecodeResultError(
+ DECODE_STATUS_PROTECTED_ERROR,
+ new InvalidSyntaxException("Malformed IKE Payload", e));
} catch (IkeProtocolException e) {
- return new DecodeResult(
- DECODE_STATUS_PROTECTED_ERROR_MESSAGE, null /*ikeMessage*/, e);
+ return new DecodeResultError(DECODE_STATUS_PROTECTED_ERROR, e);
}
}
+
+ 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,
+ IkeSkfPayload skf,
+ int nextPayloadType,
+ @Nullable DecodeResultPartial collectedFragments) {
+ if (collectedFragments == null) {
+ return new DecodeResultPartial(header, 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, 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, skf, nextPayloadType, collectedFragments);
+ }
}
/** Status to describe the result of decoding an inbound IKE message. */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
DECODE_STATUS_OK,
- DECODE_STATUS_PROTECTED_ERROR_MESSAGE,
- DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE
+ DECODE_STATUS_PARTIAL,
+ DECODE_STATUS_PROTECTED_ERROR,
+ DECODE_STATUS_UNPROTECTED_ERROR,
})
public @interface DecodeStatus {}
- /** Represents a message that has been successfuly (decrypted and) decoded. */
+ /**
+ * 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_MESSAGE = 1;
+ 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_MESSAGE = 2;
+ public static final int DECODE_STATUS_UNPROTECTED_ERROR = 3;
- /** This class represents a result of decoding an IKE message. */
- public static class DecodeResult {
- // TODO: Extend this class to support IKE fragmentation.
-
+ /** This class represents common decoding result of an IKE message. */
+ public abstract static class DecodeResult {
public final int status;
- public final IkeMessage ikeMessage;
- public final IkeException ikeException;
/** Construct an instance of DecodeResult. */
- public DecodeResult(int status, IkeMessage ikeMessage, IkeException ikeException) {
+ 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 DecodeResultOk(IkeMessage ikeMessage) {
+ super(DECODE_STATUS_OK);
this.ikeMessage = ikeMessage;
+ }
+ }
+
+ /**
+ * 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 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,
+ 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.collectedFragsList = new byte[skfPayload.totalFragments][];
+ } else {
+ this.ikeHeader = collectedFragments.ikeHeader;
+ this.firstPayloadType =
+ isFirstFragment ? nextPayloadType : collectedFragments.firstPayloadType;
+ 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 that errors have been found during decrypting or decoding. */
+ public static class DecodeResultError extends DecodeResult {
+ public final IkeException ikeException;
+
+ public DecodeResultError(int status, IkeException ikeException) {
+ super(status);
this.ikeException = ikeException;
}
}
diff --git a/src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java b/src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java
index 352932c..58dbfb7 100644
--- a/src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java
@@ -116,6 +116,10 @@
* being negotiated. Only allowed in the request/response for negotiating a Child SA.
*/
public static final int NOTIFY_TYPE_ESP_TFC_PADDING_NOT_SUPPORTED = 16394;
+ /**
+ * Indicates that the sender supports IKE fragmentation.
+ */
+ public static final int NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED = 16430;
// TODO: List all supported notify types.
private static final int NOTIFY_HEADER_LEN = 4;
diff --git a/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java b/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java
index 74dd4c9..0e4290c 100644
--- a/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java
+++ b/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java
@@ -16,6 +16,7 @@
package com.android.ike.ikev2.message;
+import android.annotation.Nullable;
import android.util.Pair;
import com.android.ike.ikev2.crypto.IkeCipher;
@@ -90,6 +91,35 @@
return new IkeUnsupportedPayload(payloadType, isCritical);
}
}
+
+ @Override
+ public IkeSkPayload decodeIkeSkPayload(
+ boolean isSkf,
+ boolean critical,
+ byte[] message,
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher decryptCipher,
+ byte[] integrityKey,
+ byte[] decryptionKey)
+ throws IkeProtocolException, GeneralSecurityException {
+ if (isSkf) {
+ return new IkeSkfPayload(
+ critical,
+ message,
+ integrityMac,
+ decryptCipher,
+ integrityKey,
+ decryptionKey);
+ } else {
+ return new IkeSkPayload(
+ critical,
+ message,
+ integrityMac,
+ decryptCipher,
+ integrityKey,
+ decryptionKey);
+ }
+ }
}
/**
@@ -134,7 +164,7 @@
* @param integrityMac the negotiated integrity algorithm.
* @param decryptCipher the negotiated encryption algorithm.
* @param integrityKey the negotiated integrity algorithm key.
- * @param decryptKey the negotiated decryption key.
+ * @param decryptionKey the negotiated decryption key.
* @return a pair including IkePayload and next payload type.
* @throws IkeProtocolException for decoding errors.
* @throws GeneralSecurityException if there is any error during integrity check or decryption.
@@ -145,7 +175,7 @@
IkeMacIntegrity integrityMac,
IkeCipher decryptCipher,
byte[] integrityKey,
- byte[] decryptKey)
+ byte[] decryptionKey)
throws IkeProtocolException, GeneralSecurityException {
ByteBuffer input =
ByteBuffer.wrap(
@@ -174,26 +204,15 @@
+ " or SK Payload is not the only payload.");
}
- IkeSkPayload payload = null;
- if (isSkf) {
- payload =
- new IkeSkfPayload(
- isCritical,
- message,
- integrityMac,
- decryptCipher,
- integrityKey,
- decryptKey);
- } else {
- payload =
- new IkeSkPayload(
- isCritical,
- message,
- integrityMac,
- decryptCipher,
- integrityKey,
- decryptKey);
- }
+ IkeSkPayload payload =
+ sDecoderInstance.decodeIkeSkPayload(
+ isSkf,
+ isCritical,
+ message,
+ integrityMac,
+ decryptCipher,
+ integrityKey,
+ decryptionKey);
return new Pair(payload, nextPayloadType);
}
@@ -209,5 +228,15 @@
IkePayload decodeIkePayload(
int payloadType, boolean isCritical, boolean isResp, byte[] payloadBody)
throws IkeProtocolException;
+
+ IkeSkPayload decodeIkeSkPayload(
+ boolean isSkf,
+ boolean critical,
+ byte[] message,
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher decryptCipher,
+ byte[] integrityKey,
+ byte[] decryptionKey)
+ throws IkeProtocolException, GeneralSecurityException;
}
}
diff --git a/src/java/com/android/ike/ikev2/message/IkeSkPayload.java b/src/java/com/android/ike/ikev2/message/IkeSkPayload.java
index d50dede..40c5ec7 100644
--- a/src/java/com/android/ike/ikev2/message/IkeSkPayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkeSkPayload.java
@@ -40,7 +40,7 @@
*/
public class IkeSkPayload extends IkePayload {
- private final IkeEncryptedPayloadBody mIkeEncryptedPayloadBody;
+ protected final IkeEncryptedPayloadBody mIkeEncryptedPayloadBody;
/**
* Construct an instance of IkeSkPayload from decrypting an incoming packet.
@@ -50,7 +50,7 @@
* @param integrityMac the negotiated integrity algorithm.
* @param decryptCipher the negotiated encryption algorithm.
* @param integrityKey the negotiated integrity algorithm key.
- * @param decryptKey the negotiated decryption key.
+ * @param decryptionKey the negotiated decryption key.
*/
@VisibleForTesting
IkeSkPayload(
@@ -59,7 +59,7 @@
@Nullable IkeMacIntegrity integrityMac,
IkeCipher decryptCipher,
byte[] integrityKey,
- byte[] decryptKey)
+ byte[] decryptionKey)
throws IkeProtocolException, GeneralSecurityException {
this(
@@ -70,7 +70,14 @@
integrityMac,
decryptCipher,
integrityKey,
- decryptKey);
+ decryptionKey);
+ }
+
+ /** Construct an instance of IkeSkPayload for testing.*/
+ @VisibleForTesting
+ IkeSkPayload(boolean isSkf, IkeEncryptedPayloadBody encryptedPayloadBody) {
+ super(isSkf ? PAYLOAD_TYPE_SKF : PAYLOAD_TYPE_SK, false/*critical*/);
+ mIkeEncryptedPayloadBody = encryptedPayloadBody;
}
/** Construct an instance of IkeSkPayload from decrypting an incoming packet. */
@@ -82,7 +89,7 @@
@Nullable IkeMacIntegrity integrityMac,
IkeCipher decryptCipher,
byte[] integrityKey,
- byte[] decryptKey)
+ byte[] decryptionKey)
throws IkeProtocolException, GeneralSecurityException {
super(isSkf ? PAYLOAD_TYPE_SKF : PAYLOAD_TYPE_SK, critical);
@@ -95,7 +102,7 @@
integrityMac,
decryptCipher,
integrityKey,
- decryptKey);
+ decryptionKey);
}
/**
@@ -107,7 +114,7 @@
* @param integrityMac the negotiated integrity algorithm.
* @param encryptCipher the negotiated encryption algorithm.
* @param integrityKey the negotiated integrity algorithm key.
- * @param encryptKey the negotiated encryption key.
+ * @param encryptionKey the negotiated encryption key.
*/
@VisibleForTesting
IkeSkPayload(
@@ -117,8 +124,31 @@
@Nullable IkeMacIntegrity integrityMac,
IkeCipher encryptCipher,
byte[] integrityKey,
- byte[] encryptKey) {
- super(PAYLOAD_TYPE_SK, false);
+ byte[] encryptionKey) {
+
+ this(
+ false /*isSkf*/,
+ ikeHeader,
+ firstPayloadType,
+ unencryptedPayloads,
+ integrityMac,
+ encryptCipher,
+ integrityKey,
+ encryptionKey);
+ }
+
+ /** Construct an instance of IkeSkPayload for building outbound packet. */
+ @VisibleForTesting
+ protected IkeSkPayload(
+ boolean isSkf,
+ IkeHeader ikeHeader,
+ @PayloadType int firstPayloadType,
+ byte[] unencryptedPayloads,
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher encryptCipher,
+ byte[] integrityKey,
+ byte[] encryptionKey) {
+ super(isSkf ? PAYLOAD_TYPE_SKF : PAYLOAD_TYPE_SK, false);
// TODO: Support constructing IkeEncryptedPayloadBody using AEAD.
@@ -130,7 +160,7 @@
integrityMac,
encryptCipher,
integrityKey,
- encryptKey);
+ encryptionKey);
}
/**
diff --git a/src/java/com/android/ike/ikev2/message/IkeSkfPayload.java b/src/java/com/android/ike/ikev2/message/IkeSkfPayload.java
index cd7802f..709d2e1 100644
--- a/src/java/com/android/ike/ikev2/message/IkeSkfPayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkeSkfPayload.java
@@ -22,6 +22,7 @@
import com.android.ike.ikev2.crypto.IkeMacIntegrity;
import com.android.ike.ikev2.exceptions.IkeProtocolException;
import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
+import com.android.internal.annotations.VisibleForTesting;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
@@ -33,7 +34,7 @@
* Version 2 (IKEv2) Message Fragmentation</a>
*/
public final class IkeSkfPayload extends IkeSkPayload {
- private static final int SKF_HEADER_LEN = 4;
+ public static final int SKF_HEADER_LEN = 4;
/** Current Fragment message number, starting from 1 */
public final int fragmentNum;
@@ -51,7 +52,7 @@
* @param integrityMac the negotiated integrity algorithm.
* @param decryptCipher the negotiated encryption algorithm.
* @param integrityKey the negotiated integrity algorithm key.
- * @param decryptKey the negotiated decryption key.
+ * @param decryptionKey the negotiated decryption key.
*/
IkeSkfPayload(
boolean critical,
@@ -59,7 +60,7 @@
@Nullable IkeMacIntegrity integrityMac,
IkeCipher decryptCipher,
byte[] integrityKey,
- byte[] decryptKey)
+ byte[] decryptionKey)
throws IkeProtocolException, GeneralSecurityException {
super(
true /*isSkf*/,
@@ -69,7 +70,7 @@
integrityMac,
decryptCipher,
integrityKey,
- decryptKey);
+ decryptionKey);
// TODO: Support constructing IkeEncryptedPayloadBody using AEAD.
@@ -88,9 +89,72 @@
}
}
- // TODO: Support constructing outbound SKF Payload.
+ /**
+ * Construct an instance of IkeSkfPayload for building outbound packet.
+ *
+ * @param ikeHeader the IKE header.
+ * @param firstPayloadType the type of first payload nested in SkPayload.
+ * @param unencryptedPayloads the encoded payload list to protect.
+ * @param integrityMac the negotiated integrity algorithm.
+ * @param encryptCipher the negotiated encryption algorithm.
+ * @param integrityKey the negotiated integrity algorithm key.
+ * @param encryptionKey the negotiated encryption key.
+ */
+ IkeSkfPayload(
+ IkeHeader ikeHeader,
+ @PayloadType int firstPayloadType,
+ byte[] unencryptedPayloads,
+ @Nullable IkeMacIntegrity integrityMac,
+ IkeCipher encryptCipher,
+ byte[] integrityKey,
+ byte[] encryptionKey,
+ int fragNum,
+ int totalFrags) {
+ super(
+ true /*isSkf*/,
+ ikeHeader,
+ firstPayloadType,
+ unencryptedPayloads,
+ integrityMac,
+ encryptCipher,
+ integrityKey,
+ encryptionKey);
+ fragmentNum = fragNum;
+ totalFragments = totalFrags;
+ }
- // TODO: Override encodeToByteBuffer and getPayloadLength
+ /** Construct an instance of IkeSkfPayload for testing. */
+ @VisibleForTesting
+ IkeSkfPayload(IkeEncryptedPayloadBody encryptedPayloadBody, int fragNum, int totalFrags) {
+ super(true /*isSkf*/, encryptedPayloadBody);
+ fragmentNum = fragNum;
+ totalFragments = totalFrags;
+ }
+
+ /**
+ * Encode this payload to a ByteBuffer.
+ *
+ * @param nextPayload type of payload that follows this payload.
+ * @param byteBuffer destination ByteBuffer that stores encoded payload.
+ */
+ @Override
+ protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) {
+ encodePayloadHeaderToByteBuffer(nextPayload, getPayloadLength(), byteBuffer);
+ byteBuffer
+ .putShort((short) fragmentNum)
+ .putShort((short) totalFragments)
+ .put(mIkeEncryptedPayloadBody.encode());
+ }
+
+ /**
+ * Get entire payload length.
+ *
+ * @return entire payload length.
+ */
+ @Override
+ protected int getPayloadLength() {
+ return GENERIC_HEADER_LENGTH + SKF_HEADER_LEN + mIkeEncryptedPayloadBody.getLength();
+ }
/**
* Return the payload type as a String.
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java b/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java
index b08def6..435c976 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java
@@ -31,11 +31,13 @@
import static com.android.ike.ikev2.exceptions.IkeProtocolException.ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD;
import static com.android.ike.ikev2.message.IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA;
import static com.android.ike.ikev2.message.IkeHeader.EXCHANGE_TYPE_INFORMATIONAL;
-import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_OK;
-import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_PROTECTED_ERROR_MESSAGE;
+import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_PROTECTED_ERROR;
+import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED;
import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP;
import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP;
+import static com.android.ike.ikev2.message.IkePayload.PAYLOAD_TYPE_AUTH;
import static com.android.ike.ikev2.message.IkePayload.PAYLOAD_TYPE_NOTIFY;
+import static com.android.ike.ikev2.message.IkePayload.PAYLOAD_TYPE_SA;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -46,6 +48,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
@@ -88,6 +91,7 @@
import com.android.ike.ikev2.crypto.IkeMacIntegrity;
import com.android.ike.ikev2.crypto.IkeMacPrf;
import com.android.ike.ikev2.exceptions.AuthenticationFailedException;
+import com.android.ike.ikev2.exceptions.IkeException;
import com.android.ike.ikev2.exceptions.IkeInternalException;
import com.android.ike.ikev2.exceptions.IkeProtocolException;
import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
@@ -104,6 +108,9 @@
import com.android.ike.ikev2.message.IkeKePayload;
import com.android.ike.ikev2.message.IkeMessage;
import com.android.ike.ikev2.message.IkeMessage.DecodeResult;
+import com.android.ike.ikev2.message.IkeMessage.DecodeResultError;
+import com.android.ike.ikev2.message.IkeMessage.DecodeResultOk;
+import com.android.ike.ikev2.message.IkeMessage.DecodeResultPartial;
import com.android.ike.ikev2.message.IkeMessage.IIkeMessageHelper;
import com.android.ike.ikev2.message.IkeMessage.IkeMessageHelper;
import com.android.ike.ikev2.message.IkeNoncePayload;
@@ -115,6 +122,7 @@
import com.android.ike.ikev2.message.IkeSaPayload.EsnTransform;
import com.android.ike.ikev2.message.IkeSaPayload.IntegrityTransform;
import com.android.ike.ikev2.message.IkeSaPayload.PrfTransform;
+import com.android.ike.ikev2.message.IkeSkfPayload;
import com.android.ike.ikev2.message.IkeTestUtils;
import com.android.ike.ikev2.message.IkeTsPayload;
import com.android.ike.utils.Log;
@@ -185,6 +193,7 @@
"2900001c00004004e54f73b7d83f6beb881eab2051d8663f421d10b0";
private static final String NAT_DETECTION_DESTINATION_PAYLOAD_HEX_STRING =
"2b00001c00004005d915368ca036004cb578ae3e3fb268509aeab190";
+ private static final String FRAGMENTATION_SUPPORTED_PAYLOAD_HEX_STRING = "290000080000402e";
private static final String DELETE_IKE_PAYLOAD_HEX_STRING = "0000000801000000";
private static final String NOTIFY_REKEY_IKE_PAYLOAD_HEX_STRING = "2100000800004009";
private static final String ID_PAYLOAD_INITIATOR_HEX_STRING =
@@ -328,7 +337,7 @@
byte[] dummyIkePacketBytes = new byte[0];
when(mMockIkeMessageHelper.decode(0, dummyIkeMessage.ikeHeader, dummyIkePacketBytes))
- .thenReturn(new DecodeResult(DECODE_STATUS_OK, dummyIkeMessage, null));
+ .thenReturn(new DecodeResultOk(dummyIkeMessage));
return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes);
}
@@ -383,14 +392,11 @@
true /*isEncyprted*/,
payloadList);
- when(mMockIkeMessageHelper.decode(
- anyInt(),
- any(),
- any(),
- eq(ikeSaRecord),
- eq(dummyIkeMessage.ikeHeader),
- eq(dummyIkePacketBytes)))
- .thenReturn(new DecodeResult(DECODE_STATUS_OK, dummyIkeMessage, null));
+ setDecodeEncryptedPacketResult(
+ ikeSaRecord,
+ dummyIkeMessage.ikeHeader,
+ null /*collectedFrags*/,
+ new DecodeResultOk(dummyIkeMessage));
return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes);
}
@@ -404,24 +410,95 @@
private ReceivedIkePacket makeDummyReceivedIkePacketWithDecodingError(
IkeSaRecord ikeSaRecord, boolean isResp, int eType, IkeProtocolException exception) {
IkeHeader header =
- new IkeHeader(
- ikeSaRecord.getInitiatorSpi(),
- ikeSaRecord.getResponderSpi(),
- IkePayload.PAYLOAD_TYPE_SK,
- eType,
- isResp,
- !ikeSaRecord.isLocalInit,
- isResp
- ? ikeSaRecord.getLocalRequestMessageId()
- : ikeSaRecord.getRemoteRequestMessageId());
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SK);
when(mMockIkeMessageHelper.decode(
- anyInt(), any(), any(), eq(ikeSaRecord), eq(header), any()))
- .thenReturn(
- new DecodeResult(DECODE_STATUS_PROTECTED_ERROR_MESSAGE, null, exception));
+ anyInt(), any(), any(), eq(ikeSaRecord), eq(header), any(), any()))
+ .thenReturn(new DecodeResultError(DECODE_STATUS_PROTECTED_ERROR, exception));
return new ReceivedIkePacket(header, new byte[0]);
}
+ private ReceivedIkePacket makeDummyReceivedIkeFragmentPacket(
+ IkeSaRecord ikeSaRecord,
+ boolean isResp,
+ int eType,
+ IkeSkfPayload skfPayload,
+ int nextPayloadType,
+ DecodeResultPartial collectedFrags) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SKF);
+ DecodeResultPartial resultFrags =
+ new DecodeResultPartial(header, skfPayload, nextPayloadType, collectedFrags);
+ setDecodeEncryptedPacketResult(ikeSaRecord, header, collectedFrags, resultFrags);
+
+ return new ReceivedIkePacket(header, new byte[0]);
+ }
+
+ private ReceivedIkePacket makeDummyReceivedLastIkeFragmentPacketOk(
+ IkeSaRecord ikeSaRecord,
+ boolean isResp,
+ int eType,
+ DecodeResultPartial collectedFrags,
+ List<IkePayload> payloadList) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SKF);
+
+ IkeMessage completeMessage = new IkeMessage(header, payloadList);
+
+ setDecodeEncryptedPacketResult(
+ ikeSaRecord, header, collectedFrags, new DecodeResultOk(completeMessage));
+
+ return new ReceivedIkePacket(header, new byte[0] /*dummyIkePacketBytes*/);
+ }
+
+ private ReceivedIkePacket makeDummyReceivedLastIkeFragmentPacketError(
+ IkeSaRecord ikeSaRecord,
+ boolean isResp,
+ int eType,
+ DecodeResultPartial collectedFrags,
+ IkeException exception) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SKF);
+
+ setDecodeEncryptedPacketResult(
+ ikeSaRecord,
+ header,
+ collectedFrags,
+ new DecodeResultError(DECODE_STATUS_PROTECTED_ERROR, exception));
+
+ return new ReceivedIkePacket(header, new byte[0] /*dummyIkePacketBytes*/);
+ }
+
+ private IkeHeader makeDummyIkeHeader(
+ IkeSaRecord ikeSaRecord, boolean isResp, int eType, int firstPayloadType) {
+ return new IkeHeader(
+ ikeSaRecord.getInitiatorSpi(),
+ ikeSaRecord.getResponderSpi(),
+ firstPayloadType,
+ eType,
+ isResp,
+ !ikeSaRecord.isLocalInit,
+ isResp
+ ? ikeSaRecord.getLocalRequestMessageId()
+ : ikeSaRecord.getRemoteRequestMessageId());
+ }
+
+ private void setDecodeEncryptedPacketResult(
+ IkeSaRecord ikeSaRecord,
+ IkeHeader header,
+ DecodeResultPartial collectedFrags,
+ DecodeResult result) {
+ when(mMockIkeMessageHelper.decode(
+ anyInt(),
+ any(),
+ any(),
+ eq(ikeSaRecord),
+ eq(header),
+ any(),
+ eq(collectedFrags)))
+ .thenReturn(result);
+ }
+
private IkeMessage makeDummyIkeMessageForTest(
long initSpi,
long respSpi,
@@ -463,7 +540,8 @@
any(),
eq(record),
eq(rcvPacket.ikeHeader),
- eq(rcvPacket.ikePacketBytes));
+ eq(rcvPacket.ikePacketBytes),
+ eq(null));
}
private static IkeSaRecord makeDummyIkeSaRecord(long initSpi, long respSpi, boolean isLocalInit)
@@ -539,10 +617,8 @@
mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsPsk(mPsk));
mMockIkeMessageHelper = mock(IkeMessage.IIkeMessageHelper.class);
- when(mMockIkeMessageHelper.encode(any())).thenReturn(new byte[0]);
- when(mMockIkeMessageHelper.encryptAndEncode(any(), any(), any(), any()))
- .thenReturn(new byte[0]);
IkeMessage.setIkeMessageHelper(mMockIkeMessageHelper);
+ resetMockIkeMessageHelper();
mMockSaRecordHelper = mock(SaRecord.ISaRecordHelper.class);
SaRecord.setSaRecordHelper(mMockSaRecordHelper);
@@ -646,12 +722,14 @@
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE);
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
payloadHexStringList.add(IKE_SA_PAYLOAD_HEX_STRING);
payloadHexStringList.add(KE_PAYLOAD_HEX_STRING);
payloadHexStringList.add(NONCE_RESP_PAYLOAD_HEX_STRING);
payloadHexStringList.add(NAT_DETECTION_SOURCE_PAYLOAD_HEX_STRING);
payloadHexStringList.add(NAT_DETECTION_DESTINATION_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(FRAGMENTATION_SUPPORTED_PAYLOAD_HEX_STRING);
// In each test assign different IKE responder SPI in IKE INIT response to avoid remote SPI
// collision during response validation.
@@ -667,8 +745,8 @@
payloadHexStringList);
}
- private ReceivedIkePacket makeIkeAuthRespWithChildPayloads(List<IkePayload> authRelatedPayloads)
- throws Exception {
+ private List<IkePayload> getIkeAuthPayloadListWithChildPayloads(
+ List<IkePayload> authRelatedPayloads) throws Exception {
List<Integer> payloadTypeList = new LinkedList<>();
List<String> payloadHexStringList = new LinkedList<>();
@@ -684,6 +762,13 @@
hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, true /*isResp*/);
payloadList.addAll(authRelatedPayloads);
+ return payloadList;
+ }
+
+ private ReceivedIkePacket makeIkeAuthRespWithChildPayloads(List<IkePayload> authRelatedPayloads)
+ throws Exception {
+ List<IkePayload> payloadList = getIkeAuthPayloadListWithChildPayloads(authRelatedPayloads);
+
return makeDummyEncryptedReceivedIkePacketWithPayloadList(
mSpyCurrentIkeSaRecord,
IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
@@ -911,6 +996,48 @@
.hasMessages(IkeSessionStateMachine.CMD_RETRANSMIT));
}
+ private IkeMessage verifyEncryptAndEncodeAndGetMessage(IkeSaRecord ikeSaRecord) {
+ verify(mMockIkeMessageHelper)
+ .encryptAndEncode(
+ anyObject(),
+ anyObject(),
+ eq(ikeSaRecord),
+ mIkeMessageCaptor.capture(),
+ anyBoolean(),
+ anyInt());
+ return mIkeMessageCaptor.getValue();
+ }
+
+ private void verifyEncryptAndEncodeNeverCalled(IkeSaRecord ikeSaRecord) {
+ verify(mMockIkeMessageHelper, never())
+ .encryptAndEncode(
+ anyObject(),
+ anyObject(),
+ eq(ikeSaRecord),
+ any(IkeMessage.class),
+ anyBoolean(),
+ anyInt());
+ }
+
+ private void verifyEncryptAndEncodeNeverCalled() {
+ verify(mMockIkeMessageHelper, never())
+ .encryptAndEncode(
+ anyObject(),
+ anyObject(),
+ any(IkeSaRecord.class),
+ any(IkeMessage.class),
+ anyBoolean(),
+ anyInt());
+ }
+
+ private void resetMockIkeMessageHelper() {
+ reset(mMockIkeMessageHelper);
+ when(mMockIkeMessageHelper.encode(any())).thenReturn(new byte[0]);
+ when(mMockIkeMessageHelper.encryptAndEncode(
+ any(), any(), any(), any(), anyBoolean(), anyInt()))
+ .thenReturn(new byte[1][0]);
+ }
+
@Test
public void testQuit() {
mIkeSessionStateMachine.quit();
@@ -1018,6 +1145,7 @@
assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_NONCE));
assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP));
assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP));
+ assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED));
verify(mSpyIkeSocket)
.registerIke(eq(mSpyCurrentIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine));
@@ -1058,6 +1186,9 @@
// Validate NAT detection
assertTrue(mIkeSessionStateMachine.mIsLocalBehindNat);
assertFalse(mIkeSessionStateMachine.mIsRemoteBehindNat);
+
+ // Validate fragmentation support negotiation
+ assertTrue(mIkeSessionStateMachine.mSupportFragment);
}
private void setIkeInitResults() throws Exception {
@@ -1069,6 +1200,7 @@
mIkeSessionStateMachine.mLocalAddress = LOCAL_ADDRESS;
mIkeSessionStateMachine.mIsLocalBehindNat = true;
mIkeSessionStateMachine.mIsRemoteBehindNat = false;
+ mIkeSessionStateMachine.mSupportFragment = true;
mIkeSessionStateMachine.addIkeSaRecord(mSpyCurrentIkeSaRecord);
}
@@ -1183,13 +1315,7 @@
mLooper.dispatchAll();
verifyRetransmissionStarted();
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
- IkeMessage createChildRequest = mIkeMessageCaptor.getValue();
+ IkeMessage createChildRequest = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = createChildRequest.ikeHeader;
assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, ikeHeader.exchangeType);
@@ -1360,13 +1486,7 @@
}
private List<IkePayload> verifyOutInfoMsgHeaderAndGetPayloads(boolean isResp) {
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
- IkeMessage deleteChildMessage = mIkeMessageCaptor.getValue();
+ IkeMessage deleteChildMessage = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = deleteChildMessage.ikeHeader;
assertEquals(mSpyCurrentIkeSaRecord.getInitiatorSpi(), ikeHeader.ikeInitiatorSpi);
@@ -1464,12 +1584,7 @@
mLooper.dispatchAll();
// Verify that no response is sent
- verify(mMockIkeMessageHelper, never())
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- any(IkeMessage.class));
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
// childTwo outbound payload list ready
IkeDeletePayload outDelPayloadTwo = new IkeDeletePayload(new int[] {childTwoLocalSpi});
@@ -1703,13 +1818,7 @@
}
private IkeMessage verifyAuthReqAndGetMsg() {
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
- IkeMessage ikeAuthReqMessage = mIkeMessageCaptor.getValue();
+ IkeMessage ikeAuthReqMessage = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = ikeAuthReqMessage.ikeHeader;
assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_AUTH, ikeHeader.exchangeType);
@@ -1748,9 +1857,9 @@
boolean hasChildPayloads)
throws Exception {
// Send IKE AUTH response to IKE state machine
+ ReceivedIkePacket authResp = makeIkeAuthRespWithChildPayloads(authRelatedPayloads);
mIkeSessionStateMachine.sendMessage(
- IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
- makeIkeAuthRespWithChildPayloads(authRelatedPayloads));
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, authResp);
mLooper.dispatchAll();
// Validate outbound IKE AUTH request
@@ -1766,7 +1875,7 @@
// Validate inbound IKE AUTH response
verifyIncrementLocaReqMsgId();
- verify(mMockIkeMessageHelper).decode(anyInt(), any(), any(), any(), any(), any());
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, authResp);
// Validate authentication is done. Cannot use matchers because IkeAuthPskPayload is final.
verify(spyAuthPayload)
@@ -1838,13 +1947,7 @@
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
}
- @Test
- public void testCreateIkeLocalIkeAuthPsk() throws Exception {
- mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
- verifyRetransmissionStarted();
-
- // Build IKE AUTH response with Auth-PSK Payload and ID-Responder Payload.
- List<IkePayload> authRelatedPayloads = new LinkedList<>();
+ private IkeAuthPskPayload makeSpyRespPskPayload() throws Exception {
IkeAuthPskPayload spyAuthPayload =
spy(
(IkeAuthPskPayload)
@@ -1856,14 +1959,28 @@
doNothing()
.when(spyAuthPayload)
.verifyInboundSignature(any(), any(), any(), any(), any(), any());
+ return spyAuthPayload;
+ }
+
+ private IkeIdPayload makeRespIdPayload() throws Exception {
+ return (IkeIdPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_ID_RESPONDER,
+ true /*isResp*/,
+ ID_PAYLOAD_RESPONDER_HEX_STRING);
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthPsk() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Build IKE AUTH response with Auth-PSK Payload and ID-Responder Payload.
+ List<IkePayload> authRelatedPayloads = new LinkedList<>();
+ IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
authRelatedPayloads.add(spyAuthPayload);
- IkeIdPayload respIdPayload =
- (IkeIdPayload)
- IkeTestUtils.hexStringToIkePayload(
- IkePayload.PAYLOAD_TYPE_ID_RESPONDER,
- true /*isResp*/,
- ID_PAYLOAD_RESPONDER_HEX_STRING);
+ IkeIdPayload respIdPayload = makeRespIdPayload();
authRelatedPayloads.add(respIdPayload);
verifySharedKeyAuthentication(spyAuthPayload, respIdPayload, authRelatedPayloads, true);
@@ -1874,29 +1991,17 @@
public void testCreateIkeLocalIkeAuthPskVerifyFail() throws Exception {
mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
verifyRetransmissionStarted();
- reset(mMockIkeMessageHelper);
+ resetMockIkeMessageHelper();
// Build IKE AUTH response with invalid Auth-PSK Payload and ID-Responder Payload.
List<IkePayload> authRelatedPayloads = new LinkedList<>();
- IkeAuthPskPayload spyAuthPayload =
- spy(
- (IkeAuthPskPayload)
- IkeTestUtils.hexStringToIkePayload(
- IkePayload.PAYLOAD_TYPE_AUTH,
- true /*isResp*/,
- PSK_AUTH_RESP_PAYLOAD_HEX_STRING));
-
+ IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
doThrow(new AuthenticationFailedException("DummyAuthFailException"))
.when(spyAuthPayload)
.verifyInboundSignature(any(), any(), any(), any(), any(), any());
authRelatedPayloads.add(spyAuthPayload);
- IkeIdPayload respIdPayload =
- (IkeIdPayload)
- IkeTestUtils.hexStringToIkePayload(
- IkePayload.PAYLOAD_TYPE_ID_RESPONDER,
- true /*isResp*/,
- ID_PAYLOAD_RESPONDER_HEX_STRING);
+ IkeIdPayload respIdPayload = makeRespIdPayload();
authRelatedPayloads.add(respIdPayload);
// Send response to IKE state machine
@@ -1919,7 +2024,7 @@
public void testAuthPskHandleRespWithParsingError() throws Exception {
mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
verifyRetransmissionStarted();
- reset(mMockIkeMessageHelper);
+ resetMockIkeMessageHelper();
// Mock receiving packet with syntax error
ReceivedIkePacket mockInvalidPacket =
@@ -2038,15 +2143,8 @@
mLooper.dispatchAll();
verifyRetransmissionStarted();
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
-
// Verify EAP response
- IkeMessage resp = mIkeMessageCaptor.getValue();
+ IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = resp.ikeHeader;
assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_AUTH, ikeHeader.exchangeType);
@@ -2191,31 +2289,131 @@
// Build IKE AUTH response with Auth-PSK Payload and ID-Responder Payload.
List<IkePayload> authRelatedPayloads = new LinkedList<>();
- IkeAuthPskPayload spyAuthPayload =
- spy(
- (IkeAuthPskPayload)
- IkeTestUtils.hexStringToIkePayload(
- IkePayload.PAYLOAD_TYPE_AUTH,
- true /*isResp*/,
- PSK_AUTH_RESP_PAYLOAD_HEX_STRING));
-
- doNothing()
- .when(spyAuthPayload)
- .verifyInboundSignature(any(), any(), any(), any(), any(), any());
+ IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
authRelatedPayloads.add(spyAuthPayload);
- IkeIdPayload respIdPayload =
- (IkeIdPayload)
- IkeTestUtils.hexStringToIkePayload(
- IkePayload.PAYLOAD_TYPE_ID_RESPONDER,
- true /*isResp*/,
- ID_PAYLOAD_RESPONDER_HEX_STRING);
+ IkeIdPayload respIdPayload = makeRespIdPayload();
verifySharedKeyAuthentication(spyAuthPayload, respIdPayload, authRelatedPayloads, false);
verifyRetransmissionStopped();
}
@Test
+ public void testCreateIkeLocalIkeAuthHandlesFirstFrag() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Received IKE fragment
+ byte[] unencryptedData = "testCreateIkeLocalIkeAuthHandlesFrag".getBytes();
+ int fragNum = 1;
+ int totalFragments = 2;
+ IkeSkfPayload skfPayload =
+ IkeTestUtils.makeDummySkfPayload(unencryptedData, fragNum, totalFragments);
+
+ ReceivedIkePacket packet =
+ makeDummyReceivedIkeFragmentPacket(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ skfPayload,
+ PAYLOAD_TYPE_AUTH,
+ null /* collectedFrags*/);
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
+ mLooper.dispatchAll();
+
+ // Verify state doesn't change
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth);
+
+ // Verify the IkeSaRecord has stored the fragment.
+ DecodeResultPartial resultPartial =
+ mSpyCurrentIkeSaRecord.getCollectedFragments(true /*isResp*/);
+ assertEquals(PAYLOAD_TYPE_AUTH, resultPartial.firstPayloadType);
+ assertEquals(totalFragments, resultPartial.collectedFragsList.length);
+ assertArrayEquals(unencryptedData, resultPartial.collectedFragsList[fragNum - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+
+ assertNull(mSpyCurrentIkeSaRecord.getCollectedFragments(false /*isResp*/));
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthHandlesLastFragOk() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Set previously collected IKE fragments
+ DecodeResultPartial mockCollectedFrags = mock(DecodeResultPartial.class);
+ mSpyCurrentIkeSaRecord.updateCollectedFragments(mockCollectedFrags, true /*isResp*/);
+
+ // Build reassembled IKE AUTH response with Auth-PSK Payload and ID-Responder Payload.
+ List<IkePayload> authRelatedPayloads = new LinkedList<>();
+ IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
+ authRelatedPayloads.add(spyAuthPayload);
+
+ IkeIdPayload respIdPayload = makeRespIdPayload();
+ authRelatedPayloads.add(respIdPayload);
+
+ List<IkePayload> authPayloadList =
+ getIkeAuthPayloadListWithChildPayloads(authRelatedPayloads);
+
+ // Receive last auth response and do IKE_AUTH
+ ReceivedIkePacket packet =
+ makeDummyReceivedLastIkeFragmentPacketOk(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ mockCollectedFrags,
+ authPayloadList);
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
+ mLooper.dispatchAll();
+
+ // Verify IKE AUTH is done
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+
+ // Verify collected response fragments are cleared.
+ assertNull(mSpyCurrentIkeSaRecord.getCollectedFragments(true /*isResp*/));
+ verify(mSpyCurrentIkeSaRecord).resetCollectedFragments(true /*isResp*/);
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthHandlesLastFragError() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Set previously collected IKE fragments
+ DecodeResultPartial mockCollectedFrags = mock(DecodeResultPartial.class);
+ mSpyCurrentIkeSaRecord.updateCollectedFragments(mockCollectedFrags, true /*isResp*/);
+
+ // Receive last auth response with syntax error
+ ReceivedIkePacket packet =
+ makeDummyReceivedLastIkeFragmentPacketError(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ mockCollectedFrags,
+ new InvalidSyntaxException("IkeStateMachineTest"));
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
+ mLooper.dispatchAll();
+
+ // Verify Delete request is sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/);
+ assertEquals(1, payloads.size());
+ assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onError(any(InvalidSyntaxException.class));
+
+ // Collected response fragments are cleared
+ assertNull(mSpyCurrentIkeSaRecord.getCollectedFragments(true /*isResp*/));
+ verify(mSpyCurrentIkeSaRecord).resetCollectedFragments(true /*isResp*/);
+ }
+
+ @Test
public void testRekeyIkeLocalCreateSendsRequest() throws Exception {
setupIdleStateMachine();
@@ -2227,16 +2425,10 @@
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
verifyRetransmissionStarted();
// Verify outbound message
- IkeMessage rekeyMsg = mIkeMessageCaptor.getValue();
+ IkeMessage rekeyMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = rekeyMsg.ikeHeader;
assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
@@ -2311,7 +2503,7 @@
new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
mLooper.dispatchAll();
verifyRetransmissionStarted();
- reset(mMockIkeMessageHelper);
+ resetMockIkeMessageHelper();
// Mock receiving packet with syntax error
ReceivedIkePacket mockInvalidPacket =
@@ -2370,7 +2562,7 @@
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
mLooper.dispatchAll();
- reset(mMockIkeMessageHelper);
+ resetMockIkeMessageHelper();
// Mock receiving packet with NO_PROPOSAL_CHOSEN
ReceivedIkePacket resp =
@@ -2379,9 +2571,7 @@
mLooper.dispatchAll();
// Verify no message was sent because a fatal error notification was received
- verify(mMockIkeMessageHelper, never())
- .encryptAndEncode(
- anyObject(), anyObject(), eq(mSpyCurrentIkeSaRecord), anyObject());
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
// Verify IKE Session is closed properly
assertNull(mIkeSessionStateMachine.getCurrentState());
@@ -2401,7 +2591,7 @@
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
mLooper.dispatchAll();
- reset(mMockIkeMessageHelper);
+ resetMockIkeMessageHelper();
// Receive Rekey response
ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse();
@@ -2429,7 +2619,7 @@
new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
mLooper.dispatchAll();
verifyRetransmissionStarted();
- reset(mMockIkeMessageHelper);
+ resetMockIkeMessageHelper();
// Build protocol exception
List<Integer> unsupportedPayloads = new LinkedList<>();
@@ -2462,16 +2652,21 @@
instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
}
- @Test
- public void testRekeyIkeLocalDeleteSendsRequest() throws Exception {
- setupIdleStateMachine();
-
+ private void mockCreateAndTransitionToRekeyDeleteLocal() {
// Seed fake rekey data and force transition to RekeyIkeLocalDelete
mIkeSessionStateMachine.mLocalInitNewIkeSaRecord = mSpyLocalInitIkeSaRecord;
+ mIkeSessionStateMachine.addIkeSaRecord(mSpyLocalInitIkeSaRecord);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION,
mIkeSessionStateMachine.mRekeyIkeLocalDelete);
mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+ }
+
+ @Test
+ public void testRekeyIkeLocalDeleteSendsRequest() throws Exception {
+ setupIdleStateMachine();
+ mockCreateAndTransitionToRekeyDeleteLocal();
// Verify Rekey-Delete request
assertTrue(
@@ -2479,15 +2674,8 @@
instanceof IkeSessionStateMachine.RekeyIkeLocalDelete);
verifyRetransmissionStarted();
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
-
// Verify outbound message
- IkeMessage delMsg = mIkeMessageCaptor.getValue();
+ IkeMessage delMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = delMsg.ikeHeader;
assertEquals(mSpyCurrentIkeSaRecord.getInitiatorSpi(), ikeHeader.ikeInitiatorSpi);
@@ -2522,14 +2710,7 @@
@Test
public void testRekeyIkeLocalDeleteHandlesResponse() throws Exception {
setupIdleStateMachine();
-
- // Seed fake rekey data and force transition to RekeyIkeLocalDelete
- mIkeSessionStateMachine.mLocalInitNewIkeSaRecord = mSpyLocalInitIkeSaRecord;
- mIkeSessionStateMachine.sendMessage(
- IkeSessionStateMachine.CMD_FORCE_TRANSITION,
- mIkeSessionStateMachine.mRekeyIkeLocalDelete);
- mLooper.dispatchAll();
- verifyRetransmissionStarted();
+ mockCreateAndTransitionToRekeyDeleteLocal();
// Receive Delete response
ReceivedIkePacket dummyDeleteIkeRespReceivedPacket =
@@ -2551,15 +2732,8 @@
@Test
public void testRekeyIkeLocalDeleteHandlesRespWithParsingError() throws Exception {
setupIdleStateMachine();
-
- // Seed fake rekey data and force transition to RekeyIkeLocalDelete
- mIkeSessionStateMachine.mLocalInitNewIkeSaRecord = mSpyLocalInitIkeSaRecord;
- mIkeSessionStateMachine.sendMessage(
- IkeSessionStateMachine.CMD_FORCE_TRANSITION,
- mIkeSessionStateMachine.mRekeyIkeLocalDelete);
- mLooper.dispatchAll();
- verifyRetransmissionStarted();
- reset(mMockIkeMessageHelper);
+ mockCreateAndTransitionToRekeyDeleteLocal();
+ resetMockIkeMessageHelper();
// Mock receiving packet with syntax error
ReceivedIkePacket mockInvalidPacket =
@@ -2571,12 +2745,7 @@
mLooper.dispatchAll();
// Verify no more request out
- verify(mMockIkeMessageHelper, never())
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- any(IkeMessage.class));
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
// Verify final state - Idle, with new SA, and old SA closed.
verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord);
@@ -2588,15 +2757,7 @@
@Test
public void testRekeyIkeLocalDeleteWithRequestOnNewSa() throws Exception {
setupIdleStateMachine();
-
- // Seed fake rekey data and force transition to RekeyIkeLocalDelete
- mIkeSessionStateMachine.mLocalInitNewIkeSaRecord = mSpyLocalInitIkeSaRecord;
- mIkeSessionStateMachine.addIkeSaRecord(mSpyLocalInitIkeSaRecord);
- mIkeSessionStateMachine.sendMessage(
- IkeSessionStateMachine.CMD_FORCE_TRANSITION,
- mIkeSessionStateMachine.mRekeyIkeLocalDelete);
- mLooper.dispatchAll();
- verifyRetransmissionStarted();
+ mockCreateAndTransitionToRekeyDeleteLocal();
// Receive an empty (DPD) request on the new IKE SA
mIkeSessionStateMachine.sendMessage(
@@ -2612,6 +2773,42 @@
}
@Test
+ public void testRekeyIkeLocalDeleteWithRequestFragOnNewSa() throws Exception {
+ setupIdleStateMachine();
+ mockCreateAndTransitionToRekeyDeleteLocal();
+
+ // Received IKE fragment
+ byte[] unencryptedData = "testRekeyIkeLocalDeleteWithRequestFragOnNewSa".getBytes();
+ int fragNum = 1;
+ int totalFragments = 2;
+ IkeSkfPayload skfPayload =
+ IkeTestUtils.makeDummySkfPayload(unencryptedData, fragNum, totalFragments);
+
+ ReceivedIkePacket packet =
+ makeDummyReceivedIkeFragmentPacket(
+ mSpyLocalInitIkeSaRecord,
+ false /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ skfPayload,
+ PAYLOAD_TYPE_SA,
+ null /* collectedFrags*/);
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
+ mLooper.dispatchAll();
+
+ // Verify rekey is done.
+ verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord);
+ verifyRetransmissionStopped();
+
+ // Verify the IkeSaRecord has stored the new fragment.
+ DecodeResultPartial resultPartial =
+ mSpyLocalInitIkeSaRecord.getCollectedFragments(false /*isResp*/);
+ assertEquals(PAYLOAD_TYPE_SA, resultPartial.firstPayloadType);
+ assertEquals(totalFragments, resultPartial.collectedFragsList.length);
+ assertArrayEquals(unencryptedData, resultPartial.collectedFragsList[fragNum - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+ }
+
+ @Test
public void testRekeyIkeRemoteDeleteWithRequestOnNewSa() throws Exception {
setupIdleStateMachine();
@@ -2657,13 +2854,7 @@
assertEquals(IKE_REKEY_SA_INITIATOR_SPI, recordConfigCaptor.getValue().initSpi.getSpi());
// Verify outbound CREATE_CHILD_SA message
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
- IkeMessage rekeyCreateResp = mIkeMessageCaptor.getValue();
+ IkeMessage rekeyCreateResp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader rekeyCreateRespHeader = rekeyCreateResp.ikeHeader;
assertEquals(IkePayload.PAYLOAD_TYPE_SK, rekeyCreateRespHeader.nextPayloadType);
assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyCreateRespHeader.exchangeType);
@@ -2748,13 +2939,7 @@
verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRequestReceivedPacket);
// Verify outbound DELETE_IKE_SA message
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
- IkeMessage rekeyDeleteResp = mIkeMessageCaptor.getValue();
+ IkeMessage rekeyDeleteResp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader rekeyDeleteRespHeader = rekeyDeleteResp.ikeHeader;
assertEquals(IkePayload.PAYLOAD_TYPE_SK, rekeyDeleteRespHeader.nextPayloadType);
assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, rekeyDeleteRespHeader.exchangeType);
@@ -2797,9 +2982,7 @@
// Verify no request received, or response sent.
verify(mMockIkeMessageHelper, never()).decode(anyInt(), anyObject(), anyObject());
- verify(mMockIkeMessageHelper, never())
- .encryptAndEncode(
- anyObject(), anyObject(), eq(mSpyCurrentIkeSaRecord), anyObject());
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
// Verify final state has not changed - signal was not sent.
assertTrue(
@@ -2823,9 +3006,7 @@
// Verify no request received, or response sent.
verify(mMockIkeMessageHelper, never()).decode(anyInt(), anyObject(), anyObject());
- verify(mMockIkeMessageHelper, never())
- .encryptAndEncode(
- anyObject(), anyObject(), eq(mSpyCurrentIkeSaRecord), anyObject());
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
// Verify final state - Idle, with new SA, and old SA closed.
verifyRekeyReplaceSa(mSpyRemoteInitIkeSaRecord);
@@ -2987,7 +3168,8 @@
@Test
public void testEncryptedRetransmitterImmediatelySendsRequest() throws Exception {
setupIdleStateMachine();
- byte[] dummyLastRespBytes = "testRetransmitterSendsRequestLastResp".getBytes();
+ byte[][] dummyLastRespBytes =
+ new byte[][] {"testRetransmitterSendsRequestLastResp".getBytes()};
mIkeSessionStateMachine.mLastSentIkeResp = dummyLastRespBytes;
IkeMessage spyIkeReqMessage =
@@ -3004,43 +3186,60 @@
new LinkedList<>()));
// Use something unique as a sentinel value
- byte[] dummyReqBytes = "testRetransmitterSendsRequest".getBytes();
- doReturn(dummyReqBytes)
+ byte[][] dummyReqBytesList =
+ new byte[][] {
+ "testRetransmitterSendsReqFrag1".getBytes(),
+ "testRetransmitterSendsReqFrag2".getBytes()
+ };
+
+ doReturn(dummyReqBytesList)
.when(spyIkeReqMessage)
- .encryptAndEncode(any(), any(), eq(mSpyCurrentIkeSaRecord));
+ .encryptAndEncode(any(), any(), eq(mSpyCurrentIkeSaRecord), anyBoolean(), anyInt());
IkeSessionStateMachine.EncryptedRetransmitter retransmitter =
mIkeSessionStateMachine.new EncryptedRetransmitter(spyIkeReqMessage);
// Verify message is sent out, and that request does not change cached retransmit-response
// mLastSentIkeResp.
- verify(mSpyIkeSocket).sendIkePacket(eq(dummyReqBytes), eq(REMOTE_ADDRESS));
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyReqBytesList[0]), eq(REMOTE_ADDRESS));
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyReqBytesList[1]), eq(REMOTE_ADDRESS));
assertArrayEquals(dummyLastRespBytes, mIkeSessionStateMachine.mLastSentIkeResp);
}
@Test
public void testCacheLastRequestAndResponse() throws Exception {
setupIdleStateMachine();
- mIkeSessionStateMachine.mLastReceivedIkeReq = null;
+ mIkeSessionStateMachine.mLastReceivedIkeReqFirstPacket = null;
mIkeSessionStateMachine.mLastSentIkeResp = null;
- byte[] dummyIkeReq = "testLastSentRequest".getBytes();
- byte[] dummyIkeResp = "testLastSentResponse".getBytes();
+ byte[] dummyIkeReqFirstPacket = "testLastSentRequest".getBytes();
+ byte[][] dummyIkeResp =
+ new byte[][] {
+ "testLastSentRespFrag1".getBytes(), "testLastSentRespFrag2".getBytes()
+ };
when(mMockIkeMessageHelper.encryptAndEncode(
- any(), any(), eq(mSpyCurrentIkeSaRecord), any(IkeMessage.class)))
+ any(),
+ any(),
+ eq(mSpyCurrentIkeSaRecord),
+ any(IkeMessage.class),
+ anyBoolean(),
+ anyInt()))
.thenReturn(dummyIkeResp);
// Receive a DPD request, expect to send dummyIkeResp
ReceivedIkePacket dummyDpdRequest =
- makeDpdIkeRequest(mSpyCurrentIkeSaRecord.getRemoteRequestMessageId(), dummyIkeReq);
+ makeDpdIkeRequest(
+ mSpyCurrentIkeSaRecord.getRemoteRequestMessageId(), dummyIkeReqFirstPacket);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDpdRequest);
mLooper.dispatchAll();
- verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp), eq(REMOTE_ADDRESS));
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp[0]), eq(REMOTE_ADDRESS));
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp[1]), eq(REMOTE_ADDRESS));
assertArrayEquals(dummyIkeResp, mIkeSessionStateMachine.mLastSentIkeResp);
- assertArrayEquals(dummyIkeReq, mIkeSessionStateMachine.mLastReceivedIkeReq);
+ assertArrayEquals(
+ dummyIkeReqFirstPacket, mIkeSessionStateMachine.mLastReceivedIkeReqFirstPacket);
}
@Test
@@ -3048,23 +3247,25 @@
setupIdleStateMachine();
// Mock last sent request and response
- byte[] dummyIkeReq = "testRcvRetransmittedRequestReq".getBytes();
- byte[] dummyIkeResp = "testRcvRetransmittedRequestResp".getBytes();
- mIkeSessionStateMachine.mLastReceivedIkeReq = dummyIkeReq;
+ byte[] dummyIkeReqFisrtPacket = "testRcvRetransmittedRequestReq".getBytes();
+ byte[][] dummyIkeResp = new byte[][] {"testRcvRetransmittedRequestResp".getBytes()};
+
+ mIkeSessionStateMachine.mLastReceivedIkeReqFirstPacket = dummyIkeReqFisrtPacket;
mIkeSessionStateMachine.mLastSentIkeResp = dummyIkeResp;
mSpyCurrentIkeSaRecord.incrementRemoteRequestMessageId();
// Build request with last validated message ID
ReceivedIkePacket request =
makeDpdIkeRequest(
- mSpyCurrentIkeSaRecord.getRemoteRequestMessageId() - 1, dummyIkeReq);
+ mSpyCurrentIkeSaRecord.getRemoteRequestMessageId() - 1,
+ dummyIkeReqFisrtPacket);
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
mLooper.dispatchAll();
assertArrayEquals(dummyIkeResp, mIkeSessionStateMachine.mLastSentIkeResp);
- verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp), eq(REMOTE_ADDRESS));
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp[0]), eq(REMOTE_ADDRESS));
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
@@ -3075,9 +3276,9 @@
setupIdleStateMachine();
// Mock last sent request and response
- byte[] dummyIkeReq = "testDiscardFakeRetransmittedRequestReq".getBytes();
- byte[] dummyIkeResp = "testDiscardFakeRetransmittedRequestResp".getBytes();
- mIkeSessionStateMachine.mLastReceivedIkeReq = dummyIkeReq;
+ byte[] dummyIkeReqFirstPacket = "testDiscardFakeRetransmittedRequestReq".getBytes();
+ byte[][] dummyIkeResp = new byte[][] {"testDiscardFakeRetransmittedRequestResp".getBytes()};
+ mIkeSessionStateMachine.mLastReceivedIkeReqFirstPacket = dummyIkeReqFirstPacket;
mIkeSessionStateMachine.mLastSentIkeResp = dummyIkeResp;
mSpyCurrentIkeSaRecord.incrementRemoteRequestMessageId();
@@ -3129,15 +3330,8 @@
mLooper.dispatchAll();
verifyRetransmissionStarted();
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
-
// Verify outbound message
- IkeMessage delMsg = mIkeMessageCaptor.getValue();
+ IkeMessage delMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = delMsg.ikeHeader;
assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
@@ -3188,7 +3382,7 @@
new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
mLooper.dispatchAll();
verifyRetransmissionStarted();
- reset(mMockIkeMessageHelper);
+ resetMockIkeMessageHelper();
// Mock receiving response with syntax error
ReceivedIkePacket mockInvalidPacket =
@@ -3200,12 +3394,7 @@
mLooper.dispatchAll();
// Verify no more request out
- verify(mMockIkeMessageHelper, never())
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- any(IkeMessage.class));
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
// Verify state machine quit properly
verify(mMockIkeSessionCallback).onError(any(InvalidSyntaxException.class));
@@ -3247,12 +3436,9 @@
verifyRetransmissionStarted();
// Verify delete sent out.
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(), anyObject(), eq(mSpyCurrentIkeSaRecord), anyObject());
- reset(mMockIkeMessageHelper); // Discard value.
- when(mMockIkeMessageHelper.encryptAndEncode(any(), any(), any(), any()))
- .thenReturn(new byte[0]);
+ verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ resetMockIkeMessageHelper(); // Discard value.
ReceivedIkePacket received = makeRekeyIkeRequest();
mIkeSessionStateMachine.sendMessage(
@@ -3262,15 +3448,8 @@
verifyRetransmissionStarted();
verifyIncrementRemoteReqMsgId();
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
-
// Verify outbound response
- IkeMessage resp = mIkeMessageCaptor.getValue();
+ IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = resp.ikeHeader;
assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
@@ -3296,15 +3475,8 @@
mLooper.dispatchAll();
verifyIncrementRemoteReqMsgId();
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
-
// Verify outbound message
- IkeMessage delMsg = mIkeMessageCaptor.getValue();
+ IkeMessage delMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = delMsg.ikeHeader;
assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
@@ -3333,15 +3505,9 @@
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDpdRequest);
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
// Verify outbound response
- IkeMessage resp = mIkeMessageCaptor.getValue();
+ IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = resp.ikeHeader;
assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
@@ -3370,15 +3536,9 @@
instanceof IkeSessionStateMachine.RekeyIkeRemoteDelete);
verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDpdRequest);
- verify(mMockIkeMessageHelper)
- .encryptAndEncode(
- anyObject(),
- anyObject(),
- eq(mSpyCurrentIkeSaRecord),
- mIkeMessageCaptor.capture());
// Verify outbound response
- IkeMessage resp = mIkeMessageCaptor.getValue();
+ IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = resp.ikeHeader;
assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
@@ -3411,7 +3571,7 @@
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mReceiving);
mLooper.dispatchAll();
- verify(mMockIkeMessageHelper, never()).encryptAndEncode(any(), any(), any(), any());
+ verifyEncryptAndEncodeNeverCalled();
// Queue a local request, and expect that it is not run (yet)
mIkeSessionStateMachine.sendMessage(
@@ -3420,7 +3580,7 @@
mLooper.dispatchAll();
// Verify that the state machine is still in the Receiving state
- verify(mMockIkeMessageHelper, never()).encryptAndEncode(any(), any(), any(), any());
+ verifyEncryptAndEncodeNeverCalled();
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.Receiving);
@@ -3433,7 +3593,7 @@
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
- verify(mMockIkeMessageHelper, times(1)).encryptAndEncode(any(), any(), any(), any());
+ verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
}
@Test
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/SaRecordTest.java b/tests/iketests/src/java/com/android/ike/ikev2/SaRecordTest.java
index 0aea35a..d66a864 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/SaRecordTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/SaRecordTest.java
@@ -252,8 +252,8 @@
byte[] initAuthKey = TestUtils.hexStringToByteArray(FIRST_CHILD_AUTH_INIT_HEX_STRING);
byte[] respAuthKey = TestUtils.hexStringToByteArray(FIRST_CHILD_AUTH_RESP_HEX_STRING);
- byte[] initEncryptKey = TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_INIT_HEX_STRING);
- byte[] respEncryptKey = TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_RESP_HEX_STRING);
+ byte[] initEncryptionKey = TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_INIT_HEX_STRING);
+ byte[] respEncryptionKey = TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_RESP_HEX_STRING);
IIpSecTransformHelper mockIpSecHelper;
mockIpSecHelper = mock(IIpSecTransformHelper.class);
@@ -271,7 +271,7 @@
eq(mHmacSha1IntegrityMac),
eq(mAesCbcCipher),
aryEq(initAuthKey),
- aryEq(initEncryptKey),
+ aryEq(initEncryptionKey),
eq(false)))
.thenReturn(mockOutTransform);
@@ -283,7 +283,7 @@
eq(mHmacSha1IntegrityMac),
eq(mAesCbcCipher),
aryEq(respAuthKey),
- aryEq(respEncryptKey),
+ aryEq(respEncryptionKey),
eq(false)))
.thenReturn(mockInTransform);
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/crypto/IkeCipherTest.java b/tests/iketests/src/java/com/android/ike/ikev2/crypto/IkeCipherTest.java
index c51d58f..be1052c 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/crypto/IkeCipherTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/crypto/IkeCipherTest.java
@@ -115,10 +115,10 @@
@Test
public void testEncryptWithWrongKey() throws Exception {
- byte[] encryptKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP + "00");
+ byte[] encryptionKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP + "00");
try {
- mAesCbcCipher.decrypt(mEncryptedPaddedData, mAesCbcKey, encryptKey);
+ mAesCbcCipher.decrypt(mEncryptedPaddedData, mAesCbcKey, encryptionKey);
fail("Expected to fail due to encryption key with wrong length.");
} catch (IllegalArgumentException expected) {
@@ -150,10 +150,10 @@
@Test
public void buildIpSecAlgorithmWithInvalidKey() throws Exception {
- byte[] encryptKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP + "00");
+ byte[] encryptionKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP + "00");
try {
- mAesCbcCipher.buildIpSecAlgorithmWithKey(encryptKey);
+ mAesCbcCipher.buildIpSecAlgorithmWithKey(encryptionKey);
fail("Expected to fail due to encryption key with wrong length.");
} catch (IllegalArgumentException expected) {
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/crypto/IkeMacIntegrityTest.java b/tests/iketests/src/java/com/android/ike/ikev2/crypto/IkeMacIntegrityTest.java
index 5301ab6..d3c857c 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/crypto/IkeMacIntegrityTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/crypto/IkeMacIntegrityTest.java
@@ -115,10 +115,10 @@
@Test
public void buildIpSecAlgorithmWithInvalidKey() throws Exception {
- byte[] encryptKey = TestUtils.hexStringToByteArray(INTEGRITY_KEY_HEX_STRING + "00");
+ byte[] encryptionKey = TestUtils.hexStringToByteArray(INTEGRITY_KEY_HEX_STRING + "00");
try {
- mHmacSha1IntegrityMac.buildIpSecAlgorithmWithKey(encryptKey);
+ mHmacSha1IntegrityMac.buildIpSecAlgorithmWithKey(encryptionKey);
fail("Expected to fail due to integrity key with wrong length.");
} catch (IllegalArgumentException expected) {
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeMessageTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeMessageTest.java
index d19b869..2354c80 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeMessageTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeMessageTest.java
@@ -17,26 +17,46 @@
package com.android.ike.ikev2.message;
import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_OK;
+import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_PROTECTED_ERROR;
+import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR;
+import static com.android.ike.ikev2.message.IkePayload.PAYLOAD_TYPE_AUTH;
+import static com.android.ike.ikev2.message.IkePayload.PAYLOAD_TYPE_ID_INITIATOR;
+import static com.android.ike.ikev2.message.IkePayload.PAYLOAD_TYPE_NO_NEXT;
+import static com.android.ike.ikev2.message.IkeTestUtils.makeDummySkfPayload;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.android.ike.TestUtils;
import com.android.ike.ikev2.SaRecord.IkeSaRecord;
import com.android.ike.ikev2.crypto.IkeCipher;
import com.android.ike.ikev2.crypto.IkeMacIntegrity;
+import com.android.ike.ikev2.exceptions.IkeException;
import com.android.ike.ikev2.exceptions.IkeInternalException;
-import com.android.ike.ikev2.exceptions.IkeProtocolException;
import com.android.ike.ikev2.exceptions.InvalidMessageIdException;
import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
import com.android.ike.ikev2.exceptions.UnsupportedCriticalPayloadException;
import com.android.ike.ikev2.message.IkeMessage.DecodeResult;
+import com.android.ike.ikev2.message.IkeMessage.DecodeResultError;
+import com.android.ike.ikev2.message.IkeMessage.DecodeResultOk;
+import com.android.ike.ikev2.message.IkeMessage.DecodeResultPartial;
+import com.android.ike.ikev2.message.IkePayloadFactory.IIkePayloadDecoder;
import org.junit.After;
import org.junit.Before;
@@ -105,6 +125,18 @@
+ "29000008000040000000000c000040010000000100000000"
+ "000000000000000b";
+ private static final String IKE_FRAG_HEX_STRING =
+ "939ae1251d18eb9077a99551b15c6e9335202320000000010000"
+ + "00c0000000a400020002fd7c7931705af184b7be76bbd45a"
+ + "8ecbb3ffd58b9438b93f67e9fe86b06229f80e9b52d2ff6a"
+ + "fde3f2c13ae93ce55a801f62e1a818c9003880a36bbe986f"
+ + "e6979ba233b9f4f0ddc992d06dbad5a2b998be18fae947e5"
+ + "ccfb37775d069344e711fbf499bb289cf4cca245bd450ad8"
+ + "9d18689207759507ba18d47247e920b9e000a25a7596e413"
+ + "0929e5cdc37d5c1b0d90bbaae946c260f4d3cf815f6d";
+ private static final String ID_INIT_PAYLOAD_HEX_STRING = "2400000c010000000a50500d";
+ private static final String ID_RESP_PAYLOAD_HEX_STRING = "0000000c010000000a505050";
+
private static final long INIT_SPI = 0x5f54bf6d8b48e6e1L;
private static final long RESP_SPI = 0x909232b3d1edcb5cL;
private static final String IKE_EMPTY_INFO_MSG_HEX_STRING =
@@ -118,6 +150,17 @@
"f83b48ba0dbc68bcc4a78136832100aa";
private static final String IKE_EMPTY_INFO_MSG_CHECKSUM_HEX_STRING = "4192f251cd4d1b97d298e550";
+ private static final byte[] FRAGMENT_ONE_UNENCRYPTED_DATA = "fragmentOne".getBytes();
+ private static final byte[] FRAGMENT_TWO_UNENCRYPTED_DATA = "fragmentTwo".getBytes();
+
+ private static final int TOTAL_FRAGMENTS = 2;
+ private static final int FRAGMENT_NUM_ONE = 1;
+ private static final int FRAGMENT_NUM_TWO = 2;
+
+ private static final int IKE_FRAG_EXPECTED_MESSAGE_ID = 1;
+ private static final int TOTAL_FRAGMENTS_OFFSET =
+ IkeHeader.IKE_HEADER_LENGTH + IkePayload.GENERIC_HEADER_LENGTH + 2;
+
private static final int IKE_AUTH_EXPECTED_MESSAGE_ID = 1;
private static final int IKE_AUTH_CIPHER_IV_SIZE = 16;
private static final int IKE_AUTH_CIPHER_BLOCK_SIZE = 16;
@@ -127,10 +170,19 @@
private byte[] mUnencryptedPaddedData;
private IkeHeader mIkeAuthHeader;
+ private IIkePayloadDecoder mSpyIkePayloadDecoder;
+
private IkeMacIntegrity mMockIntegrity;
private IkeCipher mMockCipher;
private IkeSaRecord mMockIkeSaRecord;
+ private byte[] mIkeFragPacket;
+ private IkeHeader mFragOneHeader;
+ private IkeHeader mFragTwoHeader;
+
+ private IkeSkfPayload mDummySkfPayloadOne;
+ private IkeSkfPayload mDummySkfPayloadTwo;
+
private static final int[] EXPECTED_IKE_INIT_PAYLOAD_LIST = {
IkePayload.PAYLOAD_TYPE_SA,
IkePayload.PAYLOAD_TYPE_KE,
@@ -165,20 +217,20 @@
@Before
public void setUp() throws Exception {
- IkePayloadFactory.sDecoderInstance =
- new IkePayloadFactory.IIkePayloadDecoder() {
-
- @Override
- public IkePayload decodeIkePayload(
- int payloadType, boolean isCritical, boolean isResp, byte[] payloadBody)
- throws IkeProtocolException {
- if (support(payloadType)) {
- return new TestIkeSupportedPayload(payloadType, isCritical);
- } else {
- return new IkeUnsupportedPayload(payloadType, isCritical);
- }
+ mSpyIkePayloadDecoder = spy(new IkePayloadFactory.IkePayloadDecoder());
+ doAnswer(
+ (invocation) -> {
+ int payloadType = (int) invocation.getArguments()[0];
+ boolean isCritical = (boolean) invocation.getArguments()[1];
+ if (support(payloadType)) {
+ return new TestIkeSupportedPayload(payloadType, isCritical);
}
- };
+ return new IkeUnsupportedPayload(payloadType, isCritical);
+ })
+ .when(mSpyIkePayloadDecoder)
+ .decodeIkePayload(anyInt(), anyBoolean(), anyBoolean(), any());
+
+ IkePayloadFactory.sDecoderInstance = mSpyIkePayloadDecoder;
mIkeAuthPacket = TestUtils.hexStringToByteArray(IKE_AUTH_HEX_STRING);
mUnencryptedPaddedData =
@@ -199,6 +251,17 @@
mMockIkeSaRecord = mock(IkeSaRecord.class);
when(mMockIkeSaRecord.getInboundDecryptionKey()).thenReturn(new byte[0]);
when(mMockIkeSaRecord.getInboundIntegrityKey()).thenReturn(new byte[0]);
+
+ mIkeFragPacket = TestUtils.hexStringToByteArray(IKE_FRAG_HEX_STRING);
+ mFragOneHeader = new IkeHeader(mIkeFragPacket);
+ mFragTwoHeader = new IkeHeader(mIkeFragPacket);
+
+ mDummySkfPayloadOne =
+ makeDummySkfPayload(
+ FRAGMENT_ONE_UNENCRYPTED_DATA, FRAGMENT_NUM_ONE, TOTAL_FRAGMENTS);
+ mDummySkfPayloadTwo =
+ makeDummySkfPayload(
+ FRAGMENT_TWO_UNENCRYPTED_DATA, FRAGMENT_NUM_TWO, TOTAL_FRAGMENTS);
}
@After
@@ -206,17 +269,35 @@
IkePayloadFactory.sDecoderInstance = new IkePayloadFactory.IkePayloadDecoder();
}
+ private IkeMessage verifyDecodeResultOkAndGetMessage(DecodeResult decodeResult)
+ throws Exception {
+ assertEquals(DECODE_STATUS_OK, decodeResult.status);
+
+ DecodeResultOk resultOk = (DecodeResultOk) decodeResult;
+ assertNotNull(resultOk.ikeMessage);
+
+ return resultOk.ikeMessage;
+ }
+
+ private IkeException verifyDecodeResultErrorAndGetIkeException(
+ DecodeResult decodeResult, int decodeStatus) throws Exception {
+ assertEquals(decodeStatus, decodeResult.status);
+
+ DecodeResultError resultError = (DecodeResultError) decodeResult;
+ assertNotNull(resultError.ikeException);
+
+ return resultError.ikeException;
+ }
+
@Test
public void testDecodeIkeMessage() throws Exception {
byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
IkeHeader header = new IkeHeader(inputPacket);
DecodeResult decodeResult = IkeMessage.decode(0, header, inputPacket);
- assertEquals(DECODE_STATUS_OK, decodeResult.status);
- assertNotNull(decodeResult.ikeMessage);
- assertNull(decodeResult.ikeException);
- IkeMessage message = decodeResult.ikeMessage;
+ IkeMessage message = verifyDecodeResultOkAndGetMessage(decodeResult);
+
assertEquals(EXPECTED_IKE_INIT_PAYLOAD_LIST.length, message.ikePayloadList.size());
for (int i = 0; i < EXPECTED_IKE_INIT_PAYLOAD_LIST.length; i++) {
assertEquals(
@@ -232,11 +313,9 @@
IkeHeader header = new IkeHeader(inputPacket);
DecodeResult decodeResult = IkeMessage.decode(0, header, inputPacket);
- assertEquals(DECODE_STATUS_OK, decodeResult.status);
- assertNotNull(decodeResult.ikeMessage);
- assertNull(decodeResult.ikeException);
- IkeMessage message = decodeResult.ikeMessage;
+ IkeMessage message = verifyDecodeResultOkAndGetMessage(decodeResult);
+
assertEquals(EXPECTED_IKE_INIT_PAYLOAD_LIST.length - 1, message.ikePayloadList.size());
for (int i = 0; i < EXPECTED_IKE_INIT_PAYLOAD_LIST.length - 1; i++) {
assertEquals(
@@ -294,12 +373,11 @@
mMockCipher,
mMockIkeSaRecord,
mIkeAuthHeader,
- mIkeAuthPacket);
- assertEquals(IkeMessage.DECODE_STATUS_OK, decodeResult.status);
- assertNotNull(decodeResult.ikeMessage);
- assertNull(decodeResult.ikeException);
+ mIkeAuthPacket,
+ null /*collectedFragments*/);
+ IkeMessage ikeMessage = verifyDecodeResultOkAndGetMessage(decodeResult);
- assertEquals(IKE_AUTH_PAYLOAD_SIZE, decodeResult.ikeMessage.ikePayloadList.size());
+ assertEquals(IKE_AUTH_PAYLOAD_SIZE, ikeMessage.ikePayloadList.size());
}
@Test
@@ -311,12 +389,13 @@
mMockCipher,
mMockIkeSaRecord,
mIkeAuthHeader,
- mIkeAuthPacket);
- assertEquals(IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE, decodeResult.status);
- assertNull(decodeResult.ikeMessage);
- assertNotNull(decodeResult.ikeException);
+ mIkeAuthPacket,
+ null /*collectedFragments*/);
+ IkeException ikeException =
+ verifyDecodeResultErrorAndGetIkeException(
+ decodeResult, DECODE_STATUS_UNPROTECTED_ERROR);
- assertTrue(decodeResult.ikeException instanceof InvalidMessageIdException);
+ assertTrue(ikeException instanceof InvalidMessageIdException);
}
@Test
@@ -330,13 +409,14 @@
mMockCipher,
mMockIkeSaRecord,
mIkeAuthHeader,
- mIkeAuthPacket);
- assertEquals(IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE, decodeResult.status);
- assertNull(decodeResult.ikeMessage);
- assertNotNull(decodeResult.ikeException);
+ mIkeAuthPacket,
+ null /*collectedFragments*/);
+ IkeException ikeException =
+ verifyDecodeResultErrorAndGetIkeException(
+ decodeResult, DECODE_STATUS_UNPROTECTED_ERROR);
assertTrue(
- ((IkeInternalException) decodeResult.ikeException).getCause()
+ ((IkeInternalException) ikeException).getCause()
instanceof GeneralSecurityException);
}
@@ -351,13 +431,14 @@
mMockCipher,
mMockIkeSaRecord,
mIkeAuthHeader,
- mIkeAuthPacket);
- assertEquals(IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE, decodeResult.status);
- assertNull(decodeResult.ikeMessage);
- assertNotNull(decodeResult.ikeException);
+ mIkeAuthPacket,
+ null /*collectedFragments*/);
+ IkeException ikeException =
+ verifyDecodeResultErrorAndGetIkeException(
+ decodeResult, DECODE_STATUS_UNPROTECTED_ERROR);
assertTrue(
- ((IkeInternalException) decodeResult.ikeException).getCause()
+ ((IkeInternalException) ikeException).getCause()
instanceof IllegalBlockSizeException);
}
@@ -377,12 +458,13 @@
mMockCipher,
mMockIkeSaRecord,
mIkeAuthHeader,
- mIkeAuthPacket);
- assertEquals(IkeMessage.DECODE_STATUS_PROTECTED_ERROR_MESSAGE, decodeResult.status);
- assertNull(decodeResult.ikeMessage);
- assertNotNull(decodeResult.ikeException);
+ mIkeAuthPacket,
+ null /*collectedFragments*/);
+ IkeException ikeException =
+ verifyDecodeResultErrorAndGetIkeException(
+ decodeResult, DECODE_STATUS_PROTECTED_ERROR);
- assertTrue(decodeResult.ikeException instanceof InvalidSyntaxException);
+ assertTrue(ikeException instanceof InvalidSyntaxException);
}
private boolean support(int payloadType) {
@@ -395,7 +477,8 @@
byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
byte[] ikeBodyBytes = TestUtils.hexStringToByteArray(IKE_SA_INIT_BODY_RAW_PACKET);
IkeHeader header = new IkeHeader(inputPacket);
- IkeMessage message = IkeMessage.decode(0, header, inputPacket).ikeMessage;
+ IkeMessage message =
+ ((DecodeResultOk) IkeMessage.decode(0, header, inputPacket)).ikeMessage;
byte[] encodedIkeMessage = message.attachEncodedHeader(ikeBodyBytes);
assertArrayEquals(inputPacket, encodedIkeMessage);
@@ -425,12 +508,398 @@
0);
IkeMessage ikeMessage = new IkeMessage(ikeHeader, new LinkedList<>());
- byte[] ikeMessageBytes =
- ikeMessage.encryptAndEncode(mMockIntegrity, mMockCipher, mMockIkeSaRecord);
- byte[] expectedBytes = TestUtils.hexStringToByteArray(IKE_EMPTY_INFO_MSG_HEX_STRING);
+ byte[][] ikeMessageBytes =
+ ikeMessage.encryptAndEncode(
+ mMockIntegrity,
+ mMockCipher,
+ mMockIkeSaRecord,
+ true /*supportFragment*/,
+ 1280 /*fragSize*/);
+ byte[][] expectedBytes =
+ new byte[][] {TestUtils.hexStringToByteArray(IKE_EMPTY_INFO_MSG_HEX_STRING)};
assertArrayEquals(expectedBytes, ikeMessageBytes);
}
- // TODO: Implement encodeToByteBuffer() of each payload and add test for encoding message
+ private DecodeResultPartial makeDecodeResultForFragOne(DecodeResultPartial collectedFrags) {
+ return new DecodeResultPartial(
+ mFragOneHeader, mDummySkfPayloadOne, PAYLOAD_TYPE_AUTH, collectedFrags);
+ }
+
+ private DecodeResultPartial makeDecodeResultForFragTwo(DecodeResultPartial collectedFrags) {
+ return new DecodeResultPartial(
+ mFragTwoHeader, mDummySkfPayloadTwo, PAYLOAD_TYPE_NO_NEXT, collectedFrags);
+ }
+
+ @Test
+ public void testConstructDecodePartialFirstFragArriveFirst() throws Exception {
+ DecodeResultPartial resultPartial = makeDecodeResultForFragOne(null /*collectedFragments*/);
+
+ assertEquals(mFragOneHeader, resultPartial.ikeHeader);
+
+ assertEquals(TOTAL_FRAGMENTS, resultPartial.collectedFragsList.length);
+ assertArrayEquals(
+ FRAGMENT_ONE_UNENCRYPTED_DATA,
+ resultPartial.collectedFragsList[FRAGMENT_NUM_ONE - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+ }
+
+ @Test
+ public void testConstructDecodePartialSecondFragArriveFirst() throws Exception {
+ DecodeResultPartial resultPartial = makeDecodeResultForFragTwo(null /*collectedFragments*/);
+
+ assertEquals(PAYLOAD_TYPE_NO_NEXT, resultPartial.firstPayloadType);
+ assertEquals(mFragTwoHeader, resultPartial.ikeHeader);
+
+ assertEquals(TOTAL_FRAGMENTS, resultPartial.collectedFragsList.length);
+ assertArrayEquals(
+ FRAGMENT_TWO_UNENCRYPTED_DATA,
+ resultPartial.collectedFragsList[FRAGMENT_NUM_TWO - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+ }
+
+ @Test
+ public void testConstructDecodeResultPartialWithCollectedFrags() throws Exception {
+ DecodeResultPartial resultPartialIncomplete =
+ makeDecodeResultForFragTwo(null /*collectedFragments*/);
+ DecodeResultPartial resultPartialComplete =
+ makeDecodeResultForFragOne(resultPartialIncomplete);
+
+ assertEquals(PAYLOAD_TYPE_AUTH, resultPartialComplete.firstPayloadType);
+ assertEquals(mFragTwoHeader, resultPartialComplete.ikeHeader);
+
+ assertEquals(TOTAL_FRAGMENTS, resultPartialComplete.collectedFragsList.length);
+ assertTrue(resultPartialComplete.isAllFragmentsReceived());
+ }
+
+ @Test
+ public void testReassembleAllFrags() throws Exception {
+ DecodeResultPartial resultPartialIncomplete =
+ makeDecodeResultForFragOne(null /*collectedFragments*/);
+ DecodeResultPartial resultPartialComplete =
+ makeDecodeResultForFragTwo(resultPartialIncomplete);
+
+ assertEquals(PAYLOAD_TYPE_AUTH, resultPartialIncomplete.firstPayloadType);
+ assertEquals(mFragOneHeader, resultPartialIncomplete.ikeHeader);
+
+ assertEquals(TOTAL_FRAGMENTS, resultPartialIncomplete.collectedFragsList.length);
+ assertTrue(resultPartialIncomplete.isAllFragmentsReceived());
+
+ // Verify reassembly result
+ ByteBuffer expectedBuffer =
+ ByteBuffer.allocate(
+ FRAGMENT_ONE_UNENCRYPTED_DATA.length
+ + FRAGMENT_TWO_UNENCRYPTED_DATA.length);
+ expectedBuffer.put(FRAGMENT_ONE_UNENCRYPTED_DATA).put(FRAGMENT_TWO_UNENCRYPTED_DATA);
+
+ byte[] reassembledBytes = resultPartialComplete.reassembleAllFrags();
+ assertArrayEquals(expectedBuffer.array(), reassembledBytes);
+ }
+
+ @Test
+ public void testReassembleIncompleteFragmentsThrows() throws Exception {
+ DecodeResultPartial resultPartial = makeDecodeResultForFragTwo(null /*collectedFragments*/);
+
+ assertFalse(resultPartial.isAllFragmentsReceived());
+
+ try {
+ resultPartial.reassembleAllFrags();
+ fail("Expected to fail because reassembly is not done");
+ } catch (IllegalStateException expected) {
+
+ }
+ }
+
+ private void setDecryptSkfPayload(IkeSkfPayload skf) throws Exception {
+ doReturn(skf)
+ .when(mSpyIkePayloadDecoder)
+ .decodeIkeSkPayload(
+ eq(true),
+ anyBoolean(),
+ any(),
+ eq(mMockIntegrity),
+ eq(mMockCipher),
+ any(),
+ any());
+ }
+
+ private DecodeResult decodeSkf(
+ int expectedMsgId,
+ IkeHeader header,
+ byte[] packet,
+ DecodeResultPartial collectFragments)
+ throws Exception {
+ return IkeMessage.decode(
+ expectedMsgId,
+ mMockIntegrity,
+ mMockCipher,
+ mMockIkeSaRecord,
+ header,
+ packet,
+ collectFragments);
+ }
+
+ @Test
+ public void testRcvFirstArrivedFrag() throws Exception {
+ setDecryptSkfPayload(mDummySkfPayloadTwo);
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID,
+ mFragTwoHeader,
+ mIkeFragPacket,
+ null /* collectedFragments*/);
+
+ // Verify decoding result
+ assertTrue(decodeResult instanceof DecodeResultPartial);
+ DecodeResultPartial resultPartial = (DecodeResultPartial) decodeResult;
+
+ assertEquals(PAYLOAD_TYPE_NO_NEXT, resultPartial.firstPayloadType);
+ assertEquals(mFragTwoHeader, resultPartial.ikeHeader);
+
+ assertEquals(TOTAL_FRAGMENTS, resultPartial.collectedFragsList.length);
+ assertArrayEquals(
+ FRAGMENT_TWO_UNENCRYPTED_DATA,
+ resultPartial.collectedFragsList[FRAGMENT_NUM_TWO - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+ }
+
+ @Test
+ public void testRcvLastArrivedFrag() throws Exception {
+ // Create two dummy SKF Payloads so that the complete unencrypted data is two ID payloads
+ byte[] idInitPayloadBytes = TestUtils.hexStringToByteArray(ID_INIT_PAYLOAD_HEX_STRING);
+ byte[] idRespPayloadBytes = TestUtils.hexStringToByteArray(ID_RESP_PAYLOAD_HEX_STRING);
+ IkeSkfPayload skfOne =
+ makeDummySkfPayload(idInitPayloadBytes, FRAGMENT_NUM_ONE, TOTAL_FRAGMENTS);
+ IkeSkfPayload skfTwo =
+ makeDummySkfPayload(idRespPayloadBytes, FRAGMENT_NUM_TWO, TOTAL_FRAGMENTS);
+
+ DecodeResultPartial resultPartialIncomplete =
+ new DecodeResultPartial(
+ mFragOneHeader,
+ skfOne,
+ PAYLOAD_TYPE_ID_INITIATOR,
+ null /* collectedFragments*/);
+
+ setDecryptSkfPayload(skfTwo);
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID,
+ mFragTwoHeader,
+ mIkeFragPacket,
+ resultPartialIncomplete);
+
+ // Verify fragments reassembly has been finished and complete message has been decoded.
+ assertTrue(decodeResult instanceof DecodeResultOk);
+ DecodeResultOk resultOk = (DecodeResultOk) decodeResult;
+ assertEquals(2, resultOk.ikeMessage.ikePayloadList.size());
+ }
+
+ @Test
+ public void testRcvFirstArrivedFragWithUnprotectedError() throws Exception {
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID + 1,
+ mFragTwoHeader,
+ mIkeFragPacket,
+ null /* collectedFragments*/);
+
+ // Verify that unprotected error was returned
+ IkeException ikeException =
+ verifyDecodeResultErrorAndGetIkeException(
+ decodeResult, DECODE_STATUS_UNPROTECTED_ERROR);
+ assertTrue(ikeException instanceof InvalidMessageIdException);
+ }
+
+ @Test
+ public void testRcvLastArrivedFragWithUnprotectedError() throws Exception {
+ DecodeResultPartial resultPartialIncomplete =
+ makeDecodeResultForFragOne(null /* collectedFragments*/);
+
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID + 1,
+ mFragTwoHeader,
+ mIkeFragPacket,
+ resultPartialIncomplete);
+
+ // Verify that newly received fragment was discarded
+ assertEquals(resultPartialIncomplete, decodeResult);
+ }
+
+ @Test
+ public void testRcvFragWithLargerTotalFragments() throws Exception {
+ DecodeResultPartial resultPartialIncomplete =
+ new DecodeResultPartial(
+ mFragOneHeader,
+ mDummySkfPayloadOne,
+ PAYLOAD_TYPE_NO_NEXT,
+ null /* collectedFragments*/);
+
+ // Set total fragments of inbound fragment to 5
+ int totalFragments = 5;
+ byte[] fragPacket = TestUtils.hexStringToByteArray(IKE_FRAG_HEX_STRING);
+ fragPacket[TOTAL_FRAGMENTS_OFFSET] = 0;
+ fragPacket[TOTAL_FRAGMENTS_OFFSET] = 5;
+
+ byte[] unencryptedData = "testRcvFragWithLargerTotalFragments".getBytes();
+ IkeSkfPayload skfPayload =
+ makeDummySkfPayload(unencryptedData, FRAGMENT_NUM_TWO, totalFragments);
+ setDecryptSkfPayload(skfPayload);
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID,
+ mFragTwoHeader,
+ fragPacket,
+ resultPartialIncomplete);
+
+ // Verify that previously collected fragments were all discarded
+ assertTrue(decodeResult instanceof DecodeResultPartial);
+ DecodeResultPartial resultPartial = (DecodeResultPartial) decodeResult;
+
+ assertEquals(PAYLOAD_TYPE_NO_NEXT, resultPartial.firstPayloadType);
+ assertEquals(mFragTwoHeader, resultPartial.ikeHeader);
+
+ assertEquals(totalFragments, resultPartial.collectedFragsList.length);
+ assertArrayEquals(unencryptedData, resultPartial.collectedFragsList[FRAGMENT_NUM_TWO - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+ }
+
+ @Test
+ public void testRcvFragWithSmallerTotalFragments() throws Exception {
+ int totalFragments = 5;
+ byte[] unencryptedData = "testRcvFragWithSmallerTotalFragments".getBytes();
+ IkeSkfPayload skfPayload =
+ makeDummySkfPayload(unencryptedData, FRAGMENT_NUM_ONE, totalFragments);
+
+ DecodeResultPartial resultPartialIncomplete =
+ new DecodeResultPartial(
+ mFragOneHeader,
+ skfPayload,
+ PAYLOAD_TYPE_AUTH,
+ null /* collectedFragments*/);
+
+ setDecryptSkfPayload(mDummySkfPayloadTwo);
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID,
+ mFragTwoHeader,
+ mIkeFragPacket,
+ resultPartialIncomplete);
+
+ // Verify that newly received fragment was discarded
+ assertEquals(resultPartialIncomplete, decodeResult);
+ }
+
+ @Test
+ public void testRcvReplayFrag() throws Exception {
+ DecodeResultPartial resultPartialIncomplete =
+ makeDecodeResultForFragTwo(null /* collectedFragments*/);
+
+ setDecryptSkfPayload(mDummySkfPayloadTwo);
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID,
+ mFragTwoHeader,
+ mIkeFragPacket,
+ resultPartialIncomplete);
+
+ // Verify that newly received fragment was discarded
+ assertEquals(resultPartialIncomplete, decodeResult);
+ }
+
+ @Test
+ public void testRcvCompleteMessageDuringReassembly() throws Exception {
+ DecodeResultPartial resultPartialIncomplete =
+ makeDecodeResultForFragTwo(null /* collectedFragments*/);
+
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_AUTH_EXPECTED_MESSAGE_ID,
+ mIkeAuthHeader,
+ mIkeAuthPacket,
+ resultPartialIncomplete);
+
+ // Verify that newly received IKE message was discarded
+ assertEquals(resultPartialIncomplete, decodeResult);
+ }
+
+ @Test
+ public void testEncodeAndEncryptFragments() throws Exception {
+ int messageId = 1;
+ int fragSize = 140;
+ int expectedTotalFragments = 3;
+
+ byte[] integrityKey = new byte[0];
+ byte[] encryptionKey = new byte[0];
+ byte[] iv = new byte[IKE_AUTH_CIPHER_IV_SIZE];
+
+ when(mMockCipher.generateIv()).thenReturn(iv);
+ when(mMockCipher.encrypt(any(), any(), any()))
+ .thenAnswer(
+ (invocation) -> {
+ return (byte[]) invocation.getArguments()[0];
+ });
+
+ IkeHeader ikeHeader =
+ new IkeHeader(
+ INIT_SPI,
+ RESP_SPI,
+ IkePayload.PAYLOAD_TYPE_SK,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ true /*isResp*/,
+ false /*fromInit*/,
+ messageId);
+
+ byte[][] packetList =
+ new IkeMessage.IkeMessageHelper()
+ .encryptAndEncode(
+ ikeHeader,
+ IkePayload.PAYLOAD_TYPE_AUTH,
+ mUnencryptedPaddedData,
+ mMockIntegrity,
+ mMockCipher,
+ integrityKey,
+ encryptionKey,
+ true /*supportFragment*/,
+ fragSize);
+
+ assertEquals(expectedTotalFragments, packetList.length);
+
+ IkeHeader expectedIkeHeader =
+ new IkeHeader(
+ INIT_SPI,
+ RESP_SPI,
+ IkePayload.PAYLOAD_TYPE_SKF,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ true /*isResp*/,
+ false /*fromInit*/,
+ messageId);
+ for (int i = 0; i < packetList.length; i++) {
+ byte[] p = packetList[i];
+
+ // Verify fragment length
+ assertNotNull(p);
+ assertTrue(p.length <= fragSize);
+
+ ByteBuffer packetBuffer = ByteBuffer.wrap(p);
+
+ // Verify IKE header
+ byte[] headerBytes = new byte[IkeHeader.IKE_HEADER_LENGTH];
+ packetBuffer.get(headerBytes);
+
+ ByteBuffer expectedHeadBuffer = ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH);
+ expectedIkeHeader.encodeToByteBuffer(
+ expectedHeadBuffer, p.length - IkeHeader.IKE_HEADER_LENGTH);
+
+ assertArrayEquals(expectedHeadBuffer.array(), headerBytes);
+
+ // Verify fragment payload header
+ packetBuffer.get(new byte[IkePayload.GENERIC_HEADER_LENGTH]);
+ assertEquals(i + 1 /*expetced fragNum*/, Short.toUnsignedInt(packetBuffer.getShort()));
+ assertEquals(expectedTotalFragments, Short.toUnsignedInt(packetBuffer.getShort()));
+ }
+
+ verify(mMockCipher, times(expectedTotalFragments + 1))
+ .encrypt(any(), eq(encryptionKey), eq(iv));
+ }
}
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java
index 26f3d01..f3af5b6 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java
@@ -63,7 +63,7 @@
private static final int CHECKSUM_LEN = 12;
private IkeCipher mAesCbcDecryptCipher;
- private byte[] mAesCbcDecryptKey;
+ private byte[] mAesCbcDecryptionKey;
private IkeMacIntegrity mHmacSha1IntegrityMac;
private byte[] mHmacSha1IntegrityKey;
@@ -76,7 +76,7 @@
SaProposal.ENCRYPTION_ALGORITHM_AES_CBC,
SaProposal.KEY_LEN_AES_128),
IkeMessage.getSecurityProvider());
- mAesCbcDecryptKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP);
+ mAesCbcDecryptionKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP);
mHmacSha1IntegrityMac =
IkeMacIntegrity.create(
new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96),
@@ -97,7 +97,7 @@
mHmacSha1IntegrityMac,
mAesCbcDecryptCipher,
mHmacSha1IntegrityKey,
- mAesCbcDecryptKey)
+ mAesCbcDecryptionKey)
.first;
int payloadLength = payload.getPayloadLength();
ByteBuffer buffer = ByteBuffer.allocate(payloadLength);
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkfPayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkfPayloadTest.java
index e4c25d3..df81904 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkfPayloadTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkfPayloadTest.java
@@ -34,6 +34,9 @@
import org.junit.Before;
import org.junit.Test;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
public final class IkeSkfPayloadTest {
private static final String IKE_FRAG_MSG_HEX_STRING =
"bb3d5237aa558779db24aeb6c9ea183e352023200000000100000134"
@@ -111,7 +114,7 @@
mSpyHmacSha256IntegrityMac,
mSpyAesCbcCipher,
new byte[0] /*integrityKey*/,
- new byte[0] /*decryptKey*/)
+ new byte[0] /*decryptionKey*/)
.first;
return payload;
}
@@ -178,4 +181,18 @@
} catch (InvalidSyntaxException expected) {
}
}
+
+ @Test
+ public void testEncode() throws Exception {
+ byte[] message = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_HEX_STRING);
+ IkeSkfPayload payload = decodeAndDecryptFragMsg(message);
+
+ int payloadLength = payload.getPayloadLength();
+ ByteBuffer buffer = ByteBuffer.allocate(payloadLength);
+ payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, buffer);
+
+ byte[] expectedPayloadBytes =
+ Arrays.copyOfRange(message, IkeHeader.IKE_HEADER_LENGTH, message.length);
+ assertArrayEquals(expectedPayloadBytes, buffer.array());
+ }
}
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTestUtils.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTestUtils.java
index 4c4b256..a87f8ad 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTestUtils.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeTestUtils.java
@@ -16,18 +16,20 @@
package com.android.ike.ikev2.message;
-import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE;
+import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.util.Pair;
import com.android.ike.TestUtils;
import com.android.ike.ikev2.exceptions.IkeProtocolException;
import com.android.ike.ikev2.message.IkeMessage.DecodeResult;
+import com.android.ike.ikev2.message.IkeMessage.DecodeResultError;
import java.nio.ByteBuffer;
@@ -52,11 +54,18 @@
IkeHeader header = new IkeHeader(inputPacket);
DecodeResult decodeResult = IkeMessage.decode(0, header, inputPacket);
- assertEquals(DECODE_STATUS_UNPROTECTED_ERROR_MESSAGE, decodeResult.status);
- assertNull(decodeResult.ikeMessage);
- assertNotNull(decodeResult.ikeException);
- assertTrue(expectedException.isInstance(decodeResult.ikeException));
+ assertEquals(DECODE_STATUS_UNPROTECTED_ERROR, decodeResult.status);
+ DecodeResultError resultError = (DecodeResultError) decodeResult;
+ assertNotNull(resultError.ikeException);
+ assertTrue(expectedException.isInstance(resultError.ikeException));
- return (T) decodeResult.ikeException;
+ return (T) resultError.ikeException;
+ }
+
+ public static IkeSkfPayload makeDummySkfPayload(
+ byte[] unencryptedData, int fragNum, int totalFrags) throws Exception {
+ IkeEncryptedPayloadBody mockEncryptedBody = mock(IkeEncryptedPayloadBody.class);
+ when(mockEncryptedBody.getUnencryptedData()).thenReturn(unencryptedData);
+ return new IkeSkfPayload(mockEncryptedBody, fragNum, totalFrags);
}
}