| /* |
| * 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. |
| } |
| } |
| } |