1 /* 2 * Copyright (C) 2017 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 package android.net; 17 18 import android.annotation.NonNull; 19 import android.annotation.StringDef; 20 import android.content.res.Resources; 21 import android.os.Build; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.os.SystemProperties; 25 26 import com.android.internal.annotations.VisibleForTesting; 27 28 import java.lang.annotation.Retention; 29 import java.lang.annotation.RetentionPolicy; 30 import java.util.Arrays; 31 import java.util.Collections; 32 import java.util.HashMap; 33 import java.util.HashSet; 34 import java.util.Map; 35 import java.util.Map.Entry; 36 import java.util.Set; 37 38 /** 39 * This class represents a single algorithm that can be used by an {@link IpSecTransform}. 40 * 41 * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the 42 * Internet Protocol</a> 43 */ 44 public final class IpSecAlgorithm implements Parcelable { 45 private static final String TAG = "IpSecAlgorithm"; 46 47 /** 48 * Null cipher. 49 * 50 * @hide 51 */ 52 public static final String CRYPT_NULL = "ecb(cipher_null)"; 53 54 /** 55 * AES-CBC Encryption/Ciphering Algorithm. 56 * 57 * <p>Valid lengths for this key are {128, 192, 256}. 58 */ 59 public static final String CRYPT_AES_CBC = "cbc(aes)"; 60 61 /** 62 * AES-CTR Encryption/Ciphering Algorithm. 63 * 64 * <p>Valid lengths for keying material are {160, 224, 288}. 65 * 66 * <p>As per <a href="https://tools.ietf.org/html/rfc3686#section-5.1">RFC3686 (Section 67 * 5.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit 68 * nonce. RFC compliance requires that the nonce must be unique per security association. 69 * 70 * <p>This algorithm may be available on the device. Caller MUST check if it is supported before 71 * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is 72 * included in the returned algorithm set. The returned algorithm set will not change unless the 73 * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is 74 * requested on an unsupported device. 75 * 76 * <p>@see {@link #getSupportedAlgorithms()} 77 */ 78 // This algorithm may be available on devices released before Android 12, and is guaranteed 79 // to be available on devices first shipped with Android 12 or later. 80 public static final String CRYPT_AES_CTR = "rfc3686(ctr(aes))"; 81 82 /** 83 * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in 84 * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b> 85 * 86 * <p>Keys for this algorithm must be 128 bits in length. 87 * 88 * <p>Valid truncation lengths are multiples of 8 bits from 96 to 128. 89 */ 90 public static final String AUTH_HMAC_MD5 = "hmac(md5)"; 91 92 /** 93 * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in 94 * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b> 95 * 96 * <p>Keys for this algorithm must be 160 bits in length. 97 * 98 * <p>Valid truncation lengths are multiples of 8 bits from 96 to 160. 99 */ 100 public static final String AUTH_HMAC_SHA1 = "hmac(sha1)"; 101 102 /** 103 * SHA256 HMAC Authentication/Integrity Algorithm. 104 * 105 * <p>Keys for this algorithm must be 256 bits in length. 106 * 107 * <p>Valid truncation lengths are multiples of 8 bits from 96 to 256. 108 */ 109 public static final String AUTH_HMAC_SHA256 = "hmac(sha256)"; 110 111 /** 112 * SHA384 HMAC Authentication/Integrity Algorithm. 113 * 114 * <p>Keys for this algorithm must be 384 bits in length. 115 * 116 * <p>Valid truncation lengths are multiples of 8 bits from 192 to 384. 117 */ 118 public static final String AUTH_HMAC_SHA384 = "hmac(sha384)"; 119 120 /** 121 * SHA512 HMAC Authentication/Integrity Algorithm. 122 * 123 * <p>Keys for this algorithm must be 512 bits in length. 124 * 125 * <p>Valid truncation lengths are multiples of 8 bits from 256 to 512. 126 */ 127 public static final String AUTH_HMAC_SHA512 = "hmac(sha512)"; 128 129 /** 130 * AES-XCBC Authentication/Integrity Algorithm. 131 * 132 * <p>Keys for this algorithm must be 128 bits in length. 133 * 134 * <p>The only valid truncation length is 96 bits. 135 * 136 * <p>This algorithm may be available on the device. Caller MUST check if it is supported before 137 * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is 138 * included in the returned algorithm set. The returned algorithm set will not change unless the 139 * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is 140 * requested on an unsupported device. 141 * 142 * <p>@see {@link #getSupportedAlgorithms()} 143 */ 144 // This algorithm may be available on devices released before Android 12, and is guaranteed 145 // to be available on devices first shipped with Android 12 or later. 146 public static final String AUTH_AES_XCBC = "xcbc(aes)"; 147 148 /** 149 * AES-CMAC Authentication/Integrity Algorithm. 150 * 151 * <p>Keys for this algorithm must be 128 bits in length. 152 * 153 * <p>The only valid truncation length is 96 bits. 154 * 155 * <p>This algorithm may be available on the device. Caller MUST check if it is supported before 156 * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is 157 * included in the returned algorithm set. The returned algorithm set will not change unless the 158 * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is 159 * requested on an unsupported device. 160 * 161 * <p>@see {@link #getSupportedAlgorithms()} 162 */ 163 // This algorithm may be available on devices released before Android 12, and is guaranteed 164 // to be available on devices first shipped with Android 12 or later. 165 public static final String AUTH_AES_CMAC = "cmac(aes)"; 166 167 /** 168 * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm. 169 * 170 * <p>Valid lengths for keying material are {160, 224, 288}. 171 * 172 * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section 173 * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit 174 * salt. RFC compliance requires that the salt must be unique per invocation with the same key. 175 * 176 * <p>Valid ICV (truncation) lengths are {64, 96, 128}. 177 */ 178 public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))"; 179 180 /** 181 * ChaCha20-Poly1305 Authentication/Integrity + Encryption/Ciphering Algorithm. 182 * 183 * <p>Keys for this algorithm must be 288 bits in length. 184 * 185 * <p>As per <a href="https://tools.ietf.org/html/rfc7634#section-2">RFC7634 (Section 2)</a>, 186 * keying material consists of a 256 bit key followed by a 32-bit salt. The salt is fixed per 187 * security association. 188 * 189 * <p>The only valid ICV (truncation) length is 128 bits. 190 * 191 * <p>This algorithm may be available on the device. Caller MUST check if it is supported before 192 * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is 193 * included in the returned algorithm set. The returned algorithm set will not change unless the 194 * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is 195 * requested on an unsupported device. 196 * 197 * <p>@see {@link #getSupportedAlgorithms()} 198 */ 199 // This algorithm may be available on devices released before Android 12, and is guaranteed 200 // to be available on devices first shipped with Android 12 or later. 201 public static final String AUTH_CRYPT_CHACHA20_POLY1305 = "rfc7539esp(chacha20,poly1305)"; 202 203 /** @hide */ 204 @StringDef({ 205 CRYPT_AES_CBC, 206 CRYPT_AES_CTR, 207 AUTH_HMAC_MD5, 208 AUTH_HMAC_SHA1, 209 AUTH_HMAC_SHA256, 210 AUTH_HMAC_SHA384, 211 AUTH_HMAC_SHA512, 212 AUTH_AES_XCBC, 213 AUTH_AES_CMAC, 214 AUTH_CRYPT_AES_GCM, 215 AUTH_CRYPT_CHACHA20_POLY1305 216 }) 217 @Retention(RetentionPolicy.SOURCE) 218 public @interface AlgorithmName {} 219 220 /** @hide */ 221 @VisibleForTesting 222 public static final Map<String, Integer> ALGO_TO_REQUIRED_FIRST_SDK = new HashMap<>(); 223 224 private static final int SDK_VERSION_ZERO = 0; 225 226 static { ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CBC, SDK_VERSION_ZERO)227 ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CBC, SDK_VERSION_ZERO); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_MD5, SDK_VERSION_ZERO)228 ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_MD5, SDK_VERSION_ZERO); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA1, SDK_VERSION_ZERO)229 ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA1, SDK_VERSION_ZERO); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA256, SDK_VERSION_ZERO)230 ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA256, SDK_VERSION_ZERO); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA384, SDK_VERSION_ZERO)231 ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA384, SDK_VERSION_ZERO); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA512, SDK_VERSION_ZERO)232 ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA512, SDK_VERSION_ZERO); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_AES_GCM, SDK_VERSION_ZERO)233 ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_AES_GCM, SDK_VERSION_ZERO); 234 ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.S)235 ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.S); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.S)236 ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.S); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.S)237 ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.S); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.S)238 ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.S); 239 } 240 241 private static final Set<String> ENABLED_ALGOS = 242 Collections.unmodifiableSet(loadAlgos(Resources.getSystem())); 243 244 private final String mName; 245 private final byte[] mKey; 246 private final int mTruncLenBits; 247 248 /** 249 * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are 250 * defined as constants in this class. 251 * 252 * <p>For algorithms that produce an integrity check value, the truncation length is a required 253 * parameter. See {@link #IpSecAlgorithm(String algorithm, byte[] key, int truncLenBits)} 254 * 255 * @param algorithm name of the algorithm. 256 * @param key key padded to a multiple of 8 bits. 257 * @throws IllegalArgumentException if algorithm or key length is invalid. 258 */ IpSecAlgorithm(@onNull @lgorithmName String algorithm, @NonNull byte[] key)259 public IpSecAlgorithm(@NonNull @AlgorithmName String algorithm, @NonNull byte[] key) { 260 this(algorithm, key, 0); 261 } 262 263 /** 264 * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are 265 * defined as constants in this class. 266 * 267 * <p>This constructor only supports algorithms that use a truncation length. i.e. 268 * Authentication and Authenticated Encryption algorithms. 269 * 270 * @param algorithm name of the algorithm. 271 * @param key key padded to a multiple of 8 bits. 272 * @param truncLenBits number of bits of output hash to use. 273 * @throws IllegalArgumentException if algorithm, key length or truncation length is invalid. 274 */ IpSecAlgorithm( @onNull @lgorithmName String algorithm, @NonNull byte[] key, int truncLenBits)275 public IpSecAlgorithm( 276 @NonNull @AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) { 277 mName = algorithm; 278 mKey = key.clone(); 279 mTruncLenBits = truncLenBits; 280 checkValidOrThrow(mName, mKey.length * 8, mTruncLenBits); 281 } 282 283 /** Get the algorithm name */ 284 @NonNull getName()285 public String getName() { 286 return mName; 287 } 288 289 /** Get the key for this algorithm */ 290 @NonNull getKey()291 public byte[] getKey() { 292 return mKey.clone(); 293 } 294 295 /** Get the truncation length of this algorithm, in bits */ getTruncationLengthBits()296 public int getTruncationLengthBits() { 297 return mTruncLenBits; 298 } 299 300 /** Parcelable Implementation */ describeContents()301 public int describeContents() { 302 return 0; 303 } 304 305 /** Write to parcel */ writeToParcel(Parcel out, int flags)306 public void writeToParcel(Parcel out, int flags) { 307 out.writeString(mName); 308 out.writeByteArray(mKey); 309 out.writeInt(mTruncLenBits); 310 } 311 312 /** Parcelable Creator */ 313 public static final @android.annotation.NonNull Parcelable.Creator<IpSecAlgorithm> CREATOR = 314 new Parcelable.Creator<IpSecAlgorithm>() { 315 public IpSecAlgorithm createFromParcel(Parcel in) { 316 final String name = in.readString(); 317 final byte[] key = in.createByteArray(); 318 final int truncLenBits = in.readInt(); 319 320 return new IpSecAlgorithm(name, key, truncLenBits); 321 } 322 323 public IpSecAlgorithm[] newArray(int size) { 324 return new IpSecAlgorithm[size]; 325 } 326 }; 327 328 /** 329 * Returns supported IPsec algorithms for the current device. 330 * 331 * <p>Some algorithms may not be supported on old devices. Callers MUST check if an algorithm is 332 * supported before using it. 333 */ 334 @NonNull getSupportedAlgorithms()335 public static Set<String> getSupportedAlgorithms() { 336 return ENABLED_ALGOS; 337 } 338 339 /** @hide */ 340 @VisibleForTesting loadAlgos(Resources systemResources)341 public static Set<String> loadAlgos(Resources systemResources) { 342 final Set<String> enabledAlgos = new HashSet<>(); 343 344 // Load and validate the optional algorithm resource. Undefined or duplicate algorithms in 345 // the resource are not allowed. 346 final String[] resourceAlgos = systemResources.getStringArray( 347 android.R.array.config_optionalIpSecAlgorithms); 348 for (String str : resourceAlgos) { 349 if (!ALGO_TO_REQUIRED_FIRST_SDK.containsKey(str) || !enabledAlgos.add(str)) { 350 // This error should be caught by CTS and never be thrown to API callers 351 throw new IllegalArgumentException("Invalid or repeated algorithm " + str); 352 } 353 } 354 355 // T introduced calculated property 'ro.vendor.api_level', 356 // which is the API level of the VSR that the device must conform to. 357 int vendorApiLevel = SystemProperties.getInt("ro.vendor.api_level", 10000); 358 for (Entry<String, Integer> entry : ALGO_TO_REQUIRED_FIRST_SDK.entrySet()) { 359 if (vendorApiLevel >= entry.getValue()) { 360 enabledAlgos.add(entry.getKey()); 361 } 362 } 363 364 return enabledAlgos; 365 } 366 checkValidOrThrow(String name, int keyLen, int truncLen)367 private static void checkValidOrThrow(String name, int keyLen, int truncLen) { 368 final boolean isValidLen; 369 final boolean isValidTruncLen; 370 371 if (!getSupportedAlgorithms().contains(name)) { 372 throw new IllegalArgumentException("Unsupported algorithm: " + name); 373 } 374 375 switch (name) { 376 case CRYPT_AES_CBC: 377 isValidLen = keyLen == 128 || keyLen == 192 || keyLen == 256; 378 isValidTruncLen = true; 379 break; 380 case CRYPT_AES_CTR: 381 // The keying material for AES-CTR is a key plus a 32-bit salt 382 isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32; 383 isValidTruncLen = true; 384 break; 385 case AUTH_HMAC_MD5: 386 isValidLen = keyLen == 128; 387 isValidTruncLen = truncLen >= 96 && truncLen <= 128; 388 break; 389 case AUTH_HMAC_SHA1: 390 isValidLen = keyLen == 160; 391 isValidTruncLen = truncLen >= 96 && truncLen <= 160; 392 break; 393 case AUTH_HMAC_SHA256: 394 isValidLen = keyLen == 256; 395 isValidTruncLen = truncLen >= 96 && truncLen <= 256; 396 break; 397 case AUTH_HMAC_SHA384: 398 isValidLen = keyLen == 384; 399 isValidTruncLen = truncLen >= 192 && truncLen <= 384; 400 break; 401 case AUTH_HMAC_SHA512: 402 isValidLen = keyLen == 512; 403 isValidTruncLen = truncLen >= 256 && truncLen <= 512; 404 break; 405 case AUTH_AES_XCBC: 406 isValidLen = keyLen == 128; 407 isValidTruncLen = truncLen == 96; 408 break; 409 case AUTH_AES_CMAC: 410 isValidLen = keyLen == 128; 411 isValidTruncLen = truncLen == 96; 412 break; 413 case AUTH_CRYPT_AES_GCM: 414 // The keying material for GCM is a key plus a 32-bit salt 415 isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32; 416 isValidTruncLen = truncLen == 64 || truncLen == 96 || truncLen == 128; 417 break; 418 case AUTH_CRYPT_CHACHA20_POLY1305: 419 // The keying material for ChaCha20Poly1305 is a key plus a 32-bit salt 420 isValidLen = keyLen == 256 + 32; 421 isValidTruncLen = truncLen == 128; 422 break; 423 default: 424 // Should never hit here. 425 throw new IllegalArgumentException("Couldn't find an algorithm: " + name); 426 } 427 428 if (!isValidLen) { 429 throw new IllegalArgumentException("Invalid key material keyLength: " + keyLen); 430 } 431 if (!isValidTruncLen) { 432 throw new IllegalArgumentException("Invalid truncation keyLength: " + truncLen); 433 } 434 } 435 436 /** @hide */ isAuthentication()437 public boolean isAuthentication() { 438 switch (getName()) { 439 // Fallthrough 440 case AUTH_HMAC_MD5: 441 case AUTH_HMAC_SHA1: 442 case AUTH_HMAC_SHA256: 443 case AUTH_HMAC_SHA384: 444 case AUTH_HMAC_SHA512: 445 case AUTH_AES_XCBC: 446 case AUTH_AES_CMAC: 447 return true; 448 default: 449 return false; 450 } 451 } 452 453 /** @hide */ isEncryption()454 public boolean isEncryption() { 455 switch (getName()) { 456 case CRYPT_AES_CBC: // fallthrough 457 case CRYPT_AES_CTR: 458 return true; 459 default: 460 return false; 461 } 462 } 463 464 /** @hide */ isAead()465 public boolean isAead() { 466 switch (getName()) { 467 case AUTH_CRYPT_AES_GCM: // fallthrough 468 case AUTH_CRYPT_CHACHA20_POLY1305: 469 return true; 470 default: 471 return false; 472 } 473 } 474 475 @Override 476 @NonNull toString()477 public String toString() { 478 return new StringBuilder() 479 .append("{mName=") 480 .append(mName) 481 .append(", mTruncLenBits=") 482 .append(mTruncLenBits) 483 .append("}") 484 .toString(); 485 } 486 487 /** @hide */ 488 @VisibleForTesting equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs)489 public static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) { 490 if (lhs == null || rhs == null) return (lhs == rhs); 491 return (lhs.mName.equals(rhs.mName) 492 && Arrays.equals(lhs.mKey, rhs.mKey) 493 && lhs.mTruncLenBits == rhs.mTruncLenBits); 494 } 495 } 496