| /* |
| * 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 static com.android.internal.net.ipsec.ike.message.IkeSaPayload.EsnTransform.ESN_POLICY_NO_EXTENDED; |
| |
| import android.annotation.NonNull; |
| import android.annotation.SuppressLint; |
| import android.os.PersistableBundle; |
| |
| 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.EsnTransform; |
| import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform; |
| import com.android.internal.net.ipsec.ike.message.IkeSaPayload.Transform; |
| import com.android.server.vcn.util.PersistableBundleUtils; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Objects; |
| |
| /** |
| * ChildSaProposal represents a proposed configuration to negotiate a Child SA. |
| * |
| * <p>ChildSaProposal will contain cryptograhic algorithms and key generation materials for the |
| * negotiation of a Child SA. |
| * |
| * <p>User must provide at least one valid ChildSaProposal when they are creating a new 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 ChildSaProposal extends SaProposal { |
| private static final String ESN_KEY = "mEsns"; |
| private final EsnTransform[] mEsns; |
| |
| /** |
| * Construct an instance of ChildSaProposal. |
| * |
| * <p>This constructor is either called by ChildSaPayload 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 integrityAlgos integrity algorithms |
| * @param dhGroups Diffie-Hellman Groups |
| * @param esns ESN policies |
| * @hide |
| */ |
| public ChildSaProposal( |
| EncryptionTransform[] encryptionAlgos, |
| IntegrityTransform[] integrityAlgos, |
| DhGroupTransform[] dhGroups, |
| EsnTransform[] esns) { |
| super(IkePayload.PROTOCOL_ID_ESP, encryptionAlgos, integrityAlgos, dhGroups); |
| mEsns = esns; |
| } |
| |
| /** |
| * Constructs this object by deserializing a PersistableBundle |
| * |
| * <p>Constructed proposals are guaranteed to be valid, as checked by the |
| * ChildSaProposal.Builder. |
| * |
| * @hide |
| */ |
| @NonNull |
| public static ChildSaProposal fromPersistableBundle(@NonNull PersistableBundle in) { |
| Objects.requireNonNull(in, "PersistableBundle is null"); |
| |
| ChildSaProposal.Builder builder = new ChildSaProposal.Builder(); |
| |
| PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY); |
| Objects.requireNonNull(encryptionBundle, "Encryption algo bundle is null"); |
| List<EncryptionTransform> 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[] esnPolicies = in.getIntArray(ESN_KEY); |
| Objects.requireNonNull(esnPolicies, "ESN policy array is null"); |
| |
| for (int p : esnPolicies) { |
| switch (p) { |
| case ESN_POLICY_NO_EXTENDED: |
| // Ignored. All ChildSaProposal(s) are proposed with this automatically |
| break; |
| default: |
| throw new IllegalArgumentException( |
| "Proposing ESN policy: " + p + " is unsupported"); |
| } |
| } |
| |
| return builder.build(); |
| } |
| |
| /** |
| * Serializes this object to a PersistableBundle |
| * |
| * @hide |
| */ |
| @Override |
| @NonNull |
| public PersistableBundle toPersistableBundle() { |
| final PersistableBundle result = super.toPersistableBundle(); |
| int[] esnPolicies = Arrays.asList(mEsns).stream().mapToInt(esn -> esn.id).toArray(); |
| result.putIntArray(ESN_KEY, esnPolicies); |
| |
| return result; |
| } |
| |
| /** |
| * Gets all ESN policies. |
| * |
| * @hide |
| */ |
| public EsnTransform[] getEsnTransforms() { |
| return mEsns; |
| } |
| |
| /** |
| * Gets a copy of proposal without all proposed DH groups. |
| * |
| * <p>This is used to avoid negotiating DH Group for negotiating first Child SA. |
| * |
| * @hide |
| */ |
| public ChildSaProposal getCopyWithoutDhTransform() { |
| return new ChildSaProposal( |
| getEncryptionTransforms(), |
| getIntegrityTransforms(), |
| new DhGroupTransform[0], |
| getEsnTransforms()); |
| } |
| |
| /** @hide */ |
| @Override |
| public Transform[] getAllTransforms() { |
| List<Transform> transformList = getAllTransformsAsList(); |
| transformList.addAll(Arrays.asList(mEsns)); |
| |
| return transformList.toArray(new Transform[transformList.size()]); |
| } |
| |
| /** @hide */ |
| @Override |
| public boolean isNegotiatedFrom(SaProposal reqProposal) { |
| return super.isNegotiatedFrom(reqProposal) |
| && isTransformSelectedFrom(mEsns, ((ChildSaProposal) reqProposal).mEsns); |
| } |
| |
| /** @hide */ |
| public boolean isNegotiatedFromExceptDhGroup(SaProposal saProposal) { |
| return getProtocolId() == saProposal.getProtocolId() |
| && isTransformSelectedFrom( |
| getEncryptionTransforms(), saProposal.getEncryptionTransforms()) |
| && isTransformSelectedFrom( |
| getIntegrityTransforms(), saProposal.getIntegrityTransforms()) |
| && isTransformSelectedFrom(mEsns, ((ChildSaProposal) saProposal).mEsns); |
| } |
| |
| /** @hide */ |
| public ChildSaProposal getCopyWithAdditionalDhTransform(int dhGroup) { |
| return new ChildSaProposal( |
| getEncryptionTransforms(), |
| getIntegrityTransforms(), |
| new DhGroupTransform[] {new DhGroupTransform(dhGroup)}, |
| getEsnTransforms()); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(super.hashCode(), Arrays.hashCode(mEsns)); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (!super.equals(o) || !(o instanceof ChildSaProposal)) { |
| return false; |
| } |
| |
| return Arrays.equals(mEsns, ((ChildSaProposal) o).mEsns); |
| } |
| |
| /** |
| * This class is used to incrementally construct a ChildSaProposal. ChildSaProposal instances |
| * are immutable once built. |
| */ |
| public static final class Builder extends SaProposal.Builder { |
| /** |
| * Adds an encryption algorithm with a specific key length to the SA proposal being built. |
| * |
| * @param algorithm encryption algorithm to add to ChildSaProposal. |
| * @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 ChildSaProposal. |
| */ |
| // 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); |
| return this; |
| } |
| |
| /** |
| * Adds an integrity algorithm to the SA proposal being built. |
| * |
| * @param algorithm integrity algorithm to add to ChildSaProposal. |
| * @return Builder of ChildSaProposal. |
| */ |
| // The matching getter is defined in the super class. Please see |
| // {@link SaProposal#getIntegrityAlgorithms} |
| @SuppressLint("MissingGetterMatchingBuilder") |
| @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 ChildSaProposal. |
| * @return Builder of ChildSaProposal. |
| */ |
| // 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; |
| } |
| |
| private IntegrityTransform[] buildIntegAlgosOrThrow() { |
| // 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()]); |
| } |
| |
| /** |
| * Validates and builds the ChildSaProposal. |
| * |
| * @return the validated ChildSaProposal. |
| */ |
| @NonNull |
| public ChildSaProposal build() { |
| EncryptionTransform[] encryptionTransforms = buildEncryptAlgosOrThrow(); |
| IntegrityTransform[] integrityTransforms = buildIntegAlgosOrThrow(); |
| |
| return new ChildSaProposal( |
| encryptionTransforms, |
| integrityTransforms, |
| mProposedDhGroups.toArray(new DhGroupTransform[mProposedDhGroups.size()]), |
| new EsnTransform[] {new EsnTransform()}); |
| } |
| } |
| } |