/* * 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.SystemApi; 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 java.util.ArrayList; import java.util.Arrays; import java.util.List; 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) * @hide */ @SystemApi public final class IkeSaProposal extends SaProposal { 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; } /** * 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); } /** * 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. */ @NonNull public Builder addEncryptionAlgorithm(@EncryptionAlgorithm int algorithm, int keyLength) { validateAndAddEncryptAlgo(algorithm, keyLength); return this; } /** * Adds an integrity algorithm to the SA proposal being built. * * @param algorithm integrity algorithm to add to IkeSaProposal. * @return Builder of IkeSaProposal. */ @NonNull public Builder addIntegrityAlgorithm(@IntegrityAlgorithm int algorithm) { addIntegrityAlgo(algorithm); return this; } /** * Adds a Diffie-Hellman Group to the SA proposal being built. * * @param dhGroup to add to IkeSaProposal. * @return Builder of IkeSaProposal. */ @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); } } }