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 
17 package android.net;
18 
19 import static android.net.IpSecAlgorithm.ALGO_TO_REQUIRED_FIRST_SDK;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 import static org.mockito.Mockito.doReturn;
25 import static org.mockito.Mockito.mock;
26 
27 import android.content.res.Resources;
28 import android.os.Build;
29 import android.os.Parcel;
30 
31 import androidx.test.filters.SmallTest;
32 
33 import com.android.internal.util.CollectionUtils;
34 import com.android.testutils.DevSdkIgnoreRule;
35 import com.android.testutils.DevSdkIgnoreRunner;
36 
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 
40 import java.util.AbstractMap.SimpleEntry;
41 import java.util.Arrays;
42 import java.util.HashSet;
43 import java.util.Map.Entry;
44 import java.util.Random;
45 import java.util.Set;
46 
47 /** Unit tests for {@link IpSecAlgorithm}. */
48 @SmallTest
49 @RunWith(DevSdkIgnoreRunner.class)
50 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
51 public class IpSecAlgorithmTest {
52     private static final byte[] KEY_MATERIAL;
53 
54     private final Resources mMockResources = mock(Resources.class);
55 
56     static {
57         KEY_MATERIAL = new byte[128];
58         new Random().nextBytes(KEY_MATERIAL);
59     };
60 
generateKey(int keyLenInBits)61     private static byte[] generateKey(int keyLenInBits) {
62         return Arrays.copyOf(KEY_MATERIAL, keyLenInBits / 8);
63     }
64 
65     @Test
testNoTruncLen()66     public void testNoTruncLen() throws Exception {
67         Entry<String, Integer>[] authAndAeadList =
68                 new Entry[] {
69                     new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_MD5, 128),
70                     new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA1, 160),
71                     new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA256, 256),
72                     new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA384, 384),
73                     new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA512, 512),
74                     new SimpleEntry<>(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, 224),
75                 };
76 
77         // Expect auth and aead algorithms to throw errors if trunclen is omitted.
78         for (Entry<String, Integer> algData : authAndAeadList) {
79             try {
80                 new IpSecAlgorithm(
81                         algData.getKey(), Arrays.copyOf(KEY_MATERIAL, algData.getValue() / 8));
82                 fail("Expected exception on unprovided auth trunclen");
83             } catch (IllegalArgumentException expected) {
84             }
85         }
86 
87         // Ensure crypt works with no truncation length supplied.
88         new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, Arrays.copyOf(KEY_MATERIAL, 256 / 8));
89     }
90 
checkAuthKeyAndTruncLenValidation(String algoName, int keyLen, int truncLen)91     private void checkAuthKeyAndTruncLenValidation(String algoName, int keyLen, int truncLen)
92             throws Exception {
93         new IpSecAlgorithm(algoName, generateKey(keyLen), truncLen);
94 
95         try {
96             new IpSecAlgorithm(algoName, generateKey(keyLen));
97             fail("Expected exception on unprovided auth trunclen");
98         } catch (IllegalArgumentException pass) {
99         }
100 
101         try {
102             new IpSecAlgorithm(algoName, generateKey(keyLen + 8), truncLen);
103             fail("Invalid key length not validated");
104         } catch (IllegalArgumentException pass) {
105         }
106 
107         try {
108             new IpSecAlgorithm(algoName, generateKey(keyLen), truncLen + 1);
109             fail("Invalid truncation length not validated");
110         } catch (IllegalArgumentException pass) {
111         }
112     }
113 
checkCryptKeyLenValidation(String algoName, int keyLen)114     private void checkCryptKeyLenValidation(String algoName, int keyLen) throws Exception {
115         new IpSecAlgorithm(algoName, generateKey(keyLen));
116 
117         try {
118             new IpSecAlgorithm(algoName, generateKey(keyLen + 8));
119             fail("Invalid key length not validated");
120         } catch (IllegalArgumentException pass) {
121         }
122     }
123 
124     @Test
testValidationForAlgosAddedInS()125     public void testValidationForAlgosAddedInS() throws Exception {
126         if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.R) {
127             return;
128         }
129 
130         for (int len : new int[] {160, 224, 288}) {
131             checkCryptKeyLenValidation(IpSecAlgorithm.CRYPT_AES_CTR, len);
132         }
133         checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_AES_XCBC, 128, 96);
134         checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_AES_CMAC, 128, 96);
135         checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305, 288, 128);
136     }
137 
138     @Test
testTruncLenValidation()139     public void testTruncLenValidation() throws Exception {
140         for (int truncLen : new int[] {256, 512}) {
141             new IpSecAlgorithm(
142                     IpSecAlgorithm.AUTH_HMAC_SHA512,
143                     Arrays.copyOf(KEY_MATERIAL, 512 / 8),
144                     truncLen);
145         }
146 
147         for (int truncLen : new int[] {255, 513}) {
148             try {
149                 new IpSecAlgorithm(
150                         IpSecAlgorithm.AUTH_HMAC_SHA512,
151                         Arrays.copyOf(KEY_MATERIAL, 512 / 8),
152                         truncLen);
153                 fail("Invalid truncation length not validated");
154             } catch (IllegalArgumentException pass) {
155             }
156         }
157     }
158 
159     @Test
testLenValidation()160     public void testLenValidation() throws Exception {
161         for (int len : new int[] {128, 192, 256}) {
162             new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, Arrays.copyOf(KEY_MATERIAL, len / 8));
163         }
164         try {
165             new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, Arrays.copyOf(KEY_MATERIAL, 384 / 8));
166             fail("Invalid key length not validated");
167         } catch (IllegalArgumentException pass) {
168         }
169     }
170 
171     @Test
testAlgoNameValidation()172     public void testAlgoNameValidation() throws Exception {
173         try {
174             new IpSecAlgorithm("rot13", Arrays.copyOf(KEY_MATERIAL, 128 / 8));
175             fail("Invalid algorithm name not validated");
176         } catch (IllegalArgumentException pass) {
177         }
178     }
179 
180     @Test
testParcelUnparcel()181     public void testParcelUnparcel() throws Exception {
182         IpSecAlgorithm init =
183                 new IpSecAlgorithm(
184                         IpSecAlgorithm.AUTH_HMAC_SHA512, Arrays.copyOf(KEY_MATERIAL, 512 / 8), 256);
185 
186         Parcel p = Parcel.obtain();
187         p.setDataPosition(0);
188         init.writeToParcel(p, 0);
189 
190         p.setDataPosition(0);
191         IpSecAlgorithm fin = IpSecAlgorithm.CREATOR.createFromParcel(p);
192         assertTrue("Parcel/Unparcel failed!", IpSecAlgorithm.equals(init, fin));
193         p.recycle();
194     }
195 
getMandatoryAlgos()196     private static Set<String> getMandatoryAlgos() {
197         return CollectionUtils.filter(
198                 ALGO_TO_REQUIRED_FIRST_SDK.keySet(),
199                 i -> Build.VERSION.DEVICE_INITIAL_SDK_INT >= ALGO_TO_REQUIRED_FIRST_SDK.get(i));
200     }
201 
getOptionalAlgos()202     private static Set<String> getOptionalAlgos() {
203         return CollectionUtils.filter(
204                 ALGO_TO_REQUIRED_FIRST_SDK.keySet(),
205                 i -> Build.VERSION.DEVICE_INITIAL_SDK_INT < ALGO_TO_REQUIRED_FIRST_SDK.get(i));
206     }
207 
208     @Test
testGetSupportedAlgorithms()209     public void testGetSupportedAlgorithms() throws Exception {
210         assertTrue(IpSecAlgorithm.getSupportedAlgorithms().containsAll(getMandatoryAlgos()));
211         assertTrue(ALGO_TO_REQUIRED_FIRST_SDK.keySet().containsAll(
212                 IpSecAlgorithm.getSupportedAlgorithms()));
213     }
214 
215     @Test
testLoadAlgos()216     public void testLoadAlgos() throws Exception {
217         final Set<String> optionalAlgoSet = getOptionalAlgos();
218         final String[] optionalAlgos = optionalAlgoSet.toArray(new String[0]);
219 
220         doReturn(optionalAlgos).when(mMockResources)
221                 .getStringArray(com.android.internal.R.array.config_optionalIpSecAlgorithms);
222 
223         final Set<String> enabledAlgos = new HashSet<>(IpSecAlgorithm.loadAlgos(mMockResources));
224         final Set<String> expectedAlgos = ALGO_TO_REQUIRED_FIRST_SDK.keySet();
225 
226         assertEquals(expectedAlgos, enabledAlgos);
227     }
228 }
229