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.os.Build;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.internal.util.HexDump;
26 
27 import java.lang.annotation.Retention;
28 import java.lang.annotation.RetentionPolicy;
29 import java.util.Arrays;
30 
31 /**
32  * This class represents a single algorithm that can be used by an {@link IpSecTransform}.
33  *
34  * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
35  * Internet Protocol</a>
36  */
37 public final class IpSecAlgorithm implements Parcelable {
38     private static final String TAG = "IpSecAlgorithm";
39 
40     /**
41      * Null cipher.
42      *
43      * @hide
44      */
45     public static final String CRYPT_NULL = "ecb(cipher_null)";
46 
47     /**
48      * AES-CBC Encryption/Ciphering Algorithm.
49      *
50      * <p>Valid lengths for this key are {128, 192, 256}.
51      */
52     public static final String CRYPT_AES_CBC = "cbc(aes)";
53 
54     /**
55      * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
56      * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
57      *
58      * <p>Keys for this algorithm must be 128 bits in length.
59      *
60      * <p>Valid truncation lengths are multiples of 8 bits from 96 to 128.
61      */
62     public static final String AUTH_HMAC_MD5 = "hmac(md5)";
63 
64     /**
65      * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
66      * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
67      *
68      * <p>Keys for this algorithm must be 160 bits in length.
69      *
70      * <p>Valid truncation lengths are multiples of 8 bits from 96 to 160.
71      */
72     public static final String AUTH_HMAC_SHA1 = "hmac(sha1)";
73 
74     /**
75      * SHA256 HMAC Authentication/Integrity Algorithm.
76      *
77      * <p>Keys for this algorithm must be 256 bits in length.
78      *
79      * <p>Valid truncation lengths are multiples of 8 bits from 96 to 256.
80      */
81     public static final String AUTH_HMAC_SHA256 = "hmac(sha256)";
82 
83     /**
84      * SHA384 HMAC Authentication/Integrity Algorithm.
85      *
86      * <p>Keys for this algorithm must be 384 bits in length.
87      *
88      * <p>Valid truncation lengths are multiples of 8 bits from 192 to 384.
89      */
90     public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
91 
92     /**
93      * SHA512 HMAC Authentication/Integrity Algorithm.
94      *
95      * <p>Keys for this algorithm must be 512 bits in length.
96      *
97      * <p>Valid truncation lengths are multiples of 8 bits from 256 to 512.
98      */
99     public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
100 
101     /**
102      * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
103      *
104      * <p>Valid lengths for keying material are {160, 224, 288}.
105      *
106      * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section
107      * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
108      * salt. RFC compliance requires that the salt must be unique per invocation with the same key.
109      *
110      * <p>Valid ICV (truncation) lengths are {64, 96, 128}.
111      */
112     public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
113 
114     /** @hide */
115     @StringDef({
116         CRYPT_AES_CBC,
117         AUTH_HMAC_MD5,
118         AUTH_HMAC_SHA1,
119         AUTH_HMAC_SHA256,
120         AUTH_HMAC_SHA384,
121         AUTH_HMAC_SHA512,
122         AUTH_CRYPT_AES_GCM
123     })
124     @Retention(RetentionPolicy.SOURCE)
125     public @interface AlgorithmName {}
126 
127     private final String mName;
128     private final byte[] mKey;
129     private final int mTruncLenBits;
130 
131     /**
132      * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
133      * defined as constants in this class.
134      *
135      * <p>For algorithms that produce an integrity check value, the truncation length is a required
136      * parameter. See {@link #IpSecAlgorithm(String algorithm, byte[] key, int truncLenBits)}
137      *
138      * @param algorithm name of the algorithm.
139      * @param key key padded to a multiple of 8 bits.
140      */
IpSecAlgorithm(@onNull @lgorithmName String algorithm, @NonNull byte[] key)141     public IpSecAlgorithm(@NonNull @AlgorithmName String algorithm, @NonNull byte[] key) {
142         this(algorithm, key, 0);
143     }
144 
145     /**
146      * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
147      * defined as constants in this class.
148      *
149      * <p>This constructor only supports algorithms that use a truncation length. i.e.
150      * Authentication and Authenticated Encryption algorithms.
151      *
152      * @param algorithm name of the algorithm.
153      * @param key key padded to a multiple of 8 bits.
154      * @param truncLenBits number of bits of output hash to use.
155      */
IpSecAlgorithm( @onNull @lgorithmName String algorithm, @NonNull byte[] key, int truncLenBits)156     public IpSecAlgorithm(
157             @NonNull @AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) {
158         mName = algorithm;
159         mKey = key.clone();
160         mTruncLenBits = truncLenBits;
161         checkValidOrThrow(mName, mKey.length * 8, mTruncLenBits);
162     }
163 
164     /** Get the algorithm name */
165     @NonNull
getName()166     public String getName() {
167         return mName;
168     }
169 
170     /** Get the key for this algorithm */
171     @NonNull
getKey()172     public byte[] getKey() {
173         return mKey.clone();
174     }
175 
176     /** Get the truncation length of this algorithm, in bits */
getTruncationLengthBits()177     public int getTruncationLengthBits() {
178         return mTruncLenBits;
179     }
180 
181     /* Parcelable Implementation */
describeContents()182     public int describeContents() {
183         return 0;
184     }
185 
186     /** Write to parcel */
writeToParcel(Parcel out, int flags)187     public void writeToParcel(Parcel out, int flags) {
188         out.writeString(mName);
189         out.writeByteArray(mKey);
190         out.writeInt(mTruncLenBits);
191     }
192 
193     /** Parcelable Creator */
194     public static final @android.annotation.NonNull Parcelable.Creator<IpSecAlgorithm> CREATOR =
195             new Parcelable.Creator<IpSecAlgorithm>() {
196                 public IpSecAlgorithm createFromParcel(Parcel in) {
197                     final String name = in.readString();
198                     final byte[] key = in.createByteArray();
199                     final int truncLenBits = in.readInt();
200 
201                     return new IpSecAlgorithm(name, key, truncLenBits);
202                 }
203 
204                 public IpSecAlgorithm[] newArray(int size) {
205                     return new IpSecAlgorithm[size];
206                 }
207             };
208 
checkValidOrThrow(String name, int keyLen, int truncLen)209     private static void checkValidOrThrow(String name, int keyLen, int truncLen) {
210         boolean isValidLen = true;
211         boolean isValidTruncLen = true;
212 
213         switch(name) {
214             case CRYPT_AES_CBC:
215                 isValidLen = keyLen == 128 || keyLen == 192 || keyLen == 256;
216                 break;
217             case AUTH_HMAC_MD5:
218                 isValidLen = keyLen == 128;
219                 isValidTruncLen = truncLen >= 96 && truncLen <= 128;
220                 break;
221             case AUTH_HMAC_SHA1:
222                 isValidLen = keyLen == 160;
223                 isValidTruncLen = truncLen >= 96 && truncLen <= 160;
224                 break;
225             case AUTH_HMAC_SHA256:
226                 isValidLen = keyLen == 256;
227                 isValidTruncLen = truncLen >= 96 && truncLen <= 256;
228                 break;
229             case AUTH_HMAC_SHA384:
230                 isValidLen = keyLen == 384;
231                 isValidTruncLen = truncLen >= 192 && truncLen <= 384;
232                 break;
233             case AUTH_HMAC_SHA512:
234                 isValidLen = keyLen == 512;
235                 isValidTruncLen = truncLen >= 256 && truncLen <= 512;
236                 break;
237             case AUTH_CRYPT_AES_GCM:
238                 // The keying material for GCM is a key plus a 32-bit salt
239                 isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32;
240                 isValidTruncLen = truncLen == 64 || truncLen == 96 || truncLen == 128;
241                 break;
242             default:
243                 throw new IllegalArgumentException("Couldn't find an algorithm: " + name);
244         }
245 
246         if (!isValidLen) {
247             throw new IllegalArgumentException("Invalid key material keyLength: " + keyLen);
248         }
249         if (!isValidTruncLen) {
250             throw new IllegalArgumentException("Invalid truncation keyLength: " + truncLen);
251         }
252     }
253 
254     /** @hide */
isAuthentication()255     public boolean isAuthentication() {
256         switch (getName()) {
257             // Fallthrough
258             case AUTH_HMAC_MD5:
259             case AUTH_HMAC_SHA1:
260             case AUTH_HMAC_SHA256:
261             case AUTH_HMAC_SHA384:
262             case AUTH_HMAC_SHA512:
263                 return true;
264             default:
265                 return false;
266         }
267     }
268 
269     /** @hide */
isEncryption()270     public boolean isEncryption() {
271         return getName().equals(CRYPT_AES_CBC);
272     }
273 
274     /** @hide */
isAead()275     public boolean isAead() {
276         return getName().equals(AUTH_CRYPT_AES_GCM);
277     }
278 
279     // Because encryption keys are sensitive and userdebug builds are used by large user pools
280     // such as beta testers, we only allow sensitive info such as keys on eng builds.
isUnsafeBuild()281     private static boolean isUnsafeBuild() {
282         return Build.IS_DEBUGGABLE && Build.IS_ENG;
283     }
284 
285     @Override
286     @NonNull
toString()287     public String toString() {
288         return new StringBuilder()
289                 .append("{mName=")
290                 .append(mName)
291                 .append(", mKey=")
292                 .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>")
293                 .append(", mTruncLenBits=")
294                 .append(mTruncLenBits)
295                 .append("}")
296                 .toString();
297     }
298 
299     /** @hide */
300     @VisibleForTesting
equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs)301     public static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) {
302         if (lhs == null || rhs == null) return (lhs == rhs);
303         return (lhs.mName.equals(rhs.mName)
304                 && Arrays.equals(lhs.mKey, rhs.mKey)
305                 && lhs.mTruncLenBits == rhs.mTruncLenBits);
306     }
307 };
308