blob: 53206cc96f63d6589f22a7641898827822a429b3 [file] [log] [blame]
/*
* Copyright (C) 2018 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.annotation.IntDef;
import android.util.ArraySet;
import com.android.ike.ikev2.message.IkePayload;
import com.android.ike.ikev2.message.IkeSaPayload.DhGroupTransform;
import com.android.ike.ikev2.message.IkeSaPayload.EncryptionTransform;
import com.android.ike.ikev2.message.IkeSaPayload.EsnTransform;
import com.android.ike.ikev2.message.IkeSaPayload.IntegrityTransform;
import com.android.ike.ikev2.message.IkeSaPayload.PrfTransform;
import com.android.ike.ikev2.message.IkeSaPayload.Transform;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* SaProposal represents a user configured set contains cryptograhic algorithms and key generating
* materials for negotiating an IKE or Child SA.
*
* <p>User must provide at least a valid SaProposal when they are creating a new IKE SA or Child SA.
*
* @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key Exchange
* Protocol Version 2 (IKEv2)</a>
*/
public final class SaProposal {
@Retention(RetentionPolicy.SOURCE)
@IntDef({
ENCRYPTION_ALGORITHM_3DES,
ENCRYPTION_ALGORITHM_AES_CBC,
ENCRYPTION_ALGORITHM_AES_GCM_8,
ENCRYPTION_ALGORITHM_AES_GCM_12,
ENCRYPTION_ALGORITHM_AES_GCM_16
})
public @interface EncryptionAlgorithm {}
public static final int ENCRYPTION_ALGORITHM_3DES = 3;
public static final int ENCRYPTION_ALGORITHM_AES_CBC = 12;
public static final int ENCRYPTION_ALGORITHM_AES_GCM_8 = 18;
public static final int ENCRYPTION_ALGORITHM_AES_GCM_12 = 19;
public static final int ENCRYPTION_ALGORITHM_AES_GCM_16 = 20;
private static final Set<Integer> SUPPORTED_ENCRYPTION_ALGORITHM;
static {
SUPPORTED_ENCRYPTION_ALGORITHM = new ArraySet<>();
SUPPORTED_ENCRYPTION_ALGORITHM.add(ENCRYPTION_ALGORITHM_3DES);
SUPPORTED_ENCRYPTION_ALGORITHM.add(ENCRYPTION_ALGORITHM_AES_CBC);
SUPPORTED_ENCRYPTION_ALGORITHM.add(ENCRYPTION_ALGORITHM_AES_GCM_8);
SUPPORTED_ENCRYPTION_ALGORITHM.add(ENCRYPTION_ALGORITHM_AES_GCM_12);
SUPPORTED_ENCRYPTION_ALGORITHM.add(ENCRYPTION_ALGORITHM_AES_GCM_16);
}
public static final int KEY_LEN_AES_128 = 128;
public static final int KEY_LEN_AES_192 = 192;
public static final int KEY_LEN_AES_256 = 256;
@Retention(RetentionPolicy.SOURCE)
@IntDef({PSEUDORANDOM_FUNCTION_HMAC_SHA1, PSEUDORANDOM_FUNCTION_AES128_XCBC})
public @interface PseudorandomFunction {}
public static final int PSEUDORANDOM_FUNCTION_HMAC_SHA1 = 2;
public static final int PSEUDORANDOM_FUNCTION_AES128_XCBC = 4;
private static final Set<Integer> SUPPORTED_PSEUDORANDOM_FUNCTION;
static {
SUPPORTED_PSEUDORANDOM_FUNCTION = new ArraySet<>();
SUPPORTED_PSEUDORANDOM_FUNCTION.add(PSEUDORANDOM_FUNCTION_HMAC_SHA1);
SUPPORTED_PSEUDORANDOM_FUNCTION.add(PSEUDORANDOM_FUNCTION_AES128_XCBC);
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({
INTEGRITY_ALGORITHM_NONE,
INTEGRITY_ALGORITHM_HMAC_SHA1_96,
INTEGRITY_ALGORITHM_AES_XCBC_96,
INTEGRITY_ALGORITHM_HMAC_SHA2_256_128,
INTEGRITY_ALGORITHM_HMAC_SHA2_384_192,
INTEGRITY_ALGORITHM_HMAC_SHA2_512_256
})
public @interface IntegrityAlgorithm {}
public static final int INTEGRITY_ALGORITHM_NONE = 0;
public static final int INTEGRITY_ALGORITHM_HMAC_SHA1_96 = 2;
public static final int INTEGRITY_ALGORITHM_AES_XCBC_96 = 5;
public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_256_128 = 12;
public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_384_192 = 13;
public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_512_256 = 14;
private static final Set<Integer> SUPPORTED_INTEGRITY_ALGORITHM;
static {
SUPPORTED_INTEGRITY_ALGORITHM = new ArraySet<>();
SUPPORTED_INTEGRITY_ALGORITHM.add(INTEGRITY_ALGORITHM_NONE);
SUPPORTED_INTEGRITY_ALGORITHM.add(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
SUPPORTED_INTEGRITY_ALGORITHM.add(INTEGRITY_ALGORITHM_AES_XCBC_96);
SUPPORTED_INTEGRITY_ALGORITHM.add(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
SUPPORTED_INTEGRITY_ALGORITHM.add(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
SUPPORTED_INTEGRITY_ALGORITHM.add(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({DH_GROUP_NONE, DH_GROUP_1024_BIT_MODP, DH_GROUP_2048_BIT_MODP})
public @interface DhGroup {}
public static final int DH_GROUP_NONE = 0;
public static final int DH_GROUP_1024_BIT_MODP = 2;
public static final int DH_GROUP_2048_BIT_MODP = 14;
private static final Set<Integer> SUPPORTED_DH_GROUP;
static {
SUPPORTED_DH_GROUP = new ArraySet<>();
SUPPORTED_DH_GROUP.add(DH_GROUP_NONE);
SUPPORTED_DH_GROUP.add(DH_GROUP_1024_BIT_MODP);
SUPPORTED_DH_GROUP.add(DH_GROUP_2048_BIT_MODP);
}
/** Package private */
@IkePayload.ProtocolId private final int mProtocolId;
/** Package private */
private final EncryptionTransform[] mEncryptionAlgorithms;
/** Package private */
private final PrfTransform[] mPseudorandomFunctions;
/** Package private */
private final IntegrityTransform[] mIntegrityAlgorithms;
/** Package private */
private final DhGroupTransform[] mDhGroups;
/** Package private */
private final EsnTransform[] mEsns;
private SaProposal(
@IkePayload.ProtocolId int protocol,
EncryptionTransform[] encryptionAlgos,
PrfTransform[] prfs,
IntegrityTransform[] integrityAlgos,
DhGroupTransform[] dhGroups) {
mProtocolId = protocol;
mEncryptionAlgorithms = encryptionAlgos;
mPseudorandomFunctions = prfs;
mIntegrityAlgorithms = integrityAlgos;
mDhGroups = dhGroups;
if (protocol == IkePayload.PROTOCOL_ID_IKE) {
// Do not negotiate ESN for IKE SA proposal
mEsns = new EsnTransform[0];
} else {
// Do not support negotiating Child SAs using extended sequence numbers.
mEsns = new EsnTransform[] {new EsnTransform()};
}
}
/**
* Construct SaProposal from a decoded inbound IKE packet, only called by IkeSaPayload.
*
* @param protocol IP protocol ID
* @param encryptionAlgos encryption algorithms decoded from inbound IKE packet.
* @param prfs pseudorandom functions decoded from inbound IKE packet.
* @param integrityAlgos integrity algorithms decoded from inbound IKE packet.
* @param dhGroups Dh groups decoded from inbound IKE packet.
* @param esns ESN policies decoded from IKE packet.
*/
public SaProposal(
@IkePayload.ProtocolId int protocol,
EncryptionTransform[] encryptionAlgos,
PrfTransform[] prfs,
IntegrityTransform[] integrityAlgos,
DhGroupTransform[] dhGroups,
EsnTransform[] esns) {
mProtocolId = protocol;
mEncryptionAlgorithms = encryptionAlgos;
mPseudorandomFunctions = prfs;
mIntegrityAlgorithms = integrityAlgos;
mDhGroups = dhGroups;
mEsns = esns;
}
/**
* Check if the current SaProposal from the SA responder is consistent with the selected
* reqProposal from the SA initiator.
*
* @param reqProposal selected SaProposal from SA initiator
* @return if current SaProposal from SA responder is consistent with the selected reqProposal
* from SA initiator.
*/
public boolean isNegotiatedFrom(SaProposal reqProposal) {
return isTransformSelectedFrom(mEncryptionAlgorithms, reqProposal.mEncryptionAlgorithms)
&& isTransformSelectedFrom(
mPseudorandomFunctions, reqProposal.mPseudorandomFunctions)
&& isTransformSelectedFrom(mIntegrityAlgorithms, reqProposal.mIntegrityAlgorithms)
&& isTransformSelectedFrom(mDhGroups, reqProposal.mDhGroups)
&& isTransformSelectedFrom(mEsns, reqProposal.mEsns);
}
/** Package private */
static boolean isTransformSelectedFrom(Transform[] selected, Transform[] selectFrom) {
// If the selected proposal has multiple transforms with the same type, the responder MUST
// choose a single one.
if ((selected.length > 1) || (selected.length == 0) != (selectFrom.length == 0)) {
return false;
}
if (selected.length == 0) return true;
return Arrays.asList(selectFrom).contains(selected[0]);
}
/*Package private*/
@IkePayload.ProtocolId
int getProtocolId() {
return mProtocolId;
}
/*Package private*/
EncryptionTransform[] getEncryptionTransforms() {
return mEncryptionAlgorithms;
}
/*Package private*/
PrfTransform[] getPrfTransforms() {
return mPseudorandomFunctions;
}
/*Package private*/
IntegrityTransform[] getIntegrityTransforms() {
return mIntegrityAlgorithms;
}
/*Package private*/
DhGroupTransform[] getDhGroupTransforms() {
return mDhGroups;
}
/*Package private*/
EsnTransform[] getEsnTransforms() {
return mEsns;
}
/**
* Return all SA Transforms in this SaProposal to be encoded for building an outbound IKE
* message.
*
* <p>This method can be called by only IKE library.
*
* @return Array of Transforms to be encoded.
*/
public Transform[] getAllTransforms() {
int encodedNumTransforms =
mEncryptionAlgorithms.length
+ mPseudorandomFunctions.length
+ mIntegrityAlgorithms.length
+ mDhGroups.length
+ mEsns.length;
List<Transform> transformList = new ArrayList<Transform>(encodedNumTransforms);
transformList.addAll(Arrays.asList(mEncryptionAlgorithms));
transformList.addAll(Arrays.asList(mPseudorandomFunctions));
transformList.addAll(Arrays.asList(mIntegrityAlgorithms));
transformList.addAll(Arrays.asList(mDhGroups));
transformList.addAll(Arrays.asList(mEsns));
return transformList.toArray(new Transform[encodedNumTransforms]);
}
/**
* This class can be used to incrementally construct a SaProposal. SaProposal instances are
* immutable once built.
*
* <p>TODO: Support users to add algorithms from most preferred to least preferred.
*/
public static final class Builder {
private static final String ERROR_TAG = "Invalid SA Proposal: ";
/** Indicate if Builder is for building IKE SA proposal or Child SA proposal. */
private final boolean mIsIkeProposal;
/**
* Indicate if Builder is for building first Child SA proposal or addtional Child SA
* proposal. Only valid if mIsIkeProposal is false.
*/
private final boolean mIsFirstChild;
// Use set to avoid adding repeated algorithms.
private final Set<EncryptionTransform> mProposedEncryptAlgos = new ArraySet<>();
private final Set<PrfTransform> mProposedPrfs = new ArraySet<>();
private final Set<IntegrityTransform> mProposedIntegrityAlgos = new ArraySet<>();
private final Set<DhGroupTransform> mProposedDhGroups = new ArraySet<>();
private boolean mHasAead = false;
private Builder(boolean isIke, boolean isFirstChild) {
mIsIkeProposal = isIke;
mIsFirstChild = isFirstChild;
}
private static boolean isAead(@EncryptionAlgorithm int algorithm) {
switch (algorithm) {
case ENCRYPTION_ALGORITHM_3DES:
// Fall through
case ENCRYPTION_ALGORITHM_AES_CBC:
return false;
case ENCRYPTION_ALGORITHM_AES_GCM_8:
// Fall through
case ENCRYPTION_ALGORITHM_AES_GCM_12:
// Fall through
case ENCRYPTION_ALGORITHM_AES_GCM_16:
return true;
default:
// Won't hit here.
throw new IllegalArgumentException("Unsupported Encryption Algorithm.");
}
}
private EncryptionTransform[] buildEncryptAlgosOrThrow() {
if (mProposedEncryptAlgos.isEmpty()) {
throw new IllegalArgumentException(
ERROR_TAG + "Encryption algorithm must be proposed.");
}
return mProposedEncryptAlgos.toArray(
new EncryptionTransform[mProposedEncryptAlgos.size()]);
}
private PrfTransform[] buildPrfsOrThrow() {
if (mIsIkeProposal == mProposedPrfs.isEmpty()) {
throw new IllegalArgumentException(
ERROR_TAG + "Invalid PRF configuration for this SA Proposal.");
}
return mProposedPrfs.toArray(new PrfTransform[mProposedPrfs.size()]);
}
private IntegrityTransform[] buildIntegAlgosForIkeOrThrow() {
// When building IKE SA Proposal with normal-mode ciphers, mProposedIntegrityAlgos must
// not be empty and must not have INTEGRITY_ALGORITHM_NONE. When building IKE SA
// Proposal with combined-mode ciphers, mProposedIntegrityAlgos must be either empty or
// only have INTEGRITY_ALGORITHM_NONE.
if (mProposedIntegrityAlgos.isEmpty() && !mHasAead) {
throw new IllegalArgumentException(
ERROR_TAG
+ "Integrity algorithm "
+ "must be proposed with normal ciphers in IKE proposal.");
}
for (IntegrityTransform transform : mProposedIntegrityAlgos) {
if ((transform.id == INTEGRITY_ALGORITHM_NONE) != mHasAead) {
throw new IllegalArgumentException(
ERROR_TAG
+ "Invalid integrity algorithm configuration"
+ " for this SA Proposal");
}
}
return mProposedIntegrityAlgos.toArray(
new IntegrityTransform[mProposedIntegrityAlgos.size()]);
}
private IntegrityTransform[] buildIntegAlgosForChildOrThrow() {
// When building Child SA Proposal with normal-mode ciphers, there is no contraint on
// integrity algorithm. When building Child SA Proposal with combined-mode ciphers,
// mProposedIntegrityAlgos must be either empty or only have INTEGRITY_ALGORITHM_NONE.
for (IntegrityTransform transform : mProposedIntegrityAlgos) {
if (transform.id != INTEGRITY_ALGORITHM_NONE && mHasAead) {
throw new IllegalArgumentException(
ERROR_TAG
+ "Only INTEGRITY_ALGORITHM_NONE can be"
+ " proposed with combined-mode ciphers in any proposal.");
}
}
return mProposedIntegrityAlgos.toArray(
new IntegrityTransform[mProposedIntegrityAlgos.size()]);
}
private DhGroupTransform[] buildDhGroupsForIkeOrThrow() {
if (mProposedDhGroups.isEmpty()) {
throw new IllegalArgumentException(
ERROR_TAG + "DH group must be proposed in IKE SA proposal.");
}
for (DhGroupTransform transform : mProposedDhGroups) {
if (transform.id == DH_GROUP_NONE) {
throw new IllegalArgumentException(
ERROR_TAG
+ "None-value DH group must not"
+ " be proposed in IKE SA proposal");
}
}
return mProposedDhGroups.toArray(new DhGroupTransform[mProposedDhGroups.size()]);
}
private DhGroupTransform[] buildDhGroupsForChildOrThrow() {
for (DhGroupTransform transform : mProposedDhGroups) {
if (transform.id != DH_GROUP_NONE && mIsFirstChild) {
throw new IllegalArgumentException(
ERROR_TAG
+ "Only DH_GROUP_NONE can be"
+ " proposed in first Child SA proposal.");
}
}
return mProposedDhGroups.toArray(new DhGroupTransform[mProposedDhGroups.size()]);
}
/** Returns a new Builder for a IKE SA Proposal. */
public static Builder newIkeSaProposalBuilder() {
return new Builder(true, false);
}
/**
* Returns a new Builder for a Child SA Proposal.
*
* @param isFirstChildSaProposal indicates if this SA proposal for first Child SA.
* @return Builder for a Child SA Proposal.
*/
public static Builder newChildSaProposalBuilder(boolean isFirstChildSaProposal) {
return new Builder(false, isFirstChildSaProposal);
}
/**
* Adds an encryption algorithm to SA proposal being built.
*
* @param algorithm encryption algorithm to add to SaProposal.
* @return Builder of SaProposal.
*/
public Builder addEncryptionAlgorithm(@EncryptionAlgorithm int algorithm) {
// Construct EncryptionTransform and validate proposed algorithm during
// construction.
EncryptionTransform encryptionTransform = new EncryptionTransform(algorithm);
validateOnlyOneModeEncryptAlgoProposedOrThrow(algorithm);
mProposedEncryptAlgos.add(encryptionTransform);
return this;
}
/**
* Adds an encryption algorithm with specific key length to SA proposal being built.
*
* @param algorithm encryption algorithm to add to SaProposal.
* @param keyLength key length of algorithm.
* @return Builder of SaProposal.
* @throws IllegalArgumentException if AEAD and non-combined mode algorithms are mixed.
*/
public Builder addEncryptionAlgorithm(@EncryptionAlgorithm int algorithm, int keyLength) {
// Construct EncryptionTransform and validate proposed algorithm during
// construction.
EncryptionTransform encryptionTransform = new EncryptionTransform(algorithm, keyLength);
validateOnlyOneModeEncryptAlgoProposedOrThrow(algorithm);
mProposedEncryptAlgos.add(encryptionTransform);
return this;
}
private void validateOnlyOneModeEncryptAlgoProposedOrThrow(
@EncryptionAlgorithm int algorithm) {
boolean isCurrentAead = isAead(algorithm);
if (!mProposedEncryptAlgos.isEmpty() && (mHasAead ^ isCurrentAead)) {
throw new IllegalArgumentException(
ERROR_TAG
+ "Proposal cannot has both normal ciphers "
+ "and combined-mode ciphers.");
}
if (isCurrentAead) mHasAead = true;
}
/**
* Adds a pseudorandom function to SA proposal being built.
*
* @param algorithm pseudorandom function to add to SaProposal.
* @return Builder of SaProposal.
*/
public Builder addPseudorandomFunction(@PseudorandomFunction int algorithm) {
// Construct PrfTransform and validate proposed algorithm during
// construction.
mProposedPrfs.add(new PrfTransform(algorithm));
return this;
}
/**
* Adds an integrity algorithm to SA proposal being built.
*
* @param algorithm integrity algorithm to add to SaProposal.
* @return Builder of SaProposal.
*/
public Builder addIntegrityAlgorithm(@IntegrityAlgorithm int algorithm) {
// Construct IntegrityTransform and validate proposed algorithm during
// construction.
mProposedIntegrityAlgos.add(new IntegrityTransform(algorithm));
return this;
}
/**
* Adds a Diffie-Hellman Group to SA proposal being built.
*
* @param dhGroup to add to SaProposal.
* @return Builder of SaProposal.
*/
public Builder addDhGroup(@DhGroup int dhGroup) {
// Construct DhGroupTransform and validate proposed dhGroup during
// construction.
mProposedDhGroups.add(new DhGroupTransform(dhGroup));
return this;
}
/**
* Validates, builds and returns the SaProposal
*
* @return SaProposal the validated SaProposal.
* @throws IllegalArgumentException if SaProposal is invalid.
*/
public SaProposal build() {
EncryptionTransform[] encryptionTransforms = buildEncryptAlgosOrThrow();
PrfTransform[] prfTransforms = buildPrfsOrThrow();
IntegrityTransform[] integrityTransforms =
mIsIkeProposal
? buildIntegAlgosForIkeOrThrow()
: buildIntegAlgosForChildOrThrow();
DhGroupTransform[] dhGroupTransforms =
mIsIkeProposal ? buildDhGroupsForIkeOrThrow() : buildDhGroupsForChildOrThrow();
// IKE library only supports negotiating ESP Child SA.
int protocol = mIsIkeProposal ? IkePayload.PROTOCOL_ID_IKE : IkePayload.PROTOCOL_ID_ESP;
return new SaProposal(
protocol,
encryptionTransforms,
prfTransforms,
integrityTransforms,
dhGroupTransforms);
}
}
/**
* Check if the provided algorithm is a supported encryption algorithm.
*
* @param algorithm IKE standard encryption algorithm id.
* @return true if the provided algorithm is a supported encryption algorithm.
*/
public static boolean isSupportedEncryptionAlgorithm(@EncryptionAlgorithm int algorithm) {
return SUPPORTED_ENCRYPTION_ALGORITHM.contains(algorithm);
}
/**
* Check if the provided algorithm is a supported pseudorandom function.
*
* @param algorithm IKE standard pseudorandom function id.
* @return true if the provided algorithm is a supported pseudorandom function.
*/
public static boolean isSupportedPseudorandomFunction(@PseudorandomFunction int algorithm) {
return SUPPORTED_PSEUDORANDOM_FUNCTION.contains(algorithm);
}
/**
* Check if the provided algorithm is a supported integrity algorithm.
*
* @param algorithm IKE standard integrity algorithm id.
* @return true if the provided algorithm is a supported integrity algorithm.
*/
public static boolean isSupportedIntegrityAlgorithm(@IntegrityAlgorithm int algorithm) {
return SUPPORTED_INTEGRITY_ALGORITHM.contains(algorithm);
}
/**
* Check if the provided group number is for a supported Diffie-Hellman Group.
*
* @param dhGroup IKE standard DH Group id.
* @return true if the provided number is for a supported Diffie-Hellman Group.
*/
public static boolean isSupportedDhGroup(@DhGroup int dhGroup) {
return SUPPORTED_DH_GROUP.contains(dhGroup);
}
}