aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk2
-rw-r--r--src/java/com/android/ike/ikev2/ChildSessionOptions.java2
-rw-r--r--src/java/com/android/ike/ikev2/IkeSessionOptions.java104
-rw-r--r--src/java/com/android/ike/ikev2/IkeSessionStateMachine.java120
-rw-r--r--src/java/com/android/ike/ikev2/IkeSocket.java268
-rw-r--r--src/java/com/android/ike/ikev2/SaProposal.java73
-rw-r--r--src/java/com/android/ike/ikev2/SaRecord.java74
-rw-r--r--src/java/com/android/ike/ikev2/message/IkeDeletePayload.java123
-rw-r--r--src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java80
-rw-r--r--src/java/com/android/ike/ikev2/message/IkeHeader.java41
-rw-r--r--src/java/com/android/ike/ikev2/message/IkeKePayload.java128
-rw-r--r--src/java/com/android/ike/ikev2/message/IkeMessage.java45
-rw-r--r--src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java2
-rw-r--r--src/java/com/android/ike/ikev2/message/IkePayload.java5
-rw-r--r--src/java/com/android/ike/ikev2/message/IkePayloadFactory.java8
-rw-r--r--src/java/com/android/ike/ikev2/message/IkeSaPayload.java261
-rw-r--r--src/java/com/android/ike/ikev2/message/IkeSkPayload.java39
-rw-r--r--tests/iketests/Android.mk3
-rw-r--r--tests/iketests/AndroidManifest.xml2
-rw-r--r--tests/iketests/src/java/com/android/ike/ikev2/ChildSessionStateMachineTest.java7
-rw-r--r--tests/iketests/src/java/com/android/ike/ikev2/IkeSessionOptionsTest.java103
-rw-r--r--tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java122
-rw-r--r--tests/iketests/src/java/com/android/ike/ikev2/IkeSocketTest.java376
-rw-r--r--tests/iketests/src/java/com/android/ike/ikev2/SaProposalTest.java71
-rw-r--r--tests/iketests/src/java/com/android/ike/ikev2/SaRecordTest.java138
-rw-r--r--tests/iketests/src/java/com/android/ike/ikev2/message/IkeDeletePayloadTest.java144
-rw-r--r--tests/iketests/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBodyTest.java117
-rw-r--r--tests/iketests/src/java/com/android/ike/ikev2/message/IkeHeaderTest.java5
-rw-r--r--tests/iketests/src/java/com/android/ike/ikev2/message/IkeKePayloadTest.java12
-rw-r--r--tests/iketests/src/java/com/android/ike/ikev2/message/IkeSaPayloadTest.java138
-rw-r--r--tests/iketests/src/java/com/android/ike/ikev2/message/IkeSkPayloadTest.java37
31 files changed, 2322 insertions, 328 deletions
diff --git a/Android.mk b/Android.mk
index 16642a29..14e9e9ee 100644
--- a/Android.mk
+++ b/Android.mk
@@ -20,7 +20,7 @@ LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src/java
LOCAL_SRC_FILES := \
$(call all-java-files-under, src/java)
-LOCAL_JAVA_LIBRARIES := bouncycastle
+LOCAL_JAVA_LIBRARIES := bouncycastle NetworkStackBase
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := ike
diff --git a/src/java/com/android/ike/ikev2/ChildSessionOptions.java b/src/java/com/android/ike/ikev2/ChildSessionOptions.java
index cbca9f46..b311c666 100644
--- a/src/java/com/android/ike/ikev2/ChildSessionOptions.java
+++ b/src/java/com/android/ike/ikev2/ChildSessionOptions.java
@@ -20,6 +20,6 @@ package com.android.ike.ikev2;
* ChildSessionOptions contains user-provided Child SA proposals and negotiated Child SA
* information.
*/
-public class ChildSessionOptions {
+public final class ChildSessionOptions {
// TODO: Implement it.
}
diff --git a/src/java/com/android/ike/ikev2/IkeSessionOptions.java b/src/java/com/android/ike/ikev2/IkeSessionOptions.java
index 66ef94d6..9f2094f2 100644
--- a/src/java/com/android/ike/ikev2/IkeSessionOptions.java
+++ b/src/java/com/android/ike/ikev2/IkeSessionOptions.java
@@ -16,9 +16,107 @@
package com.android.ike.ikev2;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+
+import com.android.ike.ikev2.message.IkePayload;
+
+import java.net.InetAddress;
+import java.util.LinkedList;
+import java.util.List;
+
/**
- * IkeSessionOptions contains all configurations including cryptographic algorithm set of an IKE SA.
+ * IkeSessionOptions contains all user provided configurations for negotiating an IKE SA.
+ *
+ * <p>TODO: Make this doc more user-friendly.
*/
-public class IkeSessionOptions {
- // TODO: Implement it.
+public final class IkeSessionOptions {
+ private final InetAddress mServerAddress;
+ private final UdpEncapsulationSocket mUdpEncapSocket;
+ private final SaProposal[] mSaProposals;
+ private final boolean mIsIkeFragmentationSupported;
+
+ private IkeSessionOptions(
+ InetAddress serverAddress,
+ UdpEncapsulationSocket udpEncapsulationSocket,
+ SaProposal[] proposals,
+ boolean isIkeFragmentationSupported) {
+ mServerAddress = serverAddress;
+ mUdpEncapSocket = udpEncapsulationSocket;
+ mSaProposals = proposals;
+ mIsIkeFragmentationSupported = isIkeFragmentationSupported;
+ }
+
+ /** Package private */
+ InetAddress getServerAddress() {
+ return mServerAddress;
+ }
+ /** Package private */
+ UdpEncapsulationSocket getUdpEncapsulationSocket() {
+ return mUdpEncapSocket;
+ }
+ /** Package private */
+ SaProposal[] getSaProposals() {
+ return mSaProposals;
+ }
+ /** Package private */
+ boolean isIkeFragmentationSupported() {
+ return mIsIkeFragmentationSupported;
+ }
+
+ /** This class can be used to incrementally construct a IkeSessionOptions. */
+ public static final class Builder {
+ private final InetAddress mServerAddress;
+ private final UdpEncapsulationSocket mUdpEncapSocket;
+ private final List<SaProposal> mSaProposalList = new LinkedList<>();
+
+ private boolean mIsIkeFragmentationSupported = false;
+
+ /**
+ * Returns a new Builder for an IkeSessionOptions.
+ *
+ * @param serverAddress IP address of remote IKE server.
+ * @param udpEncapsulationSocket {@link IpSecManager.UdpEncapsulationSocket} for sending and
+ * receiving IKE message.
+ * @return Builder for an IkeSessionOptions.
+ */
+ public Builder(InetAddress serverAddress, UdpEncapsulationSocket udpEncapsulationSocket) {
+ mServerAddress = serverAddress;
+ mUdpEncapSocket = udpEncapsulationSocket;
+ }
+
+ /**
+ * Adds an IKE SA proposal to IkeSessionOptions being built.
+ *
+ * @param proposal IKE SA proposal.
+ * @return Builder for an IkeSessionOptions.
+ * @throws IllegalArgumentException if input proposal is not IKE SA proposal.
+ */
+ public Builder addSaProposal(SaProposal proposal) {
+ if (proposal.getProtocolId() != IkePayload.PROTOCOL_ID_IKE) {
+ throw new IllegalArgumentException(
+ "Expected IKE SA Proposal but received Child SA proposal");
+ }
+ mSaProposalList.add(proposal);
+ return this;
+ }
+
+ /**
+ * Validates, builds and returns the IkeSessionOptions
+ *
+ * @return IkeSessionOptions the validated IkeSessionOptions
+ * @throws IllegalStateException if no IKE SA proposal is provided
+ */
+ public IkeSessionOptions build() {
+ if (mSaProposalList.isEmpty()) {
+ throw new IllegalArgumentException("IKE SA proposal not found");
+ }
+ return new IkeSessionOptions(
+ mServerAddress,
+ mUdpEncapSocket,
+ mSaProposalList.toArray(new SaProposal[mSaProposalList.size()]),
+ mIsIkeFragmentationSupported);
+ }
+
+ // TODO: add methods for supporting IKE fragmentation.
+ }
}
diff --git a/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java b/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
index c887ecb0..b4a7b9c6 100644
--- a/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
+++ b/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
@@ -17,20 +17,30 @@ package com.android.ike.ikev2;
import android.os.Looper;
import android.os.Message;
+import android.system.ErrnoException;
import android.util.LongSparseArray;
-import android.util.Pair;
import android.util.SparseArray;
import com.android.ike.ikev2.SaRecord.IkeSaRecord;
import com.android.ike.ikev2.exceptions.IkeException;
import com.android.ike.ikev2.message.IkeHeader;
+import com.android.ike.ikev2.message.IkeKePayload;
import com.android.ike.ikev2.message.IkeMessage;
+import com.android.ike.ikev2.message.IkeNoncePayload;
import com.android.ike.ikev2.message.IkeNotifyPayload;
+import com.android.ike.ikev2.message.IkePayload;
+import com.android.ike.ikev2.message.IkeSaPayload;
+import com.android.ike.ikev2.message.IkeSaPayload.DhGroupTransform;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
/**
* IkeSessionStateMachine tracks states and manages exchanges of this IKE session.
@@ -74,6 +84,11 @@ public class IkeSessionStateMachine extends StateMachine {
static final int CMD_LOCAL_REQUEST_REKEY_CHILD = CMD_LOCAL_REQUEST_BASE + 7;
// TODO: Add signals for other procedure types and notificaitons.
+ // Remember locally assigned IKE SPIs to avoid SPI collision.
+ private static final Set<Long> ASSIGNED_LOCAL_IKE_SPI_SET = new HashSet<>();
+ private static final int MAX_ASSIGN_IKE_SPI_ATTEMPTS = 100;
+ private static final SecureRandom IKE_SPI_RANDOM = new SecureRandom();
+
private final IkeSessionOptions mIkeSessionOptions;
private final ChildSessionOptions mFirstChildSessionOptions;
/** Map that stores all IkeSaRecords, keyed by remotely generated IKE SPI. */
@@ -85,6 +100,12 @@ public class IkeSessionStateMachine extends StateMachine {
*/
private final SparseArray<ChildSessionStateMachine> mSpiToChildSessionMap;
+ /**
+ * Package private socket that sends and receives encoded IKE message. Initialized in Initial
+ * State.
+ */
+ @VisibleForTesting IkeSocket mIkeSocket;
+
/** Package */
@VisibleForTesting IkeSaRecord mCurrentIkeSaRecord;
/** Package */
@@ -146,9 +167,55 @@ public class IkeSessionStateMachine extends StateMachine {
setInitialState(mInitial);
}
+ // Generate IKE SPI. Throw an exception if it failed and handle this exception in current State.
+ private static Long getIkeSpiOrThrow() {
+ for (int i = 0; i < MAX_ASSIGN_IKE_SPI_ATTEMPTS; i++) {
+ long spi = IKE_SPI_RANDOM.nextLong();
+ if (ASSIGNED_LOCAL_IKE_SPI_SET.add(spi)) return spi;
+ }
+ throw new IllegalStateException("Failed to generate IKE SPI.");
+ }
+
private IkeMessage buildIkeInitReq() {
- // TODO:Build packet according to mIkeSessionOptions.
- return null;
+ // TODO: Handle IKE SPI assigning error in CreateIkeLocalIkeInit State.
+
+ List<IkePayload> payloadList = new LinkedList<>();
+
+ // Generate IKE SPI
+ long initSpi = getIkeSpiOrThrow();
+ long respSpi = 0;
+
+ // It is validated in IkeSessionOptions.Builder to ensure IkeSessionOptions has at least one
+ // SaProposal and all SaProposals are valid for IKE SA negotiation.
+ SaProposal[] saProposals = mIkeSessionOptions.getSaProposals();
+
+ // Build SA Payload
+ IkeSaPayload saPayload = new IkeSaPayload(saProposals);
+ payloadList.add(saPayload);
+
+ // Build KE Payload using the first DH group number in the first SaProposal.
+ DhGroupTransform dhGroupTransform = saProposals[0].getDhGroupTransforms()[0];
+ IkeKePayload kePayload = new IkeKePayload(dhGroupTransform.id);
+ payloadList.add(kePayload);
+
+ // Build Nonce Payload
+ IkeNoncePayload noncePayload = new IkeNoncePayload();
+ payloadList.add(noncePayload);
+
+ // TODO: Add Notification Payloads according to user configurations.
+
+ // Build IKE header
+ IkeHeader ikeHeader =
+ new IkeHeader(
+ initSpi,
+ respSpi,
+ IkePayload.PAYLOAD_TYPE_SA,
+ IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT,
+ false /*isResponseMsg*/,
+ true /*fromIkeInitiator*/,
+ 0 /*messageId*/);
+
+ return new IkeMessage(ikeHeader, payloadList);
}
private IkeMessage buildIkeAuthReq() {
@@ -214,6 +281,19 @@ public class IkeSessionStateMachine extends StateMachine {
}
/**
+ * Receive IKE packet from remote server.
+ *
+ * <p>This method is called synchronously from IkeSocket. It proxies the synchronous call as an
+ * asynchronous job to the IkeSessionStateMachine handler.
+ *
+ * @param ikeHeader the decoded IKE header.
+ * @param ikePacketBytes the byte array of the entire received IKE packet.
+ */
+ public void receiveIkePacket(IkeHeader ikeHeader, byte[] ikePacketBytes) {
+ sendMessage(CMD_RECEIVE_IKE_PACKET, new ReceivedIkePacket(ikeHeader, ikePacketBytes));
+ }
+
+ /**
* ReceivedIkePacket is a package private data container consists of decoded IkeHeader and
* encoded IKE packet in a byte array.
*/
@@ -223,9 +303,9 @@ public class IkeSessionStateMachine extends StateMachine {
/** Entire encoded IKE message including IKE header */
public final byte[] ikePacketBytes;
- ReceivedIkePacket(Pair<IkeHeader, byte[]> ikePacketPair) {
- ikeHeader = ikePacketPair.first;
- ikePacketBytes = ikePacketPair.second;
+ ReceivedIkePacket(IkeHeader ikeHeader, byte[] ikePacketBytes) {
+ this.ikeHeader = ikeHeader;
+ this.ikePacketBytes = ikePacketBytes;
}
}
@@ -260,6 +340,15 @@ public class IkeSessionStateMachine extends StateMachine {
/** Initial state of IkeSessionStateMachine. */
class Initial extends State {
@Override
+ public void enter() {
+ try {
+ mIkeSocket = IkeSocket.getIkeSocket(mIkeSessionOptions.getUdpEncapsulationSocket());
+ } catch (ErrnoException e) {
+ // TODO: handle exception and close IkeSession.
+ }
+ }
+
+ @Override
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_LOCAL_REQUEST_CREATE_IKE:
@@ -417,6 +506,7 @@ public class IkeSessionStateMachine extends StateMachine {
public void enter() {
mRequestMsg = buildRequest();
mRequestPacket = encodeRequest();
+ mIkeSocket.sendIkePacket(mRequestPacket, mIkeSessionOptions.getServerAddress());
// TODO: Send out packet and start retransmission timer.
}
@@ -432,12 +522,20 @@ public class IkeSessionStateMachine extends StateMachine {
// CreateIkeLocalInit should override encodeRequest() to encode unencrypted packet
protected byte[] encodeRequest() {
// TODO: encrypt and encode mRequestMsg
- return null;
- };
+ return new byte[0];
+ }
}
/** CreateIkeLocalIkeInit represents state when IKE library initiates IKE_INIT exchange. */
class CreateIkeLocalIkeInit extends LocalNewExchangeBase {
+
+ @Override
+ public void enter() {
+ super.enter();
+ mIkeSocket.registerIke(
+ mRequestMsg.ikeHeader.ikeInitiatorSpi, IkeSessionStateMachine.this);
+ }
+
@Override
protected IkeMessage buildRequest() {
return buildIkeInitReq();
@@ -445,8 +543,7 @@ public class IkeSessionStateMachine extends StateMachine {
@Override
protected byte[] encodeRequest() {
- // TODO: Encode an unencrypted IKE packet.
- return null;
+ return mRequestMsg.encode();
}
@Override
@@ -522,8 +619,7 @@ public class IkeSessionStateMachine extends StateMachine {
mFirstChildSessionOptions);
// TODO: Replace null input params to payload lists in IKE_AUTH request and
// IKE_AUTH response for negotiating Child SA.
- firstChild.handleFirstChildExchange(
- null, null, new ChildSessionCallback());
+ firstChild.handleFirstChildExchange(null, null, new ChildSessionCallback());
transitionTo(mIdle);
} catch (IkeException e) {
diff --git a/src/java/com/android/ike/ikev2/IkeSocket.java b/src/java/com/android/ike/ikev2/IkeSocket.java
new file mode 100644
index 00000000..e235ab88
--- /dev/null
+++ b/src/java/com/android/ike/ikev2/IkeSocket.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ike.ikev2;
+
+import static android.system.OsConstants.F_SETFL;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_NONBLOCK;
+
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.util.PacketReader;
+import android.os.Handler;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import com.android.ike.ikev2.exceptions.IkeException;
+import com.android.ike.ikev2.message.IkeHeader;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * IkeSocket sends and receives IKE packets via the user provided {@link UdpEncapsulationSocket}.
+ *
+ * <p>One UdpEncapsulationSocket instance can only be bound to one IkeSocket instance. IkeSocket
+ * maintains a static map to cache all bound UdpEncapsulationSockets and their IkeSocket instances.
+ * It returns the existing IkeSocket when it has been bound with user provided {@link
+ * UdpEncapsulationSocket}.
+ *
+ * <p>As a packet receiver, IkeSocket registers a file descriptor with a thread's Looper and handles
+ * read events (and errors). Users can expect a call life-cycle like the following:
+ *
+ * <pre>
+ * [1] when user gets a new initiated IkeSocket, start() is called and followed by createFd().
+ * [2] yield, waiting for a read event which will invoke handlePacket()
+ * [3] when user closes this IkeSocket, its reference count decreases. Then stop() is called when
+ * there is no reference of this instance.
+ * </pre>
+ *
+ * <p>IkeSocket is constructed and called only on a single IKE working thread by {@link
+ * IkeSessionStateMachine}. Since all {@link IkeSessionStateMachine}s run on the same working
+ * thread, there will not be concurrent modification problems.
+ */
+public final class IkeSocket extends PacketReader implements AutoCloseable {
+ private static final String TAG = "IkeSocket";
+
+ // TODO: b/129358324 Consider supporting IKE exchange without UDP Encapsulation.
+ // UDP-encapsulated IKE packets MUST be sent to 4500.
+ @VisibleForTesting static final int IKE_SERVER_PORT = 4500;
+
+ // A Non-ESP marker helps the recipient to distinguish IKE packets from ESP packets.
+ @VisibleForTesting static final int NON_ESP_MARKER_LEN = 4;
+ @VisibleForTesting static final byte[] NON_ESP_MARKER = new byte[NON_ESP_MARKER_LEN];
+
+ // Map from UdpEncapsulationSocket to IkeSocket instances.
+ private static Map<UdpEncapsulationSocket, IkeSocket> sFdToIkeSocketMap = new HashMap<>();
+
+ private static IPacketReceiver sPacketReceiver = new PacketReceiver();
+
+ // Package private map from locally generated IKE SPI to IkeSessionStateMachine instances.
+ @VisibleForTesting
+ final LongSparseArray<IkeSessionStateMachine> mSpiToIkeSession =
+ new LongSparseArray<>();
+ // UdpEncapsulationSocket for sending and receving IKE packet.
+ private final UdpEncapsulationSocket mUdpEncapSocket;
+
+ /** Package private */
+ @VisibleForTesting
+ int mRefCount;
+
+ private IkeSocket(UdpEncapsulationSocket udpEncapSocket, Handler handler) {
+ super(handler);
+ mRefCount = 1;
+ mUdpEncapSocket = udpEncapSocket;
+ }
+
+ /**
+ * Get an IkeSocket instance.
+ *
+ * <p>Return the existing IkeSocket instance if it has been created for the input
+ * udpEncapSocket. Otherwise, create and return a new IkeSocket instance.
+ *
+ * @param udpEncapSocket user provided UdpEncapsulationSocket
+ * @return an IkSocket instance
+ */
+ public static IkeSocket getIkeSocket(UdpEncapsulationSocket udpEncapSocket)
+ throws ErrnoException {
+ FileDescriptor fd = udpEncapSocket.getFileDescriptor();
+ // All created IkeSocket has modified its FileDescriptor to non-blocking type for handling
+ // read events in a non-blocking way.
+ Os.fcntlInt(fd, F_SETFL, SOCK_DGRAM | SOCK_NONBLOCK);
+
+ if (sFdToIkeSocketMap.containsKey(udpEncapSocket)) {
+ IkeSocket ikeSocket = sFdToIkeSocketMap.get(udpEncapSocket);
+ ikeSocket.mRefCount++;
+ return ikeSocket;
+ } else {
+ IkeSocket ikeSocket = new IkeSocket(udpEncapSocket, new Handler());
+ // Create and register FileDescriptor for receiving IKE packet on current thread.
+ ikeSocket.start();
+
+ sFdToIkeSocketMap.put(udpEncapSocket, ikeSocket);
+ return ikeSocket;
+ }
+ }
+
+ /**
+ * Get FileDecriptor of mUdpEncapSocket.
+ *
+ * <p>PacketReader registers a listener for this file descriptor on the thread where IkeSocket
+ * is constructed. When there is a read event, this listener is invoked and then calls {@link
+ * handlePacket} to handle the received packet.
+ */
+ @Override
+ protected FileDescriptor createFd() {
+ return mUdpEncapSocket.getFileDescriptor();
+ }
+
+ /**
+ * IPacketReceiver provides a package private interface for handling received packet.
+ *
+ * <p>IPacketReceiver exists so that the interface is injectable for testing.
+ */
+ interface IPacketReceiver {
+ void handlePacket(byte[] recvbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession);
+ }
+
+ /** Package private */
+ @VisibleForTesting
+ static final class PacketReceiver implements IPacketReceiver {
+ public void handlePacket(
+ byte[] recvbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession) {
+ // TODO: b/129708574 Consider only logging the error some % of the time it happens, or
+ // only logging the error the first time it happens and then keep a count to prevent
+ // logspam.
+
+ ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf);
+
+ // Check the existence of the Non-ESP Marker. A received packet can be either an IKE
+ // packet starts with 4 zero-valued bytes Non-ESP Marker or an ESP packet starts with 4
+ // bytes ESP SPI. ESP SPI value can never be zero.
+ byte[] espMarker = new byte[NON_ESP_MARKER_LEN];
+ byteBuffer.get(espMarker);
+ if (!Arrays.equals(NON_ESP_MARKER, espMarker)) {
+ // Drop the received ESP packet.
+ Log.e(TAG, "Receive an ESP packet.");
+ return;
+ }
+
+ try {
+ // Re-direct IKE packet to IkeSessionStateMachine according to the locally generated
+ // IKE SPI.
+ byte[] ikePacketBytes = new byte[byteBuffer.remaining()];
+ byteBuffer.get(ikePacketBytes);
+ IkeHeader ikeHeader = new IkeHeader(ikePacketBytes);
+
+ long localGeneratedSpi =
+ ikeHeader.fromIkeInitiator
+ ? ikeHeader.ikeResponderSpi
+ : ikeHeader.ikeInitiatorSpi;
+
+ IkeSessionStateMachine ikeStateMachine = spiToIkeSession.get(localGeneratedSpi);
+ if (ikeStateMachine == null) {
+ Log.e(TAG, "Unrecognized IKE SPI.");
+ // TODO: Handle invalid IKE SPI error
+ } else {
+ ikeStateMachine.receiveIkePacket(ikeHeader, ikePacketBytes);
+ }
+ } catch (IkeException e) {
+ // Handle invalid IKE header
+ Log.e(TAG, "Can't parse malformed IKE packet header.");
+ }
+ }
+ }
+
+ /** Package private */
+ @VisibleForTesting
+ static void setPacketReceiver(IPacketReceiver receiver) {
+ sPacketReceiver = receiver;
+ }
+
+ /**
+ * Handle received IKE packet. Invoked when there is a read event. Any desired copies of
+ * |recvbuf| should be made in here, as the underlying byte array is reused across all reads.
+ */
+ @Override
+ protected void handlePacket(byte[] recvbuf, int length) {
+ sPacketReceiver.handlePacket(Arrays.copyOfRange(recvbuf, 0, length), mSpiToIkeSession);
+ }
+
+ /**
+ * Send encoded IKE packet to destination address
+ *
+ * @param ikePacket encoded IKE packet
+ * @param serverAddress IP address of remote server
+ */
+ public void sendIkePacket(byte[] ikePacket, InetAddress serverAddress) {
+ try {
+ ByteBuffer buffer = ByteBuffer.allocate(NON_ESP_MARKER_LEN + ikePacket.length);
+
+ // Build outbound UDP Encapsulation packet body for sending IKE message.
+ buffer.put(NON_ESP_MARKER).put(ikePacket);
+ buffer.rewind();
+
+ // Use unconnected UDP socket because one {@UdpEncapsulationSocket} may be shared by
+ // multiple IKE sessions that send messages to different destinations.
+ Os.sendto(
+ mUdpEncapSocket.getFileDescriptor(), buffer, 0, serverAddress, IKE_SERVER_PORT);
+ } catch (ErrnoException | IOException e) {
+ // TODO: Handle exception
+ }
+ }
+
+ /**
+ * Register new created IKE SA
+ *
+ * @param spi the locally generated IKE SPI
+ * @param ikeSession the IKE session this IKE SA belongs to
+ */
+ public void registerIke(long spi, IkeSessionStateMachine ikeSession) {
+ mSpiToIkeSession.put(spi, ikeSession);
+ }
+
+ /**
+ * Unregister a deleted IKE SA
+ *
+ * @param spi the locally generated IKE SPI
+ */
+ public void unregisterIke(long spi) {
+ mSpiToIkeSession.remove(spi);
+ }
+
+ /** Release reference of current IkeSocket when the IKE session is closed. */
+ public void releaseReference() {
+ mRefCount--;
+ if (mRefCount == 0) close();
+ }
+
+ /** Implement {@link AutoCloseable#close()} */
+ @Override
+ public void close() {
+ sFdToIkeSocketMap.remove(mUdpEncapSocket);
+ // PackeReader unregisters file descriptor on thread with which the Handler constructor
+ // argument is associated.
+ stop();
+ }
+}
diff --git a/src/java/com/android/ike/ikev2/SaProposal.java b/src/java/com/android/ike/ikev2/SaProposal.java
index f932be24..53206cc9 100644
--- a/src/java/com/android/ike/ikev2/SaProposal.java
+++ b/src/java/com/android/ike/ikev2/SaProposal.java
@@ -29,7 +29,9 @@ import com.android.ike.ikev2.message.IkeSaPayload.Transform;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Set;
/**
@@ -137,17 +139,17 @@ public final class SaProposal {
}
/** Package private */
- @IkePayload.ProtocolId final int mProtocolId;
+ @IkePayload.ProtocolId private final int mProtocolId;
/** Package private */
- final EncryptionTransform[] mEncryptionAlgorithms;
+ private final EncryptionTransform[] mEncryptionAlgorithms;
/** Package private */
- final PrfTransform[] mPseudorandomFunctions;
+ private final PrfTransform[] mPseudorandomFunctions;
/** Package private */
- final IntegrityTransform[] mIntegrityAlgorithms;
+ private final IntegrityTransform[] mIntegrityAlgorithms;
/** Package private */
- final DhGroupTransform[] mDhGroups;
+ private final DhGroupTransform[] mDhGroups;
/** Package private */
- final EsnTransform[] mEsns;
+ private final EsnTransform[] mEsns;
private SaProposal(
@IkePayload.ProtocolId int protocol,
@@ -225,6 +227,63 @@ public final class SaProposal {
return Arrays.asList(selectFrom).contains(selected[0]);
}
+ /*Package private*/
+ @IkePayload.ProtocolId
+ int getProtocolId() {
+ return mProtocolId;
+ }
+
+ /*Package private*/
+ EncryptionTransform[] getEncryptionTransforms() {
+ return mEncryptionAlgorithms;
+ }
+
+ /*Package private*/
+ PrfTransform[] getPrfTransforms() {
+ return mPseudorandomFunctions;
+ }
+
+ /*Package private*/
+ IntegrityTransform[] getIntegrityTransforms() {
+ return mIntegrityAlgorithms;
+ }
+
+ /*Package private*/
+ DhGroupTransform[] getDhGroupTransforms() {
+ return mDhGroups;
+ }
+
+ /*Package private*/
+ EsnTransform[] getEsnTransforms() {
+ return mEsns;
+ }
+
+ /**
+ * Return all SA Transforms in this SaProposal to be encoded for building an outbound IKE
+ * message.
+ *
+ * <p>This method can be called by only IKE library.
+ *
+ * @return Array of Transforms to be encoded.
+ */
+ public Transform[] getAllTransforms() {
+ int encodedNumTransforms =
+ mEncryptionAlgorithms.length
+ + mPseudorandomFunctions.length
+ + mIntegrityAlgorithms.length
+ + mDhGroups.length
+ + mEsns.length;
+
+ List<Transform> transformList = new ArrayList<Transform>(encodedNumTransforms);
+ transformList.addAll(Arrays.asList(mEncryptionAlgorithms));
+ transformList.addAll(Arrays.asList(mPseudorandomFunctions));
+ transformList.addAll(Arrays.asList(mIntegrityAlgorithms));
+ transformList.addAll(Arrays.asList(mDhGroups));
+ transformList.addAll(Arrays.asList(mEsns));
+
+ return transformList.toArray(new Transform[encodedNumTransforms]);
+ }
+
/**
* This class can be used to incrementally construct a SaProposal. SaProposal instances are
* immutable once built.
@@ -474,7 +533,7 @@ public final class SaProposal {
* @return SaProposal the validated SaProposal.
* @throws IllegalArgumentException if SaProposal is invalid.
*/
- public SaProposal buildOrThrow() {
+ public SaProposal build() {
EncryptionTransform[] encryptionTransforms = buildEncryptAlgosOrThrow();
PrfTransform[] prfTransforms = buildPrfsOrThrow();
IntegrityTransform[] integrityTransforms =
diff --git a/src/java/com/android/ike/ikev2/SaRecord.java b/src/java/com/android/ike/ikev2/SaRecord.java
index 355d41d4..4dcddec4 100644
--- a/src/java/com/android/ike/ikev2/SaRecord.java
+++ b/src/java/com/android/ike/ikev2/SaRecord.java
@@ -17,9 +17,16 @@ package com.android.ike.ikev2;
import com.android.ike.ikev2.message.IkeMessage;
import com.android.ike.ikev2.message.IkePayload;
+import com.android.internal.annotations.VisibleForTesting;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
import java.util.List;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
/**
* SaRecord represents common information of an IKE SA and a Child SA.
*
@@ -199,4 +206,71 @@ public abstract class SaRecord {
ChildSaRecord makeChildSaRecord(
List<IkePayload> reqPayloads, List<IkePayload> respPayloads);
}
+
+ /** Generate SKEYSEED using negotiated PRF. */
+ @VisibleForTesting
+ static byte[] generateSKeySeed(
+ String prfAlgorithm, byte[] nonceInit, byte[] nonceResp, byte[] sharedDhKey) {
+ try {
+ ByteBuffer keyBuffer = ByteBuffer.allocate(nonceInit.length + nonceResp.length);
+ keyBuffer.put(nonceInit).put(nonceResp);
+ SecretKeySpec prfKeySpec = new SecretKeySpec(keyBuffer.array(), prfAlgorithm);
+
+ Mac prfMac = Mac.getInstance(prfAlgorithm, IkeMessage.getSecurityProvider());
+ prfMac.init(prfKeySpec);
+
+ ByteBuffer sharedKeyBuffer = ByteBuffer.wrap(sharedDhKey);
+ prfMac.update(sharedKeyBuffer);
+
+ return prfMac.doFinal();
+ } catch (InvalidKeyException | NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("Failed to generate SKEYSEED", e);
+ }
+ }
+
+ /**
+ * Derives key materials using negotiated PRF.
+ *
+ * <p>prf+(K, S) outputs a pseudorandom stream by using negotiated PRF iteratively. In this way
+ * it can generate long enough keying material containing all the keys for this IKE/Child SA.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.13">RFC 7296 nternet Key Exchange
+ * Protocol Version 2 (IKEv2) 2.13. Generating Keying Material </a>
+ */
+ @VisibleForTesting
+ static byte[] generateKeyMat(
+ String prfAlgorithm, byte[] prfKey, byte[] dataToSign, int keyMaterialLen)
+ throws InvalidKeyException {
+ try {
+ SecretKeySpec prfKeySpec = new SecretKeySpec(prfKey, prfAlgorithm);
+ Mac prfMac = Mac.getInstance(prfAlgorithm, IkeMessage.getSecurityProvider());
+
+ ByteBuffer keyMatBuffer = ByteBuffer.allocate(keyMaterialLen);
+
+ byte[] previousMac = new byte[0];
+ final int padLen = 1;
+ byte padValue = 1;
+
+ while (keyMatBuffer.remaining() > 0) {
+ prfMac.init(prfKeySpec);
+
+ ByteBuffer dataToSignBuffer =
+ ByteBuffer.allocate(previousMac.length + dataToSign.length + padLen);
+ dataToSignBuffer.put(previousMac).put(dataToSign).put(padValue);
+ dataToSignBuffer.rewind();
+
+ prfMac.update(dataToSignBuffer);
+
+ previousMac = prfMac.doFinal();
+ keyMatBuffer.put(
+ previousMac, 0, Math.min(previousMac.length, keyMatBuffer.remaining()));
+
+ padValue++;
+ }
+
+ return keyMatBuffer.array();
+ } catch (InvalidKeyException | NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("Failed to generate keying material", e);
+ }
+ }
}
diff --git a/src/java/com/android/ike/ikev2/message/IkeDeletePayload.java b/src/java/com/android/ike/ikev2/message/IkeDeletePayload.java
new file mode 100644
index 00000000..fd4f3644
--- /dev/null
+++ b/src/java/com/android/ike/ikev2/message/IkeDeletePayload.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ike.ikev2.message;
+
+import com.android.ike.ikev2.exceptions.IkeException;
+import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
+
+import java.nio.ByteBuffer;
+
+/**
+ * IkeDeletePayload represents a Delete Payload.
+ *
+ * <p>As instructed in RFC 7296, deletion of the IKE SA is indicated by a protocol ID of 1 (IKE) but
+ * no SPIs. Deletion of a Child SA will contain the IPsec protocol ID and SPIs of inbound IPsec
+ * packets. Since IKE library only supports negotiating Child SA using ESP, only the protocol ID of
+ * 3 (ESP) is used for deleting Child SA.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.11">RFC 7296, Internet Key Exchange
+ * Protocol Version 2 (IKEv2)</a>
+ */
+public final class IkeDeletePayload extends IkePayload {
+
+ @ProtocolId public final int protocolId;
+ public final byte spiSize;
+ public final int numSpi;
+ public final int[] spisToDelete;
+
+ /**
+ * Construct an instance of IkeDeletePayload from decoding inbound IKE packet.
+ *
+ * <p>NegativeArraySizeException and BufferUnderflowException will be caught in {@link
+ * IkeMessage}
+ *
+ * @param critical indicates if this payload is critical. Ignored in supported payload as
+ * instructed by the RFC 7296.
+ * @param payloadBody payload body in byte array
+ * @throws IkeException if there is any error
+ */
+ IkeDeletePayload(boolean critical, byte[] payloadBody) throws IkeException {
+ super(PAYLOAD_TYPE_DELETE, critical);
+
+ ByteBuffer inputBuffer = ByteBuffer.wrap(payloadBody);
+
+ protocolId = Byte.toUnsignedInt(inputBuffer.get());
+ spiSize = inputBuffer.get();
+ numSpi = Short.toUnsignedInt(inputBuffer.getShort());
+ spisToDelete = new int[numSpi];
+
+ switch (protocolId) {
+ case PROTOCOL_ID_IKE:
+ // Delete payload for IKE SA must not include SPI.
+ if (spiSize != SPI_LEN_NOT_INCLUDED
+ || numSpi != 0
+ || inputBuffer.remaining() != 0) {
+ throw new InvalidSyntaxException("Invalid Delete IKE Payload.");
+ }
+ break;
+ case PROTOCOL_ID_ESP:
+ // Delete payload for Child SA must include SPI
+ if (spiSize != SPI_LEN_IPSEC
+ || numSpi == 0
+ || inputBuffer.remaining() != SPI_LEN_IPSEC * numSpi) {
+ throw new InvalidSyntaxException("Invalid Delete Child Payload.");
+ }
+
+ for (int i = 0; i < numSpi; i++) {
+ spisToDelete[i] = inputBuffer.getInt();
+ }
+ break;
+ default:
+ throw new InvalidSyntaxException("Unrecognized protocol in Delete Payload.");
+ }
+ }
+
+ // TODO: Add a constructor for building outbound IKE message.
+
+ /**
+ * Encode Delete Payload to 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) {
+ throw new UnsupportedOperationException("Operation not supported.");
+ // TODO: Implement it.
+ }
+
+ /**
+ * Get entire payload length.
+ *
+ * @return entire payload length.
+ */
+ @Override
+ protected int getPayloadLength() {
+ throw new UnsupportedOperationException("Operation not supported.");
+ // TODO: Implement it.
+ }
+
+ /**
+ * Return the payload type as a String.
+ *
+ * @return the payload type as a String.
+ */
+ @Override
+ public String getTypeString() {
+ return "Delete Payload";
+ }
+}
diff --git a/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java b/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java
index 52ca2a76..a41d8555 100644
--- a/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java
+++ b/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBody.java
@@ -56,11 +56,7 @@ final class IkeEncryptedPayloadBody {
* decrypting an incoming packet.
*/
IkeEncryptedPayloadBody(
- byte[] message,
- Mac integrityMac,
- int expectedChecksumLen,
- Cipher decryptCipher,
- SecretKey dKey)
+ byte[] message, Mac integrityMac, int checksumLen, Cipher decryptCipher, SecretKey dKey)
throws IkeException, GeneralSecurityException {
ByteBuffer inputBuffer = ByteBuffer.wrap(message);
@@ -77,16 +73,15 @@ final class IkeEncryptedPayloadBody {
- (IkeHeader.IKE_HEADER_LENGTH
+ IkePayload.GENERIC_HEADER_LENGTH
+ expectedIvLen
- + expectedChecksumLen);
+ + checksumLen);
// IkeMessage will catch exception if encryptedDataLen is negative.
mEncryptedAndPaddedData = new byte[encryptedDataLen];
- mIntegrityChecksum = new byte[expectedChecksumLen];
+ mIntegrityChecksum = new byte[checksumLen];
inputBuffer.get(mIv).get(mEncryptedAndPaddedData).get(mIntegrityChecksum);
// Authenticate and decrypt.
- byte[] dataToAuthenticate =
- Arrays.copyOfRange(message, 0, message.length - expectedChecksumLen);
+ byte[] dataToAuthenticate = Arrays.copyOfRange(message, 0, message.length - checksumLen);
validateChecksumOrThrow(dataToAuthenticate, integrityMac, mIntegrityChecksum);
mUnencryptedData = decrypt(mEncryptedAndPaddedData, decryptCipher, dKey, mIv);
}
@@ -96,17 +91,19 @@ final class IkeEncryptedPayloadBody {
* building an outbound packet.
*/
IkeEncryptedPayloadBody(
- byte[] ikeAndPayloadHeader,
+ IkeHeader ikeHeader,
+ @IkePayload.PayloadType int firstPayloadType,
byte[] unencryptedPayloads,
Mac integrityMac,
- int expectedChecksumLen,
+ int checksumLen,
Cipher encryptCipher,
SecretKey eKey) {
this(
- ikeAndPayloadHeader,
+ ikeHeader,
+ firstPayloadType,
unencryptedPayloads,
integrityMac,
- expectedChecksumLen,
+ checksumLen,
encryptCipher,
eKey,
encryptCipher.getIV(),
@@ -116,10 +113,11 @@ final class IkeEncryptedPayloadBody {
/** Package private constructor only for testing. */
@VisibleForTesting
IkeEncryptedPayloadBody(
- byte[] ikeAndPayloadHeader,
+ IkeHeader ikeHeader,
+ @IkePayload.PayloadType int firstPayloadType,
byte[] unencryptedPayloads,
Mac integrityMac,
- int expectedChecksumLen,
+ int checksumLen,
Cipher encryptCipher,
SecretKey eKey,
byte[] iv,
@@ -130,13 +128,40 @@ final class IkeEncryptedPayloadBody {
mIv = iv;
mEncryptedAndPaddedData = encrypt(unencryptedPayloads, encryptCipher, eKey, 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
+ // payload header, iv and encrypted and padded data.
+ int dataToAuthenticateLength =
+ IkeHeader.IKE_HEADER_LENGTH
+ + IkePayload.GENERIC_HEADER_LENGTH
+ + iv.length
+ + mEncryptedAndPaddedData.length;
+ ByteBuffer authenticatedSectionBuffer = ByteBuffer.allocate(dataToAuthenticateLength);
+
+ // Encode IKE header
+ int encryptedPayloadLength =
+ IkePayload.GENERIC_HEADER_LENGTH
+ + iv.length
+ + mEncryptedAndPaddedData.length
+ + checksumLen;
+ ikeHeader.encodeToByteBuffer(authenticatedSectionBuffer, encryptedPayloadLength);
+
+ // Encode payload header. The next payload type field indicates the first payload nested in
+ // this SkPayload/SkfPayload.
+ int payloadLength =
+ IkePayload.GENERIC_HEADER_LENGTH
+ + iv.length
+ + mEncryptedAndPaddedData.length
+ + checksumLen;
+ IkePayload.encodePayloadHeaderToByteBuffer(
+ firstPayloadType, payloadLength, authenticatedSectionBuffer);
+
+ // Encode iv and padded encrypted data.
+ authenticatedSectionBuffer.put(iv).put(mEncryptedAndPaddedData);
+
// Calculate checksum
- ByteBuffer inputBuffer =
- ByteBuffer.allocate(
- ikeAndPayloadHeader.length + iv.length + mEncryptedAndPaddedData.length);
- inputBuffer.put(ikeAndPayloadHeader).put(iv).put(mEncryptedAndPaddedData);
mIntegrityChecksum =
- calculateChecksum(inputBuffer.array(), integrityMac, expectedChecksumLen);
+ calculateChecksum(authenticatedSectionBuffer.array(), integrityMac, checksumLen);
}
// TODO: Add another constructor for AEAD protected payload.
@@ -145,16 +170,16 @@ final class IkeEncryptedPayloadBody {
/** Package private for testing */
@VisibleForTesting
- static byte[] calculateChecksum(
- byte[] dataToAuthenticate, Mac integrityMac, int expectedChecksumLen) {
+ static byte[] calculateChecksum(byte[] dataToAuthenticate, Mac integrityMac, int checksumLen) {
ByteBuffer inputBuffer = ByteBuffer.wrap(dataToAuthenticate);
integrityMac.update(inputBuffer);
- byte[] calculatedChecksum =
- Arrays.copyOfRange(integrityMac.doFinal(), 0, expectedChecksumLen);
+ byte[] calculatedChecksum = Arrays.copyOfRange(integrityMac.doFinal(), 0, checksumLen);
return calculatedChecksum;
}
- private static void validateChecksumOrThrow(
+ /** Package private for testing */
+ @VisibleForTesting
+ static void validateChecksumOrThrow(
byte[] dataToAuthenticate, Mac integrityMac, byte[] integrityChecksum)
throws GeneralSecurityException {
// TODO: Make it package private and add test.
@@ -188,8 +213,9 @@ final class IkeEncryptedPayloadBody {
}
}
- private static byte[] decrypt(
- byte[] encryptedData, Cipher decryptCipher, SecretKey dKey, byte[] iv)
+ /** Package private for testing */
+ @VisibleForTesting
+ static byte[] decrypt(byte[] encryptedData, Cipher decryptCipher, SecretKey dKey, byte[] iv)
throws GeneralSecurityException {
// TODO: Make it package private and add test.
decryptCipher.init(Cipher.DECRYPT_MODE, dKey, new IvParameterSpec(iv));
diff --git a/src/java/com/android/ike/ikev2/message/IkeHeader.java b/src/java/com/android/ike/ikev2/message/IkeHeader.java
index ebd3553f..c4f215ca 100644
--- a/src/java/com/android/ike/ikev2/message/IkeHeader.java
+++ b/src/java/com/android/ike/ikev2/message/IkeHeader.java
@@ -23,6 +23,7 @@ import android.annotation.IntDef;
import com.android.ike.ikev2.exceptions.IkeException;
import com.android.ike.ikev2.exceptions.InvalidMajorVersionException;
import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
+import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -36,7 +37,7 @@ import java.nio.ByteBuffer;
* Protocol Version 2 (IKEv2)</a>
*/
public final class IkeHeader {
- //TODO: b/122838549 Change IkeHeader to static inner class of IkeMessage.
+ // TODO: b/122838549 Change IkeHeader to static inner class of IkeMessage.
private static final byte IKE_HEADER_VERSION_INFO = (byte) 0x20;
// Indicate whether this message is a response message
@@ -69,7 +70,14 @@ public final class IkeHeader {
public final boolean isResponseMsg;
public final boolean fromIkeInitiator;
public final int messageId;
- public final int messageLength;
+
+ // Cannot assign encoded message length value for an outbound IKE message before it's encoded.
+ private static final int ENCODED_MESSAGE_LEN_UNAVAILABLE = -1;
+
+ // mEncodedMessageLength is only set for an inbound IkeMessage. When building an outbound
+ // IkeMessage, message length is not set because message body length is unknown until it gets
+ // encrypted and encoded.
+ private final int mEncodedMessageLength;
/**
* Construct an instance of IkeHeader. It is only called in the process of building outbound
@@ -82,7 +90,6 @@ public final class IkeHeader {
* @param isResp indicates if this message is a response or a request
* @param fromInit indictaes if this message is sent from the IKE initiator or the IKE responder
* @param msgId the message identifier
- * @param length the length of the total message in octets
*/
public IkeHeader(
long iSpi,
@@ -91,8 +98,7 @@ public final class IkeHeader {
@ExchangeType int eType,
boolean isResp,
boolean fromInit,
- int msgId,
- int length) {
+ int msgId) {
ikeInitiatorSpi = iSpi;
ikeResponderSpi = rSpi;
nextPayloadType = nextPType;
@@ -100,7 +106,8 @@ public final class IkeHeader {
isResponseMsg = isResp;
fromIkeInitiator = fromInit;
messageId = msgId;
- messageLength = length;
+
+ mEncodedMessageLength = ENCODED_MESSAGE_LEN_UNAVAILABLE;
// Major version of IKE protocol in use; it must be set to 2 when building an IKEv2 message.
majorVersion = 2;
@@ -135,11 +142,21 @@ public final class IkeHeader {
fromIkeInitiator = ((flagsByte & 0x08) != 0);
messageId = buffer.getInt();
- messageLength = buffer.getInt();
+ mEncodedMessageLength = buffer.getInt();
+ }
+
+ /*Package private*/
+ @VisibleForTesting
+ int getInboundMessageLength() {
+ if (mEncodedMessageLength == ENCODED_MESSAGE_LEN_UNAVAILABLE) {
+ throw new UnsupportedOperationException(
+ "It is not supported to get encoded message length from an outbound message.");
+ }
+ return mEncodedMessageLength;
}
- /** Validate syntax and major version. */
- public void checkValidOrThrow(int packetLength) throws IkeException {
+ /** Validate syntax and major version of inbound IKE header. */
+ public void checkInboundValidOrThrow(int packetLength) throws IkeException {
if (majorVersion > 2) {
// Receive higher version of protocol. Stop parsing.
throw new InvalidMajorVersionException(majorVersion);
@@ -155,13 +172,13 @@ public final class IkeHeader {
|| exchangeType > EXCHANGE_TYPE_INFORMATIONAL) {
throw new InvalidSyntaxException("Invalid IKE Exchange Type.");
}
- if (messageLength != packetLength) {
+ if (mEncodedMessageLength != packetLength) {
throw new InvalidSyntaxException("Invalid IKE Message Length.");
}
}
/** Encode IKE header to ByteBuffer */
- public void encodeToByteBuffer(ByteBuffer byteBuffer) {
+ public void encodeToByteBuffer(ByteBuffer byteBuffer, int encodedMessageBodyLen) {
byteBuffer
.putLong(ikeInitiatorSpi)
.putLong(ikeResponderSpi)
@@ -177,6 +194,6 @@ public final class IkeHeader {
flag |= IKE_HEADER_FLAG_FROM_IKE_INITIATOR;
}
- byteBuffer.put(flag).putInt(messageId).putInt(messageLength);
+ byteBuffer.put(flag).putInt(messageId).putInt(IKE_HEADER_LENGTH + encodedMessageBodyLen);
}
}
diff --git a/src/java/com/android/ike/ikev2/message/IkeKePayload.java b/src/java/com/android/ike/ikev2/message/IkeKePayload.java
index a742d942..92e22381 100644
--- a/src/java/com/android/ike/ikev2/message/IkeKePayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkeKePayload.java
@@ -16,7 +16,7 @@
package com.android.ike.ikev2.message;
-import android.util.Pair;
+import android.annotation.Nullable;
import com.android.ike.ikev2.IkeDhParams;
import com.android.ike.ikev2.SaProposal;
@@ -27,9 +27,12 @@ import com.android.ike.ikev2.utils.BigIntegerUtils;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.ProviderException;
import java.security.SecureRandom;
import javax.crypto.KeyAgreement;
@@ -64,8 +67,22 @@ public final class IkeKePayload extends IkePayload {
/** Supported dhGroup falls into {@link DhGroup} */
public final int dhGroup;
+ /** Public DH key for the recipient to calculate shared key. */
public final byte[] keyExchangeData;
+ /** Flag indicates if this is an outbound payload. */
+ public final boolean isOutbound;
+
+ /**
+ * localPrivateKey caches the locally generated private key when building an outbound KE
+ * payload. It will not be sent out. It is only used to calculate DH shared
+ * key when IKE library receives a public key from the remote server.
+ *
+ * <p>localPrivateKey of a inbound payload will be set to null. Caller MUST ensure its an
+ * outbound payload before using localPrivateKey.
+ */
+ @Nullable public final DHPrivateKeySpec localPrivateKey;
+
/**
* Construct an instance of IkeKePayload in the context of IkePayloadFactory
*
@@ -79,6 +96,9 @@ public final class IkeKePayload extends IkePayload {
IkeKePayload(boolean critical, byte[] payloadBody) throws IkeException {
super(PAYLOAD_TYPE_KE, critical);
+ isOutbound = false;
+ localPrivateKey = null;
+
ByteBuffer inputBuffer = ByteBuffer.wrap(payloadBody);
dhGroup = Short.toUnsignedInt(inputBuffer.getShort());
@@ -110,17 +130,67 @@ public final class IkeKePayload extends IkePayload {
/**
* Construct an instance of IkeKePayload for building an outbound packet.
*
+ * <p>Generate a DH key pair. Cache the private key and and send out the public key as
+ * keyExchangeData.
+ *
* <p>Critical bit in this payload must not be set as instructed in RFC 7296.
*
* @param dh DH group for this KE payload
- * @param keData the Key Exchange data
* @see <a href="https://tools.ietf.org/html/rfc7296#page-76">RFC 7296, Internet Key Exchange
* Protocol Version 2 (IKEv2), Critical.
*/
- private IkeKePayload(@SaProposal.DhGroup int dh, byte[] keData) {
+ public IkeKePayload(@SaProposal.DhGroup int dh) {
super(PAYLOAD_TYPE_KE, false);
+
dhGroup = dh;
- keyExchangeData = keData;
+ isOutbound = true;
+
+ BigInteger prime = BigInteger.ZERO;
+ int keySize = 0;
+ switch (dhGroup) {
+ case SaProposal.DH_GROUP_1024_BIT_MODP:
+ prime =
+ BigIntegerUtils.unsignedHexStringToBigInteger(
+ IkeDhParams.PRIME_1024_BIT_MODP);
+ keySize = DH_GROUP_1024_BIT_MODP_DATA_LEN;
+ break;
+ case SaProposal.DH_GROUP_2048_BIT_MODP:
+ prime =
+ BigIntegerUtils.unsignedHexStringToBigInteger(
+ IkeDhParams.PRIME_2048_BIT_MODP);
+ keySize = DH_GROUP_2048_BIT_MODP_DATA_LEN;
+ break;
+ default:
+ throw new IllegalArgumentException("DH group not supported: " + dh);
+ }
+
+ try {
+ BigInteger baseGen = BigInteger.valueOf(IkeDhParams.BASE_GENERATOR_MODP);
+ DHParameterSpec dhParams = new DHParameterSpec(prime, baseGen);
+
+ KeyPairGenerator dhKeyPairGen =
+ KeyPairGenerator.getInstance(
+ KEY_EXCHANGE_ALGORITHM, IkeMessage.getSecurityProvider());
+ // By default SecureRandom uses AndroidOpenSSL provided SHA1PRNG Algorithm, which takes
+ // /dev/urandom as seed source.
+ dhKeyPairGen.initialize(dhParams, new SecureRandom());
+
+ KeyPair keyPair = dhKeyPairGen.generateKeyPair();
+
+ DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
+ DHPrivateKeySpec dhPrivateKeyspec =
+ new DHPrivateKeySpec(privateKey.getX(), prime, baseGen);
+ DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
+
+ // Zero-pad the public key without the sign bit
+ keyExchangeData =
+ BigIntegerUtils.bigIntegerToUnsignedByteArray(publicKey.getY(), keySize);
+ localPrivateKey = dhPrivateKeyspec;
+ } catch (NoSuchAlgorithmException e) {
+ throw new ProviderException("Failed to obtain " + KEY_EXCHANGE_ALGORITHM, e);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new IllegalArgumentException("Failed to initialize key generator", e);
+ }
}
/**
@@ -149,56 +219,6 @@ public final class IkeKePayload extends IkePayload {
}
/**
- * Construct an instance of IkeKePayload according to its {@link DhGroup}.
- *
- * @param dh the Dh-Group. It should be in {@link DhGroup}
- * @return Pair of generated private key and an instance of IkeKePayload with key exchange data.
- * @throws GeneralSecurityException for security-related exception.
- */
- public static Pair<DHPrivateKeySpec, IkeKePayload> getKePayload(@SaProposal.DhGroup int dh)
- throws GeneralSecurityException {
- BigInteger baseGen = BigInteger.valueOf(IkeDhParams.BASE_GENERATOR_MODP);
- BigInteger prime = BigInteger.ZERO;
- int keySize = 0;
- switch (dh) {
- case SaProposal.DH_GROUP_1024_BIT_MODP:
- prime =
- BigIntegerUtils.unsignedHexStringToBigInteger(
- IkeDhParams.PRIME_1024_BIT_MODP);
- keySize = DH_GROUP_1024_BIT_MODP_DATA_LEN;
- break;
- case SaProposal.DH_GROUP_2048_BIT_MODP:
- prime =
- BigIntegerUtils.unsignedHexStringToBigInteger(
- IkeDhParams.PRIME_2048_BIT_MODP);
- keySize = DH_GROUP_2048_BIT_MODP_DATA_LEN;
- break;
- default:
- throw new IllegalArgumentException("DH group not supported: " + dh);
- }
-
- DHParameterSpec dhParams = new DHParameterSpec(prime, baseGen);
-
- KeyPairGenerator dhKeyPairGen =
- KeyPairGenerator.getInstance(
- KEY_EXCHANGE_ALGORITHM, IkeMessage.getSecurityProvider());
- // By default SecureRandom uses AndroidOpenSSL provided SHA1PRNG Algorithm, which takes
- // /dev/urandom as seed source.
- dhKeyPairGen.initialize(dhParams, new SecureRandom());
-
- KeyPair keyPair = dhKeyPairGen.generateKeyPair();
-
- DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
- DHPrivateKeySpec dhPrivateKeyspec = new DHPrivateKeySpec(privateKey.getX(), prime, baseGen);
- DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
-
- // Zero-pad the public key without the sign bit
- byte[] keData = BigIntegerUtils.bigIntegerToUnsignedByteArray(publicKey.getY(), keySize);
-
- return new Pair(dhPrivateKeyspec, new IkeKePayload(dh, keData));
- }
-
- /**
* Calculate the shared secret.
*
* @param privateKeySpec contains the local private key, DH prime and DH base generator.
diff --git a/src/java/com/android/ike/ikev2/message/IkeMessage.java b/src/java/com/android/ike/ikev2/message/IkeMessage.java
index 15949674..5b63ee7f 100644
--- a/src/java/com/android/ike/ikev2/message/IkeMessage.java
+++ b/src/java/com/android/ike/ikev2/message/IkeMessage.java
@@ -103,7 +103,15 @@ public final class IkeMessage {
ikePayloadList = payloadList;
}
- static Provider getSecurityProvider() {
+ /**
+ * Get security provider for IKE library
+ *
+ * <p>Use BouncyCastleProvider as the default security provider.
+ *
+ * @return the security provider of IKE library.
+ */
+ public static Provider getSecurityProvider() {
+ // TODO: Move this getter out of IKE message package since not only this package uses it.
return SECURITY_PROVIDER;
}
@@ -231,7 +239,7 @@ public final class IkeMessage {
byte[] attachEncodedHeader(byte[] encodedIkeBody) {
ByteBuffer outputBuffer =
ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH + encodedIkeBody.length);
- ikeHeader.encodeToByteBuffer(outputBuffer);
+ ikeHeader.encodeToByteBuffer(outputBuffer, encodedIkeBody.length);
outputBuffer.put(encodedIkeBody);
return outputBuffer.array();
}
@@ -319,13 +327,40 @@ public final class IkeMessage {
IkeSessionOptions ikeSessionOptions,
IkeSaRecord ikeSaRecord,
IkeMessage ikeMessage) {
- // TODO: Implement it.
+ // TODO: Extract crypto attributes and call encrypt()
return null;
}
+ //TODO: Create and use a container class for crypto algorithms and keys.
+ private byte[] encryptAndEncode(
+ IkeHeader ikeHeader,
+ @PayloadType int firstPayload,
+ byte[] unencryptedPayloads,
+ Mac integrityMac,
+ int checksumLen,
+ Cipher encryptCipher,
+ SecretKey eKey) {
+ IkeSkPayload skPayload =
+ new IkeSkPayload(
+ ikeHeader,
+ firstPayload,
+ unencryptedPayloads,
+ integrityMac,
+ checksumLen,
+ encryptCipher,
+ eKey);
+
+ ByteBuffer outputBuffer =
+ ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH + skPayload.getPayloadLength());
+ ikeHeader.encodeToByteBuffer(outputBuffer, skPayload.getPayloadLength());
+ skPayload.encodeToByteBuffer(firstPayload, outputBuffer);
+
+ return outputBuffer.array();
+ }
+
@Override
public IkeMessage decode(IkeHeader header, byte[] inputPacket) throws IkeException {
- header.checkValidOrThrow(inputPacket.length);
+ header.checkInboundValidOrThrow(inputPacket.length);
byte[] unencryptedPayloads =
Arrays.copyOfRange(
@@ -362,7 +397,7 @@ public final class IkeMessage {
SecretKey dKey)
throws IkeException, GeneralSecurityException {
- header.checkValidOrThrow(inputPacket.length);
+ header.checkInboundValidOrThrow(inputPacket.length);
if (header.nextPayloadType != IkePayload.PAYLOAD_TYPE_SK) {
// TODO: b/123372339 Handle message containing unprotected payloads.
diff --git a/src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java b/src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java
index 4b581e05..ee6fe47a 100644
--- a/src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkeNotifyPayload.java
@@ -40,7 +40,7 @@ import java.util.Set;
* outbound packet.
*
* @see <a href="https://tools.ietf.org/html/rfc7296">RFC 7296, Internet Key Exchange Protocol
- * Version 2 (IKEv2).
+ * Version 2 (IKEv2)</a>
*/
public final class IkeNotifyPayload extends IkePayload {
diff --git a/src/java/com/android/ike/ikev2/message/IkePayload.java b/src/java/com/android/ike/ikev2/message/IkePayload.java
index a9d9f06a..2474bb64 100644
--- a/src/java/com/android/ike/ikev2/message/IkePayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkePayload.java
@@ -53,6 +53,7 @@ public abstract class IkePayload {
PAYLOAD_TYPE_ID_RESPONDER,
PAYLOAD_TYPE_NONCE,
PAYLOAD_TYPE_NOTIFY,
+ PAYLOAD_TYPE_DELETE,
PAYLOAD_TYPE_VENDOR,
PAYLOAD_TYPE_TS_INITIATOR,
PAYLOAD_TYPE_TS_RESPONDER,
@@ -78,6 +79,8 @@ public abstract class IkePayload {
public static final int PAYLOAD_TYPE_NONCE = 40;
/** Notify Payload */
public static final int PAYLOAD_TYPE_NOTIFY = 41;
+ /** Delete Payload */
+ public static final int PAYLOAD_TYPE_DELETE = 42;
/** Vendor Payload */
public static final int PAYLOAD_TYPE_VENDOR = 43;
/** Traffic Selector Payload of Child SA Initiator */
@@ -137,7 +140,7 @@ public abstract class IkePayload {
* @param payloadLength length of the entire payload
* @param byteBuffer destination ByteBuffer that stores encoded payload header
*/
- protected void encodePayloadHeaderToByteBuffer(
+ protected static void encodePayloadHeaderToByteBuffer(
@PayloadType int nextPayload, int payloadLength, ByteBuffer byteBuffer) {
byteBuffer
.put((byte) nextPayload)
diff --git a/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java b/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java
index 8f530187..bea87cad 100644
--- a/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java
+++ b/src/java/com/android/ike/ikev2/message/IkePayloadFactory.java
@@ -78,6 +78,8 @@ final class IkePayloadFactory {
return new IkeNoncePayload(isCritical, payloadBody);
case IkePayload.PAYLOAD_TYPE_NOTIFY:
return new IkeNotifyPayload(isCritical, payloadBody);
+ case IkePayload.PAYLOAD_TYPE_DELETE:
+ return new IkeDeletePayload(isCritical, payloadBody);
case IkePayload.PAYLOAD_TYPE_VENDOR:
return new IkeVendorPayload(isCritical, payloadBody);
case IkePayload.PAYLOAD_TYPE_TS_INITIATOR:
@@ -129,7 +131,7 @@ final class IkePayloadFactory {
*
* @param message the byte array contains the whole IKE message.
* @param integrityMac the initialized Mac for integrity check.
- * @param expectedChecksumLen the expected length of integrity checksum.
+ * @param checksumLen the checksum length of negotiated integrity algorithm.
* @param decryptCipher the uninitialized Cipher for doing decryption.
* @param dKey the decryption key.
* @return a pair including IkePayload and next payload type.
@@ -139,7 +141,7 @@ final class IkePayloadFactory {
protected static Pair<IkeSkPayload, Integer> getIkeSkPayload(
byte[] message,
Mac integrityMac,
- int expectedChecksumLen,
+ int checksumLen,
Cipher decryptCipher,
SecretKey dKey)
throws IkeException, GeneralSecurityException {
@@ -175,7 +177,7 @@ final class IkePayloadFactory {
isCritical,
message,
integrityMac,
- expectedChecksumLen,
+ checksumLen,
decryptCipher,
dKey);
return new Pair(payload, nextPayloadType);
diff --git a/src/java/com/android/ike/ikev2/message/IkeSaPayload.java b/src/java/com/android/ike/ikev2/message/IkeSaPayload.java
index 70280843..c0651ae0 100644
--- a/src/java/com/android/ike/ikev2/message/IkeSaPayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkeSaPayload.java
@@ -192,6 +192,9 @@ public final class IkeSaPayload extends IkePayload {
private static final byte LAST_PROPOSAL = 0;
private static final byte NOT_LAST_PROPOSAL = 2;
+ private static final int PROPOSAL_RESERVED_FIELD_LEN = 1;
+ private static final int PROPOSAL_HEADER_LEN = 8;
+
@VisibleForTesting
static TransformDecoder sTransformDecoder =
new TransformDecoder() {
@@ -246,7 +249,7 @@ public final class IkeSaPayload extends IkePayload {
"Invalid value of Last Proposal Substructure: " + isLast);
}
// Skip RESERVED byte
- inputBuffer.get();
+ inputBuffer.get(new byte[PROPOSAL_RESERVED_FIELD_LEN]);
int length = Short.toUnsignedInt(inputBuffer.getShort());
byte number = inputBuffer.get();
@@ -259,8 +262,8 @@ public final class IkeSaPayload extends IkePayload {
// spiSize should be either 8 for IKE or 4 for IPsec.
long spi = SPI_NOT_INCLUDED;
switch (spiSize) {
- case 0:
- // No SPI field here.
+ case SPI_LEN_NOT_INCLUDED:
+ // No SPI attached for IKE initial exchange.
break;
case SPI_LEN_IPSEC:
spi = Integer.toUnsignedLong(inputBuffer.getInt());
@@ -330,6 +333,49 @@ public final class IkeSaPayload extends IkePayload {
}
return saProposal.isNegotiatedFrom(reqProposal.saProposal);
}
+
+ protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
+ Transform[] allTransforms = saProposal.getAllTransforms();
+ byte isLastIndicator = isLast ? LAST_PROPOSAL : NOT_LAST_PROPOSAL;
+
+ byteBuffer
+ .put(isLastIndicator)
+ .put(new byte[PROPOSAL_RESERVED_FIELD_LEN])
+ .putShort((short) getProposalLength())
+ .put(number)
+ .put((byte) protocolId)
+ .put(spiSize)
+ .put((byte) allTransforms.length);
+
+ switch (spiSize) {
+ case SPI_LEN_NOT_INCLUDED:
+ // No SPI attached for IKE initial exchange.
+ break;
+ case SPI_LEN_IPSEC:
+ byteBuffer.putInt((int) spi);
+ break;
+ case SPI_LEN_IKE:
+ byteBuffer.putLong((long) spi);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid value of spiSize in Proposal Substructure: " + spiSize);
+ }
+
+ // Encode all Transform.
+ for (int i = 0; i < allTransforms.length; i++) {
+ // The last transform has the isLast flag set to true.
+ allTransforms[i].encodeToByteBuffer(i == allTransforms.length - 1, byteBuffer);
+ }
+ }
+
+ protected int getProposalLength() {
+ int len = PROPOSAL_HEADER_LEN + spiSize;
+
+ Transform[] allTransforms = saProposal.getAllTransforms();
+ for (Transform t : allTransforms) len += t.getTransformLength();
+ return len;
+ }
}
@VisibleForTesting
@@ -366,7 +412,12 @@ public final class IkeSaPayload extends IkePayload {
private static final byte LAST_TRANSFORM = 0;
private static final byte NOT_LAST_TRANSFORM = 3;
- private static final int TRANSFORM_HEADER_LEN = 8;
+
+ // Length of reserved field of a Transform.
+ private static final int TRANSFORM_RESERVED_FIELD_LEN = 1;
+
+ // Length of the Transform that with no Attribute.
+ protected static final int BASIC_TRANSFORM_LEN = 8;
// TODO: Add constants for supported algorithms
@@ -376,7 +427,7 @@ public final class IkeSaPayload extends IkePayload {
public List<Attribute> decodeAttributes(int length, ByteBuffer inputBuffer)
throws IkeException {
List<Attribute> list = new LinkedList<>();
- int parsedLength = TRANSFORM_HEADER_LEN;
+ int parsedLength = BASIC_TRANSFORM_LEN;
while (parsedLength < length) {
Pair<Attribute, Integer> pair = Attribute.readFrom(inputBuffer);
parsedLength += pair.second;
@@ -420,13 +471,13 @@ public final class IkeSaPayload extends IkePayload {
}
// Skip RESERVED byte
- inputBuffer.get();
+ inputBuffer.get(new byte[TRANSFORM_RESERVED_FIELD_LEN]);
int length = Short.toUnsignedInt(inputBuffer.getShort());
int type = Byte.toUnsignedInt(inputBuffer.get());
// Skip RESERVED byte
- inputBuffer.get();
+ inputBuffer.get(new byte[TRANSFORM_RESERVED_FIELD_LEN]);
int id = Short.toUnsignedInt(inputBuffer.getShort());
@@ -469,6 +520,23 @@ public final class IkeSaPayload extends IkePayload {
// Check if this Transform ID is supported.
protected abstract boolean isSupportedTransformId(int id);
+ // Encode Transform to a ByteBuffer.
+ protected abstract void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer);
+
+ // Get entire Transform length.
+ protected abstract int getTransformLength();
+
+ protected void encodeBasicTransformToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
+ byte isLastIndicator = isLast ? LAST_TRANSFORM : NOT_LAST_TRANSFORM;
+ byteBuffer
+ .put(isLastIndicator)
+ .put(new byte[TRANSFORM_RESERVED_FIELD_LEN])
+ .putShort((short) getTransformLength())
+ .put((byte) type)
+ .put(new byte[TRANSFORM_RESERVED_FIELD_LEN])
+ .putShort((short) id);
+ }
+
/**
* Get Tranform Type as a String.
*
@@ -487,9 +555,12 @@ public final class IkeSaPayload extends IkePayload {
* Exchange Protocol Version 2 (IKEv2)</a>
*/
public static final class EncryptionTransform extends Transform {
- private static final int KEY_LEN_UNASSIGNED = 0;
+ private static final int KEY_LEN_UNSPECIFIED = 0;
- public final int keyLength;
+ // When using encryption algorithm with variable-length keys, mSpecifiedKeyLength MUST be
+ // set and a KeyLengthAttribute MUST be attached. Otherwise, mSpecifiedKeyLength MUST NOT be
+ // set and KeyLengthAttribute MUST NOT be attached.
+ private final int mSpecifiedKeyLength;
/**
* Contruct an instance of EncryptionTransform with fixed key length for building an
@@ -498,7 +569,7 @@ public final class IkeSaPayload extends IkePayload {
* @param id the IKE standard Transform ID.
*/
public EncryptionTransform(@EncryptionAlgorithm int id) {
- this(id, KEY_LEN_UNASSIGNED);
+ this(id, KEY_LEN_UNSPECIFIED);
}
/**
@@ -506,12 +577,12 @@ public final class IkeSaPayload extends IkePayload {
* outbound packet.
*
* @param id the IKE standard Transform ID.
- * @param keyLength the specified key length of this encryption algorithm.
+ * @param specifiedKeyLength the specified key length of this encryption algorithm.
*/
- public EncryptionTransform(@EncryptionAlgorithm int id, int keyLength) {
+ public EncryptionTransform(@EncryptionAlgorithm int id, int specifiedKeyLength) {
super(Transform.TRANSFORM_TYPE_ENCR, id);
- this.keyLength = keyLength;
+ mSpecifiedKeyLength = specifiedKeyLength;
try {
validateKeyLength();
} catch (InvalidSyntaxException e) {
@@ -530,13 +601,13 @@ public final class IkeSaPayload extends IkePayload {
throws InvalidSyntaxException {
super(Transform.TRANSFORM_TYPE_ENCR, id, attributeList);
if (!isSupported) {
- keyLength = KEY_LEN_UNASSIGNED;
+ mSpecifiedKeyLength = KEY_LEN_UNSPECIFIED;
} else {
if (attributeList.size() == 0) {
- keyLength = KEY_LEN_UNASSIGNED;
+ mSpecifiedKeyLength = KEY_LEN_UNSPECIFIED;
} else {
KeyLengthAttribute attr = getKeyLengthAttribute(attributeList);
- keyLength = attr.keyLength;
+ mSpecifiedKeyLength = attr.keyLength;
}
validateKeyLength();
}
@@ -544,7 +615,7 @@ public final class IkeSaPayload extends IkePayload {
@Override
public int hashCode() {
- return Objects.hash(type, id, keyLength);
+ return Objects.hash(type, id, mSpecifiedKeyLength);
}
@Override
@@ -552,7 +623,9 @@ public final class IkeSaPayload extends IkePayload {
if (!(o instanceof EncryptionTransform)) return false;
EncryptionTransform other = (EncryptionTransform) o;
- return (type == other.type && id == other.id && keyLength == other.keyLength);
+ return (type == other.type
+ && id == other.id
+ && mSpecifiedKeyLength == other.mSpecifiedKeyLength);
}
@Override
@@ -582,7 +655,7 @@ public final class IkeSaPayload extends IkePayload {
private void validateKeyLength() throws InvalidSyntaxException {
switch (id) {
case SaProposal.ENCRYPTION_ALGORITHM_3DES:
- if (keyLength != KEY_LEN_UNASSIGNED) {
+ if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
throw new InvalidSyntaxException(
"Must not set Key Length value for this "
+ getTransformTypeString()
@@ -597,16 +670,16 @@ public final class IkeSaPayload extends IkePayload {
case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12:
/* fall through */
case SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16:
- if (keyLength == KEY_LEN_UNASSIGNED) {
+ if (mSpecifiedKeyLength == KEY_LEN_UNSPECIFIED) {
throw new InvalidSyntaxException(
"Must set Key Length value for this "
+ getTransformTypeString()
+ " Algorithm ID: "
+ id);
}
- if (keyLength != SaProposal.KEY_LEN_AES_128
- && keyLength != SaProposal.KEY_LEN_AES_192
- && keyLength != SaProposal.KEY_LEN_AES_256) {
+ if (mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_128
+ && mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_192
+ && mSpecifiedKeyLength != SaProposal.KEY_LEN_AES_256) {
throw new InvalidSyntaxException(
"Invalid key length for this "
+ getTransformTypeString()
@@ -622,6 +695,26 @@ public final class IkeSaPayload extends IkePayload {
}
@Override
+ protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
+ encodeBasicTransformToByteBuffer(isLast, byteBuffer);
+
+ if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
+ new KeyLengthAttribute(mSpecifiedKeyLength).encodeToByteBuffer(byteBuffer);
+ }
+ }
+
+ @Override
+ protected int getTransformLength() {
+ int len = BASIC_TRANSFORM_LEN;
+
+ if (mSpecifiedKeyLength != KEY_LEN_UNSPECIFIED) {
+ len += new KeyLengthAttribute(mSpecifiedKeyLength).getAttributeLength();
+ }
+
+ return len;
+ }
+
+ @Override
public String getTransformTypeString() {
return "Encryption Algorithm";
}
@@ -679,6 +772,16 @@ public final class IkeSaPayload extends IkePayload {
}
@Override
+ protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
+ encodeBasicTransformToByteBuffer(isLast, byteBuffer);
+ }
+
+ @Override
+ protected int getTransformLength() {
+ return BASIC_TRANSFORM_LEN;
+ }
+
+ @Override
public String getTransformTypeString() {
return "Pseudorandom Function";
}
@@ -740,6 +843,16 @@ public final class IkeSaPayload extends IkePayload {
}
@Override
+ protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
+ encodeBasicTransformToByteBuffer(isLast, byteBuffer);
+ }
+
+ @Override
+ protected int getTransformLength() {
+ return BASIC_TRANSFORM_LEN;
+ }
+
+ @Override
public String getTransformTypeString() {
return "Integrity Algorithm";
}
@@ -801,6 +914,16 @@ public final class IkeSaPayload extends IkePayload {
}
@Override
+ protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
+ encodeBasicTransformToByteBuffer(isLast, byteBuffer);
+ }
+
+ @Override
+ protected int getTransformLength() {
+ return BASIC_TRANSFORM_LEN;
+ }
+
+ @Override
public String getTransformTypeString() {
return "Diffie-Hellman Group";
}
@@ -868,6 +991,16 @@ public final class IkeSaPayload extends IkePayload {
}
@Override
+ protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
+ encodeBasicTransformToByteBuffer(isLast, byteBuffer);
+ }
+
+ @Override
+ protected int getTransformLength() {
+ return BASIC_TRANSFORM_LEN;
+ }
+
+ @Override
public String getTransformTypeString() {
return "Extended Sequence Numbers";
}
@@ -893,6 +1026,19 @@ public final class IkeSaPayload extends IkePayload {
return !attributeList.isEmpty();
}
+ @Override
+ protected void encodeToByteBuffer(boolean isLast, ByteBuffer byteBuffer) {
+ throw new UnsupportedOperationException(
+ "It is not supported to encode a Transform with" + getTransformTypeString());
+ }
+
+ @Override
+ protected int getTransformLength() {
+ throw new UnsupportedOperationException(
+ "It is not supported to get length of a Transform with "
+ + getTransformTypeString());
+ }
+
/**
* Return Tranform Type of Unrecognized Transform as a String.
*
@@ -900,7 +1046,7 @@ public final class IkeSaPayload extends IkePayload {
*/
@Override
public String getTransformTypeString() {
- return "Unrecognized Transform Type";
+ return "Unrecognized Transform Type.";
}
}
@@ -925,9 +1071,18 @@ public final class IkeSaPayload extends IkePayload {
// Support only one Attribute type: Key Length. Should use Type/Value format.
public static final int ATTRIBUTE_TYPE_KEY_LENGTH = 14;
- private static final int TV_ATTRIBUTE_VALUE_LEN = 2;
- private static final int TV_ATTRIBUTE_TOTAL_LEN = 4;
- private static final int TVL_ATTRIBUTE_HEADER_LEN = TV_ATTRIBUTE_TOTAL_LEN;
+ // Mask to extract the left most AF bit to indicate Attribute Format.
+ private static final int ATTRIBUTE_FORMAT_MASK = 0x8000;
+ // Mask to extract 15 bits after the AF bit to indicate Attribute Type.
+ private static final int ATTRIBUTE_TYPE_MASK = 0x7fff;
+
+ // Package private mask to indicate that Type-Value (TV) Attribute Format is used.
+ static final int ATTRIBUTE_FORMAT_TV = ATTRIBUTE_FORMAT_MASK;
+
+ // Package private
+ static final int TV_ATTRIBUTE_VALUE_LEN = 2;
+ static final int TV_ATTRIBUTE_TOTAL_LEN = 4;
+ static final int TVL_ATTRIBUTE_HEADER_LEN = TV_ATTRIBUTE_TOTAL_LEN;
// Only Key Length type belongs to AttributeType
public final int type;
@@ -940,11 +1095,12 @@ public final class IkeSaPayload extends IkePayload {
@VisibleForTesting
static Pair<Attribute, Integer> readFrom(ByteBuffer inputBuffer) throws IkeException {
short formatAndType = inputBuffer.getShort();
- int type = formatAndType & 0x7fff;
+ int format = formatAndType & ATTRIBUTE_FORMAT_MASK;
+ int type = formatAndType & ATTRIBUTE_TYPE_MASK;
int length = 0;
byte[] value = new byte[0];
- if ((formatAndType & 0x8000) == 0x8000) {
+ if (format == ATTRIBUTE_FORMAT_TV) {
// Type/Value format
length = TV_ATTRIBUTE_TOTAL_LEN;
value = new byte[TV_ATTRIBUTE_VALUE_LEN];
@@ -969,6 +1125,12 @@ public final class IkeSaPayload extends IkePayload {
return new Pair(new UnrecognizedAttribute(type, value), length);
}
}
+
+ // Encode Attribute to a ByteBuffer.
+ protected abstract void encodeToByteBuffer(ByteBuffer byteBuffer);
+
+ // Get entire Attribute length.
+ protected abstract int getAttributeLength();
}
/** KeyLengthAttribute represents a Key Length type Attribute */
@@ -983,6 +1145,18 @@ public final class IkeSaPayload extends IkePayload {
super(ATTRIBUTE_TYPE_KEY_LENGTH);
this.keyLength = keyLength;
}
+
+ @Override
+ protected void encodeToByteBuffer(ByteBuffer byteBuffer) {
+ byteBuffer
+ .putShort((short) (ATTRIBUTE_FORMAT_TV | ATTRIBUTE_TYPE_KEY_LENGTH))
+ .putShort((short) keyLength);
+ }
+
+ @Override
+ protected int getAttributeLength() {
+ return TV_ATTRIBUTE_TOTAL_LEN;
+ }
}
/**
@@ -994,6 +1168,18 @@ public final class IkeSaPayload extends IkePayload {
protected UnrecognizedAttribute(int type, byte[] value) {
super(type);
}
+
+ @Override
+ protected void encodeToByteBuffer(ByteBuffer byteBuffer) {
+ throw new UnsupportedOperationException(
+ "It is not supported to encode an unrecognized Attribute.");
+ }
+
+ @Override
+ protected int getAttributeLength() {
+ throw new UnsupportedOperationException(
+ "It is not supported to get length of an unrecognized Attribute.");
+ }
}
/**
@@ -1004,9 +1190,12 @@ public final class IkeSaPayload extends IkePayload {
*/
@Override
protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) {
- throw new UnsupportedOperationException(
- "It is not supported to encode a " + getTypeString());
- // TODO: Implement encoding SA payload.
+ encodePayloadHeaderToByteBuffer(nextPayload, getPayloadLength(), byteBuffer);
+
+ for (int i = 0; i < proposalList.size(); i++) {
+ // The last proposal has the isLast flag set to true.
+ proposalList.get(i).encodeToByteBuffer(i == proposalList.size() - 1, byteBuffer);
+ }
}
/**
@@ -1016,9 +1205,11 @@ public final class IkeSaPayload extends IkePayload {
*/
@Override
protected int getPayloadLength() {
- throw new UnsupportedOperationException(
- "It is not supported to get payload length of " + getTypeString());
- // TODO: Implement this method for SA payload.
+ int len = GENERIC_HEADER_LENGTH;
+
+ for (Proposal p : proposalList) len += p.getProposalLength();
+
+ return len;
}
/**
diff --git a/src/java/com/android/ike/ikev2/message/IkeSkPayload.java b/src/java/com/android/ike/ikev2/message/IkeSkPayload.java
index 6a89a6aa..d753ee83 100644
--- a/src/java/com/android/ike/ikev2/message/IkeSkPayload.java
+++ b/src/java/com/android/ike/ikev2/message/IkeSkPayload.java
@@ -17,7 +17,6 @@
package com.android.ike.ikev2.message;
import com.android.ike.ikev2.exceptions.IkeException;
-import com.android.ike.ikev2.message.IkePayload.PayloadType;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
@@ -47,7 +46,7 @@ public final class IkeSkPayload extends IkePayload {
* @param critical indicates if it is a critical payload.
* @param message the byte array contains the whole IKE message.
* @param integrityMac the initialized Mac for integrity check.
- * @param expectedChecksumLen the expected length of integrity checksum.
+ * @param checksumLen the checksum length of negotiated integrity algorithm.
* @param decryptCipher the uninitialized Cipher for doing decryption.
* @param dKey the decryption key.
*/
@@ -55,7 +54,7 @@ public final class IkeSkPayload extends IkePayload {
boolean critical,
byte[] message,
Mac integrityMac,
- int expectedChecksumLen,
+ int checksumLen,
Cipher decryptCipher,
SecretKey dKey)
throws IkeException, GeneralSecurityException {
@@ -63,7 +62,39 @@ public final class IkeSkPayload extends IkePayload {
mIkeEncryptedPayloadBody =
new IkeEncryptedPayloadBody(
- message, integrityMac, expectedChecksumLen, decryptCipher, dKey);
+ message, integrityMac, checksumLen, decryptCipher, dKey);
+ }
+
+ /**
+ * Construct an instance of IkeSkPayload 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 initialized Mac for calculating integrity checksum
+ * @param checksumLen the checksum length of negotiated integrity algorithm.
+ * @param encryptCipher the uninitialized Cipher for doing encryption.
+ * @param eKey the encryption key.
+ */
+ IkeSkPayload(
+ IkeHeader ikeHeader,
+ @PayloadType int firstPayloadType,
+ byte[] unencryptedPayloads,
+ Mac integrityMac,
+ int checksumLen,
+ Cipher encryptCipher,
+ SecretKey eKey) {
+ super(PAYLOAD_TYPE_SK, false);
+
+ mIkeEncryptedPayloadBody =
+ new IkeEncryptedPayloadBody(
+ ikeHeader,
+ firstPayloadType,
+ unencryptedPayloads,
+ integrityMac,
+ checksumLen,
+ encryptCipher,
+ eKey);
}
/**
diff --git a/tests/iketests/Android.mk b/tests/iketests/Android.mk
index a45703c1..0da49b47 100644
--- a/tests/iketests/Android.mk
+++ b/tests/iketests/Android.mk
@@ -29,6 +29,7 @@ LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_STATIC_JAVA_LIBRARIES := ike \
androidx.test.rules \
frameworks-base-testutils \
- mockito-target-minus-junit4
+ mockito-target-minus-junit4 \
+ NetworkStackBase
include $(BUILD_PACKAGE) \ No newline at end of file
diff --git a/tests/iketests/AndroidManifest.xml b/tests/iketests/AndroidManifest.xml
index 80f54e8d..d69cbc8f 100644
--- a/tests/iketests/AndroidManifest.xml
+++ b/tests/iketests/AndroidManifest.xml
@@ -18,6 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.ike.tests">
+ <uses-permission android:name="android.permission.INTERNET"/>
+
<application android:label="FrameworksIkeTests">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/ChildSessionStateMachineTest.java b/tests/iketests/src/java/com/android/ike/ikev2/ChildSessionStateMachineTest.java
index e81b17aa..1d760b7e 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/ChildSessionStateMachineTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/ChildSessionStateMachineTest.java
@@ -63,12 +63,13 @@ public final class ChildSessionStateMachineTest {
private ISaRecordHelper mMockSaRecordHelper;
private IChildSessionCallback mMockChildSessionCallback;
- private ChildSessionOptions mMockChildSessionOptions;
+ private ChildSessionOptions mChildSessionOptions;
public ChildSessionStateMachineTest() {
mMockSaRecordHelper = mock(SaRecord.ISaRecordHelper.class);
mMockChildSessionCallback = mock(IChildSessionCallback.class);
- mMockChildSessionOptions = mock(ChildSessionOptions.class);
+
+ mChildSessionOptions = new ChildSessionOptions();
}
@Before
@@ -77,7 +78,7 @@ public final class ChildSessionStateMachineTest {
mLooper = new TestLooper();
mChildSessionStateMachine =
new ChildSessionStateMachine(
- "ChildSessionStateMachine", mLooper.getLooper(), mMockChildSessionOptions);
+ "ChildSessionStateMachine", mLooper.getLooper(), mChildSessionOptions);
mChildSessionStateMachine.setDbg(true);
SaRecord.setSaRecordHelper(mMockSaRecordHelper);
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionOptionsTest.java b/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionOptionsTest.java
new file mode 100644
index 00000000..bdaf135a
--- /dev/null
+++ b/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionOptionsTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ike.ikev2;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+
+import androidx.test.InstrumentationRegistry;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.Inet4Address;
+
+public final class IkeSessionOptionsTest {
+ private static final Inet4Address IPV4_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100"));
+
+ private UdpEncapsulationSocket mUdpEncapSocket;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ IpSecManager ipSecManager = (IpSecManager) context.getSystemService(Context.IPSEC_SERVICE);
+ mUdpEncapSocket = ipSecManager.openUdpEncapsulationSocket();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mUdpEncapSocket.close();
+ }
+
+ @Test
+ public void testBuild() throws Exception {
+ SaProposal saProposal =
+ SaProposal.Builder.newIkeSaProposalBuilder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8,
+ SaProposal.KEY_LEN_AES_128)
+ .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC)
+ .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
+ .build();
+
+ IkeSessionOptions sessionOptions =
+ new IkeSessionOptions.Builder(IPV4_ADDRESS, mUdpEncapSocket)
+ .addSaProposal(saProposal)
+ .build();
+
+ assertEquals(IPV4_ADDRESS, sessionOptions.getServerAddress());
+ assertEquals(mUdpEncapSocket, sessionOptions.getUdpEncapsulationSocket());
+ assertArrayEquals(new SaProposal[] {saProposal}, sessionOptions.getSaProposals());
+ assertFalse(sessionOptions.isIkeFragmentationSupported());
+ }
+
+ @Test
+ public void testBuildWithoutSaProposal() throws Exception {
+ try {
+ new IkeSessionOptions.Builder(IPV4_ADDRESS, mUdpEncapSocket).build();
+ fail("Expected to fail due to absence of SA proposal.");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildWithChildSaProposal() throws Exception {
+ SaProposal saProposal =
+ SaProposal.Builder.newChildSaProposalBuilder(true)
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8,
+ SaProposal.KEY_LEN_AES_128)
+ .build();
+ try {
+ new IkeSessionOptions.Builder(IPV4_ADDRESS, mUdpEncapSocket)
+ .addSaProposal(saProposal)
+ .build();
+ fail("Expected to fail due to wrong type of SA proposal.");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+}
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 02aaa4c3..1dcf5b82 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/IkeSessionStateMachineTest.java
@@ -17,6 +17,9 @@
package com.android.ike.ikev2;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
@@ -26,8 +29,13 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.os.Looper;
import android.os.test.TestLooper;
-import android.util.Pair;
+
+import androidx.test.InstrumentationRegistry;
import com.android.ike.ikev2.ChildSessionStateMachineFactory.ChildSessionFactoryHelper;
import com.android.ike.ikev2.ChildSessionStateMachineFactory.IChildSessionFactoryHelper;
@@ -44,35 +52,46 @@ import com.android.ike.ikev2.message.IkePayload;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import java.net.InetAddress;
import java.util.LinkedList;
+import java.util.List;
public final class IkeSessionStateMachineTest {
+ private static final String SERVER_ADDRESS = "192.0.2.100";
+
+ private UdpEncapsulationSocket mUdpEncapSocket;
+
private TestLooper mLooper;
private IkeSessionStateMachine mIkeSessionStateMachine;
+ private IkeSessionOptions mIkeSessionOptions;
+ private ChildSessionOptions mChildSessionOptions;
+
private IIkeMessageHelper mMockIkeMessageHelper;
private ISaRecordHelper mMockSaRecordHelper;
- private IkeSessionOptions mMockIkeSessionOptions;
private ChildSessionStateMachine mMockChildSessionStateMachine;
- private ChildSessionOptions mMockChildSessionOptions;
private IChildSessionFactoryHelper mMockChildSessionFactoryHelper;
private IkeSaRecord mSpyCurrentIkeSaRecord;
private IkeSaRecord mSpyLocalInitIkeSaRecord;
private IkeSaRecord mSpyRemoteInitIkeSaRecord;
+ private ArgumentCaptor<IkeMessage> mIkeMessageCaptor =
+ ArgumentCaptor.forClass(IkeMessage.class);
+
private ReceivedIkePacket makeDummyUnencryptedReceivedIkePacket(int packetType)
throws Exception {
IkeMessage dummyIkeMessage = makeDummyIkeMessageForTest(0, 0, false, false);
- Pair<IkeHeader, byte[]> dummyIkePacketPair =
- new Pair<>(dummyIkeMessage.ikeHeader, new byte[0]);
- when(mMockIkeMessageHelper.decode(dummyIkePacketPair.first, dummyIkePacketPair.second))
+ byte[] dummyIkePacketBytes = new byte[0];
+
+ when(mMockIkeMessageHelper.decode(dummyIkeMessage.ikeHeader, dummyIkePacketBytes))
.thenReturn(dummyIkeMessage);
when(mMockIkeMessageHelper.getMessageType(dummyIkeMessage)).thenReturn(packetType);
- return new ReceivedIkePacket(dummyIkePacketPair);
+ return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes);
}
private ReceivedIkePacket makeDummyEncryptedReceivedIkePacket(
@@ -81,16 +100,16 @@ public final class IkeSessionStateMachineTest {
IkeMessage dummyIkeMessage =
makeDummyIkeMessageForTest(
ikeSaRecord.initiatorSpi, ikeSaRecord.responderSpi, fromIkeInit, true);
- Pair<IkeHeader, byte[]> dummyIkePacketPair =
- new Pair<>(dummyIkeMessage.ikeHeader, new byte[0]);
+ byte[] dummyIkePacketBytes = new byte[0];
+
when(mMockIkeMessageHelper.decode(
- mMockIkeSessionOptions,
+ mIkeSessionOptions,
ikeSaRecord,
- dummyIkePacketPair.first,
- dummyIkePacketPair.second))
+ dummyIkeMessage.ikeHeader,
+ dummyIkePacketBytes))
.thenReturn(dummyIkeMessage);
when(mMockIkeMessageHelper.getMessageType(dummyIkeMessage)).thenReturn(packetType);
- return new ReceivedIkePacket(dummyIkePacketPair);
+ return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes);
}
private IkeMessage makeDummyIkeMessageForTest(
@@ -98,27 +117,21 @@ public final class IkeSessionStateMachineTest {
int firstPayloadType =
isEncrypted ? IkePayload.PAYLOAD_TYPE_SK : IkePayload.PAYLOAD_TYPE_NO_NEXT;
IkeHeader header =
- new IkeHeader(initSpi, respSpi, firstPayloadType, 0, true, fromikeInit, 0, 0);
+ new IkeHeader(initSpi, respSpi, firstPayloadType, 0, true, fromikeInit, 0);
return new IkeMessage(header, new LinkedList<IkePayload>());
}
private void verifyDecodeEncryptedMessage(IkeSaRecord record, ReceivedIkePacket rcvPacket)
throws Exception {
verify(mMockIkeMessageHelper)
- .decode(
- mMockIkeSessionOptions,
- record,
- rcvPacket.ikeHeader,
- rcvPacket.ikePacketBytes);
+ .decode(mIkeSessionOptions, record, rcvPacket.ikeHeader, rcvPacket.ikePacketBytes);
}
public IkeSessionStateMachineTest() {
mMockIkeMessageHelper = mock(IkeMessage.IIkeMessageHelper.class);
mMockSaRecordHelper = mock(SaRecord.ISaRecordHelper.class);
- mMockIkeSessionOptions = mock(IkeSessionOptions.class);
mMockChildSessionStateMachine = mock(ChildSessionStateMachine.class);
- mMockChildSessionOptions = mock(ChildSessionOptions.class);
mMockChildSessionFactoryHelper = mock(IChildSessionFactoryHelper.class);
mSpyCurrentIkeSaRecord = spy(new IkeSaRecord(11, 12, true, null, null));
@@ -132,17 +145,25 @@ public final class IkeSessionStateMachineTest {
}
@Before
- public void setUp() {
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ IpSecManager ipSecManager = (IpSecManager) context.getSystemService(Context.IPSEC_SERVICE);
+ mUdpEncapSocket = ipSecManager.openUdpEncapsulationSocket();
+
+ mIkeSessionOptions = buildIkeSessionOptions();
+ mChildSessionOptions = new ChildSessionOptions();
+
// Setup thread and looper
mLooper = new TestLooper();
mIkeSessionStateMachine =
new IkeSessionStateMachine(
"IkeSessionStateMachine",
mLooper.getLooper(),
- mMockIkeSessionOptions,
- mMockChildSessionOptions);
+ mIkeSessionOptions,
+ mChildSessionOptions);
mIkeSessionStateMachine.setDbg(true);
mIkeSessionStateMachine.start();
+
IkeMessage.setIkeMessageHelper(mMockIkeMessageHelper);
SaRecord.setSaRecordHelper(mMockSaRecordHelper);
ChildSessionStateMachineFactory.setChildSessionFactoryHelper(
@@ -150,17 +171,46 @@ public final class IkeSessionStateMachineTest {
}
@After
- public void tearDown() {
+ public void tearDown() throws Exception {
mIkeSessionStateMachine.quit();
mIkeSessionStateMachine.setDbg(false);
+ mUdpEncapSocket.close();
+
IkeMessage.setIkeMessageHelper(new IkeMessageHelper());
SaRecord.setSaRecordHelper(new SaRecordHelper());
ChildSessionStateMachineFactory.setChildSessionFactoryHelper(
new ChildSessionFactoryHelper());
}
+ private IkeSessionOptions buildIkeSessionOptions() throws Exception {
+ SaProposal saProposal =
+ SaProposal.Builder.newIkeSaProposalBuilder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+ .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1)
+ .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
+ .build();
+
+ InetAddress serveAddress = InetAddress.getByName(SERVER_ADDRESS);
+ IkeSessionOptions sessionOptions =
+ new IkeSessionOptions.Builder(serveAddress, mUdpEncapSocket)
+ .addSaProposal(saProposal)
+ .build();
+ return sessionOptions;
+ }
+
+ private static boolean isIkePayloadExist(
+ List<IkePayload> payloadList, @IkePayload.PayloadType int payloadType) {
+ for (IkePayload payload : payloadList) {
+ if (payload.payloadType == payloadType) return true;
+ }
+ return false;
+ }
+
@Test
public void testCreateIkeLocalIkeInit() throws Exception {
+ if (Looper.myLooper() == null) Looper.myLooper().prepare();
// Mock IKE_INIT response.
ReceivedIkePacket dummyReceivedIkePacket =
makeDummyUnencryptedReceivedIkePacket(IkeMessage.MESSAGE_TYPE_IKE_INIT_RESP);
@@ -172,15 +222,37 @@ public final class IkeSessionStateMachineTest {
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyReceivedIkePacket);
mLooper.dispatchAll();
+
+ // Validate outbound IKE INIT request
+ verify(mMockIkeMessageHelper).encode(mIkeMessageCaptor.capture());
+ IkeMessage ikeInitReqMessage = mIkeMessageCaptor.getValue();
+
+ IkeHeader ikeHeader = ikeInitReqMessage.ikeHeader;
+ assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT, ikeHeader.exchangeType);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertTrue(ikeHeader.fromIkeInitiator);
+
+ List<IkePayload> payloadList = ikeInitReqMessage.ikePayloadList;
+ assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_SA));
+ assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_KE));
+ assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_NONCE));
+
+ IkeSocket ikeSocket = mIkeSessionStateMachine.mIkeSocket;
+ assertNotNull(ikeSocket);
+ assertNotEquals(
+ -1 /*not found*/, ikeSocket.mSpiToIkeSession.indexOfValue(mIkeSessionStateMachine));
+
verify(mMockIkeMessageHelper)
.decode(dummyReceivedIkePacket.ikeHeader, dummyReceivedIkePacket.ikePacketBytes);
verify(mMockIkeMessageHelper).getMessageType(any());
+
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth);
}
private void mockIkeSetup() throws Exception {
+ if (Looper.myLooper() == null) Looper.myLooper().prepare();
// Mock IKE_INIT response
ReceivedIkePacket dummyIkeInitRespReceivedPacket =
makeDummyUnencryptedReceivedIkePacket(IkeMessage.MESSAGE_TYPE_IKE_INIT_RESP);
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/IkeSocketTest.java b/tests/iketests/src/java/com/android/ike/ikev2/IkeSocketTest.java
new file mode 100644
index 00000000..5f15f554
--- /dev/null
+++ b/tests/iketests/src/java/com/android/ike/ikev2/IkeSocketTest.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ike.ikev2;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.ike.ikev2.IkeSocket.PacketReceiver;
+import com.android.ike.ikev2.message.IkeHeader;
+import com.android.ike.ikev2.message.TestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public final class IkeSocketTest {
+ private static final int REMOTE_RECV_BUFF_SIZE = 2048;
+ private static final int TIMEOUT = 1000;
+
+ private static final String NON_ESP_MARKER_HEX_STRING = "00000000";
+ private static final String IKE_REQ_MESSAGE_HEX_STRING =
+ "5f54bf6d8b48e6e100000000000000002120220800000000"
+ + "00000150220000300000002c010100040300000c0100000c"
+ + "800e00800300000803000002030000080400000200000008"
+ + "020000022800008800020000b4a2faf4bb54878ae21d6385"
+ + "12ece55d9236fc5046ab6cef82220f421f3ce6361faf3656"
+ + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9ece8ac37"
+ + "534036040610ebdd92f46bef84f0be7db860351843858f8a"
+ + "cf87056e272377f70c9f2d81e29c7b0ce4f291a3a72476bb"
+ + "0b278fd4b7b0a4c26bbeb08214c707137607958729000024"
+ + "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7"
+ + "2cb4240eb5c464122900001c00004004e54f73b7d83f6beb"
+ + "881eab2051d8663f421d10b02b00001c00004005d915368c"
+ + "a036004cb578ae3e3fb268509aeab1900000002069936922"
+ + "8741c6d4ca094c93e242c9de19e7b7c60000000500000500";
+
+ private static final String LOCAL_SPI = "0000000000000000";
+ private static final String REMOTE_SPI = "5f54bf6d8b48e6e1";
+
+ private static final String DATA_ONE = "one 1";
+ private static final String DATA_TWO = "two 2";
+
+ private static final String IPV4_LOOPBACK = "127.0.0.1";
+
+ private byte[] mDataOne;
+ private byte[] mDataTwo;
+
+ private long mLocalSpi;
+ private long mRemoteSpi;
+
+ private LongSparseArray mSpiToIkeStateMachineMap;
+ private PacketReceiver mPacketReceiver;
+
+ private UdpEncapsulationSocket mClientUdpEncapSocket;
+ private InetAddress mLocalAddress;
+ private FileDescriptor mDummyRemoteServerFd;
+
+ private IkeSessionStateMachine mMockIkeSessionStateMachine;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ IpSecManager ipSecManager = (IpSecManager) context.getSystemService(Context.IPSEC_SERVICE);
+ mClientUdpEncapSocket = ipSecManager.openUdpEncapsulationSocket();
+
+ mLocalAddress = InetAddress.getByName(IPV4_LOOPBACK);
+ mDummyRemoteServerFd = getBoundUdpSocket(mLocalAddress);
+
+ mDataOne = DATA_ONE.getBytes("UTF-8");
+ mDataTwo = DATA_TWO.getBytes("UTF-8");
+
+ ByteBuffer localSpiBuffer = ByteBuffer.wrap(TestUtils.hexStringToByteArray(LOCAL_SPI));
+ mLocalSpi = localSpiBuffer.getLong();
+ ByteBuffer remoteSpiBuffer = ByteBuffer.wrap(TestUtils.hexStringToByteArray(REMOTE_SPI));
+ mRemoteSpi = remoteSpiBuffer.getLong();
+
+ mMockIkeSessionStateMachine = mock(IkeSessionStateMachine.class);
+
+ mSpiToIkeStateMachineMap = new LongSparseArray<IkeSessionStateMachine>();
+ mSpiToIkeStateMachineMap.put(mLocalSpi, mMockIkeSessionStateMachine);
+
+ mPacketReceiver = new IkeSocket.PacketReceiver();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mClientUdpEncapSocket.close();
+ IkeSocket.setPacketReceiver(mPacketReceiver);
+ Os.close(mDummyRemoteServerFd);
+ }
+
+ private static FileDescriptor getBoundUdpSocket(InetAddress address) throws Exception {
+ FileDescriptor sock =
+ Os.socket(OsConstants.AF_INET, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP);
+ Os.bind(sock, address, IkeSocket.IKE_SERVER_PORT);
+ return sock;
+ }
+
+ @Test
+ public void testGetAndCloseIkeSocket() throws Exception {
+ if (Looper.myLooper() == null) Looper.myLooper().prepare();
+
+ IkeSocket ikeSocketOne = IkeSocket.getIkeSocket(mClientUdpEncapSocket);
+ assertEquals(1, ikeSocketOne.mRefCount);
+
+ IkeSocket ikeSocketTwo = IkeSocket.getIkeSocket(mClientUdpEncapSocket);
+ assertEquals(ikeSocketOne, ikeSocketTwo);
+ assertEquals(2, ikeSocketTwo.mRefCount);
+
+ ikeSocketOne.releaseReference();
+ assertEquals(1, ikeSocketOne.mRefCount);
+
+ ikeSocketTwo.releaseReference();
+ assertEquals(0, ikeSocketTwo.mRefCount);
+ }
+
+ @Test
+ public void testSendIkePacket() throws Exception {
+ if (Looper.myLooper() == null) Looper.myLooper().prepare();
+
+ // Send IKE packet
+ IkeSocket ikeSocket = IkeSocket.getIkeSocket(mClientUdpEncapSocket);
+ ikeSocket.sendIkePacket(mDataOne, mLocalAddress);
+
+ byte[] receivedData = receive(mDummyRemoteServerFd);
+
+ // Verify received data
+ ByteBuffer expectedBuffer =
+ ByteBuffer.allocate(IkeSocket.NON_ESP_MARKER_LEN + mDataOne.length);
+ expectedBuffer.put(IkeSocket.NON_ESP_MARKER).put(mDataOne);
+
+ assertArrayEquals(expectedBuffer.array(), receivedData);
+
+ ikeSocket.releaseReference();
+ }
+
+ @Test
+ public void testReceiveIkePacket() throws Exception {
+ // Create working thread.
+ HandlerThread mIkeThread = new HandlerThread("IkeSocketTest");
+ mIkeThread.start();
+
+ // Create IkeSocket on working thread.
+ IkeSocketReceiver socketReceiver = new IkeSocketReceiver();
+ TestCountDownLatch createLatch = new TestCountDownLatch();
+ mIkeThread
+ .getThreadHandler()
+ .post(
+ () -> {
+ try {
+ socketReceiver.setIkeSocket(
+ IkeSocket.getIkeSocket(mClientUdpEncapSocket));
+ createLatch.countDown();
+ Log.d("IkeSocketTest", "IkeSocket created.");
+ } catch (ErrnoException e) {
+ Log.e("IkeSocketTest", "error encountered creating IkeSocket ", e);
+ }
+ });
+ createLatch.await();
+
+ IkeSocket ikeSocket = socketReceiver.getIkeSocket();
+ assertNotNull(ikeSocket);
+
+ // Configure IkeSocket
+ TestCountDownLatch receiveLatch = new TestCountDownLatch();
+ DummyPacketReceiver packetReceiver = new DummyPacketReceiver(receiveLatch);
+ IkeSocket.setPacketReceiver(packetReceiver);
+
+ // Send first packet.
+ sendToIkeSocket(mDummyRemoteServerFd, mDataOne, mLocalAddress);
+ receiveLatch.await();
+
+ assertEquals(1, ikeSocket.numPacketsReceived());
+ assertArrayEquals(mDataOne, packetReceiver.mReceivedData);
+
+ // Send second packet.
+ sendToIkeSocket(mDummyRemoteServerFd, mDataTwo, mLocalAddress);
+ receiveLatch.await();
+
+ assertEquals(2, ikeSocket.numPacketsReceived());
+ assertArrayEquals(mDataTwo, packetReceiver.mReceivedData);
+
+ // Close IkeSocket.
+ TestCountDownLatch closeLatch = new TestCountDownLatch();
+ ikeSocket
+ .getHandler()
+ .post(
+ () -> {
+ ikeSocket.releaseReference();
+ closeLatch.countDown();
+ });
+ closeLatch.await();
+
+ mIkeThread.quitSafely();
+ }
+
+ @Test
+ public void testHandlePacket() throws Exception {
+ byte[] recvBuf =
+ TestUtils.hexStringToByteArray(
+ NON_ESP_MARKER_HEX_STRING + IKE_REQ_MESSAGE_HEX_STRING);
+
+ mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap);
+
+ byte[] expectedIkePacketBytes = TestUtils.hexStringToByteArray(IKE_REQ_MESSAGE_HEX_STRING);
+ ArgumentCaptor<IkeHeader> ikeHeaderCaptor = ArgumentCaptor.forClass(IkeHeader.class);
+ verify(mMockIkeSessionStateMachine)
+ .receiveIkePacket(ikeHeaderCaptor.capture(), eq(expectedIkePacketBytes));
+
+ IkeHeader capturedIkeHeader = ikeHeaderCaptor.getValue();
+ assertEquals(mRemoteSpi, capturedIkeHeader.ikeInitiatorSpi);
+ assertEquals(mLocalSpi, capturedIkeHeader.ikeResponderSpi);
+ }
+
+ @Test
+ public void testHandleEspPacket() throws Exception {
+ byte[] recvBuf =
+ TestUtils.hexStringToByteArray(
+ NON_ESP_MARKER_HEX_STRING + IKE_REQ_MESSAGE_HEX_STRING);
+ // Modify Non-ESP Marker
+ recvBuf[0] = 1;
+
+ mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap);
+
+ verify(mMockIkeSessionStateMachine, never()).receiveIkePacket(any(), any());
+ }
+
+ @Test
+ public void testHandlePacketWithMalformedHeader() throws Exception {
+ String malformedIkePacketHexString = "5f54bf6d8b48e6e100000000000000002120220800000000";
+ byte[] recvBuf =
+ TestUtils.hexStringToByteArray(
+ NON_ESP_MARKER_HEX_STRING + malformedIkePacketHexString);
+
+ mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap);
+
+ verify(mMockIkeSessionStateMachine, never()).receiveIkePacket(any(), any());
+ }
+
+ private byte[] receive(FileDescriptor mfd) throws Exception {
+ byte[] receiveBuffer = new byte[REMOTE_RECV_BUFF_SIZE];
+ AtomicInteger bytesRead = new AtomicInteger(-1);
+ Thread receiveThread =
+ new Thread(
+ () -> {
+ while (bytesRead.get() < 0) {
+ try {
+ bytesRead.set(
+ Os.recvfrom(
+ mDummyRemoteServerFd,
+ receiveBuffer,
+ 0,
+ REMOTE_RECV_BUFF_SIZE,
+ 0,
+ null));
+ } catch (Exception e) {
+ Log.e(
+ "IkeSocketTest",
+ "Error encountered reading from socket",
+ e);
+ }
+ }
+ Log.d(
+ "IkeSocketTest",
+ "Packet received with size of " + bytesRead.get());
+ });
+
+ receiveThread.start();
+ receiveThread.join(TIMEOUT);
+
+ return Arrays.copyOfRange(receiveBuffer, 0, bytesRead.get());
+ }
+
+ private void sendToIkeSocket(FileDescriptor fd, byte[] data, InetAddress destAddress)
+ throws Exception {
+ Os.sendto(fd, data, 0, data.length, 0, destAddress, mClientUdpEncapSocket.getPort());
+ }
+
+ private static class IkeSocketReceiver {
+ private IkeSocket mIkeSocket;
+
+ void setIkeSocket(IkeSocket ikeSocket) {
+ mIkeSocket = ikeSocket;
+ }
+
+ IkeSocket getIkeSocket() {
+ return mIkeSocket;
+ }
+ }
+
+ private static class DummyPacketReceiver implements IkeSocket.IPacketReceiver {
+ byte[] mReceivedData = null;
+ final TestCountDownLatch mLatch;
+
+ DummyPacketReceiver(TestCountDownLatch latch) {
+ mLatch = latch;
+ }
+
+ public void handlePacket(
+ byte[] revbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession) {
+ mReceivedData = Arrays.copyOfRange(revbuf, 0, revbuf.length);
+ mLatch.countDown();
+ Log.d("IkeSocketTest", "Packet received");
+ }
+ }
+
+ private static class TestCountDownLatch {
+ private CountDownLatch mLatch;
+
+ TestCountDownLatch() {
+ reset();
+ }
+
+ private void reset() {
+ mLatch = new CountDownLatch(1);
+ }
+
+ void countDown() {
+ mLatch.countDown();
+ }
+
+ void await() {
+ try {
+ if (!mLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
+ fail("Time out");
+ }
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ }
+ reset();
+ }
+ }
+}
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/SaProposalTest.java b/tests/iketests/src/java/com/android/ike/ikev2/SaProposalTest.java
index 2ce2a9d9..7f40d729 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/SaProposalTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/SaProposalTest.java
@@ -64,18 +64,19 @@ public final class SaProposalTest {
.addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
.addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC)
.addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
- .buildOrThrow();
+ .build();
- assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.mProtocolId);
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.getProtocolId());
assertArrayEquals(
new EncryptionTransform[] {mEncryption3DesTransform},
- proposal.mEncryptionAlgorithms);
+ proposal.getEncryptionTransforms());
assertArrayEquals(
new IntegrityTransform[] {mIntegrityHmacSha1Transform},
- proposal.mIntegrityAlgorithms);
+ proposal.getIntegrityTransforms());
assertArrayEquals(
- new PrfTransform[] {mPrfAes128XCbcTransform}, proposal.mPseudorandomFunctions);
- assertArrayEquals(new DhGroupTransform[] {mDhGroup1024Transform}, proposal.mDhGroups);
+ new PrfTransform[] {mPrfAes128XCbcTransform}, proposal.getPrfTransforms());
+ assertArrayEquals(
+ new DhGroupTransform[] {mDhGroup1024Transform}, proposal.getDhGroupTransforms());
}
@Test
@@ -87,16 +88,17 @@ public final class SaProposalTest {
SaProposal.KEY_LEN_AES_128)
.addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC)
.addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
- .buildOrThrow();
+ .build();
- assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.mProtocolId);
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.getProtocolId());
assertArrayEquals(
new EncryptionTransform[] {mEncryptionAesGcm8Transform},
- proposal.mEncryptionAlgorithms);
+ proposal.getEncryptionTransforms());
+ assertArrayEquals(
+ new PrfTransform[] {mPrfAes128XCbcTransform}, proposal.getPrfTransforms());
assertArrayEquals(
- new PrfTransform[] {mPrfAes128XCbcTransform}, proposal.mPseudorandomFunctions);
- assertArrayEquals(new DhGroupTransform[] {mDhGroup1024Transform}, proposal.mDhGroups);
- assertTrue(proposal.mIntegrityAlgorithms.length == 0);
+ new DhGroupTransform[] {mDhGroup1024Transform}, proposal.getDhGroupTransforms());
+ assertTrue(proposal.getIntegrityTransforms().length == 0);
}
@Test
@@ -107,16 +109,17 @@ public final class SaProposalTest {
SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8,
SaProposal.KEY_LEN_AES_128)
.addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_NONE)
- .buildOrThrow();
+ .build();
- assertEquals(IkePayload.PROTOCOL_ID_ESP, proposal.mProtocolId);
+ assertEquals(IkePayload.PROTOCOL_ID_ESP, proposal.getProtocolId());
assertArrayEquals(
new EncryptionTransform[] {mEncryptionAesGcm8Transform},
- proposal.mEncryptionAlgorithms);
+ proposal.getEncryptionTransforms());
assertArrayEquals(
- new IntegrityTransform[] {mIntegrityNoneTransform}, proposal.mIntegrityAlgorithms);
- assertTrue(proposal.mPseudorandomFunctions.length == 0);
- assertTrue(proposal.mDhGroups.length == 0);
+ new IntegrityTransform[] {mIntegrityNoneTransform},
+ proposal.getIntegrityTransforms());
+ assertTrue(proposal.getPrfTransforms().length == 0);
+ assertTrue(proposal.getDhGroupTransforms().length == 0);
}
@Test
@@ -127,23 +130,25 @@ public final class SaProposalTest {
builder.addEncryptionAlgorithm(SaProposal.ENCRYPTION_ALGORITHM_3DES)
.addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_NONE)
.addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
- .buildOrThrow();
+ .build();
- assertEquals(IkePayload.PROTOCOL_ID_ESP, proposal.mProtocolId);
+ assertEquals(IkePayload.PROTOCOL_ID_ESP, proposal.getProtocolId());
assertArrayEquals(
new EncryptionTransform[] {mEncryption3DesTransform},
- proposal.mEncryptionAlgorithms);
+ proposal.getEncryptionTransforms());
+ assertArrayEquals(
+ new IntegrityTransform[] {mIntegrityNoneTransform},
+ proposal.getIntegrityTransforms());
assertArrayEquals(
- new IntegrityTransform[] {mIntegrityNoneTransform}, proposal.mIntegrityAlgorithms);
- assertArrayEquals(new DhGroupTransform[] {mDhGroup1024Transform}, proposal.mDhGroups);
- assertTrue(proposal.mPseudorandomFunctions.length == 0);
+ new DhGroupTransform[] {mDhGroup1024Transform}, proposal.getDhGroupTransforms());
+ assertTrue(proposal.getPrfTransforms().length == 0);
}
@Test
public void testBuildEncryptAlgosWithNoAlgorithm() throws Exception {
Builder builder = Builder.newIkeSaProposalBuilder();
try {
- builder.buildOrThrow();
+ builder.build();
fail("Expected to fail when no encryption algorithm is proposed.");
} catch (IllegalArgumentException expected) {
@@ -179,7 +184,7 @@ public final class SaProposalTest {
public void testBuildIkeProposalWithoutPrf() throws Exception {
Builder builder = Builder.newIkeSaProposalBuilder();
try {
- builder.addEncryptionAlgorithm(SaProposal.ENCRYPTION_ALGORITHM_3DES).buildOrThrow();
+ builder.addEncryptionAlgorithm(SaProposal.ENCRYPTION_ALGORITHM_3DES).build();
fail("Expected to fail when PRF is not provided in IKE SA proposal.");
} catch (IllegalArgumentException expected) {
@@ -192,7 +197,7 @@ public final class SaProposalTest {
try {
builder.addEncryptionAlgorithm(SaProposal.ENCRYPTION_ALGORITHM_3DES)
.addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1)
- .buildOrThrow();
+ .build();
fail("Expected to fail when PRF is provided in Child SA proposal.");
} catch (IllegalArgumentException expected) {
@@ -209,7 +214,7 @@ public final class SaProposalTest {
builder.addEncryptionAlgorithm(SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12)
.addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_NONE)
.addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
- .buildOrThrow();
+ .build();
fail("Expected to fail when not-none integrity algorithm is proposed with AEAD");
} catch (IllegalArgumentException expected) {
@@ -225,7 +230,7 @@ public final class SaProposalTest {
try {
builder.addEncryptionAlgorithm(SaProposal.ENCRYPTION_ALGORITHM_3DES)
.addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1)
- .buildOrThrow();
+ .build();
fail(
"Expected to fail when"
@@ -245,7 +250,7 @@ public final class SaProposalTest {
.addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1)
.addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_NONE)
.addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
- .buildOrThrow();
+ .build();
fail(
"Expected to fail when none-value integrity algorithm is proposed"
@@ -262,7 +267,7 @@ public final class SaProposalTest {
builder.addEncryptionAlgorithm(SaProposal.ENCRYPTION_ALGORITHM_3DES)
.addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
.addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC)
- .buildOrThrow();
+ .build();
fail("Expected to fail when no DH Group is proposed in IKE SA proposal.");
} catch (IllegalArgumentException expected) {
@@ -279,7 +284,7 @@ public final class SaProposalTest {
.addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC)
.addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
.addDhGroup(SaProposal.DH_GROUP_NONE)
- .buildOrThrow();
+ .build();
fail("Expected to fail when none-value DH Group is proposed in IKE SA proposal.");
} catch (IllegalArgumentException expected) {
@@ -295,7 +300,7 @@ public final class SaProposalTest {
builder.addEncryptionAlgorithm(SaProposal.ENCRYPTION_ALGORITHM_3DES)
.addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
.addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
- .buildOrThrow();
+ .build();
fail(
"Expected to fail when"
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/SaRecordTest.java b/tests/iketests/src/java/com/android/ike/ikev2/SaRecordTest.java
new file mode 100644
index 00000000..5c61105c
--- /dev/null
+++ b/tests/iketests/src/java/com/android/ike/ikev2/SaRecordTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ike.ikev2;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import com.android.ike.ikev2.message.TestUtils;
+
+import org.junit.Test;
+
+public final class SaRecordTest {
+ private static final String IKE_INIT_SPI = "5F54BF6D8B48E6E1";
+ private static final String IKE_RESP_SPI = "909232B3D1EDCB5C";
+
+ private static final String IKE_NONCE_INIT_HEX_STRING =
+ "C39B7F368F4681B89FA9B7BE6465ABD7C5F68B6ED5D3B4C72CB4240EB5C46412";
+ private static final String IKE_NONCE_RESP_HEX_STRING =
+ "9756112CA539F5C25ABACC7EE92B73091942A9C06950F98848F1AF1694C4DDFF";
+
+ private static final String IKE_SHARED_DH_KEY_HEX_STRING =
+ "C14155DEA40056BD9C76FB4819687B7A397582F4CD5AFF4B"
+ + "8F441C56E0C08C84234147A0BA249A555835A048E3CA2980"
+ + "7D057A61DD26EEFAD9AF9C01497005E52858E29FB42EB849"
+ + "6731DF96A11CCE1F51137A9A1B900FA81AEE7898E373D4E4"
+ + "8B899BBECA091314ECD4B6E412EF4B0FEF798F54735F3180"
+ + "7424A318287F20E8";
+
+ private static final String IKE_SKEYSEED_HEX_STRING =
+ "8C42F3B1F5F81C7BAAC5F33E9A4F01987B2F9657";
+ private static final String IKE_SK_D_HEX_STRING = "C86B56EFCF684DCC2877578AEF3137167FE0EBF6";
+ private static final String IKE_SK_AUTH_INIT_HEX_STRING =
+ "554FBF5A05B7F511E05A30CE23D874DB9EF55E51";
+ private static final String IKE_SK_AUTH_RESP_HEX_STRING =
+ "36D83420788337CA32ECAA46892C48808DCD58B1";
+ private static final String IKE_SK_ENCR_INIT_HEX_STRING = "5CBFD33F75796C0188C4A3A546AEC4A1";
+ private static final String IKE_SK_ENCR_RESP_HEX_STRING = "C33B35FCF29514CD9D8B4A695E1A816E";
+ private static final String IKE_SK_PRF_INIT_HEX_STRING =
+ "094787780EE466E2CB049FA327B43908BC57E485";
+ private static final String IKE_SK_PRF_RESP_HEX_STRING =
+ "A30E6B08BE56C0E6BFF4744143C75219299E1BEB";
+ private static final String IKE_KEY_MAT =
+ IKE_SK_D_HEX_STRING
+ + IKE_SK_AUTH_INIT_HEX_STRING
+ + IKE_SK_AUTH_RESP_HEX_STRING
+ + IKE_SK_ENCR_INIT_HEX_STRING
+ + IKE_SK_ENCR_RESP_HEX_STRING
+ + IKE_SK_PRF_INIT_HEX_STRING
+ + IKE_SK_PRF_RESP_HEX_STRING;
+
+ private static final int IKE_AUTH_ALGO_KEY_LEN = 20;
+ private static final int IKE_ENCR_ALGO_KEY_LEN = 16;
+ private static final int IKE_PRF_KEY_LEN = 20;
+ private static final int IKE_SK_D_KEY_LEN = IKE_PRF_KEY_LEN;
+
+ private static final String FIRST_CHILD_ENCR_INIT_HEX_STRING =
+ "1B865CEA6E2C23973E8C5452ADC5CD7D";
+ private static final String FIRST_CHILD_ENCR_RESP_HEX_STRING =
+ "5E82FEDACC6DCB0756DDD7553907EBD1";
+ private static final String FIRST_CHILD_AUTH_INIT_HEX_STRING =
+ "A7A5A44F7EF4409657206C7DC52B7E692593B51E";
+ private static final String FIRST_CHILD_AUTH_RESP_HEX_STRING =
+ "CDE612189FD46DE870FAEC04F92B40B0BFDBD9E1";
+ private static final String FIRST_CHILD_KEY_MAT =
+ FIRST_CHILD_ENCR_INIT_HEX_STRING
+ + FIRST_CHILD_AUTH_INIT_HEX_STRING
+ + FIRST_CHILD_ENCR_RESP_HEX_STRING
+ + FIRST_CHILD_AUTH_RESP_HEX_STRING;
+
+ private static final int FIRST_CHILD_AUTH_ALGO_KEY_LEN = 20;
+ private static final int FIRST_CHILD_ENCR_ALGO_KEY_LEN = 16;
+
+ private static final String PRF_HMAC_SHA1_ALGO_NAME = "HmacSHA1";
+
+ @Test
+ public void testCalculateSKeySeed() throws Exception {
+ byte[] nonceInit = TestUtils.hexStringToByteArray(IKE_NONCE_INIT_HEX_STRING);
+ byte[] nonceResp = TestUtils.hexStringToByteArray(IKE_NONCE_RESP_HEX_STRING);
+ byte[] sharedDhKey = TestUtils.hexStringToByteArray(IKE_SHARED_DH_KEY_HEX_STRING);
+
+ byte[] calculatedSKeySeed =
+ SaRecord.generateSKeySeed(
+ PRF_HMAC_SHA1_ALGO_NAME, nonceInit, nonceResp, sharedDhKey);
+
+ byte[] expectedSKeySeed = TestUtils.hexStringToByteArray(IKE_SKEYSEED_HEX_STRING);
+ assertArrayEquals(expectedSKeySeed, calculatedSKeySeed);
+ }
+
+ @Test
+ public void testSignWithPrfPlusForIke() throws Exception {
+ byte[] prfKey = TestUtils.hexStringToByteArray(IKE_SKEYSEED_HEX_STRING);
+ byte[] prfData =
+ TestUtils.hexStringToByteArray(
+ IKE_NONCE_INIT_HEX_STRING
+ + IKE_NONCE_RESP_HEX_STRING
+ + IKE_INIT_SPI
+ + IKE_RESP_SPI);
+ int keyMaterialLen =
+ IKE_SK_D_KEY_LEN
+ + IKE_AUTH_ALGO_KEY_LEN * 2
+ + IKE_ENCR_ALGO_KEY_LEN * 2
+ + IKE_PRF_KEY_LEN * 2;
+
+ byte[] calculatedKeyMat =
+ SaRecord.generateKeyMat(PRF_HMAC_SHA1_ALGO_NAME, prfKey, prfData, keyMaterialLen);
+
+ byte[] expectedKeyMat = TestUtils.hexStringToByteArray(IKE_KEY_MAT);
+ assertArrayEquals(expectedKeyMat, calculatedKeyMat);
+ }
+
+ @Test
+ public void testSignWithPrfPlusForFirstChild() throws Exception {
+ byte[] prfKey = TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING);
+ byte[] prfData =
+ TestUtils.hexStringToByteArray(
+ IKE_NONCE_INIT_HEX_STRING + IKE_NONCE_RESP_HEX_STRING);
+ int keyMaterialLen = FIRST_CHILD_AUTH_ALGO_KEY_LEN * 2 + FIRST_CHILD_ENCR_ALGO_KEY_LEN * 2;
+
+ byte[] calculatedKeyMat =
+ SaRecord.generateKeyMat(PRF_HMAC_SHA1_ALGO_NAME, prfKey, prfData, keyMaterialLen);
+
+ byte[] expectedKeyMat = TestUtils.hexStringToByteArray(FIRST_CHILD_KEY_MAT);
+ assertArrayEquals(expectedKeyMat, calculatedKeyMat);
+ }
+}
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeDeletePayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeDeletePayloadTest.java
new file mode 100644
index 00000000..1a7b9a29
--- /dev/null
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeDeletePayloadTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ike.ikev2.message;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+public final class IkeDeletePayloadTest {
+ private static final String DELETE_IKE_PAYLOAD_HEX_STRING = "0000000801000000";
+ private static final String DELETE_CHILD_PAYLOAD_HEX_STRING = "0000000c030400012ad4c0a2";
+ private static final String CHILD_SPI = "2ad4c0a2";
+
+ private static final int NUM_CHILD_SPI = 1;
+
+ private static final int PROTOCOL_ID_OFFSET = 4;
+ private static final int SPI_SIZE_OFFSET = 5;
+ private static final int NUM_OF_SPI_OFFSET = 6;
+
+ @Test
+ public void testDecodeDeleteIkePayload() throws Exception {
+ ByteBuffer inputBuffer =
+ ByteBuffer.wrap(TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING));
+
+ IkePayload payload =
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer)
+ .first;
+
+ assertTrue(payload instanceof IkeDeletePayload);
+
+ IkeDeletePayload deletePayload = (IkeDeletePayload) payload;
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, deletePayload.protocolId);
+ assertEquals(IkePayload.SPI_LEN_NOT_INCLUDED, deletePayload.spiSize);
+ assertEquals(0, deletePayload.numSpi);
+ assertArrayEquals(new int[0], deletePayload.spisToDelete);
+ }
+
+ @Test
+ public void testDecodeDeleteChildPayload() throws Exception {
+ ByteBuffer inputBuffer =
+ ByteBuffer.wrap(TestUtils.hexStringToByteArray(DELETE_CHILD_PAYLOAD_HEX_STRING));
+
+ IkePayload payload =
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer)
+ .first;
+
+ assertTrue(payload instanceof IkeDeletePayload);
+
+ IkeDeletePayload deletePayload = (IkeDeletePayload) payload;
+ assertEquals(IkePayload.PROTOCOL_ID_ESP, deletePayload.protocolId);
+ assertEquals(IkePayload.SPI_LEN_IPSEC, deletePayload.spiSize);
+ assertEquals(NUM_CHILD_SPI, deletePayload.numSpi);
+
+ byte[] childSpiBytes = TestUtils.hexStringToByteArray(CHILD_SPI);
+ ByteBuffer buffer = ByteBuffer.wrap(childSpiBytes);
+ int expectedChildSpi = buffer.getInt();
+
+ assertArrayEquals(new int[] {expectedChildSpi}, deletePayload.spisToDelete);
+ }
+
+ @Test
+ public void testDecodeWithInvalidProtocol() throws Exception {
+ byte[] deletePayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING);
+ deletePayloadBytes[PROTOCOL_ID_OFFSET] = -1;
+ ByteBuffer inputBuffer = ByteBuffer.wrap(deletePayloadBytes);
+
+ try {
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer);
+ fail("Expected to fail due to unrecognized protocol ID.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecodeWithInvalidSpiSize() throws Exception {
+ byte[] deletePayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING);
+ deletePayloadBytes[SPI_SIZE_OFFSET] = IkePayload.SPI_LEN_IPSEC;
+ ByteBuffer inputBuffer = ByteBuffer.wrap(deletePayloadBytes);
+
+ try {
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer);
+ fail("Expected to fail due to invalid SPI size in Delete IKE Payload.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecodeWithInvalidNumSpi() throws Exception {
+ byte[] deletePayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING);
+ deletePayloadBytes[NUM_OF_SPI_OFFSET] = 1;
+ ByteBuffer inputBuffer = ByteBuffer.wrap(deletePayloadBytes);
+
+ try {
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer);
+ fail("Expected to fail because number of SPI is not zero in Delete IKE Payload.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecodeWithInvalidNumSpiAndSpiSize() throws Exception {
+ byte[] deletePayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING);
+ deletePayloadBytes[SPI_SIZE_OFFSET] = 1;
+ deletePayloadBytes[NUM_CHILD_SPI] = 4;
+ ByteBuffer inputBuffer = ByteBuffer.wrap(deletePayloadBytes);
+
+ try {
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer);
+ fail("Expected to fail due to invalid SPI size in Delete IKE Payload.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+}
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBodyTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBodyTest.java
index 48669e89..fcb3eff6 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBodyTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeEncryptedPayloadBodyTest.java
@@ -17,10 +17,14 @@ package com.android.ike.ikev2.message;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import org.junit.Before;
import org.junit.Test;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
@@ -65,9 +69,40 @@ public final class IkeEncryptedPayloadBodyTest {
private SecretKey mAesCbcKey;
private Mac mHmacSha1IntegrityMac;
+ private byte[] mDataToPadAndEncrypt;
+ private byte[] mDataToAuthenticate;
+ private byte[] mEncryptedPaddedData;
+ private byte[] mIkeMessage;
+
+ private byte[] mChecksum;
+ private byte[] mIv;
+ private byte[] mPadding;
+
// TODO: Add tests for authenticating and decrypting received message.
@Before
public void setUp() throws Exception {
+ mDataToPadAndEncrypt =
+ TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_UNENCRYPTED_DATA);
+ String hexStringToAuthenticate =
+ IKE_AUTH_INIT_REQUEST_HEADER
+ + IKE_AUTH_INIT_REQUEST_SK_HEADER
+ + IKE_AUTH_INIT_REQUEST_IV
+ + IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA;
+ mDataToAuthenticate = TestUtils.hexStringToByteArray(hexStringToAuthenticate);
+ mEncryptedPaddedData =
+ TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA);
+ mIkeMessage =
+ TestUtils.hexStringToByteArray(
+ IKE_AUTH_INIT_REQUEST_HEADER
+ + IKE_AUTH_INIT_REQUEST_SK_HEADER
+ + IKE_AUTH_INIT_REQUEST_IV
+ + IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA
+ + IKE_AUTH_INIT_REQUEST_CHECKSUM);
+
+ mChecksum = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_CHECKSUM);
+ mIv = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_IV);
+ mPadding = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_PADDING);
+
mAesCbcCipher = Cipher.getInstance(ENCR_ALGO_AES_CBC, IkeMessage.getSecurityProvider());
byte[] encryptKeyBytes = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP);
mAesCbcKey = new SecretKeySpec(encryptKeyBytes, ENCR_ALGO_AES_CBC);
@@ -81,20 +116,30 @@ public final class IkeEncryptedPayloadBodyTest {
@Test
public void testCalculateChecksum() throws Exception {
- String hexStringToAuthenticate =
- IKE_AUTH_INIT_REQUEST_HEADER
- + IKE_AUTH_INIT_REQUEST_SK_HEADER
- + IKE_AUTH_INIT_REQUEST_IV
- + IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA;
- byte[] byteToAuthenticate = TestUtils.hexStringToByteArray(hexStringToAuthenticate);
-
- byte[] expectedCheckSum = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_CHECKSUM);
-
byte[] calculatedChecksum =
IkeEncryptedPayloadBody.calculateChecksum(
- byteToAuthenticate, mHmacSha1IntegrityMac, HMAC_SHA1_CHECKSUM_LEN);
+ mDataToAuthenticate, mHmacSha1IntegrityMac, HMAC_SHA1_CHECKSUM_LEN);
+
+ assertArrayEquals(mChecksum, calculatedChecksum);
+ }
+
+ @Test
+ public void testValidateChecksum() throws Exception {
+ IkeEncryptedPayloadBody.validateChecksumOrThrow(
+ mDataToAuthenticate, mHmacSha1IntegrityMac, mChecksum);
+ }
- assertArrayEquals(expectedCheckSum, calculatedChecksum);
+ @Test
+ public void testThrowForInvalidChecksum() throws Exception {
+ byte[] dataToAuthenticate = Arrays.copyOf(mDataToAuthenticate, mDataToAuthenticate.length);
+ dataToAuthenticate[0]++;
+
+ try {
+ IkeEncryptedPayloadBody.validateChecksumOrThrow(
+ dataToAuthenticate, mHmacSha1IntegrityMac, mChecksum);
+ fail("Expected GeneralSecurityException due to mismatched checksum.");
+ } catch (GeneralSecurityException expected) {
+ }
}
@Test
@@ -132,40 +177,37 @@ public final class IkeEncryptedPayloadBodyTest {
@Test
public void testEncrypt() throws Exception {
- byte[] dataToEncrypt =
- TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_UNENCRYPTED_DATA);
- byte[] iv = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_IV);
- byte[] padding = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_PADDING);
-
byte[] calculatedData =
IkeEncryptedPayloadBody.encrypt(
- dataToEncrypt, mAesCbcCipher, mAesCbcKey, iv, padding);
- byte[] expectedData =
- TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA);
+ mDataToPadAndEncrypt, mAesCbcCipher, mAesCbcKey, mIv, mPadding);
+
+ assertArrayEquals(mEncryptedPaddedData, calculatedData);
+ }
+
+ @Test
+ public void testDecrypt() throws Exception {
+ byte[] calculatedPlainText =
+ IkeEncryptedPayloadBody.decrypt(
+ mEncryptedPaddedData, mAesCbcCipher, mAesCbcKey, mIv);
- assertArrayEquals(expectedData, calculatedData);
+ assertArrayEquals(mDataToPadAndEncrypt, calculatedPlainText);
}
@Test
public void testBuildAndEncodeOutboundIkeEncryptedPayloadBody() throws Exception {
- byte[] ikeAndPayloadHeader =
- TestUtils.hexStringToByteArray(
- IKE_AUTH_INIT_REQUEST_HEADER + IKE_AUTH_INIT_REQUEST_SK_HEADER);
- byte[] unencryptedPayloads =
- TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_UNENCRYPTED_DATA);
- byte[] iv = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_IV);
- byte[] padding = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_PADDING);
+ IkeHeader ikeHeader = new IkeHeader(mIkeMessage);
IkeEncryptedPayloadBody paylaodBody =
new IkeEncryptedPayloadBody(
- ikeAndPayloadHeader,
- unencryptedPayloads,
+ ikeHeader,
+ IkePayload.PAYLOAD_TYPE_ID_INITIATOR,
+ mDataToPadAndEncrypt,
mHmacSha1IntegrityMac,
HMAC_SHA1_CHECKSUM_LEN,
mAesCbcCipher,
mAesCbcKey,
- iv,
- padding);
+ mIv,
+ mPadding);
byte[] expectedEncodedData =
TestUtils.hexStringToByteArray(
@@ -174,4 +216,17 @@ public final class IkeEncryptedPayloadBodyTest {
+ IKE_AUTH_INIT_REQUEST_CHECKSUM);
assertArrayEquals(expectedEncodedData, paylaodBody.encode());
}
+
+ @Test
+ public void testAuthenticateAndDecryptInboundIkeEncryptedPayloadBody() throws Exception {
+ IkeEncryptedPayloadBody paylaodBody =
+ new IkeEncryptedPayloadBody(
+ mIkeMessage,
+ mHmacSha1IntegrityMac,
+ HMAC_SHA1_CHECKSUM_LEN,
+ mAesCbcCipher,
+ mAesCbcKey);
+
+ assertArrayEquals(mDataToPadAndEncrypt, paylaodBody.getUnencryptedData());
+ }
}
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeHeaderTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeHeaderTest.java
index 1bc52c3b..08b1612b 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeHeaderTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeHeaderTest.java
@@ -62,6 +62,7 @@ public final class IkeHeaderTest {
private static final int IKE_MSG_ID = 0;
private static final int IKE_MSG_LENGTH = 336;
+ private static final int IKE_MSG_BODY_LENGTH = IKE_MSG_LENGTH - IkeHeader.IKE_HEADER_LENGTH;
// Byte offsets of version field in IKE message header.
private static final int VERSION_OFFSET = 17;
@@ -89,7 +90,7 @@ public final class IkeHeaderTest {
assertFalse(header.isResponseMsg);
assertTrue(header.fromIkeInitiator);
assertEquals(IKE_MSG_ID, header.messageId);
- assertEquals(IKE_MSG_LENGTH, header.messageLength);
+ assertEquals(IKE_MSG_LENGTH, header.getInboundMessageLength());
}
@Test
@@ -142,7 +143,7 @@ public final class IkeHeaderTest {
IkeHeader header = new IkeHeader(inputPacket);
ByteBuffer byteBuffer = ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH);
- header.encodeToByteBuffer(byteBuffer);
+ header.encodeToByteBuffer(byteBuffer, IKE_MSG_BODY_LENGTH);
byte[] expectedPacket = TestUtils.hexStringToByteArray(IKE_HEADER_HEX_STRING);
assertArrayEquals(expectedPacket, byteBuffer.array());
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeKePayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeKePayloadTest.java
index 4f45f26d..1bb0b709 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeKePayloadTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeKePayloadTest.java
@@ -18,11 +18,10 @@ package com.android.ike.ikev2.message;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.util.Pair;
-
import com.android.ike.ikev2.IkeDhParams;
import com.android.ike.ikev2.SaProposal;
import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
@@ -100,6 +99,7 @@ public final class IkeKePayloadTest {
IkeKePayload payload = new IkeKePayload(CRITICAL_BIT, inputPacket);
+ assertFalse(payload.isOutbound);
assertEquals(EXPECTED_DH_GROUP, payload.dhGroup);
byte[] keyExchangeData = TestUtils.hexStringToByteArray(KEY_EXCHANGE_DATA_RAW_PACKET);
@@ -138,11 +138,11 @@ public final class IkeKePayloadTest {
@Test
public void testGetIkeKePayload() throws Exception {
- Pair<DHPrivateKeySpec, IkeKePayload> pair =
- IkeKePayload.getKePayload(SaProposal.DH_GROUP_1024_BIT_MODP);
+ IkeKePayload payload = new IkeKePayload(SaProposal.DH_GROUP_1024_BIT_MODP);
// Test DHPrivateKeySpec
- DHPrivateKeySpec privateKeySpec = pair.first;
+ assertTrue(payload.isOutbound);
+ DHPrivateKeySpec privateKeySpec = payload.localPrivateKey;
BigInteger primeValue = privateKeySpec.getP();
BigInteger expectedPrimeValue = new BigInteger(IkeDhParams.PRIME_1024_BIT_MODP, 16);
@@ -153,8 +153,6 @@ public final class IkeKePayloadTest {
assertEquals(0, expectedGenValue.compareTo(genValue));
// Test IkeKePayload
- IkeKePayload payload = pair.second;
-
assertEquals(EXPECTED_DH_GROUP, payload.dhGroup);
assertEquals(EXPECTED_KE_DATA_LEN, payload.keyExchangeData.length);
}
diff --git a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSaPayloadTest.java b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSaPayloadTest.java
index b0d927d8..afb3a810 100644
--- a/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSaPayloadTest.java
+++ b/tests/iketests/src/java/com/android/ike/ikev2/message/IkeSaPayloadTest.java
@@ -16,6 +16,7 @@
package com.android.ike.ikev2.message;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -57,11 +58,14 @@ import java.util.List;
import java.util.Random;
public final class IkeSaPayloadTest {
- private static final String PROPOSAL_RAW_PACKET =
+ private static final String OUTBOUND_SA_PAYLOAD_HEADER = "22000030";
+ private static final String OUTBOUND_PROPOSAL_RAW_PACKET =
+ "0000002C010100040300000C0100000C800E0080030000080200000203000008030"
+ + "000020000000804000002";
+ private static final String INBOUND_PROPOSAL_RAW_PACKET =
"0000002c010100040300000c0100000c800e0080030000080300000203000008040"
+ "000020000000802000002";
-
- private static final String TWO_PROPOSAL_RAW_PACKET =
+ private static final String INBOUND_TWO_PROPOSAL_RAW_PACKET =
"020000dc010100190300000c0100000c800e00800300000c0100000c800e00c0030"
+ "0000c0100000c800e01000300000801000003030000080300000c0300"
+ "00080300000d030000080300000e03000008030000020300000803000"
@@ -91,16 +95,10 @@ public final class IkeSaPayloadTest {
private static final String ATTRIBUTE_RAW_PACKET = "800e0080";
private static final int PROPOSAL_NUMBER = 1;
- private static final int PROPOSAL_NUMBER_OFFSET = 4;
-
- @IkePayload.ProtocolId
- private static final int PROPOSAL_PROTOCOL_ID = IkePayload.PROTOCOL_ID_IKE;
+ private static final int PROPOSAL_NUMBER_OFFSET = 4;
private static final int PROTOCOL_ID_OFFSET = 5;
- private static final byte PROPOSAL_SPI_SIZE = 0;
- private static final byte PROPOSAL_SPI = 0;
-
// Constants for multiple proposals test
private static final byte[] PROPOSAL_NUMBER_LIST = {1, 2};
@@ -153,7 +151,7 @@ public final class IkeSaPayloadTest {
.addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
.addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
.addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1)
- .buildOrThrow();
+ .build();
mSaProposalTwo =
SaProposal.Builder.newIkeSaProposalBuilder()
@@ -166,7 +164,7 @@ public final class IkeSaPayloadTest {
.addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC)
.addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
.addDhGroup(SaProposal.DH_GROUP_2048_BIT_MODP)
- .buildOrThrow();
+ .build();
mTwoSaProposalsArray = new SaProposal[] {mSaProposalOne, mSaProposalTwo};
}
@@ -186,6 +184,16 @@ public final class IkeSaPayloadTest {
}
@Test
+ public void testEncodeAttribute() throws Exception {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(mAttributeKeyLength128.getAttributeLength());
+ mAttributeKeyLength128.encodeToByteBuffer(byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(ATTRIBUTE_RAW_PACKET);
+
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
public void testDecodeEncryptionTransform() throws Exception {
byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
@@ -221,6 +229,16 @@ public final class IkeSaPayloadTest {
}
@Test
+ public void testEncodeEncryptionTransform() throws Exception {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(mEncrAesCbc128Transform.getTransformLength());
+ mEncrAesCbc128Transform.encodeToByteBuffer(false, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
public void testConstructEncryptionTransformWithUnsupportedId() throws Exception {
try {
new EncryptionTransform(-1);
@@ -255,6 +273,16 @@ public final class IkeSaPayloadTest {
}
@Test
+ public void testEncodePrfTransform() throws Exception {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(mPrfHmacSha1Transform.getTransformLength());
+ mPrfHmacSha1Transform.encodeToByteBuffer(true, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(PRF_TRANSFORM_RAW_PACKET);
+
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
public void testConstructPrfTransformWithUnsupportedId() throws Exception {
try {
new PrfTransform(-1);
@@ -296,6 +324,16 @@ public final class IkeSaPayloadTest {
}
@Test
+ public void testEncodeIntegrityTransform() throws Exception {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(mIntegHmacSha1Transform.getTransformLength());
+ mIntegHmacSha1Transform.encodeToByteBuffer(false, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(INTEG_TRANSFORM_RAW_PACKET);
+
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
public void testConstructIntegrityTransformWithUnsupportedId() throws Exception {
try {
new IntegrityTransform(-1);
@@ -321,6 +359,16 @@ public final class IkeSaPayloadTest {
}
@Test
+ public void testEncodeDhGroupTransform() throws Exception {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(mDhGroup1024Transform.getTransformLength());
+ mDhGroup1024Transform.encodeToByteBuffer(false, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(DH_GROUP_TRANSFORM_RAW_PACKET);
+
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
public void testConstructDhGroupTransformWithUnsupportedId() throws Exception {
try {
new DhGroupTransform(-1);
@@ -378,6 +426,17 @@ public final class IkeSaPayloadTest {
}
@Test
+ public void testEncodeEsnTransform() throws Exception {
+ EsnTransform mEsnTransform = new EsnTransform();
+ ByteBuffer byteBuffer = ByteBuffer.allocate(mEsnTransform.getTransformLength());
+ mEsnTransform.encodeToByteBuffer(true, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(ESN_TRANSFORM_RAW_PACKET);
+
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
public void testDecodeUnrecognizedTransform() throws Exception {
byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
inputPacket[TRANSFORM_TYPE_OFFSET] = 6;
@@ -484,23 +543,23 @@ public final class IkeSaPayloadTest {
@Test
public void testDecodeSingleProposal() throws Exception {
- byte[] inputPacket = TestUtils.hexStringToByteArray(PROPOSAL_RAW_PACKET);
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
Proposal.sTransformDecoder = getDummyTransformDecoder(new Transform[0]);
Proposal proposal = Proposal.readFrom(inputBuffer);
assertEquals(PROPOSAL_NUMBER, proposal.number);
- assertEquals(PROPOSAL_PROTOCOL_ID, proposal.protocolId);
- assertEquals(PROPOSAL_SPI_SIZE, proposal.spiSize);
- assertEquals(PROPOSAL_SPI, proposal.spi);
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId);
+ assertEquals(IkePayload.SPI_LEN_NOT_INCLUDED, proposal.spiSize);
+ assertEquals(IkePayload.SPI_NOT_INCLUDED, proposal.spi);
assertFalse(proposal.hasUnrecognizedTransform);
assertNotNull(proposal.saProposal);
}
@Test
public void testDecodeSaRequestWithMultipleProposal() throws Exception {
- byte[] inputPacket = TestUtils.hexStringToByteArray(TWO_PROPOSAL_RAW_PACKET);
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_TWO_PROPOSAL_RAW_PACKET);
Proposal.sTransformDecoder = getDummyTransformDecoder(new Transform[0]);
IkeSaPayload payload = new IkeSaPayload(false, false, inputPacket);
@@ -515,8 +574,26 @@ public final class IkeSaPayloadTest {
}
@Test
+ public void testEncodeProposal() throws Exception {
+ Proposal proposal =
+ new Proposal(
+ (byte) PROPOSAL_NUMBER,
+ IkePayload.PROTOCOL_ID_IKE,
+ IkePayload.SPI_LEN_NOT_INCLUDED,
+ IkePayload.SPI_NOT_INCLUDED,
+ mSaProposalOne,
+ false /*has no unrecognized Tramsform*/);
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(proposal.getProposalLength());
+ proposal.encodeToByteBuffer(true /*is the last*/, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(OUTBOUND_PROPOSAL_RAW_PACKET);
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
public void testDecodeSaResponseWithMultipleProposal() throws Exception {
- byte[] inputPacket = TestUtils.hexStringToByteArray(TWO_PROPOSAL_RAW_PACKET);
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_TWO_PROPOSAL_RAW_PACKET);
Proposal.sTransformDecoder = getDummyTransformDecoder(new Transform[0]);
try {
@@ -561,6 +638,19 @@ public final class IkeSaPayloadTest {
}
}
+ @Test
+ public void testEncodeIkeSaPayload() throws Exception {
+ IkeSaPayload saPayload = new IkeSaPayload(new SaProposal[] {mSaProposalOne});
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(saPayload.getPayloadLength());
+ saPayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_KE, byteBuffer);
+
+ byte[] expectedBytes =
+ TestUtils.hexStringToByteArray(
+ OUTBOUND_SA_PAYLOAD_HEADER + OUTBOUND_PROPOSAL_RAW_PACKET);
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
private void buildAndVerifySaRespProposal(byte[] saResponseBytes, Transform[] decodedTransforms)
throws Exception {
// Build response SA payload from decoding bytes.
@@ -577,7 +667,7 @@ public final class IkeSaPayloadTest {
@Test
public void testGetVerifiedNegotiatedProposal() throws Exception {
- byte[] inputPacket = TestUtils.hexStringToByteArray(PROPOSAL_RAW_PACKET);
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
buildAndVerifySaRespProposal(inputPacket, mValidNegotiatedTransformSet);
}
@@ -585,7 +675,7 @@ public final class IkeSaPayloadTest {
// Test throwing when negotiated proposal in SA response payload has unrecognized Transform.
@Test
public void testGetVerifiedNegotiatedProposalWithUnrecogTransform() throws Exception {
- byte[] inputPacket = TestUtils.hexStringToByteArray(PROPOSAL_RAW_PACKET);
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
Transform[] negotiatedTransformSet =
Arrays.copyOfRange(
@@ -602,7 +692,7 @@ public final class IkeSaPayloadTest {
// Test throwing when negotiated proposal has invalid proposal number.
@Test
public void testGetVerifiedNegotiatedProposalWithInvalidNumber() throws Exception {
- byte[] inputPacket = TestUtils.hexStringToByteArray(PROPOSAL_RAW_PACKET);
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
inputPacket[PROPOSAL_NUMBER_OFFSET] = (byte) 10;
try {
@@ -615,7 +705,7 @@ public final class IkeSaPayloadTest {
// Test throwing when negotiated proposal has mismatched protocol ID.
@Test
public void testGetVerifiedNegotiatedProposalWithMisMatchedProtocol() throws Exception {
- byte[] inputPacket = TestUtils.hexStringToByteArray(PROPOSAL_RAW_PACKET);
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
inputPacket[PROTOCOL_ID_OFFSET] = IkePayload.PROTOCOL_ID_ESP;
try {
@@ -628,7 +718,7 @@ public final class IkeSaPayloadTest {
// Test throwing when negotiated proposal has Transform that was not proposed in request.
@Test
public void testGetVerifiedNegotiatedProposalWithMismatchedTransform() throws Exception {
- byte[] inputPacket = TestUtils.hexStringToByteArray(PROPOSAL_RAW_PACKET);
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
Transform[] negotiatedTransformSet =
Arrays.copyOfRange(
@@ -645,7 +735,7 @@ public final class IkeSaPayloadTest {
// Test throwing when negotiated proposal is lack of a certain type Transform.
@Test
public void testGetVerifiedNegotiatedProposalWithoutTransform() throws Exception {
- byte[] inputPacket = TestUtils.hexStringToByteArray(PROPOSAL_RAW_PACKET);
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
try {
buildAndVerifySaRespProposal(inputPacket, new Transform[0]);
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 aebd1194..13866fb6 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
@@ -17,13 +17,11 @@
package com.android.ike.ikev2.message;
import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.fail;
import org.junit.Before;
import org.junit.Test;
import java.nio.ByteBuffer;
-import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.crypto.Cipher;
@@ -82,44 +80,11 @@ public final class IkeSkPayloadTest {
}
@Test
- public void testAuthenticateAndDecryptMessage() throws Exception {
- byte[] message = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_HEX_STRING);
-
- IkeSkPayload payload =
- IkePayloadFactory.getIkeSkPayload(
- message,
- mHmacSha1IntegrityMac,
- CHECKSUM_LEN,
- mAesCbcDecryptCipher,
- mAesCbcDecryptKey)
- .first;
- byte[] expectedPlaintext =
- TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_DECRYPTED_BODY_HEX_STRING);
- assertArrayEquals(expectedPlaintext, payload.getUnencryptedPayloads());
- }
-
- @Test
- public void testThrowExceptionForInvalidChecksum() throws Exception {
- byte[] message = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_HEX_STRING);
- // Change last bit of checksum.
- message[message.length - 1]++;
- try {
- IkePayloadFactory.getIkeSkPayload(
- message,
- mHmacSha1IntegrityMac,
- CHECKSUM_LEN,
- mAesCbcDecryptCipher,
- mAesCbcDecryptKey);
- fail("Expected GeneralSecurityException: Invalid checksum.");
- } catch (GeneralSecurityException expected) {
- }
- }
-
- @Test
public void testEncode() throws Exception {
byte[] message = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_HEX_STRING);
byte[] payloadBytes =
Arrays.copyOfRange(message, IkeHeader.IKE_HEADER_LENGTH, message.length);
+
IkeSkPayload payload =
IkePayloadFactory.getIkeSkPayload(
message,