blob: b4a7b9c63351148bc22543311605956f3de519fc [file] [log] [blame]
/*
* 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.
}
}
}