/*
* 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 android.net.ipsec.ike;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.net.ipsec.ike.message.IkePayload;
import com.android.internal.net.ipsec.ike.message.IkeSaPayload.DhGroupTransform;
import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform;
import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform;
import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform;
import com.android.internal.net.ipsec.ike.message.IkeSaPayload.Transform;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
/**
* SaProposal represents a proposed configuration to negotiate an IKE or Child SA.
*
*
SaProposal will contain cryptograhic algorithms and key generation materials for the
* negotiation of an IKE or Child SA.
*
*
User must provide at least one valid SaProposal when they are creating a new IKE or Child SA.
*
* @see RFC 7296, Internet Key Exchange
* Protocol Version 2 (IKEv2)
* @hide
*/
@SystemApi
public abstract class SaProposal {
/** @hide */
@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 {}
/** 3DES Encryption/Ciphering Algorithm. */
public static final int ENCRYPTION_ALGORITHM_3DES = 3;
/** AES-CBC Encryption/Ciphering Algorithm. */
public static final int ENCRYPTION_ALGORITHM_AES_CBC = 12;
/**
* AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm with 8-octet ICV
* (truncation).
*/
public static final int ENCRYPTION_ALGORITHM_AES_GCM_8 = 18;
/**
* AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm with 12-octet ICV
* (truncation).
*/
public static final int ENCRYPTION_ALGORITHM_AES_GCM_12 = 19;
/**
* AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm with 16-octet ICV
* (truncation).
*/
public static final int ENCRYPTION_ALGORITHM_AES_GCM_16 = 20;
private static final SparseArray SUPPORTED_ENCRYPTION_ALGO_TO_STR;
static {
SUPPORTED_ENCRYPTION_ALGO_TO_STR = new SparseArray<>();
SUPPORTED_ENCRYPTION_ALGO_TO_STR.put(ENCRYPTION_ALGORITHM_3DES, "ENCR_3DES");
SUPPORTED_ENCRYPTION_ALGO_TO_STR.put(ENCRYPTION_ALGORITHM_AES_CBC, "ENCR_AES_CBC");
SUPPORTED_ENCRYPTION_ALGO_TO_STR.put(ENCRYPTION_ALGORITHM_AES_GCM_8, "ENCR_AES_GCM_8");
SUPPORTED_ENCRYPTION_ALGO_TO_STR.put(ENCRYPTION_ALGORITHM_AES_GCM_12, "ENCR_AES_GCM_12");
SUPPORTED_ENCRYPTION_ALGO_TO_STR.put(ENCRYPTION_ALGORITHM_AES_GCM_16, "ENCR_AES_GCM_16");
}
/**
* Key length unused.
*
* This value should only be used with the Encryption/Ciphering Algorithm that accepts a
* fixed key size such as {@link ENCRYPTION_ALGORITHM_3DES}.
*/
public static final int KEY_LEN_UNUSED = 0;
/** AES Encryption/Ciphering Algorithm key length 128 bits. */
public static final int KEY_LEN_AES_128 = 128;
/** AES Encryption/Ciphering Algorithm key length 192 bits. */
public static final int KEY_LEN_AES_192 = 192;
/** AES Encryption/Ciphering Algorithm key length 256 bits. */
public static final int KEY_LEN_AES_256 = 256;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
PSEUDORANDOM_FUNCTION_HMAC_SHA1,
PSEUDORANDOM_FUNCTION_AES128_XCBC,
PSEUDORANDOM_FUNCTION_SHA2_256,
PSEUDORANDOM_FUNCTION_SHA2_384,
PSEUDORANDOM_FUNCTION_SHA2_512
})
public @interface PseudorandomFunction {}
/** HMAC-SHA1 Pseudorandom Function. */
public static final int PSEUDORANDOM_FUNCTION_HMAC_SHA1 = 2;
/** AES128-XCBC Pseudorandom Function. */
public static final int PSEUDORANDOM_FUNCTION_AES128_XCBC = 4;
/** HMAC-SHA2-256 Pseudorandom Function. */
public static final int PSEUDORANDOM_FUNCTION_SHA2_256 = 5;
/** HMAC-SHA2-384 Pseudorandom Function. */
public static final int PSEUDORANDOM_FUNCTION_SHA2_384 = 6;
/** HMAC-SHA2-384 Pseudorandom Function. */
public static final int PSEUDORANDOM_FUNCTION_SHA2_512 = 7;
private static final SparseArray SUPPORTED_PRF_TO_STR;
static {
SUPPORTED_PRF_TO_STR = new SparseArray<>();
SUPPORTED_PRF_TO_STR.put(PSEUDORANDOM_FUNCTION_HMAC_SHA1, "PRF_HMAC_SHA1");
SUPPORTED_PRF_TO_STR.put(PSEUDORANDOM_FUNCTION_AES128_XCBC, "PRF_AES128_XCBC");
SUPPORTED_PRF_TO_STR.put(PSEUDORANDOM_FUNCTION_SHA2_256, "PRF_HMAC2_256");
SUPPORTED_PRF_TO_STR.put(PSEUDORANDOM_FUNCTION_SHA2_384, "PRF_HMAC2_384");
SUPPORTED_PRF_TO_STR.put(PSEUDORANDOM_FUNCTION_SHA2_512, "PRF_HMAC2_512");
}
/** @hide */
@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 {}
/** None Authentication/Integrity Algorithm. */
public static final int INTEGRITY_ALGORITHM_NONE = 0;
/** HMAC-SHA1 Authentication/Integrity Algorithm. */
public static final int INTEGRITY_ALGORITHM_HMAC_SHA1_96 = 2;
/** AES-XCBC-96 Authentication/Integrity Algorithm. */
public static final int INTEGRITY_ALGORITHM_AES_XCBC_96 = 5;
/** HMAC-SHA256 Authentication/Integrity Algorithm with 128-bit truncation. */
public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_256_128 = 12;
/** HMAC-SHA384 Authentication/Integrity Algorithm with 192-bit truncation. */
public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_384_192 = 13;
/** HMAC-SHA512 Authentication/Integrity Algorithm with 256-bit truncation. */
public static final int INTEGRITY_ALGORITHM_HMAC_SHA2_512_256 = 14;
private static final SparseArray SUPPORTED_INTEGRITY_ALGO_TO_STR;
static {
SUPPORTED_INTEGRITY_ALGO_TO_STR = new SparseArray<>();
SUPPORTED_INTEGRITY_ALGO_TO_STR.put(INTEGRITY_ALGORITHM_NONE, "AUTH_NONE");
SUPPORTED_INTEGRITY_ALGO_TO_STR.put(INTEGRITY_ALGORITHM_HMAC_SHA1_96, "AUTH_HMAC_SHA1_96");
SUPPORTED_INTEGRITY_ALGO_TO_STR.put(INTEGRITY_ALGORITHM_AES_XCBC_96, "AUTH_AES_XCBC_96");
SUPPORTED_INTEGRITY_ALGO_TO_STR.put(
INTEGRITY_ALGORITHM_HMAC_SHA2_256_128, "AUTH_HMAC_SHA2_256_128");
SUPPORTED_INTEGRITY_ALGO_TO_STR.put(
INTEGRITY_ALGORITHM_HMAC_SHA2_384_192, "AUTH_HMAC_SHA2_384_192");
SUPPORTED_INTEGRITY_ALGO_TO_STR.put(
INTEGRITY_ALGORITHM_HMAC_SHA2_512_256, "AUTH_HMAC_SHA2_512_256");
}
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({DH_GROUP_NONE, DH_GROUP_1024_BIT_MODP, DH_GROUP_2048_BIT_MODP})
public @interface DhGroup {}
/** None Diffie-Hellman Group. */
public static final int DH_GROUP_NONE = 0;
/** 1024-bit MODP Diffie-Hellman Group. */
public static final int DH_GROUP_1024_BIT_MODP = 2;
/** 2048-bit MODP Diffie-Hellman Group. */
public static final int DH_GROUP_2048_BIT_MODP = 14;
/** 3072-bit MODP Diffie-Hellman Group. @hide */
public static final int DH_GROUP_3072_BIT_MODP = 15;
/** 4096-bit MODP Diffie-Hellman Group. @hide */
public static final int DH_GROUP_4096_BIT_MODP = 16;
private static final SparseArray SUPPORTED_DH_GROUP_TO_STR;
static {
SUPPORTED_DH_GROUP_TO_STR = new SparseArray<>();
SUPPORTED_DH_GROUP_TO_STR.put(DH_GROUP_NONE, "DH_NONE");
SUPPORTED_DH_GROUP_TO_STR.put(DH_GROUP_1024_BIT_MODP, "DH_1024_BIT_MODP");
SUPPORTED_DH_GROUP_TO_STR.put(DH_GROUP_2048_BIT_MODP, "DH_2048_BIT_MODP");
SUPPORTED_DH_GROUP_TO_STR.put(DH_GROUP_3072_BIT_MODP, "DH_3072_BIT_MODP");
SUPPORTED_DH_GROUP_TO_STR.put(DH_GROUP_4096_BIT_MODP, "DH_4096_BIT_MODP");
}
@IkePayload.ProtocolId private final int mProtocolId;
private final EncryptionTransform[] mEncryptionAlgorithms;
private final IntegrityTransform[] mIntegrityAlgorithms;
private final DhGroupTransform[] mDhGroups;
/** @hide */
protected SaProposal(
@IkePayload.ProtocolId int protocol,
EncryptionTransform[] encryptionAlgos,
IntegrityTransform[] integrityAlgos,
DhGroupTransform[] dhGroups) {
mProtocolId = protocol;
mEncryptionAlgorithms = encryptionAlgos;
mIntegrityAlgorithms = integrityAlgos;
mDhGroups = dhGroups;
}
/**
* Check if the current SaProposal from the SA responder is consistent with the selected
* reqProposal from the SA initiator.
*
* As per RFC 7296, The accepted cryptographic suite MUST contain exactly one transform of
* each type included in the proposal. But for interoperability reason, IKE library allows
* exceptions when the accepted suite or the request proposal has a NONE value transform.
* Currently only IntegrityTransform and DhGroupTransform have NONE value transform ID defined.
*
* @param reqProposal selected SaProposal from SA initiator
* @return if current SaProposal from SA responder is consistent with the selected reqProposal
* from SA initiator.
* @hide
*/
public boolean isNegotiatedFrom(SaProposal reqProposal) {
return this.mProtocolId == reqProposal.mProtocolId
&& isTransformSelectedFrom(mEncryptionAlgorithms, reqProposal.mEncryptionAlgorithms)
&& isIntegrityTransformSelectedFrom(
mIntegrityAlgorithms, reqProposal.mIntegrityAlgorithms)
&& isDhGroupTransformSelectedFrom(mDhGroups, reqProposal.mDhGroups);
}
/**
* Check if the response transform can be selected from the request transforms
*
*
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]);
}
/**
* Check if the response integrity transform can be selected from the request integrity
* transforms.
*
*
For interoperability reason, it is allowed to do not include integrity transform in the
* response proposal when the request proposal has a NONE value integrity transform; and it is
* also allowed to have a NONE value integrity transform when the request proposal does not have
* integrity transforms.
*/
private static boolean isIntegrityTransformSelectedFrom(
IntegrityTransform[] selected, IntegrityTransform[] selectFrom) {
if (selected.length == 0) {
selected = new IntegrityTransform[] {new IntegrityTransform(INTEGRITY_ALGORITHM_NONE)};
}
if (selectFrom.length == 0) {
selectFrom =
new IntegrityTransform[] {new IntegrityTransform(INTEGRITY_ALGORITHM_NONE)};
}
return isTransformSelectedFrom(selected, selectFrom);
}
/**
* Check if the response DH group can be selected from the request DH groups
*
*
For interoperability reason, it is allowed to do not include DH group in the response
* proposal when the request proposal has a NONE value DH group; and it is also allowed to have
* a NONE value DH group when the request proposal does not have DH groups.
*/
private static boolean isDhGroupTransformSelectedFrom(
DhGroupTransform[] selected, DhGroupTransform[] selectFrom) {
if (selected.length == 0) {
selected = new DhGroupTransform[] {new DhGroupTransform(DH_GROUP_NONE)};
}
if (selectFrom.length == 0) {
selectFrom = new DhGroupTransform[] {new DhGroupTransform(DH_GROUP_NONE)};
}
return isTransformSelectedFrom(selected, selectFrom);
}
/** @hide */
@IkePayload.ProtocolId
public int getProtocolId() {
return mProtocolId;
}
/**
* Gets all proposed encryption algorithms
*
* @return A list of Pairs, with the IANA-defined ID for the proposed encryption algorithm as
* the first item, and the key length (in bits) as the second.
*/
@NonNull
public List> getEncryptionAlgorithms() {
final List> result = new ArrayList<>();
for (EncryptionTransform transform : mEncryptionAlgorithms) {
result.add(new Pair(transform.id, transform.getSpecifiedKeyLength()));
}
return result;
}
/**
* Gets all proposed integrity algorithms
*
* @return A list of the IANA-defined IDs for the proposed integrity algorithms
*/
@NonNull
public List getIntegrityAlgorithms() {
final List result = new ArrayList<>();
for (Transform transform : mIntegrityAlgorithms) {
result.add(transform.id);
}
return result;
}
/**
* Gets all proposed Diffie-Hellman groups
*
* @return A list of the IANA-defined IDs for the proposed Diffie-Hellman groups
*/
@NonNull
public List getDhGroups() {
final List result = new ArrayList<>();
for (Transform transform : mDhGroups) {
result.add(transform.id);
}
return result;
}
/** @hide */
public EncryptionTransform[] getEncryptionTransforms() {
return mEncryptionAlgorithms;
}
/** @hide */
public IntegrityTransform[] getIntegrityTransforms() {
return mIntegrityAlgorithms;
}
/** @hide */
public DhGroupTransform[] getDhGroupTransforms() {
return mDhGroups;
}
/** @hide */
protected List getAllTransformsAsList() {
List transformList = new LinkedList<>();
transformList.addAll(Arrays.asList(mEncryptionAlgorithms));
transformList.addAll(Arrays.asList(mIntegrityAlgorithms));
transformList.addAll(Arrays.asList(mDhGroups));
return transformList;
}
/**
* Return all SA Transforms in this SaProposal to be encoded for building an outbound IKE
* message.
*
* This method should be called by only IKE library.
*
* @return Array of Transforms to be encoded.
* @hide
*/
public abstract Transform[] getAllTransforms();
/**
* This class is an abstract Builder for building a SaProposal.
*
* @hide
*/
protected abstract static class Builder {
protected static final String ERROR_TAG = "Invalid SA Proposal: ";
// Use LinkedHashSet to ensure uniqueness and that ordering is maintained.
protected final LinkedHashSet mProposedEncryptAlgos =
new LinkedHashSet<>();
protected final LinkedHashSet mProposedPrfs = new LinkedHashSet<>();
protected final LinkedHashSet mProposedIntegrityAlgos =
new LinkedHashSet<>();
protected final LinkedHashSet mProposedDhGroups = new LinkedHashSet<>();
protected boolean mHasAead = false;
protected 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.");
}
}
protected EncryptionTransform[] buildEncryptAlgosOrThrow() {
if (mProposedEncryptAlgos.isEmpty()) {
throw new IllegalArgumentException(
ERROR_TAG + "Encryption algorithm must be proposed.");
}
return mProposedEncryptAlgos.toArray(
new EncryptionTransform[mProposedEncryptAlgos.size()]);
}
protected void validateAndAddEncryptAlgo(
@EncryptionAlgorithm int algorithm, int keyLength) {
// Construct EncryptionTransform and validate proposed algorithm during
// construction.
EncryptionTransform encryptionTransform = new EncryptionTransform(algorithm, keyLength);
// Validate that only one mode encryption algorithm has been proposed.
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;
mProposedEncryptAlgos.add(encryptionTransform);
}
protected void addIntegrityAlgo(@IntegrityAlgorithm int algorithm) {
// Construct IntegrityTransform and validate proposed algorithm during
// construction.
mProposedIntegrityAlgos.add(new IntegrityTransform(algorithm));
}
protected void addDh(@DhGroup int dhGroup) {
// Construct DhGroupTransform and validate proposed dhGroup during
// construction.
mProposedDhGroups.add(new DhGroupTransform(dhGroup));
}
}
/** @hide */
@Override
@NonNull
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(IkePayload.getProtocolTypeString(mProtocolId)).append(": ");
int len = getAllTransforms().length;
for (int i = 0; i < len; i++) {
sb.append(getAllTransforms()[i].toString());
if (i < len - 1) sb.append("|");
}
return sb.toString();
}
/**
* 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.
* @hide
*/
public static boolean isSupportedEncryptionAlgorithm(@EncryptionAlgorithm int algorithm) {
return SUPPORTED_ENCRYPTION_ALGO_TO_STR.get(algorithm) != null;
}
/**
* 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.
* @hide
*/
public static boolean isSupportedPseudorandomFunction(@PseudorandomFunction int algorithm) {
return SUPPORTED_PRF_TO_STR.get(algorithm) != null;
}
/**
* 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.
* @hide
*/
public static boolean isSupportedIntegrityAlgorithm(@IntegrityAlgorithm int algorithm) {
return SUPPORTED_INTEGRITY_ALGO_TO_STR.get(algorithm) != null;
}
/**
* 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.
* @hide
*/
public static boolean isSupportedDhGroup(@DhGroup int dhGroup) {
return SUPPORTED_DH_GROUP_TO_STR.get(dhGroup) != null;
}
/**
* Return the encryption algorithm as a String.
*
* @hide
*/
public static String getEncryptionAlgorithmString(int algorithm) {
if (isSupportedEncryptionAlgorithm(algorithm)) {
return SUPPORTED_ENCRYPTION_ALGO_TO_STR.get(algorithm);
}
return "ENC_Unknown_" + algorithm;
}
/**
* Return the pseudorandom function as a String.
*
* @hide
*/
public static String getPseudorandomFunctionString(int algorithm) {
if (isSupportedPseudorandomFunction(algorithm)) {
return SUPPORTED_PRF_TO_STR.get(algorithm);
}
return "PRF_Unknown_" + algorithm;
}
/**
* Return the integrity algorithm as a String.
*
* @hide
*/
public static String getIntegrityAlgorithmString(int algorithm) {
if (isSupportedIntegrityAlgorithm(algorithm)) {
return SUPPORTED_INTEGRITY_ALGO_TO_STR.get(algorithm);
}
return "AUTH_Unknown_" + algorithm;
}
/**
* Return Diffie-Hellman Group as a String.
*
* @hide
*/
public static String getDhGroupString(int dhGroup) {
if (isSupportedDhGroup(dhGroup)) {
return SUPPORTED_DH_GROUP_TO_STR.get(dhGroup);
}
return "DH_Unknown_" + dhGroup;
}
}