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