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 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.internal.util.HexDump; 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 235 // STOPSHIP: b/170424293 Use Build.VERSION_CODES.S when it is defined ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.R + 1)236 ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.R + 1); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.R + 1)237 ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.R + 1); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.R + 1)238 ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.R + 1); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.R + 1)239 ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.R + 1); 240 } 241 242 private static final Set<String> ENABLED_ALGOS = 243 Collections.unmodifiableSet(loadAlgos(Resources.getSystem())); 244 245 private final String mName; 246 private final byte[] mKey; 247 private final int mTruncLenBits; 248 249 /** 250 * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are 251 * defined as constants in this class. 252 * 253 * <p>For algorithms that produce an integrity check value, the truncation length is a required 254 * parameter. See {@link #IpSecAlgorithm(String algorithm, byte[] key, int truncLenBits)} 255 * 256 * @param algorithm name of the algorithm. 257 * @param key key padded to a multiple of 8 bits. 258 * @throws IllegalArgumentException if algorithm or key length is invalid. 259 */ IpSecAlgorithm(@onNull @lgorithmName String algorithm, @NonNull byte[] key)260 public IpSecAlgorithm(@NonNull @AlgorithmName String algorithm, @NonNull byte[] key) { 261 this(algorithm, key, 0); 262 } 263 264 /** 265 * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are 266 * defined as constants in this class. 267 * 268 * <p>This constructor only supports algorithms that use a truncation length. i.e. 269 * Authentication and Authenticated Encryption algorithms. 270 * 271 * @param algorithm name of the algorithm. 272 * @param key key padded to a multiple of 8 bits. 273 * @param truncLenBits number of bits of output hash to use. 274 * @throws IllegalArgumentException if algorithm, key length or truncation length is invalid. 275 */ IpSecAlgorithm( @onNull @lgorithmName String algorithm, @NonNull byte[] key, int truncLenBits)276 public IpSecAlgorithm( 277 @NonNull @AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) { 278 mName = algorithm; 279 mKey = key.clone(); 280 mTruncLenBits = truncLenBits; 281 checkValidOrThrow(mName, mKey.length * 8, mTruncLenBits); 282 } 283 284 /** Get the algorithm name */ 285 @NonNull getName()286 public String getName() { 287 return mName; 288 } 289 290 /** Get the key for this algorithm */ 291 @NonNull getKey()292 public byte[] getKey() { 293 return mKey.clone(); 294 } 295 296 /** Get the truncation length of this algorithm, in bits */ getTruncationLengthBits()297 public int getTruncationLengthBits() { 298 return mTruncLenBits; 299 } 300 301 /* Parcelable Implementation */ describeContents()302 public int describeContents() { 303 return 0; 304 } 305 306 /** Write to parcel */ writeToParcel(Parcel out, int flags)307 public void writeToParcel(Parcel out, int flags) { 308 out.writeString(mName); 309 out.writeByteArray(mKey); 310 out.writeInt(mTruncLenBits); 311 } 312 313 /** Parcelable Creator */ 314 public static final @android.annotation.NonNull Parcelable.Creator<IpSecAlgorithm> CREATOR = 315 new Parcelable.Creator<IpSecAlgorithm>() { 316 public IpSecAlgorithm createFromParcel(Parcel in) { 317 final String name = in.readString(); 318 final byte[] key = in.createByteArray(); 319 final int truncLenBits = in.readInt(); 320 321 return new IpSecAlgorithm(name, key, truncLenBits); 322 } 323 324 public IpSecAlgorithm[] newArray(int size) { 325 return new IpSecAlgorithm[size]; 326 } 327 }; 328 329 /** 330 * Returns supported IPsec algorithms for the current device. 331 * 332 * <p>Some algorithms may not be supported on old devices. Callers MUST check if an algorithm is 333 * supported before using it. 334 */ 335 @NonNull getSupportedAlgorithms()336 public static Set<String> getSupportedAlgorithms() { 337 return ENABLED_ALGOS; 338 } 339 340 /** @hide */ 341 @VisibleForTesting loadAlgos(Resources systemResources)342 public static Set<String> loadAlgos(Resources systemResources) { 343 final Set<String> enabledAlgos = new HashSet<>(); 344 345 // Load and validate the optional algorithm resource. Undefined or duplicate algorithms in 346 // the resource are not allowed. 347 final String[] resourceAlgos = systemResources.getStringArray( 348 com.android.internal.R.array.config_optionalIpSecAlgorithms); 349 for (String str : resourceAlgos) { 350 if (!ALGO_TO_REQUIRED_FIRST_SDK.containsKey(str) || !enabledAlgos.add(str)) { 351 // This error should be caught by CTS and never be thrown to API callers 352 throw new IllegalArgumentException("Invalid or repeated algorithm " + str); 353 } 354 } 355 356 for (Entry<String, Integer> entry : ALGO_TO_REQUIRED_FIRST_SDK.entrySet()) { 357 if (Build.VERSION.DEVICE_INITIAL_SDK_INT >= entry.getValue()) { 358 enabledAlgos.add(entry.getKey()); 359 } 360 } 361 362 return enabledAlgos; 363 } 364 checkValidOrThrow(String name, int keyLen, int truncLen)365 private static void checkValidOrThrow(String name, int keyLen, int truncLen) { 366 final boolean isValidLen; 367 final boolean isValidTruncLen; 368 369 if (!getSupportedAlgorithms().contains(name)) { 370 throw new IllegalArgumentException("Unsupported algorithm: " + name); 371 } 372 373 switch (name) { 374 case CRYPT_AES_CBC: 375 isValidLen = keyLen == 128 || keyLen == 192 || keyLen == 256; 376 isValidTruncLen = true; 377 break; 378 case CRYPT_AES_CTR: 379 // The keying material for AES-CTR is a key plus a 32-bit salt 380 isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32; 381 isValidTruncLen = true; 382 break; 383 case AUTH_HMAC_MD5: 384 isValidLen = keyLen == 128; 385 isValidTruncLen = truncLen >= 96 && truncLen <= 128; 386 break; 387 case AUTH_HMAC_SHA1: 388 isValidLen = keyLen == 160; 389 isValidTruncLen = truncLen >= 96 && truncLen <= 160; 390 break; 391 case AUTH_HMAC_SHA256: 392 isValidLen = keyLen == 256; 393 isValidTruncLen = truncLen >= 96 && truncLen <= 256; 394 break; 395 case AUTH_HMAC_SHA384: 396 isValidLen = keyLen == 384; 397 isValidTruncLen = truncLen >= 192 && truncLen <= 384; 398 break; 399 case AUTH_HMAC_SHA512: 400 isValidLen = keyLen == 512; 401 isValidTruncLen = truncLen >= 256 && truncLen <= 512; 402 break; 403 case AUTH_AES_XCBC: 404 isValidLen = keyLen == 128; 405 isValidTruncLen = truncLen == 96; 406 break; 407 case AUTH_AES_CMAC: 408 isValidLen = keyLen == 128; 409 isValidTruncLen = truncLen == 96; 410 break; 411 case AUTH_CRYPT_AES_GCM: 412 // The keying material for GCM is a key plus a 32-bit salt 413 isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32; 414 isValidTruncLen = truncLen == 64 || truncLen == 96 || truncLen == 128; 415 break; 416 case AUTH_CRYPT_CHACHA20_POLY1305: 417 // The keying material for ChaCha20Poly1305 is a key plus a 32-bit salt 418 isValidLen = keyLen == 256 + 32; 419 isValidTruncLen = truncLen == 128; 420 break; 421 default: 422 // Should never hit here. 423 throw new IllegalArgumentException("Couldn't find an algorithm: " + name); 424 } 425 426 if (!isValidLen) { 427 throw new IllegalArgumentException("Invalid key material keyLength: " + keyLen); 428 } 429 if (!isValidTruncLen) { 430 throw new IllegalArgumentException("Invalid truncation keyLength: " + truncLen); 431 } 432 } 433 434 /** @hide */ isAuthentication()435 public boolean isAuthentication() { 436 switch (getName()) { 437 // Fallthrough 438 case AUTH_HMAC_MD5: 439 case AUTH_HMAC_SHA1: 440 case AUTH_HMAC_SHA256: 441 case AUTH_HMAC_SHA384: 442 case AUTH_HMAC_SHA512: 443 case AUTH_AES_XCBC: 444 case AUTH_AES_CMAC: 445 return true; 446 default: 447 return false; 448 } 449 } 450 451 /** @hide */ isEncryption()452 public boolean isEncryption() { 453 switch (getName()) { 454 case CRYPT_AES_CBC: // fallthrough 455 case CRYPT_AES_CTR: 456 return true; 457 default: 458 return false; 459 } 460 } 461 462 /** @hide */ isAead()463 public boolean isAead() { 464 switch (getName()) { 465 case AUTH_CRYPT_AES_GCM: // fallthrough 466 case AUTH_CRYPT_CHACHA20_POLY1305: 467 return true; 468 default: 469 return false; 470 } 471 } 472 473 // Because encryption keys are sensitive and userdebug builds are used by large user pools 474 // such as beta testers, we only allow sensitive info such as keys on eng builds. isUnsafeBuild()475 private static boolean isUnsafeBuild() { 476 return Build.IS_DEBUGGABLE && Build.IS_ENG; 477 } 478 479 @Override 480 @NonNull toString()481 public String toString() { 482 return new StringBuilder() 483 .append("{mName=") 484 .append(mName) 485 .append(", mKey=") 486 .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>") 487 .append(", mTruncLenBits=") 488 .append(mTruncLenBits) 489 .append("}") 490 .toString(); 491 } 492 493 /** @hide */ 494 @VisibleForTesting equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs)495 public static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) { 496 if (lhs == null || rhs == null) return (lhs == rhs); 497 return (lhs.mName.equals(rhs.mName) 498 && Arrays.equals(lhs.mKey, rhs.mKey) 499 && lhs.mTruncLenBits == rhs.mTruncLenBits); 500 } 501 }; 502