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 static com.android.internal.net.ipsec.ike.message.IkeSaPayload.EsnTransform.ESN_POLICY_NO_EXTENDED; 20 21 import android.annotation.NonNull; 22 import android.annotation.SuppressLint; 23 import android.net.IpSecAlgorithm; 24 import android.os.PersistableBundle; 25 import android.util.ArraySet; 26 27 import com.android.internal.net.ipsec.ike.crypto.IkeCipher; 28 import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity; 29 import com.android.internal.net.ipsec.ike.message.IkePayload; 30 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.DhGroupTransform; 31 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform; 32 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EsnTransform; 33 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform; 34 import com.android.internal.net.ipsec.ike.message.IkeSaPayload.Transform; 35 import com.android.modules.utils.build.SdkLevel; 36 import com.android.server.vcn.util.PersistableBundleUtils; 37 38 import java.util.Arrays; 39 import java.util.List; 40 import java.util.Objects; 41 import java.util.Set; 42 43 /** 44 * ChildSaProposal represents a proposed configuration to negotiate a Child SA. 45 * 46 * <p>ChildSaProposal will contain cryptograhic algorithms and key generation materials for the 47 * negotiation of a Child SA. 48 * 49 * <p>User must provide at least one valid ChildSaProposal when they are creating a new Child SA. 50 * 51 * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3">RFC 7296, Internet Key Exchange 52 * Protocol Version 2 (IKEv2)</a> 53 */ 54 public final class ChildSaProposal extends SaProposal { 55 // Before SDK S, there is no API in IpSecAlgorithm to retrieve supported algorithms. Thus hard 56 // coded these algorithms here. 57 private static final Set<Integer> SUPPORTED_IPSEC_ENCRYPTION_BEFORE_SDK_S; 58 private static final Set<Integer> SUPPORTED_IPSEC_INTEGRITY_BEFORE_SDK_S; 59 60 static { 61 SUPPORTED_IPSEC_ENCRYPTION_BEFORE_SDK_S = new ArraySet<>(); 62 SUPPORTED_IPSEC_ENCRYPTION_BEFORE_SDK_S.add(ENCRYPTION_ALGORITHM_AES_CBC); 63 SUPPORTED_IPSEC_ENCRYPTION_BEFORE_SDK_S.add(ENCRYPTION_ALGORITHM_AES_GCM_8); 64 SUPPORTED_IPSEC_ENCRYPTION_BEFORE_SDK_S.add(ENCRYPTION_ALGORITHM_AES_GCM_12); 65 SUPPORTED_IPSEC_ENCRYPTION_BEFORE_SDK_S.add(ENCRYPTION_ALGORITHM_AES_GCM_16); 66 67 SUPPORTED_IPSEC_INTEGRITY_BEFORE_SDK_S = new ArraySet<>(); 68 SUPPORTED_IPSEC_INTEGRITY_BEFORE_SDK_S.add(INTEGRITY_ALGORITHM_HMAC_SHA1_96); 69 SUPPORTED_IPSEC_INTEGRITY_BEFORE_SDK_S.add(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128); 70 SUPPORTED_IPSEC_INTEGRITY_BEFORE_SDK_S.add(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192); 71 SUPPORTED_IPSEC_INTEGRITY_BEFORE_SDK_S.add(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256); 72 } 73 74 private static final String ESN_KEY = "mEsns"; 75 private final EsnTransform[] mEsns; 76 77 /** 78 * Construct an instance of ChildSaProposal. 79 * 80 * <p>This constructor is either called by ChildSaPayload for building an inbound proposal from 81 * a decoded packet, or called by the inner Builder to build an outbound proposal from user 82 * provided parameters 83 * 84 * @param encryptionAlgos encryption algorithms 85 * @param integrityAlgos integrity algorithms 86 * @param dhGroups Diffie-Hellman Groups 87 * @param esns ESN policies 88 * @hide 89 */ ChildSaProposal( EncryptionTransform[] encryptionAlgos, IntegrityTransform[] integrityAlgos, DhGroupTransform[] dhGroups, EsnTransform[] esns)90 public ChildSaProposal( 91 EncryptionTransform[] encryptionAlgos, 92 IntegrityTransform[] integrityAlgos, 93 DhGroupTransform[] dhGroups, 94 EsnTransform[] esns) { 95 super(IkePayload.PROTOCOL_ID_ESP, encryptionAlgos, integrityAlgos, dhGroups); 96 mEsns = esns; 97 } 98 99 /** 100 * Constructs this object by deserializing a PersistableBundle 101 * 102 * <p>Constructed proposals are guaranteed to be valid, as checked by the 103 * ChildSaProposal.Builder. 104 * 105 * @hide 106 */ 107 @NonNull fromPersistableBundle(@onNull PersistableBundle in)108 public static ChildSaProposal fromPersistableBundle(@NonNull PersistableBundle in) { 109 Objects.requireNonNull(in, "PersistableBundle is null"); 110 111 ChildSaProposal.Builder builder = new ChildSaProposal.Builder(); 112 113 PersistableBundle encryptionBundle = in.getPersistableBundle(ENCRYPT_ALGO_KEY); 114 Objects.requireNonNull(encryptionBundle, "Encryption algo bundle is null"); 115 List<EncryptionTransform> encryptList = 116 PersistableBundleUtils.toList( 117 encryptionBundle, EncryptionTransform::fromPersistableBundle); 118 for (EncryptionTransform t : encryptList) { 119 builder.addEncryptionAlgorithm(t.id, t.getSpecifiedKeyLength()); 120 } 121 122 int[] integrityAlgoIdArray = in.getIntArray(INTEGRITY_ALGO_KEY); 123 Objects.requireNonNull(integrityAlgoIdArray, "Integrity algo array is null"); 124 for (int algo : integrityAlgoIdArray) { 125 builder.addIntegrityAlgorithm(algo); 126 } 127 128 int[] dhGroupArray = in.getIntArray(DH_GROUP_KEY); 129 Objects.requireNonNull(dhGroupArray, "DH Group array is null"); 130 for (int dh : dhGroupArray) { 131 builder.addDhGroup(dh); 132 } 133 134 int[] esnPolicies = in.getIntArray(ESN_KEY); 135 Objects.requireNonNull(esnPolicies, "ESN policy array is null"); 136 137 for (int p : esnPolicies) { 138 switch (p) { 139 case ESN_POLICY_NO_EXTENDED: 140 // Ignored. All ChildSaProposal(s) are proposed with this automatically 141 break; 142 default: 143 throw new IllegalArgumentException( 144 "Proposing ESN policy: " + p + " is unsupported"); 145 } 146 } 147 148 return builder.build(); 149 } 150 151 /** 152 * Serializes this object to a PersistableBundle 153 * 154 * @hide 155 */ 156 @Override 157 @NonNull toPersistableBundle()158 public PersistableBundle toPersistableBundle() { 159 final PersistableBundle result = super.toPersistableBundle(); 160 int[] esnPolicies = Arrays.asList(mEsns).stream().mapToInt(esn -> esn.id).toArray(); 161 result.putIntArray(ESN_KEY, esnPolicies); 162 163 return result; 164 } 165 166 /** 167 * Returns supported encryption algorithms for Child SA proposal negotiation. 168 * 169 * <p>Some algorithms may not be supported on old devices. 170 */ 171 @NonNull getSupportedEncryptionAlgorithms()172 public static Set<Integer> getSupportedEncryptionAlgorithms() { 173 if (SdkLevel.isAtLeastS()) { 174 Set<Integer> algoIds = new ArraySet<>(); 175 for (int i = 0; i < SUPPORTED_ENCRYPTION_ALGO_TO_STR.size(); i++) { 176 int ikeAlgoId = SUPPORTED_ENCRYPTION_ALGO_TO_STR.keyAt(i); 177 String ipSecAlgoName = IkeCipher.getIpSecAlgorithmName(ikeAlgoId); 178 if (IpSecAlgorithm.getSupportedAlgorithms().contains(ipSecAlgoName)) { 179 algoIds.add(ikeAlgoId); 180 } 181 } 182 return algoIds; 183 } else { 184 return SUPPORTED_IPSEC_ENCRYPTION_BEFORE_SDK_S; 185 } 186 } 187 188 /** 189 * Returns supported integrity algorithms for Child SA proposal negotiation. 190 * 191 * <p>Some algorithms may not be supported on old devices. 192 */ 193 @NonNull getSupportedIntegrityAlgorithms()194 public static Set<Integer> getSupportedIntegrityAlgorithms() { 195 Set<Integer> algoIds = new ArraySet<>(); 196 197 // Although IpSecAlgorithm does not support INTEGRITY_ALGORITHM_NONE, IKE supports 198 // negotiating it and won't build IpSecAlgorithm with it. 199 algoIds.add(INTEGRITY_ALGORITHM_NONE); 200 201 if (SdkLevel.isAtLeastS()) { 202 for (int i = 0; i < SUPPORTED_INTEGRITY_ALGO_TO_STR.size(); i++) { 203 int ikeAlgoId = SUPPORTED_INTEGRITY_ALGO_TO_STR.keyAt(i); 204 String ipSecAlgoName = IkeMacIntegrity.getIpSecAlgorithmName(ikeAlgoId); 205 if (IpSecAlgorithm.getSupportedAlgorithms().contains(ipSecAlgoName)) { 206 algoIds.add(ikeAlgoId); 207 } 208 } 209 } else { 210 algoIds.addAll(SUPPORTED_IPSEC_INTEGRITY_BEFORE_SDK_S); 211 } 212 return algoIds; 213 } 214 215 /** 216 * Gets all ESN policies. 217 * 218 * @hide 219 */ getEsnTransforms()220 public EsnTransform[] getEsnTransforms() { 221 return mEsns; 222 } 223 224 /** 225 * Gets a copy of proposal without all proposed DH groups. 226 * 227 * <p>This is used to avoid negotiating DH Group for negotiating first Child SA. 228 * 229 * @hide 230 */ getCopyWithoutDhTransform()231 public ChildSaProposal getCopyWithoutDhTransform() { 232 return new ChildSaProposal( 233 getEncryptionTransforms(), 234 getIntegrityTransforms(), 235 new DhGroupTransform[0], 236 getEsnTransforms()); 237 } 238 239 /** @hide */ 240 @Override getAllTransforms()241 public Transform[] getAllTransforms() { 242 List<Transform> transformList = getAllTransformsAsList(); 243 transformList.addAll(Arrays.asList(mEsns)); 244 245 return transformList.toArray(new Transform[transformList.size()]); 246 } 247 248 /** @hide */ 249 @Override isNegotiatedFrom(SaProposal reqProposal)250 public boolean isNegotiatedFrom(SaProposal reqProposal) { 251 return super.isNegotiatedFrom(reqProposal) 252 && isTransformSelectedFrom(mEsns, ((ChildSaProposal) reqProposal).mEsns); 253 } 254 255 /** @hide */ isNegotiatedFromExceptDhGroup(SaProposal saProposal)256 public boolean isNegotiatedFromExceptDhGroup(SaProposal saProposal) { 257 return getProtocolId() == saProposal.getProtocolId() 258 && isTransformSelectedFrom( 259 getEncryptionTransforms(), saProposal.getEncryptionTransforms()) 260 && isTransformSelectedFrom( 261 getIntegrityTransforms(), saProposal.getIntegrityTransforms()) 262 && isTransformSelectedFrom(mEsns, ((ChildSaProposal) saProposal).mEsns); 263 } 264 265 /** @hide */ getCopyWithAdditionalDhTransform(int dhGroup)266 public ChildSaProposal getCopyWithAdditionalDhTransform(int dhGroup) { 267 return new ChildSaProposal( 268 getEncryptionTransforms(), 269 getIntegrityTransforms(), 270 new DhGroupTransform[] {new DhGroupTransform(dhGroup)}, 271 getEsnTransforms()); 272 } 273 274 @Override hashCode()275 public int hashCode() { 276 return Objects.hash(super.hashCode(), Arrays.hashCode(mEsns)); 277 } 278 279 @Override equals(Object o)280 public boolean equals(Object o) { 281 if (!super.equals(o) || !(o instanceof ChildSaProposal)) { 282 return false; 283 } 284 285 return Arrays.equals(mEsns, ((ChildSaProposal) o).mEsns); 286 } 287 288 /** 289 * This class is used to incrementally construct a ChildSaProposal. ChildSaProposal instances 290 * are immutable once built. 291 */ 292 public static final class Builder extends SaProposal.Builder { 293 /** 294 * Adds an encryption algorithm with a specific key length to the SA proposal being built. 295 * 296 * @param algorithm encryption algorithm to add to ChildSaProposal. 297 * @param keyLength key length of algorithm. For algorithms that have fixed key length (e.g. 298 * 3DES) only {@link SaProposal#KEY_LEN_UNUSED} is allowed. 299 * @return Builder of ChildSaProposal. 300 */ 301 // The matching getter is defined in the super class. Please see {@link 302 // SaProposal#getEncryptionAlgorithms} 303 @SuppressLint("MissingGetterMatchingBuilder") 304 @NonNull addEncryptionAlgorithm(@ncryptionAlgorithm int algorithm, int keyLength)305 public Builder addEncryptionAlgorithm(@EncryptionAlgorithm int algorithm, int keyLength) { 306 validateAndAddEncryptAlgo(algorithm, keyLength, true /* isChild */); 307 return this; 308 } 309 310 /** 311 * Adds an integrity algorithm to the SA proposal being built. 312 * 313 * @param algorithm integrity algorithm to add to ChildSaProposal. 314 * @return Builder of ChildSaProposal. 315 */ 316 // The matching getter is defined in the super class. Please see 317 // {@link SaProposal#getIntegrityAlgorithms} 318 @SuppressLint("MissingGetterMatchingBuilder") 319 @NonNull addIntegrityAlgorithm(@ntegrityAlgorithm int algorithm)320 public Builder addIntegrityAlgorithm(@IntegrityAlgorithm int algorithm) { 321 validateAndAddIntegrityAlgo(algorithm, true /* isChild */); 322 return this; 323 } 324 325 /** 326 * Adds a Diffie-Hellman Group to the SA proposal being built. 327 * 328 * @param dhGroup to add to ChildSaProposal. 329 * @return Builder of ChildSaProposal. 330 */ 331 // The matching getter is defined in the super class. Please see 332 // {@link SaProposal#getDhGroups} 333 @SuppressLint("MissingGetterMatchingBuilder") 334 @NonNull addDhGroup(@hGroup int dhGroup)335 public Builder addDhGroup(@DhGroup int dhGroup) { 336 addDh(dhGroup); 337 return this; 338 } 339 buildIntegAlgosOrThrow()340 private IntegrityTransform[] buildIntegAlgosOrThrow() { 341 // When building Child SA Proposal with normal-mode ciphers, there is no contraint on 342 // integrity algorithm. When building Child SA Proposal with combined-mode ciphers, 343 // mProposedIntegrityAlgos must be either empty or only have INTEGRITY_ALGORITHM_NONE. 344 for (IntegrityTransform transform : mProposedIntegrityAlgos) { 345 if (transform.id != INTEGRITY_ALGORITHM_NONE && mHasAead) { 346 throw new IllegalArgumentException( 347 ERROR_TAG 348 + "Only INTEGRITY_ALGORITHM_NONE can be" 349 + " proposed with combined-mode ciphers in any proposal."); 350 } 351 } 352 353 return mProposedIntegrityAlgos.toArray( 354 new IntegrityTransform[mProposedIntegrityAlgos.size()]); 355 } 356 357 /** 358 * Validates and builds the ChildSaProposal. 359 * 360 * @return the validated ChildSaProposal. 361 */ 362 @NonNull build()363 public ChildSaProposal build() { 364 EncryptionTransform[] encryptionTransforms = buildEncryptAlgosOrThrow(); 365 IntegrityTransform[] integrityTransforms = buildIntegAlgosOrThrow(); 366 367 return new ChildSaProposal( 368 encryptionTransforms, 369 integrityTransforms, 370 mProposedDhGroups.toArray(new DhGroupTransform[mProposedDhGroups.size()]), 371 new EsnTransform[] {new EsnTransform()}); 372 } 373 } 374 } 375