aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/com/android/ike/ikev2/IkeSessionStateMachine.java')
-rw-r--r--src/java/com/android/ike/ikev2/IkeSessionStateMachine.java952
1 files changed, 952 insertions, 0 deletions
diff --git a/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java b/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
new file mode 100644
index 00000000..b4a7b9c6
--- /dev/null
+++ b/src/java/com/android/ike/ikev2/IkeSessionStateMachine.java
@@ -0,0 +1,952 @@
+/*
+ * 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 android.os.Looper;
+import android.os.Message;
+import android.system.ErrnoException;
+import android.util.LongSparseArray;
+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.
+ *
+ * <p>IkeSessionStateMachine has two types of states. One type are states where there is no ongoing
+ * procedure affecting IKE session (non-procedure state), including Initial, Closed, Idle and
+ * Receiving. All other states are "procedure" states which are named as follows:
+ *
+ * <pre>
+ * State Name = [Procedure Type] + [Exchange Initiator] + [Exchange Type].
+ * - An IKE procedure consists of one or two IKE exchanges:
+ * Procedure Type = {CreateIke | DeleteIke | Info | RekeyIke | SimulRekeyIke}.
+ * - Exchange Initiator indicates whether local or remote peer is the exchange initiator:
+ * Exchange Initiator = {Local | Remote}
+ * - Exchange type defines the function of this exchange. To make it more descriptive, we separate
+ * Delete Exchange from generic Informational Exchange:
+ * Exchange Type = {IkeInit | IkeAuth | Create | Delete | Info}
+ * </pre>
+ */
+public class IkeSessionStateMachine extends StateMachine {
+
+ private static final String TAG = "IkeSessionStateMachine";
+
+ /** Package private signals accessible for testing code. */
+ private static final int CMD_GENERAL_BASE = 0;
+ /** Receive encoded IKE packet on IkeSessionStateMachine. */
+ static final int CMD_RECEIVE_IKE_PACKET = CMD_GENERAL_BASE + 1;
+ /** Receive locally built payloads from Child Session for building outbound IKE message. */
+ static final int CMD_RECEIVE_OUTBOUND_CHILD_PAYLOADS = CMD_GENERAL_BASE + 2;
+ /** Receive encoded IKE packet with unrecognized IKE SPI on IkeSessionStateMachine. */
+ static final int CMD_RECEIVE_PACKET_INVALID_IKE_SPI = CMD_GENERAL_BASE + 3;
+ // TODO: Add signal for retransmission.
+
+ private static final int CMD_LOCAL_REQUEST_BASE = CMD_GENERAL_BASE + 100;
+ static final int CMD_LOCAL_REQUEST_CREATE_IKE = CMD_LOCAL_REQUEST_BASE + 1;
+ static final int CMD_LOCAL_REQUEST_DELETE_IKE = CMD_LOCAL_REQUEST_BASE + 2;
+ static final int CMD_LOCAL_REQUEST_REKEY_IKE = CMD_LOCAL_REQUEST_BASE + 3;
+ static final int CMD_LOCAL_REQUEST_INFO = CMD_LOCAL_REQUEST_BASE + 4;
+ static final int CMD_LOCAL_REQUEST_CREATE_CHILD = CMD_LOCAL_REQUEST_BASE + 5;
+ static final int CMD_LOCAL_REQUEST_DELETE_CHILD = CMD_LOCAL_REQUEST_BASE + 6;
+ 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. */
+ private final LongSparseArray<IkeSaRecord> mSpiToSaRecordMap;
+ /**
+ * Map that stores all ChildSessionStateMachines, keyed by remotely generated Child SPI for
+ * sending IPsec packet. Different SPIs may point to the same ChildSessionStateMachine if this
+ * Child Session is doing Rekey.
+ */
+ 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 */
+ @VisibleForTesting IkeSaRecord mLocalInitNewIkeSaRecord;
+ /** Package */
+ @VisibleForTesting IkeSaRecord mRemoteInitNewIkeSaRecord;
+
+ /** Package */
+ @VisibleForTesting IkeSaRecord mIkeSaRecordSurviving;
+ /** Package */
+ @VisibleForTesting IkeSaRecord mIkeSaRecordAwaitingLocalDel;
+ /** Package */
+ @VisibleForTesting IkeSaRecord mIkeSaRecordAwaitingRemoteDel;
+
+ // States
+ private final State mInitial = new Initial();
+ private final State mClosed = new Closed();
+ private final State mIdle = new Idle();
+ private final State mReceiving = new Receiving();
+ private final State mCreateIkeLocalIkeInit = new CreateIkeLocalIkeInit();
+ private final State mCreateIkeLocalIkeAuth = new CreateIkeLocalIkeAuth();
+ private final State mRekeyIkeLocalCreate = new RekeyIkeLocalCreate();
+ private final State mSimulRekeyIkeLocalCreate = new SimulRekeyIkeLocalCreate();
+ private final State mSimulRekeyIkeLocalDeleteRemoteDelete =
+ new SimulRekeyIkeLocalDeleteRemoteDelete();
+ private final State mSimulRekeyIkeLocalDelete = new SimulRekeyIkeLocalDelete();
+ private final State mSimulRekeyIkeRemoteDelete = new SimulRekeyIkeRemoteDelete();
+ private final State mRekeyIkeLocalDelete = new RekeyIkeLocalDelete();
+ private final State mRekeyIkeRemoteDelete = new RekeyIkeRemoteDelete();
+ // TODO: Add InfoLocal and DeleteIkeLocal.
+
+ /** Package private constructor */
+ IkeSessionStateMachine(
+ String name,
+ Looper looper,
+ IkeSessionOptions ikeOptions,
+ ChildSessionOptions firstChildOptions) {
+ super(name, looper);
+ mIkeSessionOptions = ikeOptions;
+ mFirstChildSessionOptions = firstChildOptions;
+ // There are at most three IkeSaRecords co-existing during simultaneous rekeying.
+ mSpiToSaRecordMap = new LongSparseArray<>(3);
+ mSpiToChildSessionMap = new SparseArray<>();
+
+ addState(mInitial);
+ addState(mClosed);
+ addState(mCreateIkeLocalIkeInit);
+ addState(mCreateIkeLocalIkeAuth);
+ addState(mIdle);
+ addState(mReceiving);
+ addState(mRekeyIkeLocalCreate);
+ addState(mSimulRekeyIkeLocalCreate, mRekeyIkeLocalCreate);
+ addState(mSimulRekeyIkeLocalDeleteRemoteDelete);
+ addState(mSimulRekeyIkeLocalDelete, mSimulRekeyIkeLocalDeleteRemoteDelete);
+ addState(mSimulRekeyIkeRemoteDelete, mSimulRekeyIkeLocalDeleteRemoteDelete);
+ addState(mRekeyIkeLocalDelete);
+ addState(mRekeyIkeRemoteDelete);
+
+ 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: 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() {
+ // TODO: Build IKE_AUTH request according to mIkeSessionOptions and
+ // firstChildSessionOptions.
+ return null;
+ }
+
+ private IkeMessage buildIkeDeleteReq(IkeSaRecord ikeSaRecord) {
+ // TODO: Implement it.
+ return null;
+ }
+
+ private IkeMessage buildIkeDeleteResp(IkeSaRecord ikeSaRecord) {
+ // TODO: Implement it.
+ return null;
+ }
+
+ private IkeMessage buildIkeRekeyReq() {
+ // TODO: Implement it.
+ return null;
+ }
+
+ private IkeMessage buildIkeRekeyResp(IkeMessage reqMsg) {
+ // TODO: Implement it.
+ return null;
+ }
+
+ private void validateIkeInitResp(IkeMessage reqMsg, IkeMessage respMsg) throws IkeException {
+ // TODO: Validate ikeMessage against IKE_INIT request and set confiugration negotiation
+ // results
+ // in mIkeSessionOptions(e.g.NAT detecting result).
+ }
+
+ private void validateIkeAuthResp(IkeMessage reqMsg, IkeMessage respMsg) throws IkeException {
+ // TODO: Validate ikeMessage against IKE_AUTH request and mIkeSessionOptions.
+ }
+
+ private void validateIkeDeleteReq(IkeMessage ikeMessage) throws IkeException {
+ // TODO: Validate ikeMessage.
+ }
+
+ private void validateIkeDeleteResp(IkeMessage ikeMessage) throws IkeException {
+ // TODO: Validate ikeMessage.
+ }
+
+ private void validateIkeRekeyReq(IkeMessage ikeMessage) throws IkeException {
+ // TODO: Validate it againsr mIkeSessionOptions.
+ }
+
+ private void validateIkeRekeyResp(IkeMessage reqMsg, IkeMessage respMsg) throws IkeException {
+ // TODO: Validate ikeMessage against Rekey request.
+ }
+
+ // TODO: Add methods for building and validating general Informational packet.
+
+ private void addIkeSaRecord(IkeSaRecord record) {
+ mSpiToSaRecordMap.put(record.getRemoteSpi(), record);
+ }
+
+ private void removeIkeSaRecord(IkeSaRecord record) {
+ mSpiToSaRecordMap.remove(record.getRemoteSpi());
+ }
+
+ /**
+ * 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.
+ */
+ static class ReceivedIkePacket {
+ /** Decoded IKE header */
+ public final IkeHeader ikeHeader;
+ /** Entire encoded IKE message including IKE header */
+ public final byte[] ikePacketBytes;
+
+ ReceivedIkePacket(IkeHeader ikeHeader, byte[] ikePacketBytes) {
+ this.ikeHeader = ikeHeader;
+ this.ikePacketBytes = ikePacketBytes;
+ }
+ }
+
+ /**
+ * Interface for ChildSessionStateMachine to notify IkeSessionStateMachine.
+ *
+ * <p>Package private so as to be injectable for testing.
+ */
+ interface IChildSessionCallback {
+ /** Notify that new Child SA is created. */
+ void onCreateChildSa(int remoteSpi, ChildSessionStateMachine childSession);
+ /** Notify that the Child SA is deleted. */
+ void onDeleteChildSa(int remoteSpi);
+ // TODO: Add methods for handling errors and sending out locally built payloads.
+ }
+
+ /**
+ * Callback for ChildSessionStateMachine to notify IkeSessionStateMachine.
+ *
+ * <p>Package private for being passed to only ChildSessionStateMachine.
+ */
+ class ChildSessionCallback implements IChildSessionCallback {
+ public void onCreateChildSa(int remoteSpi, ChildSessionStateMachine childSession) {
+ mSpiToChildSessionMap.put(remoteSpi, childSession);
+ }
+
+ public void onDeleteChildSa(int remoteSpi) {
+ mSpiToChildSessionMap.remove(remoteSpi);
+ }
+ }
+
+ /** 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:
+ transitionTo(mCreateIkeLocalIkeInit);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ /**
+ * Closed represents the state when this IkeSessionStateMachine is closed, and no further
+ * actions can be performed on it.
+ */
+ class Closed extends State {
+ // TODO:Implement it.
+ }
+
+ /**
+ * Idle represents a state when there is no ongoing IKE exchange affecting established IKE SA.
+ */
+ class Idle extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_RECEIVE_IKE_PACKET:
+ deferMessage(message);
+ transitionTo(mReceiving);
+ return HANDLED;
+ case CMD_LOCAL_REQUEST_REKEY_IKE:
+ transitionTo(mRekeyIkeLocalCreate);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ // TODO: Add more cases for supporting local request.
+ }
+ }
+ }
+
+ /** Base state defines common behaviours when receiving an IKE packet. */
+ private abstract class BaseState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_RECEIVE_IKE_PACKET:
+ handleReceivedIkePacket(message);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ protected void handleReceivedIkePacket(Message message) {
+ ReceivedIkePacket receivedIkePacket = (ReceivedIkePacket) message.obj;
+ IkeHeader ikeHeader = receivedIkePacket.ikeHeader;
+ byte[] ikePacketBytes = receivedIkePacket.ikePacketBytes;
+ IkeSaRecord ikeSaRecord = getIkeSaRecordForPacket(ikeHeader);
+ try {
+ IkeMessage ikeMessage =
+ IkeMessage.decode(
+ mIkeSessionOptions, ikeSaRecord, ikeHeader, ikePacketBytes);
+ int messageType = ikeMessage.getMessageType();
+ // TODO: Handle fatal error notifications.
+ handleIkeMessage(ikeMessage, messageType, message);
+ } catch (IkeException e) {
+
+ } catch (GeneralSecurityException e) {
+ // IKE library failed on intergity checksum validation or on message decryption.
+ // TODO: Handle decrypting exception
+ }
+ }
+
+ // Default handler for decode errors in encrypted request.
+ protected void handleDecodingErrorInEncryptedRequest(
+ IkeException exception, IkeSaRecord ikeSaRecord) {
+ switch (exception.errorCode) {
+ case IkeNotifyPayload.NOTIFY_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD:
+ // TODO: Send encrypted error notification.
+ return;
+ case IkeNotifyPayload.NOTIFY_TYPE_INVALID_MAJOR_VERSION:
+ // TODO: Send unencrypted error notification.
+ return;
+ case IkeNotifyPayload.NOTIFY_TYPE_INVALID_SYNTAX:
+ // TODO: Send encrypted error notification and close IKE session if Message ID
+ // and cryptogtaphic checksum were invalid.
+ return;
+ default:
+ // Won't hit this case.
+ throw new UnsupportedOperationException("Unknown error decoding IKE Message.");
+ }
+ }
+
+ // Default handler for decode errors in encrypted responses.
+ // NOTE: The DeleteIkeLocal state MUST override this state to avoid the possibility of an
+ // infinite loop.
+ protected void handleDecodingErrorInEncryptedResponse(
+ IkeException exception, IkeSaRecord ikeSaRecord) {
+ // All errors in parsing or processing reponse packets should cause the IKE library to
+ // initiate a Delete IKE Exchange.
+
+ // TODO: Initiate Delete IKE Exchange
+ }
+
+ protected IkeSaRecord getIkeSaRecordForPacket(IkeHeader ikeHeader) {
+ if (ikeHeader.fromIkeInitiator) {
+ return mSpiToSaRecordMap.get(ikeHeader.ikeInitiatorSpi);
+ } else {
+ return mSpiToSaRecordMap.get(ikeHeader.ikeResponderSpi);
+ }
+ }
+
+ protected abstract void handleIkeMessage(
+ IkeMessage ikeMessage, int messageType, Message message);
+ }
+
+ /**
+ * Receiving represents a state when idle IkeSessionStateMachine receives an incoming packet.
+ */
+ class Receiving extends BaseState {
+ @Override
+ protected void handleIkeMessage(IkeMessage ikeMessage, int messageType, Message message) {
+ switch (messageType) {
+ case IkeMessage.MESSAGE_TYPE_REKEY_IKE_REQ:
+ try {
+ validateIkeRekeyReq(ikeMessage);
+ // Reply
+ IkeMessage responseIkeMessage = buildIkeRekeyResp(ikeMessage);
+ // TODO: Encode and send out responseIkeMessage
+
+ mRemoteInitNewIkeSaRecord =
+ IkeSaRecord.makeNewIkeSaRecord(
+ mCurrentIkeSaRecord, ikeMessage, responseIkeMessage);
+ addIkeSaRecord(mRemoteInitNewIkeSaRecord);
+ transitionTo(mRekeyIkeRemoteDelete);
+ } catch (IkeException e) {
+ // TODO: Handle processing errors.
+ }
+ return;
+ // TODO: Add more cases for supporting local request.
+ default:
+ }
+ }
+ }
+
+ /**
+ * LocalNewExchangeBase represents the common behaviours when IKE library initiates a new
+ * exchange.
+ */
+ private abstract class LocalNewExchangeBase extends BaseState {
+ protected IkeMessage mRequestMsg;
+ protected byte[] mRequestPacket;
+
+ @Override
+ public void enter() {
+ mRequestMsg = buildRequest();
+ mRequestPacket = encodeRequest();
+ mIkeSocket.sendIkePacket(mRequestPacket, mIkeSessionOptions.getServerAddress());
+ // TODO: Send out packet and start retransmission timer.
+ }
+
+ @Override
+ public void exit() {
+ // TODO: Stop retransmission
+ mRequestMsg = null;
+ mRequestPacket = null;
+ }
+
+ protected abstract IkeMessage buildRequest();
+
+ // CreateIkeLocalInit should override encodeRequest() to encode unencrypted packet
+ protected byte[] encodeRequest() {
+ // TODO: encrypt and encode mRequestMsg
+ 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();
+ }
+
+ @Override
+ protected byte[] encodeRequest() {
+ return mRequestMsg.encode();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_RECEIVE_IKE_PACKET:
+ handleReceivedIkePacket(message);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ protected void handleReceivedIkePacket(Message message) {
+ ReceivedIkePacket receivedIkePacket = (ReceivedIkePacket) message.obj;
+ IkeHeader ikeHeader = receivedIkePacket.ikeHeader;
+ byte[] ikePacketBytes = receivedIkePacket.ikePacketBytes;
+ try {
+ IkeMessage ikeMessage = IkeMessage.decode(ikeHeader, ikePacketBytes);
+ int messageType = ikeMessage.getMessageType();
+ // TODO: Handle fatal error notifications.
+ handleIkeMessage(ikeMessage, messageType, message);
+ } catch (IkeException e) {
+ // TODO:Since IKE_INIT is not protected, log and ignore this message.
+ }
+ }
+
+ @Override
+ protected void handleIkeMessage(IkeMessage ikeMessage, int messageType, Message message) {
+ switch (messageType) {
+ case IkeMessage.MESSAGE_TYPE_IKE_INIT_RESP:
+ try {
+ validateIkeInitResp(mRequestMsg, ikeMessage);
+ mCurrentIkeSaRecord =
+ IkeSaRecord.makeFirstIkeSaRecord(mRequestMsg, ikeMessage);
+ addIkeSaRecord(mCurrentIkeSaRecord);
+ transitionTo(mCreateIkeLocalIkeAuth);
+ } catch (IkeException e) {
+ // TODO: Handle processing errors.
+ }
+ return;
+ default:
+ // TODO: Handle unexpected message type.
+ }
+ }
+
+ @Override
+ public void exit() {
+ super.exit();
+ // TODO: Store IKE_INIT request and response in mIkeSessionOptions for IKE_AUTH
+ }
+ }
+
+ /** CreateIkeLocalIkeAuth represents state when IKE library initiates IKE_AUTH exchange. */
+ class CreateIkeLocalIkeAuth extends LocalNewExchangeBase {
+ @Override
+ protected IkeMessage buildRequest() {
+ return buildIkeAuthReq();
+ }
+
+ @Override
+ protected void handleIkeMessage(IkeMessage ikeMessage, int messageType, Message message) {
+ switch (messageType) {
+ // TODO: Handle EAP Authentication.
+ case IkeMessage.MESSAGE_TYPE_IKE_AUTH_RESP:
+ try {
+ validateIkeAuthResp(mRequestMsg, ikeMessage);
+
+ ChildSessionStateMachine firstChild =
+ ChildSessionStateMachineFactory.makeChildSessionStateMachine(
+ "ChildSessionStateMachine",
+ getHandler().getLooper(),
+ 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());
+
+ transitionTo(mIdle);
+ } catch (IkeException e) {
+ // TODO: Handle processing errors.
+ }
+ return;
+ default:
+ // TODO: Add more cases for other packet types (e.g. for receiving and sending
+ // EAP).
+ }
+ }
+ }
+
+ /** RekeyIkeLocalCreate represents state when IKE library initiates Rekey IKE exchange. */
+ class RekeyIkeLocalCreate extends LocalNewExchangeBase {
+ @Override
+ public IkeMessage buildRequest() {
+ return buildIkeRekeyReq();
+ }
+
+ @Override
+ protected void handleIkeMessage(IkeMessage ikeMessage, int messageType, Message message) {
+ switch (messageType) {
+ case IkeMessage.MESSAGE_TYPE_REKEY_IKE_RESP:
+ try {
+ handleRekeyResp(ikeMessage);
+ transitionTo(mRekeyIkeLocalDelete);
+ } catch (IkeException e) {
+ // TODO: Handle processing errors.
+ }
+ return;
+ case IkeMessage.MESSAGE_TYPE_REKEY_IKE_REQ:
+ try {
+ validateIkeRekeyReq(ikeMessage);
+ // Reply
+ IkeMessage responseIkeMessage = buildIkeRekeyResp(ikeMessage);
+ mRemoteInitNewIkeSaRecord =
+ IkeSaRecord.makeNewIkeSaRecord(
+ mCurrentIkeSaRecord, ikeMessage, responseIkeMessage);
+ addIkeSaRecord(mRemoteInitNewIkeSaRecord);
+ // TODO: Encode and send responseIkeMessage.
+
+ transitionTo(mSimulRekeyIkeLocalCreate);
+ } catch (IkeException e) {
+ // TODO: Handle processing errors.
+ }
+ return;
+ default:
+ // TODO: Add more cases for other packet types.
+ }
+ }
+
+ // Is also called by SimulRekeyIkeLocalCreate to handle incoming rekey response.
+ protected void handleRekeyResp(IkeMessage ikeMessage) throws IkeException {
+ validateIkeRekeyResp(mRequestMsg, ikeMessage);
+ mLocalInitNewIkeSaRecord =
+ IkeSaRecord.makeNewIkeSaRecord(mCurrentIkeSaRecord, mRequestMsg, ikeMessage);
+ addIkeSaRecord(mLocalInitNewIkeSaRecord);
+ // TODO: Stop retransmission
+ }
+ }
+
+ /**
+ * SimulRekeyIkeLocalCreate represents the state where IKE library has replied to rekey request
+ * sent from the remote and is waiting for a rekey response for a locally initiated rekey
+ * request.
+ *
+ * <p>SimulRekeyIkeLocalCreate extends RekeyIkeLocalCreate so that it can call super class to
+ * validate incoming rekey response against locally initiated rekey request.
+ */
+ class SimulRekeyIkeLocalCreate extends RekeyIkeLocalCreate {
+ @Override
+ public void enter() {
+ // Do not send request.
+ }
+
+ @Override
+ public IkeMessage buildRequest() {
+ throw new UnsupportedOperationException(
+ "Do not support sending request in " + getCurrentState().getName());
+ }
+
+ @Override
+ public void exit() {
+ // Do nothing.
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_RECEIVE_IKE_PACKET:
+ ReceivedIkePacket receivedIkePacket = (ReceivedIkePacket) message.obj;
+ IkeHeader ikeHeader = receivedIkePacket.ikeHeader;
+
+ if (mRemoteInitNewIkeSaRecord == getIkeSaRecordForPacket(ikeHeader)) {
+ deferMessage(message);
+ } else {
+ handleReceivedIkePacket(message);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ @Override
+ protected void handleIkeMessage(IkeMessage ikeMessage, int messageType, Message message) {
+ switch (messageType) {
+ case IkeMessage.MESSAGE_TYPE_DELETE_IKE_REQ:
+ deferMessage(message);
+ return;
+ case IkeMessage.MESSAGE_TYPE_REKEY_IKE_RESP:
+ try {
+ super.handleRekeyResp(ikeMessage);
+ transitionTo(mSimulRekeyIkeLocalDeleteRemoteDelete);
+ } catch (IkeException e) {
+ // TODO: Handle processing errors.
+ }
+ return;
+ default:
+ // TODO: Add more cases for other packet types.
+ }
+ }
+ }
+
+ /** RekeyIkeDeleteBase represents common behaviours of deleting stage during rekeying IKE SA. */
+ private abstract class RekeyIkeDeleteBase extends BaseState {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_RECEIVE_IKE_PACKET:
+ ReceivedIkePacket receivedIkePacket = (ReceivedIkePacket) message.obj;
+ IkeHeader ikeHeader = receivedIkePacket.ikeHeader;
+
+ // Request received on the new/surviving SA; treat it as acknowledgement that
+ // remote has successfully rekeyed.
+ if (mIkeSaRecordSurviving == getIkeSaRecordForPacket(ikeHeader)) {
+ deferMessage(message);
+ // TODO: Locally close old (and losing) IKE SAs.
+ finishRekey();
+ } else {
+ handleReceivedIkePacket(message);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ // TODO: Add more cases for other packet types.
+ }
+ }
+
+ protected void finishRekey() {
+ mCurrentIkeSaRecord = mIkeSaRecordSurviving;
+ mLocalInitNewIkeSaRecord = null;
+ mRemoteInitNewIkeSaRecord = null;
+
+ mIkeSaRecordSurviving = null;
+ mIkeSaRecordAwaitingLocalDel = null;
+ mIkeSaRecordAwaitingRemoteDel = null;
+ }
+ }
+
+ /**
+ * SimulRekeyIkeLocalDeleteRemoteDelete represents the deleting stage during simultaneous
+ * rekeying when IKE library is waiting for both a Delete request and a Delete response.
+ */
+ class SimulRekeyIkeLocalDeleteRemoteDelete extends RekeyIkeDeleteBase {
+ @Override
+ public void enter() {
+ // Detemine surviving IKE SA. According to RFC 7296: "The new IKE SA containing the
+ // lowest nonce SHOULD be deleted by the node that created it, and the other surviving
+ // new IKE SA MUST inherit all the Child SAs."
+ if (mLocalInitNewIkeSaRecord.compareTo(mRemoteInitNewIkeSaRecord) > 0) {
+ mIkeSaRecordSurviving = mLocalInitNewIkeSaRecord;
+ mIkeSaRecordAwaitingLocalDel = mCurrentIkeSaRecord;
+ mIkeSaRecordAwaitingRemoteDel = mRemoteInitNewIkeSaRecord;
+ } else {
+ mIkeSaRecordSurviving = mRemoteInitNewIkeSaRecord;
+ mIkeSaRecordAwaitingLocalDel = mLocalInitNewIkeSaRecord;
+ mIkeSaRecordAwaitingRemoteDel = mCurrentIkeSaRecord;
+ }
+ IkeMessage ikeMessage = buildIkeDeleteReq(mIkeSaRecordAwaitingLocalDel);
+ // TODO: Encode and send out delete request and start retransmission timer.
+ // TODO: Set timer awaiting for delete request.
+ }
+
+ @Override
+ protected void handleIkeMessage(IkeMessage ikeMessage, int messageType, Message message) {
+ IkeSaRecord ikeSaRecordForPacket = getIkeSaRecordForPacket(ikeMessage.ikeHeader);
+ switch (messageType) {
+ case IkeMessage.MESSAGE_TYPE_DELETE_IKE_REQ:
+ if (ikeSaRecordForPacket == mIkeSaRecordAwaitingRemoteDel) {
+ try {
+ validateIkeDeleteReq(ikeMessage);
+ IkeMessage respMsg = buildIkeDeleteResp(mIkeSaRecordAwaitingRemoteDel);
+ removeIkeSaRecord(mIkeSaRecordAwaitingRemoteDel);
+ // TODO: Encode and send response and close
+ // mIkeSaRecordAwaitingRemoteDel.
+ // TODO: Stop timer awating delete request.
+ transitionTo(mSimulRekeyIkeLocalDelete);
+ } catch (IkeException e) {
+ // TODO: Handle processing errors.
+ }
+ } else {
+ // TODO: The other side deletes wrong IKE SA and we should close whole IKE
+ // session.
+ }
+ return;
+ case IkeMessage.MESSAGE_TYPE_DELETE_IKE_RESP:
+ if (ikeSaRecordForPacket == mIkeSaRecordAwaitingLocalDel) {
+ try {
+ validateIkeDeleteResp(ikeMessage);
+ transitionTo(mSimulRekeyIkeRemoteDelete);
+ removeIkeSaRecord(mIkeSaRecordAwaitingLocalDel);
+ // TODO: Close mIkeSaRecordAwaitingLocalDel
+ // TODO: Stop retransmission timer
+ } catch (IkeException e) {
+ // TODO: Handle processing errors.
+ }
+ } else {
+ // TODO: Close whole IKE session
+ }
+ return;
+ default:
+ // TODO: Add more cases for other packet types.
+ }
+ }
+
+ @Override
+ public void exit() {
+ finishRekey();
+ // TODO: Stop retransmission timer and awaiting delete request timer.
+ }
+ }
+
+ /**
+ * SimulRekeyIkeLocalDelete represents the state when IKE library is waiting for a Delete
+ * response during simultaneous rekeying.
+ */
+ class SimulRekeyIkeLocalDelete extends RekeyIkeDeleteBase {
+ @Override
+ protected void handleIkeMessage(IkeMessage ikeMessage, int messageType, Message message) {
+ switch (messageType) {
+ case IkeMessage.MESSAGE_TYPE_DELETE_IKE_RESP:
+ try {
+ validateIkeDeleteResp(ikeMessage);
+ removeIkeSaRecord(mIkeSaRecordAwaitingLocalDel);
+ // TODO: Close mIkeSaRecordAwaitingLocalDel.
+ transitionTo(mIdle);
+ } catch (IkeException e) {
+ // TODO: Handle processing errors.
+ }
+ return;
+ default:
+ // TODO: Add more cases for other packet types.
+ }
+ }
+ }
+
+ /**
+ * SimulRekeyIkeRemoteDelete represents the state that waiting for a Delete request during
+ * simultaneous rekeying.
+ */
+ class SimulRekeyIkeRemoteDelete extends RekeyIkeDeleteBase {
+ // TODO: Implement methods for processing Delete response
+ @Override
+ protected void handleIkeMessage(IkeMessage ikeMessage, int messageType, Message message) {
+ switch (messageType) {
+ case IkeMessage.MESSAGE_TYPE_DELETE_IKE_REQ:
+ try {
+ validateIkeDeleteReq(ikeMessage);
+ IkeMessage respMsg = buildIkeDeleteResp(mIkeSaRecordAwaitingRemoteDel);
+ // TODO: Encode and send response and close mIkeSaRecordAwaitingRemoteDel
+ removeIkeSaRecord(mIkeSaRecordAwaitingRemoteDel);
+ transitionTo(mIdle);
+ } catch (IkeException e) {
+ // TODO: Handle processing errors.
+ }
+ return;
+ default:
+ // TODO: Add more cases for other packet types.
+ }
+ }
+ }
+
+ /**
+ * RekeyIkeLocalDelete represents the deleting stage when IKE library is initiating a Rekey
+ * procedure.
+ *
+ * <p>RekeyIkeLocalDelete and SimulRekeyIkeLocalDelete have same behaviours in processMessage().
+ * While RekeyIkeLocalDelete overrides enter() and exit() methods for initiating and finishing
+ * the deleting stage for IKE rekeying.
+ */
+ class RekeyIkeLocalDelete extends SimulRekeyIkeLocalDelete {
+ @Override
+ public void enter() {
+ mIkeSaRecordSurviving = mLocalInitNewIkeSaRecord;
+ mIkeSaRecordAwaitingLocalDel = mCurrentIkeSaRecord;
+ IkeMessage ikeMessage = buildIkeDeleteReq(mIkeSaRecordAwaitingLocalDel);
+ // TODO: Encode ikeMessage, send out packet and start retransmission timer.
+ }
+
+ @Override
+ public void exit() {
+ finishRekey();
+ // TODO: Stop retransmission.
+ }
+ }
+
+ /**
+ * RekeyIkeRemoteDelete represents the deleting stage when responding to a Rekey procedure.
+ *
+ * <p>RekeyIkeRemoteDelete and SimulRekeyIkeRemoteDelete have same behaviours in
+ * processMessage(). While RekeyIkeLocalDelete overrides enter() and exit() methods for waiting
+ * incoming delete request and for finishing the deleting stage for IKE rekeying.
+ */
+ class RekeyIkeRemoteDelete extends SimulRekeyIkeRemoteDelete {
+ @Override
+ public void enter() {
+ mIkeSaRecordSurviving = mRemoteInitNewIkeSaRecord;
+ mIkeSaRecordAwaitingRemoteDel = mCurrentIkeSaRecord;
+ // TODO: Set timer awaiting delete request.
+ }
+
+ @Override
+ public void exit() {
+ finishRekey();
+ // TODO: Stop timer awaiting delete request.
+ }
+ }
+}