• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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