diff options
Diffstat (limited to 'src/java/com/android/internal/net/ipsec/ike/SaRecord.java')
-rw-r--r-- | src/java/com/android/internal/net/ipsec/ike/SaRecord.java | 1098 |
1 files changed, 1098 insertions, 0 deletions
diff --git a/src/java/com/android/internal/net/ipsec/ike/SaRecord.java b/src/java/com/android/internal/net/ipsec/ike/SaRecord.java new file mode 100644 index 00000000..035af175 --- /dev/null +++ b/src/java/com/android/internal/net/ipsec/ike/SaRecord.java @@ -0,0 +1,1098 @@ +/* + * 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.internal.net.ipsec.ike; + +import static android.net.ipsec.ike.IkeManager.getIkeLog; + +import android.annotation.Nullable; +import android.content.Context; +import android.net.IpSecManager; +import android.net.IpSecManager.ResourceUnavailableException; +import android.net.IpSecManager.SecurityParameterIndex; +import android.net.IpSecManager.SpiUnavailableException; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.net.IpSecTransform; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.ChildLocalRequest; +import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.LocalRequest; +import com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IkeSecurityParameterIndex; +import com.android.internal.net.ipsec.ike.crypto.IkeCipher; +import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity; +import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf; +import com.android.internal.net.ipsec.ike.message.IkeKePayload; +import com.android.internal.net.ipsec.ike.message.IkeMessage; +import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultPartial; +import com.android.internal.net.ipsec.ike.message.IkeNoncePayload; +import com.android.internal.net.ipsec.ike.message.IkePayload; + +import dalvik.system.CloseGuard; + +import java.io.IOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.List; + +/** + * SaRecord represents common information of an IKE SA and a Child SA. + * + * <p>When doing rekey, there can be multiple SAs in the same IkeSessionStateMachine or + * ChildSessionStateMachine, where they use same cryptographic algorithms but with different keys. + * We store cryptographic algorithms and unchanged SA configurations in IkeSessionOptions or + * ChildSessionOptions and store changed information including keys, SPIs, and nonces in SaRecord. + * + * <p>All keys are named by the key type plus the source of the traffic this key is protecting. For + * example, "mSkAi" represents the integrity key that protects traffic from the SA initiator to the + * SA responder. + * + * <p>Except for keys, all other paramters (SPIs, nonces and messages) are named by the creator. For + * example, "initSPI" represents a SPI that is created by the SA initiator. + */ +public abstract class SaRecord implements AutoCloseable { + private static ISaRecordHelper sSaRecordHelper = new SaRecordHelper(); + private static IIpSecTransformHelper sIpSecTransformHelper = new IpSecTransformHelper(); + + /** Flag indicates if this SA is locally initiated */ + public final boolean isLocalInit; + + public final byte[] nonceInitiator; + public final byte[] nonceResponder; + + private final byte[] mSkAi; + private final byte[] mSkAr; + private final byte[] mSkEi; + private final byte[] mSkEr; + + private final LocalRequest mFutureRekeyEvent; + + private final CloseGuard mCloseGuard = CloseGuard.get(); + + /** Package private */ + SaRecord( + boolean localInit, + byte[] nonceInit, + byte[] nonceResp, + byte[] skAi, + byte[] skAr, + byte[] skEi, + byte[] skEr, + LocalRequest futureRekeyEvent) { + isLocalInit = localInit; + nonceInitiator = nonceInit; + nonceResponder = nonceResp; + + mSkAi = skAi; + mSkAr = skAr; + mSkEi = skEi; + mSkEr = skEr; + + logKey("SK_ai", skAi); + logKey("SK_ar", skAr); + logKey("SK_ei", skEi); + logKey("SK_er", skEr); + + mFutureRekeyEvent = futureRekeyEvent; + + mCloseGuard.open("close"); + } + + private void logKey(String type, byte[] key) { + getIkeLog().d(getTag(), type + ": " + getIkeLog().pii(key)); + } + + protected abstract String getTag(); + + /** + * Get the integrity key for calculate integrity checksum for an outbound packet. + * + * @return the integrity key in a byte array, which will be empty if integrity algorithm is not + * used in this SA. + */ + public byte[] getOutboundIntegrityKey() { + return isLocalInit ? mSkAi : mSkAr; + } + + /** + * Get the integrity key to authenticate an inbound packet. + * + * @return the integrity key in a byte array, which will be empty if integrity algorithm is not + * used in this SA. + */ + public byte[] getInboundIntegrityKey() { + return isLocalInit ? mSkAr : mSkAi; + } + + /** + * Get the encryption key for protecting an outbound packet. + * + * @return the encryption key in a byte array. + */ + public byte[] getOutboundEncryptionKey() { + return isLocalInit ? mSkEi : mSkEr; + } + + /** + * Get the decryption key for an inbound packet. + * + * @return the decryption key in a byte array. + */ + public byte[] getInboundDecryptionKey() { + return isLocalInit ? mSkEr : mSkEi; + } + + /** Check that the SaRecord was closed properly. */ + @Override + protected void finalize() throws Throwable { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } + + @Override + public void close() { + mFutureRekeyEvent.cancel(); + } + + /** Package private */ + LocalRequest getFutureRekeyEvent() { + return mFutureRekeyEvent; + } + + /** Package private */ + @VisibleForTesting + static void setSaRecordHelper(ISaRecordHelper helper) { + sSaRecordHelper = helper; + } + + /** Package private */ + @VisibleForTesting + static void setIpSecTransformHelper(IIpSecTransformHelper helper) { + sIpSecTransformHelper = helper; + } + + /** + * SaRecordHelper implements methods for constructing SaRecord. + * + * <p>Package private + */ + static class SaRecordHelper implements ISaRecordHelper { + @Override + public IkeSaRecord makeFirstIkeSaRecord( + IkeMessage initRequest, + IkeMessage initResponse, + IkeSaRecordConfig ikeSaRecordConfig) + throws GeneralSecurityException { + // Extract nonces + byte[] nonceInit = + initRequest.getPayloadForType( + IkePayload.PAYLOAD_TYPE_NONCE, IkeNoncePayload.class) + .nonceData; + byte[] nonceResp = + initResponse.getPayloadForType( + IkePayload.PAYLOAD_TYPE_NONCE, IkeNoncePayload.class) + .nonceData; + + // Get SKEYSEED + byte[] sharedDhKey = getSharedKey(initRequest, initResponse); + byte[] sKeySeed = + ikeSaRecordConfig.prf.generateSKeySeed(nonceInit, nonceResp, sharedDhKey); + + return makeIkeSaRecord(sKeySeed, nonceInit, nonceResp, ikeSaRecordConfig); + } + + @Override + public IkeSaRecord makeRekeyedIkeSaRecord( + IkeSaRecord oldSaRecord, + IkeMacPrf oldPrf, + IkeMessage rekeyRequest, + IkeMessage rekeyResponse, + IkeSaRecordConfig ikeSaRecordConfig) + throws GeneralSecurityException { + // Extract nonces + byte[] nonceInit = + rekeyRequest.getPayloadForType( + IkePayload.PAYLOAD_TYPE_NONCE, IkeNoncePayload.class) + .nonceData; + byte[] nonceResp = + rekeyResponse.getPayloadForType( + IkePayload.PAYLOAD_TYPE_NONCE, IkeNoncePayload.class) + .nonceData; + + // Get SKEYSEED + IkeMessage localMsg = ikeSaRecordConfig.isLocalInit ? rekeyRequest : rekeyResponse; + IkeMessage remoteMsg = ikeSaRecordConfig.isLocalInit ? rekeyResponse : rekeyRequest; + + byte[] sharedDhKey = getSharedKey(localMsg, remoteMsg); + byte[] sKeySeed = + oldPrf.generateRekeyedSKeySeed( + oldSaRecord.mSkD, nonceInit, nonceResp, sharedDhKey); + + return makeIkeSaRecord(sKeySeed, nonceInit, nonceResp, ikeSaRecordConfig); + } + + private byte[] getSharedKey(IkeMessage keLocalMessage, IkeMessage keRemoteMessage) + throws GeneralSecurityException { + IkeKePayload keLocalPayload = + keLocalMessage.getPayloadForType( + IkePayload.PAYLOAD_TYPE_KE, IkeKePayload.class); + IkeKePayload keRemotePayload = + keRemoteMessage.getPayloadForType( + IkePayload.PAYLOAD_TYPE_KE, IkeKePayload.class); + + return IkeKePayload.getSharedKey( + keLocalPayload.localPrivateKey, keRemotePayload.keyExchangeData); + } + + /** + * Package private method for calculating keys and construct IkeSaRecord. + * + * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.13">RFC 7296, Internet Key + * Exchange Protocol Version 2 (IKEv2), Generating Keying Material</a> + */ + @VisibleForTesting + IkeSaRecord makeIkeSaRecord( + byte[] sKeySeed, + byte[] nonceInit, + byte[] nonceResp, + IkeSaRecordConfig ikeSaRecordConfig) { + // Build data to sign for generating the keying material. + ByteBuffer bufferToSign = + ByteBuffer.allocate( + nonceInit.length + nonceResp.length + 2 * IkePayload.SPI_LEN_IKE); + + IkeSecurityParameterIndex initSpi = ikeSaRecordConfig.initSpi; + IkeSecurityParameterIndex respSpi = ikeSaRecordConfig.respSpi; + IkeMacPrf prf = ikeSaRecordConfig.prf; + int integrityKeyLength = ikeSaRecordConfig.integrityKeyLength; + int encryptionKeyLength = ikeSaRecordConfig.encryptionKeyLength; + + bufferToSign + .put(nonceInit) + .put(nonceResp) + .putLong(initSpi.getSpi()) + .putLong(respSpi.getSpi()); + + // Get length of the keying material according to RFC 7296, 2.13 and 2.14. The length of + // SK_D is always equal to the length of PRF key. + int skDLength = prf.getKeyLength(); + int keyMaterialLen = + skDLength + + 2 * integrityKeyLength + + 2 * encryptionKeyLength + + 2 * prf.getKeyLength(); + byte[] keyMat = prf.generateKeyMat(sKeySeed, bufferToSign.array(), keyMaterialLen); + + // Extract keys. + byte[] skD = new byte[skDLength]; + byte[] skAi = new byte[integrityKeyLength]; + byte[] skAr = new byte[integrityKeyLength]; + byte[] skEi = new byte[encryptionKeyLength]; + byte[] skEr = new byte[encryptionKeyLength]; + byte[] skPi = new byte[prf.getKeyLength()]; + byte[] skPr = new byte[prf.getKeyLength()]; + + ByteBuffer keyMatBuffer = ByteBuffer.wrap(keyMat); + keyMatBuffer.get(skD).get(skAi).get(skAr).get(skEi).get(skEr).get(skPi).get(skPr); + return new IkeSaRecord( + initSpi, + respSpi, + ikeSaRecordConfig.isLocalInit, + nonceInit, + nonceResp, + skD, + skAi, + skAr, + skEi, + skEr, + skPi, + skPr, + ikeSaRecordConfig.futureRekeyEvent); + } + + @Override + public ChildSaRecord makeChildSaRecord( + List<IkePayload> reqPayloads, + List<IkePayload> respPayloads, + ChildSaRecordConfig childSaRecordConfig) + throws GeneralSecurityException, ResourceUnavailableException, + SpiUnavailableException, IOException { + // Extract nonces. Encoding/Decoding of payload list guarantees that there is only one + // nonce payload in the reqPayloads and respPayloads lists + byte[] nonceInit = + IkePayload.getPayloadForTypeInProvidedList( + IkePayload.PAYLOAD_TYPE_NONCE, + IkeNoncePayload.class, + reqPayloads) + .nonceData; + byte[] nonceResp = + IkePayload.getPayloadForTypeInProvidedList( + IkePayload.PAYLOAD_TYPE_NONCE, + IkeNoncePayload.class, + respPayloads) + .nonceData; + + // Check if KE Payload exists and get DH shared key. Encoding/Decoding of payload list + // guarantees that there is either no KE payload in the reqPayloads and respPayloads + // lists, or only one KE payload in each list. + byte[] sharedDhKey = new byte[0]; + IkeKePayload keInitPayload = + IkePayload.getPayloadForTypeInProvidedList( + IkePayload.PAYLOAD_TYPE_KE, IkeKePayload.class, reqPayloads); + if (keInitPayload != null) { + IkeKePayload keRespPayload = + IkePayload.getPayloadForTypeInProvidedList( + IkePayload.PAYLOAD_TYPE_KE, IkeKePayload.class, respPayloads); + sharedDhKey = + IkeKePayload.getSharedKey( + keInitPayload.localPrivateKey, keRespPayload.keyExchangeData); + } + + return makeChildSaRecord(sharedDhKey, nonceInit, nonceResp, childSaRecordConfig); + } + /** + * Package private method for calculating keys, build IpSecTransforms and construct + * ChildSaRecord. + * + * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.17">RFC 7296, Internet Key + * Exchange Protocol Version 2 (IKEv2), Generating Keying Material for Child SAs</a> + */ + @VisibleForTesting + ChildSaRecord makeChildSaRecord( + byte[] sharedKey, + byte[] nonceInit, + byte[] nonceResp, + ChildSaRecordConfig childSaRecordConfig) + throws ResourceUnavailableException, SpiUnavailableException, IOException { + // Build data to sign for generating the keying material. + ByteBuffer bufferToSign = + ByteBuffer.allocate(sharedKey.length + nonceInit.length + nonceResp.length); + bufferToSign.put(sharedKey).put(nonceInit).put(nonceResp); + + // Get length of the keying material according to RFC 7296, 2.17. + int encryptionKeyLength = childSaRecordConfig.encryptionAlgo.getKeyLength(); + int integrityKeyLength = + childSaRecordConfig.hasIntegrityAlgo + ? childSaRecordConfig.integrityAlgo.getKeyLength() + : 0; + int keyMaterialLen = 2 * encryptionKeyLength + 2 * integrityKeyLength; + byte[] keyMat = + childSaRecordConfig.ikePrf.generateKeyMat( + childSaRecordConfig.skD, bufferToSign.array(), keyMaterialLen); + + // Extract keys according to the order that keys carrying data from initiator to + // responder are taken before keys for the other direction and encryption keys are taken + // before integrity keys. + byte[] skEi = new byte[encryptionKeyLength]; + byte[] skAi = new byte[integrityKeyLength]; + byte[] skEr = new byte[encryptionKeyLength]; + byte[] skAr = new byte[integrityKeyLength]; + + ByteBuffer keyMatBuffer = ByteBuffer.wrap(keyMat); + keyMatBuffer.get(skEi).get(skAi).get(skEr).get(skAr); + + // IpSecTransform for traffic from the initiator + IpSecTransform initTransform = null; + // IpSecTransform for traffic from the responder + IpSecTransform respTransform = null; + try { + // Build IpSecTransform + initTransform = + sIpSecTransformHelper.makeIpSecTransform( + childSaRecordConfig.context, + childSaRecordConfig.initAddress /*source address*/, + childSaRecordConfig.udpEncapSocket, + childSaRecordConfig.respSpi /*destination SPI*/, + childSaRecordConfig.integrityAlgo, + childSaRecordConfig.encryptionAlgo, + skAi, + skEi, + childSaRecordConfig.isTransport); + respTransform = + sIpSecTransformHelper.makeIpSecTransform( + childSaRecordConfig.context, + childSaRecordConfig.respAddress /*source address*/, + childSaRecordConfig.udpEncapSocket, + childSaRecordConfig.initSpi /*destination SPI*/, + childSaRecordConfig.integrityAlgo, + childSaRecordConfig.encryptionAlgo, + skAr, + skEr, + childSaRecordConfig.isTransport); + + int initSpi = childSaRecordConfig.initSpi.getSpi(); + int respSpi = childSaRecordConfig.respSpi.getSpi(); + + boolean isLocalInit = childSaRecordConfig.isLocalInit; + int inSpi = isLocalInit ? initSpi : respSpi; + int outSpi = isLocalInit ? respSpi : initSpi; + IpSecTransform inTransform = isLocalInit ? respTransform : initTransform; + IpSecTransform outTransform = isLocalInit ? initTransform : respTransform; + + return new ChildSaRecord( + inSpi, + outSpi, + isLocalInit, + nonceInit, + nonceResp, + skAi, + skAr, + skEi, + skEr, + inTransform, + outTransform, + childSaRecordConfig.futureRekeyEvent); + + } catch (Exception e) { + if (initTransform != null) initTransform.close(); + if (respTransform != null) respTransform.close(); + throw e; + } + } + } + + /** + * IpSecTransformHelper implements the IIpSecTransformHelper interface for constructing {@link + * IpSecTransform}}. + * + * <p>Package private + */ + static class IpSecTransformHelper implements IIpSecTransformHelper { + private static final String TAG = "IpSecTransformHelper"; + + @Override + public IpSecTransform makeIpSecTransform( + Context context, + InetAddress sourceAddress, + UdpEncapsulationSocket udpEncapSocket, + IpSecManager.SecurityParameterIndex spi, + @Nullable IkeMacIntegrity integrityAlgo, + IkeCipher encryptionAlgo, + byte[] integrityKey, + byte[] encryptionKey, + boolean isTransport) + throws ResourceUnavailableException, SpiUnavailableException, IOException { + IpSecTransform.Builder builder = new IpSecTransform.Builder(context); + + if (encryptionAlgo.isAead()) { + builder.setAuthenticatedEncryption( + encryptionAlgo.buildIpSecAlgorithmWithKey(encryptionKey)); + } else { + builder.setEncryption(encryptionAlgo.buildIpSecAlgorithmWithKey(encryptionKey)); + builder.setAuthentication(integrityAlgo.buildIpSecAlgorithmWithKey(integrityKey)); + } + + if (udpEncapSocket != null && sourceAddress instanceof Inet6Address) { + getIkeLog().wtf(TAG, "Kernel does not support UDP encapsulation for IPv6 SAs"); + } + if (udpEncapSocket != null && sourceAddress instanceof Inet4Address) { + builder.setIpv4Encapsulation(udpEncapSocket, IkeSocket.IKE_SERVER_PORT); + } + + if (isTransport) { + return builder.buildTransportModeTransform(sourceAddress, spi); + } else { + return builder.buildTunnelModeTransform(sourceAddress, spi); + } + } + } + + /** Package private class to group parameters for building a ChildSaRecord. */ + @VisibleForTesting + static final class ChildSaRecordConfig { + public final Context context; + public final SecurityParameterIndex initSpi; + public final SecurityParameterIndex respSpi; + public final InetAddress initAddress; + public final InetAddress respAddress; + @Nullable public final UdpEncapsulationSocket udpEncapSocket; + public final IkeMacPrf ikePrf; + @Nullable public final IkeMacIntegrity integrityAlgo; + public final IkeCipher encryptionAlgo; + public final byte[] skD; + public final boolean isTransport; + public final boolean isLocalInit; + public final boolean hasIntegrityAlgo; + public final ChildLocalRequest futureRekeyEvent; + + ChildSaRecordConfig( + Context context, + SecurityParameterIndex initSpi, + SecurityParameterIndex respSpi, + InetAddress localAddress, + InetAddress remoteAddress, + @Nullable UdpEncapsulationSocket udpEncapSocket, + IkeMacPrf ikePrf, + @Nullable IkeMacIntegrity integrityAlgo, + IkeCipher encryptionAlgo, + byte[] skD, + boolean isTransport, + boolean isLocalInit, + ChildLocalRequest futureRekeyEvent) { + this.context = context; + this.initSpi = initSpi; + this.respSpi = respSpi; + this.initAddress = isLocalInit ? localAddress : remoteAddress; + this.respAddress = isLocalInit ? remoteAddress : localAddress; + this.udpEncapSocket = udpEncapSocket; + this.ikePrf = ikePrf; + this.integrityAlgo = integrityAlgo; + this.encryptionAlgo = encryptionAlgo; + this.skD = skD; + this.isTransport = isTransport; + this.isLocalInit = isLocalInit; + hasIntegrityAlgo = (integrityAlgo != null); + this.futureRekeyEvent = futureRekeyEvent; + } + } + + /** IkeSaRecord represents an IKE SA. */ + public static class IkeSaRecord extends SaRecord implements Comparable<IkeSaRecord> { + private static final String TAG = "IkeSaRecord"; + + /** SPI of IKE SA initiator */ + private final IkeSecurityParameterIndex mInitiatorSpiResource; + /** SPI of IKE SA responder */ + private final IkeSecurityParameterIndex mResponderSpiResource; + + private final byte[] mSkD; + private final byte[] mSkPi; + private final byte[] mSkPr; + + private int mLocalRequestMessageId; + private int mRemoteRequestMessageId; + + private DecodeResultPartial mCollectedReqFragments; + private DecodeResultPartial mCollectedRespFragments; + + private byte[] mLastRecivedReqFirstPacket; + private List<byte[]> mLastSentRespAllPackets; + + /** Package private */ + IkeSaRecord( + IkeSecurityParameterIndex initSpi, + IkeSecurityParameterIndex respSpi, + boolean localInit, + byte[] nonceInit, + byte[] nonceResp, + byte[] skD, + byte[] skAi, + byte[] skAr, + byte[] skEi, + byte[] skEr, + byte[] skPi, + byte[] skPr, + LocalRequest futureRekeyEvent) { + super(localInit, nonceInit, nonceResp, skAi, skAr, skEi, skEr, futureRekeyEvent); + + mInitiatorSpiResource = initSpi; + mResponderSpiResource = respSpi; + + mSkD = skD; + mSkPi = skPi; + mSkPr = skPr; + + mLocalRequestMessageId = 0; + mRemoteRequestMessageId = 0; + + mCollectedReqFragments = null; + mCollectedRespFragments = null; + + logKey("SK_d", skD); + logKey("SK_pi", skPi); + logKey("SK_pr", skPr); + } + + /** + * Package private interface for IkeSessionStateMachien to construct an IkeSaRecord + * instance. + */ + static IkeSaRecord makeFirstIkeSaRecord( + IkeMessage initRequest, + IkeMessage initResponse, + IkeSecurityParameterIndex initSpi, + IkeSecurityParameterIndex respSpi, + IkeMacPrf prf, + int integrityKeyLength, + int encryptionKeyLength, + LocalRequest futureRekeyEvent) + throws GeneralSecurityException { + return sSaRecordHelper.makeFirstIkeSaRecord( + initRequest, + initResponse, + new IkeSaRecordConfig( + initSpi, + respSpi, + prf, + integrityKeyLength, + encryptionKeyLength, + true /*isLocalInit*/, + futureRekeyEvent)); + } + + /** Package private */ + static IkeSaRecord makeRekeyedIkeSaRecord( + IkeSaRecord oldSaRecord, + IkeMacPrf oldPrf, + IkeMessage rekeyRequest, + IkeMessage rekeyResponse, + IkeSecurityParameterIndex initSpi, + IkeSecurityParameterIndex respSpi, + IkeMacPrf prf, + int integrityKeyLength, + int encryptionKeyLength, + boolean isLocalInit, + LocalRequest futureRekeyEvent) + throws GeneralSecurityException { + return sSaRecordHelper.makeRekeyedIkeSaRecord( + oldSaRecord, + oldPrf, + rekeyRequest, + rekeyResponse, + new IkeSaRecordConfig( + initSpi, + respSpi, + prf, + integrityKeyLength, + encryptionKeyLength, + isLocalInit, + futureRekeyEvent)); + } + + private void logKey(String type, byte[] key) { + getIkeLog().d(TAG, type + ": " + getIkeLog().pii(key)); + } + + @Override + protected String getTag() { + return TAG; + } + + /** Package private */ + long getInitiatorSpi() { + return mInitiatorSpiResource.getSpi(); + } + + /** Package private */ + long getResponderSpi() { + return mResponderSpiResource.getSpi(); + } + + /** Package private */ + long getLocalSpi() { + return isLocalInit ? mInitiatorSpiResource.getSpi() : mResponderSpiResource.getSpi(); + } + + /** Package private */ + long getRemoteSpi() { + return isLocalInit ? mResponderSpiResource.getSpi() : mInitiatorSpiResource.getSpi(); + } + + /** Package private */ + byte[] getSkD() { + return mSkD; + } + + /** + * Get the PRF key of IKE initiator for building an outbound Auth Payload. + * + * @return the PRF key in a byte array. + */ + public byte[] getSkPi() { + return mSkPi; + } + + /** + * Get the PRF key of IKE responder for validating an inbound Auth Payload. + * + * @return the PRF key in a byte array. + */ + public byte[] getSkPr() { + return mSkPr; + } + + /** + * Compare with a specific IkeSaRecord + * + * @param record IkeSaRecord to be compared. + * @return a negative integer if input IkeSaRecord contains lowest nonce; a positive integer + * if this IkeSaRecord has lowest nonce; return zero if lowest nonces of two + * IkeSaRecords match. + */ + public int compareTo(IkeSaRecord record) { + // TODO: Implement it b/122924815. + return 1; + } + + /** + * Get current message ID for the local requesting window. + * + * <p>Called for building an outbound request or for validating the message ID of an inbound + * response. + * + * @return the local request message ID. + */ + public int getLocalRequestMessageId() { + return mLocalRequestMessageId; + } + + /** + * Get current message ID for the remote requesting window. + * + * <p>Called for validating the message ID of an inbound request. If the message ID of the + * inbound request is smaller than the current remote message ID by one, it means the + * message is a retransmitted request. + * + * @return the remote request message ID + */ + public int getRemoteRequestMessageId() { + return mRemoteRequestMessageId; + } + + /** + * Increment the local request message ID by one. + * + * <p>It should be called when IKE library has received an authenticated and protected + * response with the correct local request message ID. + */ + public void incrementLocalRequestMessageId() { + mLocalRequestMessageId++; + } + + /** + * Increment the remote request message ID by one. + * + * <p>It should be called when IKE library has received an authenticated and protected + * request with the correct remote request message ID. + */ + public void incrementRemoteRequestMessageId() { + mRemoteRequestMessageId++; + } + + /** Return all collected IKE fragments that have been collected. */ + public DecodeResultPartial getCollectedFragments(boolean isResp) { + return isResp ? mCollectedRespFragments : mCollectedReqFragments; + } + + /** + * Update collected IKE fragments when receiving new IKE fragment. + * + * <p>TODO: b/140264067 Investigate if we need to support reassembling timeout. It is safe + * to do not support it because as an initiator, we will re-transmit the request anyway. As + * a responder, caching these fragments until getting a complete message won't affect + * anything. + */ + public void updateCollectedFragments( + DecodeResultPartial updatedFragments, boolean isResp) { + if (isResp) { + mCollectedRespFragments = updatedFragments; + } else { + mCollectedReqFragments = updatedFragments; + } + } + + /** Reset collected IKE fragemnts */ + public void resetCollectedFragments(boolean isResp) { + updateCollectedFragments(null, isResp); + } + + /** Update first packet of last received request. */ + public void updateLastReceivedReqFirstPacket(byte[] reqPacket) { + mLastRecivedReqFirstPacket = reqPacket; + } + + /** Update all packets of last sent response. */ + public void updateLastSentRespAllPackets(List<byte[]> respPacketList) { + mLastSentRespAllPackets = respPacketList; + } + + /** Returns if received IKE packet is the first packet of a re-transmistted request. */ + public boolean isRetransmittedRequest(byte[] request) { + return Arrays.equals(mLastRecivedReqFirstPacket, request); + } + + /** Get all encoded packets of last sent response. */ + public List<byte[]> getLastSentRespAllPackets() { + return mLastSentRespAllPackets; + } + + /** Release IKE SPI resource. */ + @Override + public void close() { + super.close(); + mInitiatorSpiResource.close(); + mResponderSpiResource.close(); + } + } + + /** Package private class that groups parameters to construct an IkeSaRecord instance. */ + @VisibleForTesting + static class IkeSaRecordConfig { + public final IkeSecurityParameterIndex initSpi; + public final IkeSecurityParameterIndex respSpi; + public final IkeMacPrf prf; + public final int integrityKeyLength; + public final int encryptionKeyLength; + public final boolean isLocalInit; + public final LocalRequest futureRekeyEvent; + + IkeSaRecordConfig( + IkeSecurityParameterIndex initSpi, + IkeSecurityParameterIndex respSpi, + IkeMacPrf prf, + int integrityKeyLength, + int encryptionKeyLength, + boolean isLocalInit, + LocalRequest futureRekeyEvent) { + this.initSpi = initSpi; + this.respSpi = respSpi; + this.prf = prf; + this.integrityKeyLength = integrityKeyLength; + this.encryptionKeyLength = encryptionKeyLength; + this.isLocalInit = isLocalInit; + this.futureRekeyEvent = futureRekeyEvent; + } + } + + /** ChildSaRecord represents an Child SA. */ + public static class ChildSaRecord extends SaRecord implements Comparable<ChildSaRecord> { + private static final String TAG = "ChildSaRecord"; + + /** Locally generated SPI for receiving IPsec Packet. */ + private final int mInboundSpi; + /** Remotely generated SPI for sending IPsec Packet. */ + private final int mOutboundSpi; + + /** IPsec Transform applied to traffic towards the host. */ + private final IpSecTransform mInboundTransform; + /** IPsec Transform applied to traffic from the host. */ + private final IpSecTransform mOutboundTransform; + + /** Package private */ + ChildSaRecord( + int inSpi, + int outSpi, + boolean localInit, + byte[] nonceInit, + byte[] nonceResp, + byte[] skAi, + byte[] skAr, + byte[] skEi, + byte[] skEr, + IpSecTransform inTransform, + IpSecTransform outTransform, + ChildLocalRequest futureRekeyEvent) { + super(localInit, nonceInit, nonceResp, skAi, skAr, skEi, skEr, futureRekeyEvent); + + mInboundSpi = inSpi; + mOutboundSpi = outSpi; + mInboundTransform = inTransform; + mOutboundTransform = outTransform; + } + + /** + * Package private interface for ChildSessionStateMachine to construct a ChildSaRecord + * instance. + */ + static ChildSaRecord makeChildSaRecord( + Context context, + List<IkePayload> reqPayloads, + List<IkePayload> respPayloads, + SecurityParameterIndex initSpi, + SecurityParameterIndex respSpi, + InetAddress localAddress, + InetAddress remoteAddress, + @Nullable UdpEncapsulationSocket udpEncapSocket, + IkeMacPrf prf, + @Nullable IkeMacIntegrity integrityAlgo, + IkeCipher encryptionAlgo, + byte[] skD, + boolean isTransport, + boolean isLocalInit, + ChildLocalRequest futureRekeyEvent) + throws GeneralSecurityException, ResourceUnavailableException, + SpiUnavailableException, IOException { + return sSaRecordHelper.makeChildSaRecord( + reqPayloads, + respPayloads, + new ChildSaRecordConfig( + context, + initSpi, + respSpi, + localAddress, + remoteAddress, + udpEncapSocket, + prf, + integrityAlgo, + encryptionAlgo, + skD, + isTransport, + isLocalInit, + futureRekeyEvent)); + } + + @Override + protected String getTag() { + return TAG; + } + + /** Package private */ + int getLocalSpi() { + return mInboundSpi; + } + + /** Package private */ + int getRemoteSpi() { + return mOutboundSpi; + } + + /** Package private */ + IpSecTransform getInboundIpSecTransform() { + return mInboundTransform; + } + + /** Package private */ + IpSecTransform getOutboundIpSecTransform() { + return mOutboundTransform; + } + + /** + * Compare with a specific ChildSaRecord + * + * @param record ChildSaRecord to be compared. + * @return a negative integer if input ChildSaRecord contains lowest nonce; a positive + * integer if this ChildSaRecord has lowest nonce; return zero if lowest nonces of two + * ChildSaRecord match. + */ + public int compareTo(ChildSaRecord record) { + // TODO: Implement it b/122924815 + return 1; + } + + /** Release IpSecTransform pair. */ + @Override + public void close() { + super.close(); + mInboundTransform.close(); + mOutboundTransform.close(); + } + } + + /** + * ISaRecordHelper provides a package private interface for constructing SaRecord. + * + * <p>ISaRecordHelper exists so that the interface is injectable for testing. + */ + interface ISaRecordHelper { + /** + * Construct IkeSaRecord as results of IKE initial exchange. + * + * @param initRequest IKE_INIT request. + * @param initResponse IKE_INIT request. + * @param ikeSaRecordConfig that contains IKE SPI resources and negotiated algorithm + * information for constructing an IkeSaRecord instance. + * @return ikeSaRecord for initial IKE SA. + * @throws GeneralSecurityException if the DH public key in the response is invalid. + */ + IkeSaRecord makeFirstIkeSaRecord( + IkeMessage initRequest, + IkeMessage initResponse, + IkeSaRecordConfig ikeSaRecordConfig) + throws GeneralSecurityException; + + /** + * Construct new IkeSaRecord when doing rekey. + * + * @param oldSaRecord old IKE SA + * @param oldPrf the PRF function from the old SA + * @param rekeyRequest Rekey IKE request. + * @param rekeyResponse Rekey IKE response. + * @param ikeSaRecordConfig that contains IKE SPI resources and negotiated algorithm + * information for constructing an IkeSaRecord instance. + * @return ikeSaRecord for new IKE SA. + */ + IkeSaRecord makeRekeyedIkeSaRecord( + IkeSaRecord oldSaRecord, + IkeMacPrf oldPrf, + IkeMessage rekeyRequest, + IkeMessage rekeyResponse, + IkeSaRecordConfig ikeSaRecordConfig) + throws GeneralSecurityException; + + /** + * Construct ChildSaRecord and generate IpSecTransform pairs. + * + * @param reqPayloads payload list in request. + * @param respPayloads payload list in response. + * @param childSaRecordConfig the grouped parameters for constructing ChildSaRecord. + * @return new Child SA. + */ + ChildSaRecord makeChildSaRecord( + List<IkePayload> reqPayloads, + List<IkePayload> respPayloads, + ChildSaRecordConfig childSaRecordConfig) + throws GeneralSecurityException, ResourceUnavailableException, + SpiUnavailableException, IOException; + } + + /** + * IIpSecTransformHelper provides a package private interface to construct {@link + * IpSecTransform} + * + * <p>IIpSecTransformHelper exists so that the interface is injectable for testing. + */ + @VisibleForTesting + interface IIpSecTransformHelper { + /** + * Construct an instance of {@link IpSecTransform} + * + * @param context current context + * @param sourceAddress the source {@code InetAddress} of traffic on sockets of interfaces + * that will use this transform + * @param udpEncapSocket the UDP-Encap socket that allows IpSec traffic to pass through a + * NAT. Null if no NAT exists. + * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed + * traffic + * @param integrityAlgo specifying the authentication algorithm to be applied. + * @param encryptionAlgo specifying the encryption algorithm or authenticated encryption + * algorithm to be applied. + * @param integrityKey the negotiated authentication key to be applied. + * @param encryptionKey the negotiated encryption key to be applied. + * @param isTransport the flag indicates if a transport or a tunnel mode transform will be + * built. + * @return an instance of {@link IpSecTransform} + * @throws ResourceUnavailableException indicating that too many transforms are active + * @throws SpiUnavailableException indicating the rare case where an SPI collides with an + * existing transform + * @throws IOException indicating other errors + */ + IpSecTransform makeIpSecTransform( + Context context, + InetAddress sourceAddress, + UdpEncapsulationSocket udpEncapSocket, + IpSecManager.SecurityParameterIndex spi, + @Nullable IkeMacIntegrity integrityAlgo, + IkeCipher encryptionAlgo, + byte[] integrityKey, + byte[] encryptionKey, + boolean isTransport) + throws ResourceUnavailableException, SpiUnavailableException, IOException; + } +} |