1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.ipsec.ike; 18 19 import android.annotation.NonNull; 20 import android.annotation.SuppressLint; 21 import android.os.PersistableBundle; 22 import android.util.ArraySet; 23 24 import com.android.internal.net.ipsec.ike.message.IkePayload; 25 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.DhGroupTransform; 26 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform; 27 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform; 28 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform; 29 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.Transform; 30 import com.android.modules.utils.build.SdkLevel; 31 import com.android.server.vcn.util.PersistableBundleUtils; 32 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.HashSet; 36 import java.util.List; 37 import java.util.Objects; 38 import java.util.Set; 39 40 /** 41 * IkeSaProposal represents a proposed configuration to negotiate an IKE SA. 42 * 43 * <p>IkeSaProposal will contain cryptograhic algorithms and key generation materials for the 44 * negotiation of an IKE SA. 45 * 46 * <p>User must provide at least one valid IkeSaProposal when they are creating a new IKE SA. 47 * 48 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key Exchange 49 * Protocol Version 2 (IKEv2)</a> 50 */ 51 public final class IkeSaProposal extends SaProposal { 52 private static final String PRF_KEY = "mPseudorandomFunctions"; 53 private final PrfTransform[] mPseudorandomFunctions; 54 55 /** 56 * Construct an instance of IkeSaProposal. 57 * 58 * <p>This constructor is either called by IkeSaPayload for building an inbound proposal from a 59 * decoded packet, or called by the inner Builder to build an outbound proposal from user 60 * provided parameters 61 * 62 * @param encryptionAlgos encryption algorithms 63 * @param prfs pseudorandom functions 64 * @param integrityAlgos integrity algorithms 65 * @param dhGroups Diffie-Hellman Groups 66 * @hide 67 */ IkeSaProposal( EncryptionTransform[] encryptionAlgos, PrfTransform[] prfs, IntegrityTransform[] integrityAlgos, DhGroupTransform[] dhGroups)68 public IkeSaProposal( 69 EncryptionTransform[] encryptionAlgos, 70 PrfTransform[] prfs, 71 IntegrityTransform[] integrityAlgos, 72 DhGroupTransform[] dhGroups) { 73 super(IkePayload.PROTOCOL_ID_IKE, encryptionAlgos, integrityAlgos, dhGroups); 74 mPseudorandomFunctions = prfs; 75 } 76 77 /** 78 * Constructs this object by deserializing a PersistableBundle 79 * 80 * <p>Constructed proposals are guaranteed to be valid, as checked by the IkeSaProposal.Builder. 81 * 82 * @hide 83 */ 84 @NonNull fromPersistableBundle(@onNull PersistableBundle in)85 public static IkeSaProposal fromPersistableBundle(@NonNull PersistableBundle in) { 86 Objects.requireNonNull(in, "PersistableBundle is null"); 87 88 IkeSaProposal.Builder builder = new IkeSaProposal.Builder(); 89 90 PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY); 91 Objects.requireNonNull(encryptionBundle, "Encryption algo bundle is null"); 92 List<EncryptionTransform> encryptList = 93 PersistableBundleUtils.toList( 94 encryptionBundle, EncryptionTransform::fromPersistableBundle); 95 for (EncryptionTransform t : encryptList) { 96 builder.addEncryptionAlgorithm(t.id, t.getSpecifiedKeyLength()); 97 } 98 99 int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY); 100 Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array is null"); 101 for (int algo : integrityAlgoIdArray) { 102 builder.addIntegrityAlgorithm(algo); 103 } 104 105 int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY); 106 Objects.requireNonNull(dhGroupArray, "DH Group array is null"); 107 for (int dh : dhGroupArray) { 108 builder.addDhGroup(dh); 109 } 110 111 int[] prfArray = in.getIntArray(PRF_KEY); 112 Objects.requireNonNull(prfArray, "PRF array is null"); 113 for (int prf : prfArray) { 114 builder.addPseudorandomFunction(prf); 115 } 116 117 return builder.build(); 118 } 119 120 /** 121 * Serializes this object to a PersistableBundle 122 * 123 * @hide 124 */ 125 @Override 126 @NonNull toPersistableBundle()127 public PersistableBundle toPersistableBundle() { 128 final PersistableBundle result = super.toPersistableBundle(); 129 130 int[] prfArray = getPseudorandomFunctions().stream().mapToInt(i -> i).toArray(); 131 result.putIntArray(PRF_KEY, prfArray); 132 133 return result; 134 } 135 136 /** Returns supported encryption algorithms for IKE SA proposal negotiation. */ 137 @NonNull getSupportedEncryptionAlgorithms()138 public static Set<Integer> getSupportedEncryptionAlgorithms() { 139 return getKeySet(SUPPORTED_ENCRYPTION_ALGO_TO_STR); 140 } 141 142 /** Returns supported integrity algorithms for IKE SA proposal negotiation. */ 143 @NonNull getSupportedIntegrityAlgorithms()144 public static Set<Integer> getSupportedIntegrityAlgorithms() { 145 final Set<Integer> supportedSet = new HashSet<>(); 146 for (int algo : getKeySet(SUPPORTED_INTEGRITY_ALGO_TO_STR)) { 147 if (algo == INTEGRITY_ALGORITHM_AES_CMAC_96 && !SdkLevel.isAtLeastS()) { 148 continue; 149 } else { 150 supportedSet.add(algo); 151 } 152 } 153 154 return supportedSet; 155 } 156 157 /** Returns supported pseudorandom functions for IKE SA proposal negotiation. */ 158 @NonNull getSupportedPseudorandomFunctions()159 public static Set<Integer> getSupportedPseudorandomFunctions() { 160 final Set<Integer> supportedSet = new HashSet<>(); 161 for (int algo : getKeySet(SUPPORTED_PRF_TO_STR)) { 162 if (algo == PSEUDORANDOM_FUNCTION_AES128_CMAC && !SdkLevel.isAtLeastS()) { 163 continue; 164 } else { 165 supportedSet.add(algo); 166 } 167 } 168 169 return supportedSet; 170 } 171 172 /** 173 * Gets all proposed Pseudorandom Functions 174 * 175 * @return A list of the IANA-defined IDs for the proposed Pseudorandom Functions 176 */ 177 @NonNull getPseudorandomFunctions()178 public List<Integer> getPseudorandomFunctions() { 179 final List<Integer> result = new ArrayList<>(); 180 for (Transform transform : mPseudorandomFunctions) { 181 result.add(transform.id); 182 } 183 return result; 184 } 185 186 /** 187 * Gets all PRF Transforms 188 * 189 * @hide 190 */ getPrfTransforms()191 public PrfTransform[] getPrfTransforms() { 192 return mPseudorandomFunctions; 193 } 194 195 /** @hide */ 196 @Override getAllTransforms()197 public Transform[] getAllTransforms() { 198 List<Transform> transformList = getAllTransformsAsList(); 199 transformList.addAll(Arrays.asList(mPseudorandomFunctions)); 200 201 return transformList.toArray(new Transform[transformList.size()]); 202 } 203 204 /** @hide */ 205 @Override isNegotiatedFrom(SaProposal reqProposal)206 public boolean isNegotiatedFrom(SaProposal reqProposal) { 207 return super.isNegotiatedFrom(reqProposal) 208 && isTransformSelectedFrom( 209 mPseudorandomFunctions, 210 ((IkeSaProposal) reqProposal).mPseudorandomFunctions); 211 } 212 213 @Override hashCode()214 public int hashCode() { 215 return Objects.hash(super.hashCode(), Arrays.hashCode(mPseudorandomFunctions)); 216 } 217 218 @Override equals(Object o)219 public boolean equals(Object o) { 220 if (!super.equals(o) || !(o instanceof IkeSaProposal)) { 221 return false; 222 } 223 224 return Arrays.equals(mPseudorandomFunctions, ((IkeSaProposal) o).mPseudorandomFunctions); 225 } 226 227 /** 228 * This class is used to incrementally construct a IkeSaProposal. IkeSaProposal instances are 229 * immutable once built. 230 */ 231 public static final class Builder extends SaProposal.Builder { 232 // TODO: Support users to add algorithms from most preferred to least preferred. 233 234 // Use set to avoid adding repeated algorithms. 235 private final Set<PrfTransform> mProposedPrfs = new ArraySet<>(); 236 237 /** 238 * Adds an encryption algorithm with a specific key length to the SA proposal being built. 239 * 240 * @param algorithm encryption algorithm to add to IkeSaProposal. 241 * @param keyLength key length of algorithm. For algorithms that have fixed key length (e.g. 242 * 3DES) only {@link SaProposal#KEY_LEN_UNUSED} is allowed. 243 * @return Builder of IkeSaProposal. 244 */ 245 // The matching getter is defined in the super class. Please see {@link 246 // SaProposal#getEncryptionAlgorithms} 247 @SuppressLint("MissingGetterMatchingBuilder") 248 @NonNull addEncryptionAlgorithm(@ncryptionAlgorithm int algorithm, int keyLength)249 public Builder addEncryptionAlgorithm(@EncryptionAlgorithm int algorithm, int keyLength) { 250 validateAndAddEncryptAlgo(algorithm, keyLength, false /* isChild */); 251 return this; 252 } 253 254 /** 255 * Adds an integrity algorithm to the SA proposal being built. 256 * 257 * @param algorithm integrity algorithm to add to IkeSaProposal. 258 * @return Builder of IkeSaProposal. 259 */ 260 // The matching getter is defined in the super class. Please see 261 // {@link SaProposal#getIntegrityAlgorithms} 262 @SuppressLint("MissingGetterMatchingBuilder") 263 @NonNull addIntegrityAlgorithm(@ntegrityAlgorithm int algorithm)264 public Builder addIntegrityAlgorithm(@IntegrityAlgorithm int algorithm) { 265 validateAndAddIntegrityAlgo(algorithm, false /* isChild */); 266 return this; 267 } 268 269 /** 270 * Adds a Diffie-Hellman Group to the SA proposal being built. 271 * 272 * @param dhGroup to add to IkeSaProposal. 273 * @return Builder of IkeSaProposal. 274 */ 275 // The matching getter is defined in the super class. Please see 276 // {@link SaProposal#getDhGroups} 277 @SuppressLint("MissingGetterMatchingBuilder") 278 @NonNull addDhGroup(@hGroup int dhGroup)279 public Builder addDhGroup(@DhGroup int dhGroup) { 280 addDh(dhGroup); 281 return this; 282 } 283 284 /** 285 * Adds a pseudorandom function to the SA proposal being built. 286 * 287 * @param algorithm pseudorandom function to add to IkeSaProposal. 288 * @return Builder of IkeSaProposal. 289 */ 290 @NonNull addPseudorandomFunction(@seudorandomFunction int algorithm)291 public Builder addPseudorandomFunction(@PseudorandomFunction int algorithm) { 292 // Construct PrfTransform and validate proposed algorithm during construction. 293 mProposedPrfs.add(new PrfTransform(algorithm)); 294 return this; 295 } 296 buildIntegAlgosOrThrow()297 private IntegrityTransform[] buildIntegAlgosOrThrow() { 298 // When building IKE SA Proposal with normal-mode ciphers, mProposedIntegrityAlgos must 299 // not be empty and must not have INTEGRITY_ALGORITHM_NONE. When building IKE SA 300 // Proposal with combined-mode ciphers, mProposedIntegrityAlgos must be either empty or 301 // only have INTEGRITY_ALGORITHM_NONE. 302 if (mProposedIntegrityAlgos.isEmpty() && !mHasAead) { 303 throw new IllegalArgumentException( 304 ERROR_TAG 305 + "Integrity algorithm " 306 + "must be proposed with normal ciphers in IKE proposal."); 307 } 308 309 for (IntegrityTransform transform : mProposedIntegrityAlgos) { 310 if ((transform.id == INTEGRITY_ALGORITHM_NONE) != mHasAead) { 311 throw new IllegalArgumentException( 312 ERROR_TAG 313 + "Invalid integrity algorithm configuration" 314 + " for this SA Proposal"); 315 } 316 } 317 318 return mProposedIntegrityAlgos.toArray( 319 new IntegrityTransform[mProposedIntegrityAlgos.size()]); 320 } 321 buildDhGroupsOrThrow()322 private DhGroupTransform[] buildDhGroupsOrThrow() { 323 if (mProposedDhGroups.isEmpty()) { 324 throw new IllegalArgumentException( 325 ERROR_TAG + "DH group must be proposed in IKE SA proposal."); 326 } 327 328 for (DhGroupTransform transform : mProposedDhGroups) { 329 if (transform.id == DH_GROUP_NONE) { 330 throw new IllegalArgumentException( 331 ERROR_TAG + "None-value DH group invalid in IKE SA proposal"); 332 } 333 } 334 335 return mProposedDhGroups.toArray(new DhGroupTransform[mProposedDhGroups.size()]); 336 } 337 buildPrfsOrThrow()338 private PrfTransform[] buildPrfsOrThrow() { 339 if (mProposedPrfs.isEmpty()) { 340 throw new IllegalArgumentException( 341 ERROR_TAG + "PRF must be proposed in IKE SA proposal."); 342 } 343 return mProposedPrfs.toArray(new PrfTransform[mProposedPrfs.size()]); 344 } 345 346 /** 347 * Validates and builds the IkeSaProposal. 348 * 349 * @return the validated IkeSaProposal. 350 */ 351 @NonNull build()352 public IkeSaProposal build() { 353 EncryptionTransform[] encryptionTransforms = buildEncryptAlgosOrThrow(); 354 PrfTransform[] prfTransforms = buildPrfsOrThrow(); 355 IntegrityTransform[] integrityTransforms = buildIntegAlgosOrThrow(); 356 DhGroupTransform[] dhGroupTransforms = buildDhGroupsOrThrow(); 357 358 return new IkeSaProposal( 359 encryptionTransforms, prfTransforms, integrityTransforms, dhGroupTransforms); 360 } 361 } 362 } 363