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