blob: 4dcddec4f21dac79a84184a079c173a1f0d7f157 [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 com.android.ike.ikev2.message.IkeMessage;
import com.android.ike.ikev2.message.IkePayload;
import com.android.internal.annotations.VisibleForTesting;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* 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.
*/
public abstract class SaRecord {
private static ISaRecordHelper sSaRecordHelper = new SaRecordHelper();
public final byte[] nonceInitiator;
public final byte[] nonceResponder;
/** Package private */
SaRecord(byte[] nonceInit, byte[] nonceResp) {
nonceInitiator = nonceInit;
nonceResponder = nonceResp;
}
/**
* SaRecordHelper implements methods for constructing SaRecord.
*
* <p>Package private
*/
static class SaRecordHelper implements ISaRecordHelper {
@Override
public IkeSaRecord makeFirstIkeSaRecord(IkeMessage initRequest, IkeMessage initResponse) {
// TODO: Generate keying materials
return null;
}
@Override
public IkeSaRecord makeNewIkeSaRecord(
IkeSaRecord oldSaRecord, IkeMessage rekeyRequest, IkeMessage rekeyResponse) {
// TODO: Generate keying materials based on old SK_d
return null;
}
@Override
public ChildSaRecord makeChildSaRecord(
List<IkePayload> reqPayloads, List<IkePayload> respPayloads) {
// TODO: Calculate keys and build IpSecTransform.
return null;
}
}
/** Package private */
static void setSaRecordHelper(ISaRecordHelper helper) {
sSaRecordHelper = helper;
}
/** IkeSaRecord represents an IKE SA. */
public static class IkeSaRecord extends SaRecord implements Comparable<IkeSaRecord> {
/** SPI of IKE SA initiator */
public final long initiatorSpi;
/** SPI of IKE SA responder */
public final long responderSpi;
/** Flag indicates if this IKE SA is locally initiated */
public final boolean isLocalInit;
/** Package private */
IkeSaRecord(
long initSpi, long respSpi, boolean localInit, byte[] nonceInit, byte[] nonceResp) {
super(nonceInit, nonceResp);
initiatorSpi = initSpi;
responderSpi = respSpi;
isLocalInit = localInit;
// TODO: Impement constructor. There will be more input parameters.
}
/** Package private */
static IkeSaRecord makeFirstIkeSaRecord(IkeMessage initRequest, IkeMessage initResponse) {
return sSaRecordHelper.makeFirstIkeSaRecord(initRequest, initResponse);
}
/** Package private */
static IkeSaRecord makeNewIkeSaRecord(
IkeSaRecord oldSaRecord, IkeMessage rekeyRequest, IkeMessage rekeyResponse) {
return sSaRecordHelper.makeNewIkeSaRecord(oldSaRecord, rekeyRequest, rekeyResponse);
}
/** Package private */
long getRemoteSpi() {
if (isLocalInit) {
return responderSpi;
} else {
return initiatorSpi;
}
}
/**
* 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;
}
}
/** ChildSaRecord represents an Child SA. */
public static class ChildSaRecord extends SaRecord implements Comparable<ChildSaRecord> {
/** Locally generated SPI for receiving IPsec Packet. */
public final int inboundSpi;
/** Remotely generated SPI for sending IPsec Packet. */
public final int outboundSpi;
/** Package private */
ChildSaRecord(int inSpi, int outSpi, byte[] nonceInit, byte[] nonceResp) {
super(nonceInit, nonceResp);
inboundSpi = inSpi;
outboundSpi = outSpi;
// TODO: Impement constructor. Will be more input parameters.
}
/** Package private */
static ChildSaRecord makeChildSaRecord(
List<IkePayload> reqPayloads, List<IkePayload> respPayloads) {
return sSaRecordHelper.makeChildSaRecord(reqPayloads, respPayloads);
}
/**
* 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;
}
}
/**
* 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.
* @return ikeSaRecord for initial IKE SA.
*/
IkeSaRecord makeFirstIkeSaRecord(IkeMessage initRequest, IkeMessage initResponse);
/**
* Construct new IkeSaRecord when doing rekey.
*
* @param oldSaRecord old IKE SA
* @param rekeyRequest Rekey IKE request.
* @param rekeyResponse Rekey IKE response.
* @return ikeSaRecord for new IKE SA.
*/
IkeSaRecord makeNewIkeSaRecord(
IkeSaRecord oldSaRecord, IkeMessage rekeyRequest, IkeMessage rekeyResponse);
/**
* Construct ChildSaRecord and generate IpSecTransform pairs.
*
* @param reqPayloads payload list in request.
* @param respPayloads payload list in response.
* @return new Child SA.
*/
ChildSaRecord makeChildSaRecord(
List<IkePayload> reqPayloads, List<IkePayload> respPayloads);
}
/** Generate SKEYSEED using negotiated PRF. */
@VisibleForTesting
static byte[] generateSKeySeed(
String prfAlgorithm, byte[] nonceInit, byte[] nonceResp, byte[] sharedDhKey) {
try {
ByteBuffer keyBuffer = ByteBuffer.allocate(nonceInit.length + nonceResp.length);
keyBuffer.put(nonceInit).put(nonceResp);
SecretKeySpec prfKeySpec = new SecretKeySpec(keyBuffer.array(), prfAlgorithm);
Mac prfMac = Mac.getInstance(prfAlgorithm, IkeMessage.getSecurityProvider());
prfMac.init(prfKeySpec);
ByteBuffer sharedKeyBuffer = ByteBuffer.wrap(sharedDhKey);
prfMac.update(sharedKeyBuffer);
return prfMac.doFinal();
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new IllegalArgumentException("Failed to generate SKEYSEED", e);
}
}
/**
* Derives key materials using negotiated PRF.
*
* <p>prf+(K, S) outputs a pseudorandom stream by using negotiated PRF iteratively. In this way
* it can generate long enough keying material containing all the keys for this IKE/Child SA.
*
* @see <a href="https://tools.ietf.org/html/rfc7296#section-2.13">RFC 7296 nternet Key Exchange
* Protocol Version 2 (IKEv2) 2.13. Generating Keying Material </a>
*/
@VisibleForTesting
static byte[] generateKeyMat(
String prfAlgorithm, byte[] prfKey, byte[] dataToSign, int keyMaterialLen)
throws InvalidKeyException {
try {
SecretKeySpec prfKeySpec = new SecretKeySpec(prfKey, prfAlgorithm);
Mac prfMac = Mac.getInstance(prfAlgorithm, IkeMessage.getSecurityProvider());
ByteBuffer keyMatBuffer = ByteBuffer.allocate(keyMaterialLen);
byte[] previousMac = new byte[0];
final int padLen = 1;
byte padValue = 1;
while (keyMatBuffer.remaining() > 0) {
prfMac.init(prfKeySpec);
ByteBuffer dataToSignBuffer =
ByteBuffer.allocate(previousMac.length + dataToSign.length + padLen);
dataToSignBuffer.put(previousMac).put(dataToSign).put(padValue);
dataToSignBuffer.rewind();
prfMac.update(dataToSignBuffer);
previousMac = prfMac.doFinal();
keyMatBuffer.put(
previousMac, 0, Math.min(previousMac.length, keyMatBuffer.remaining()));
padValue++;
}
return keyMatBuffer.array();
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new IllegalArgumentException("Failed to generate keying material", e);
}
}
}