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