diff options
Diffstat (limited to 'src/java/com/android/ike/ikev2/IkeSessionStateMachine.java')
-rw-r--r-- | src/java/com/android/ike/ikev2/IkeSessionStateMachine.java | 952 |
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. + } + } +} |