/* * 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 android.net.ipsec.ike; import android.annotation.NonNull; import android.annotation.SuppressLint; import android.os.PersistableBundle; import android.util.ArraySet; 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 com.android.modules.utils.build.SdkLevel; import com.android.server.vcn.util.PersistableBundleUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; /** * IkeSaProposal represents a proposed configuration to negotiate an IKE SA. * *

IkeSaProposal will contain cryptograhic algorithms and key generation materials for the * negotiation of an IKE SA. * *

User must provide at least one valid IkeSaProposal when they are creating a new IKE SA. * * @see RFC 7296, Internet Key Exchange * Protocol Version 2 (IKEv2) */ public final class IkeSaProposal extends SaProposal { private static final String PRF_KEY = "mPseudorandomFunctions"; private final PrfTransform[] mPseudorandomFunctions; /** * Construct an instance of IkeSaProposal. * *

This constructor is either called by IkeSaPayload for building an inbound proposal from a * decoded packet, or called by the inner Builder to build an outbound proposal from user * provided parameters * * @param encryptionAlgos encryption algorithms * @param prfs pseudorandom functions * @param integrityAlgos integrity algorithms * @param dhGroups Diffie-Hellman Groups * @hide */ public IkeSaProposal( EncryptionTransform[] encryptionAlgos, PrfTransform[] prfs, IntegrityTransform[] integrityAlgos, DhGroupTransform[] dhGroups) { super(IkePayload.PROTOCOL_ID_IKE, encryptionAlgos, integrityAlgos, dhGroups); mPseudorandomFunctions = prfs; } /** * Constructs this object by deserializing a PersistableBundle * *

Constructed proposals are guaranteed to be valid, as checked by the IkeSaProposal.Builder. * * @hide */ @NonNull public static IkeSaProposal fromPersistableBundle(@NonNull PersistableBundle in) { Objects.requireNonNull(in, "PersistableBundle is null"); IkeSaProposal.Builder builder = new IkeSaProposal.Builder(); PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY); Objects.requireNonNull(encryptionBundle, "Encryption algo bundle is null"); List encryptList = PersistableBundleUtils.toList( encryptionBundle, EncryptionTransform::fromPersistableBundle); for (EncryptionTransform t : encryptList) { builder.addEncryptionAlgorithm(t.id, t.getSpecifiedKeyLength()); } int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY); Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array is null"); for (int algo : integrityAlgoIdArray) { builder.addIntegrityAlgorithm(algo); } int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY); Objects.requireNonNull(dhGroupArray, "DH Group array is null"); for (int dh : dhGroupArray) { builder.addDhGroup(dh); } int[] prfArray = in.getIntArray(PRF_KEY); Objects.requireNonNull(prfArray, "PRF array is null"); for (int prf : prfArray) { builder.addPseudorandomFunction(prf); } return builder.build(); } /** * Serializes this object to a PersistableBundle * * @hide */ @Override @NonNull public PersistableBundle toPersistableBundle() { final PersistableBundle result = super.toPersistableBundle(); int[] prfArray = getPseudorandomFunctions().stream().mapToInt(i -> i).toArray(); result.putIntArray(PRF_KEY, prfArray); return result; } /** Returns supported encryption algorithms for IKE SA proposal negotiation. */ @NonNull public static Set getSupportedEncryptionAlgorithms() { return getKeySet(SUPPORTED_ENCRYPTION_ALGO_TO_STR); } /** Returns supported integrity algorithms for IKE SA proposal negotiation. */ @NonNull public static Set getSupportedIntegrityAlgorithms() { final Set supportedSet = new HashSet<>(); for (int algo : getKeySet(SUPPORTED_INTEGRITY_ALGO_TO_STR)) { if (algo == INTEGRITY_ALGORITHM_AES_CMAC_96 && !SdkLevel.isAtLeastS()) { continue; } else { supportedSet.add(algo); } } return supportedSet; } /** Returns supported pseudorandom functions for IKE SA proposal negotiation. */ @NonNull public static Set getSupportedPseudorandomFunctions() { final Set supportedSet = new HashSet<>(); for (int algo : getKeySet(SUPPORTED_PRF_TO_STR)) { if (algo == PSEUDORANDOM_FUNCTION_AES128_CMAC && !SdkLevel.isAtLeastS()) { continue; } else { supportedSet.add(algo); } } return supportedSet; } /** * Gets all proposed Pseudorandom Functions * * @return A list of the IANA-defined IDs for the proposed Pseudorandom Functions */ @NonNull public List getPseudorandomFunctions() { final List result = new ArrayList<>(); for (Transform transform : mPseudorandomFunctions) { result.add(transform.id); } return result; } /** * Gets all PRF Transforms * * @hide */ public PrfTransform[] getPrfTransforms() { return mPseudorandomFunctions; } /** @hide */ @Override public Transform[] getAllTransforms() { List transformList = getAllTransformsAsList(); transformList.addAll(Arrays.asList(mPseudorandomFunctions)); return transformList.toArray(new Transform[transformList.size()]); } /** @hide */ @Override public boolean isNegotiatedFrom(SaProposal reqProposal) { return super.isNegotiatedFrom(reqProposal) && isTransformSelectedFrom( mPseudorandomFunctions, ((IkeSaProposal) reqProposal).mPseudorandomFunctions); } @Override public int hashCode() { return Objects.hash(super.hashCode(), Arrays.hashCode(mPseudorandomFunctions)); } @Override public boolean equals(Object o) { if (!super.equals(o) || !(o instanceof IkeSaProposal)) { return false; } return Arrays.equals(mPseudorandomFunctions, ((IkeSaProposal) o).mPseudorandomFunctions); } /** * This class is used to incrementally construct a IkeSaProposal. IkeSaProposal instances are * immutable once built. */ public static final class Builder extends SaProposal.Builder { // TODO: Support users to add algorithms from most preferred to least preferred. // Use set to avoid adding repeated algorithms. private final Set mProposedPrfs = new ArraySet<>(); /** * Adds an encryption algorithm with a specific key length to the SA proposal being built. * * @param algorithm encryption algorithm to add to IkeSaProposal. * @param keyLength key length of algorithm. For algorithms that have fixed key length (e.g. * 3DES) only {@link SaProposal#KEY_LEN_UNUSED} is allowed. * @return Builder of IkeSaProposal. */ // The matching getter is defined in the super class. Please see {@link // SaProposal#getEncryptionAlgorithms} @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder addEncryptionAlgorithm(@EncryptionAlgorithm int algorithm, int keyLength) { validateAndAddEncryptAlgo(algorithm, keyLength, false /* isChild */); return this; } /** * Adds an integrity algorithm to the SA proposal being built. * * @param algorithm integrity algorithm to add to IkeSaProposal. * @return Builder of IkeSaProposal. */ // The matching getter is defined in the super class. Please see // {@link SaProposal#getIntegrityAlgorithms} @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder addIntegrityAlgorithm(@IntegrityAlgorithm int algorithm) { validateAndAddIntegrityAlgo(algorithm, false /* isChild */); return this; } /** * Adds a Diffie-Hellman Group to the SA proposal being built. * * @param dhGroup to add to IkeSaProposal. * @return Builder of IkeSaProposal. */ // The matching getter is defined in the super class. Please see // {@link SaProposal#getDhGroups} @SuppressLint("MissingGetterMatchingBuilder") @NonNull public Builder addDhGroup(@DhGroup int dhGroup) { addDh(dhGroup); return this; } /** * Adds a pseudorandom function to the SA proposal being built. * * @param algorithm pseudorandom function to add to IkeSaProposal. * @return Builder of IkeSaProposal. */ @NonNull public Builder addPseudorandomFunction(@PseudorandomFunction int algorithm) { // Construct PrfTransform and validate proposed algorithm during construction. mProposedPrfs.add(new PrfTransform(algorithm)); return this; } private IntegrityTransform[] buildIntegAlgosOrThrow() { // 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 DhGroupTransform[] buildDhGroupsOrThrow() { 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 invalid in IKE SA proposal"); } } return mProposedDhGroups.toArray(new DhGroupTransform[mProposedDhGroups.size()]); } private PrfTransform[] buildPrfsOrThrow() { if (mProposedPrfs.isEmpty()) { throw new IllegalArgumentException( ERROR_TAG + "PRF must be proposed in IKE SA proposal."); } return mProposedPrfs.toArray(new PrfTransform[mProposedPrfs.size()]); } /** * Validates and builds the IkeSaProposal. * * @return the validated IkeSaProposal. */ @NonNull public IkeSaProposal build() { EncryptionTransform[] encryptionTransforms = buildEncryptAlgosOrThrow(); PrfTransform[] prfTransforms = buildPrfsOrThrow(); IntegrityTransform[] integrityTransforms = buildIntegAlgosOrThrow(); DhGroupTransform[] dhGroupTransforms = buildDhGroupsOrThrow(); return new IkeSaProposal( encryptionTransforms, prfTransforms, integrityTransforms, dhGroupTransforms); } } }