• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 libcore.android.crypto.hpke;
17 
18 import static android.crypto.hpke.AeadParameterSpec.AES_128_GCM;
19 import static android.crypto.hpke.AeadParameterSpec.AES_256_GCM;
20 import static android.crypto.hpke.AeadParameterSpec.CHACHA20POLY1305;
21 import static android.crypto.hpke.KdfParameterSpec.HKDF_SHA256;
22 import static android.crypto.hpke.KemParameterSpec.DHKEM_X25519_HKDF_SHA256;
23 
24 import static org.junit.Assert.assertArrayEquals;
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertNotEquals;
27 import static org.junit.Assert.assertNotNull;
28 import static org.junit.Assert.assertThrows;
29 
30 import android.crypto.hpke.AeadParameterSpec;
31 import android.crypto.hpke.Hpke;
32 import android.crypto.hpke.Message;
33 import android.crypto.hpke.Recipient;
34 import android.crypto.hpke.Sender;
35 
36 import libcore.util.NonNull;
37 
38 import org.junit.Before;
39 import org.junit.Test;
40 import org.junit.experimental.runners.Enclosed;
41 import org.junit.runner.RunWith;
42 import org.junit.runners.JUnit4;
43 import org.junit.runners.Parameterized;
44 import org.junit.runners.Parameterized.Parameter;
45 import org.junit.runners.Parameterized.Parameters;
46 
47 import java.nio.charset.StandardCharsets;
48 import java.security.InvalidKeyException;
49 import java.security.KeyPair;
50 import java.security.KeyPairGenerator;
51 import java.security.NoSuchAlgorithmException;
52 import java.security.NoSuchProviderException;
53 import java.security.PrivateKey;
54 import java.security.Provider;
55 import java.security.PublicKey;
56 import java.security.Security;
57 import java.util.List;
58 
59 @RunWith(Enclosed.class)
60 public class HpkeTest {
61 
62     @RunWith(Parameterized.class)
63     public static class SendReceiveTests {
64         private PublicKey publicKey;
65         private PrivateKey privateKey;
66 
67         private static final byte[] EMPTY = new byte[0];
68         private static final byte[] MESSAGE = "This is only a test".getBytes(StandardCharsets.US_ASCII);
69         private static final byte[] INFO = "App info".getBytes(StandardCharsets.US_ASCII);
70         private static final byte[] AAD = "Additional data".getBytes(StandardCharsets.US_ASCII);
71 
72         @Parameters
data()73         public static Object[][] data() {
74             Object[] aads = new Object[]{null, EMPTY, AAD};
75             Object[] infos = new Object[]{null, EMPTY, INFO};
76             Object[] messages = new Object[]{EMPTY, MESSAGE};
77             Object[][] ciphers = new Object[][]{
78                     {AES_128_GCM},
79                     {AES_256_GCM},
80                     {CHACHA20POLY1305}
81             };
82             return permute(aads, permute(infos, permute(messages, ciphers)));
83         }
84 
85         @Parameter()
86         public byte[] aad;
87 
88         @Parameter(1)
89         public byte[] info;
90 
91         @Parameter(2)
92         public byte[] plaintext;
93 
94         @Parameter(3)
95         public AeadParameterSpec aead;
96 
97         private String suiteName;
98 
99         @Before
before()100         public void before() throws Exception {
101             KeyPairGenerator generator = KeyPairGenerator.getInstance("XDH");
102             KeyPair pair = generator.generateKeyPair();
103             publicKey = pair.getPublic();
104             privateKey = pair.getPrivate();
105             suiteName = Hpke.getSuiteName(DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, aead);
106             assertNotNull(suiteName);
107         }
108 
109         @Test
sendMessage()110         public void sendMessage() throws Exception {
111             Hpke hpke = Hpke.getInstance(suiteName);
112             assertNotNull(hpke);
113             Sender.Builder senderBuilder = new Sender.Builder(hpke, publicKey);
114             if (info != null) {
115                 senderBuilder.setApplicationInfo(info);
116             }
117             Sender sender = senderBuilder.build();
118             byte[] ciphertext = sender.seal(plaintext, aad);
119             byte[] encapsulated = sender.getEncapsulated();
120             assertNotNull(encapsulated);
121 
122             Recipient.Builder recipientBuilder =
123                     new Recipient.Builder(hpke, encapsulated, privateKey);
124             if (info != null) {
125                 recipientBuilder.setApplicationInfo(info);
126             }
127             Recipient recipient = recipientBuilder.build();
128             byte[] decoded = recipient.open(ciphertext, aad);
129             assertNotNull(decoded);
130 
131             assertArrayEquals(plaintext, decoded);
132         }
133 
134         @Test
oneshot()135         public void oneshot() throws Exception {
136             Hpke hpke = Hpke.getInstance(suiteName);
137             Message message = hpke.seal(publicKey, info, plaintext, aad);
138             byte[] decoded = hpke.open(privateKey, info, message, aad);
139             assertArrayEquals(plaintext, decoded);
140         }
141 
142         // Permute a new set of values into an existing Parameters array, i.e. one new row
143         // is created for every combination of each new value and existing row.
permute(Object[] newValues, Object[][] existing)144         private static Object[][] permute(Object[] newValues, Object[][] existing) {
145             int newSize = newValues.length * existing.length;
146             int rowSize = existing[0].length + 1;
147             Object[][] result = new Object[newSize][];
148             for (int i = 0; i < newSize; i++) {
149                 Object[] row = new Object[rowSize];
150                 result[i] = row;
151                 row[0] = newValues[i % newValues.length];
152                 System.arraycopy(existing[i / newValues.length], 0, row, 1, rowSize - 1);
153             }
154             return result;
155         }
156     }
157 
158     @RunWith(JUnit4.class)
159     public static class OtherTests {
160         private static final String SUITE_NAME = "DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/AES_128_GCM";
161         private static final int EXPORT_LENGTH = 16;
162         private static final String CONSCRYPT_NAME = "AndroidOpenSSL";
163         private final Provider conscrypt = Security.getProvider(CONSCRYPT_NAME);
164 
165         private PublicKey publicKey;
166         private PrivateKey privateKey;
167 
168         @Before
before()169         public void before() throws Exception {
170             KeyPairGenerator generator = KeyPairGenerator.getInstance("XDH");
171             KeyPair pair = generator.generateKeyPair();
172             publicKey = pair.getPublic();
173             privateKey = pair.getPrivate();
174         }
175 
176         @Test
init_Errors()177         public void init_Errors() {
178             assertThrows(NoSuchAlgorithmException.class,
179                     () ->Hpke.getInstance("No such"));
180             assertThrows(NoSuchAlgorithmException.class,
181                     () ->Hpke.getInstance(""));
182             assertThrows(NoSuchAlgorithmException.class,
183                     () ->Hpke.getInstance(null));
184 
185             assertThrows(IllegalArgumentException.class,
186                     () ->Hpke.getInstance(SUITE_NAME, (String) null));
187             assertThrows(IllegalArgumentException.class,
188                     () ->Hpke.getInstance(SUITE_NAME, ""));
189             assertThrows(NoSuchProviderException.class,
190                     () ->Hpke.getInstance(SUITE_NAME, "No such"));
191             assertThrows(NoSuchAlgorithmException.class,
192                     () ->Hpke.getInstance("No such", CONSCRYPT_NAME));
193             assertThrows(NoSuchAlgorithmException.class,
194                     () ->Hpke.getInstance("", CONSCRYPT_NAME));
195             assertThrows(NoSuchAlgorithmException.class,
196                     () ->Hpke.getInstance(null, CONSCRYPT_NAME));
197 
198             assertThrows(IllegalArgumentException.class,
199                     () ->Hpke.getInstance(SUITE_NAME, (Provider) null));
200             assertThrows(NoSuchAlgorithmException.class,
201                     () ->Hpke.getInstance("No such", conscrypt));
202             assertThrows(NoSuchAlgorithmException.class,
203                     () ->Hpke.getInstance("", conscrypt));
204             assertThrows(NoSuchAlgorithmException.class,
205                     () ->Hpke.getInstance(null, conscrypt));
206         }
207 
208         @Test
keyType()209         public void keyType() throws Exception {
210             KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
211             KeyPair pair = generator.generateKeyPair();
212             PublicKey publicRsa = pair.getPublic();
213             PrivateKey privateRsa = pair.getPrivate();
214             Hpke hpke = Hpke.getInstance(SUITE_NAME);
215 
216             assertThrows(InvalidKeyException.class,
217                     () -> new Sender.Builder(hpke, publicRsa).build());
218             assertThrows(InvalidKeyException.class,
219                     () -> new Recipient.Builder(hpke, new byte[16], privateRsa).build());
220         }
221 
222         @Test
suiteNames()223         public void suiteNames() throws Exception {
224             List<AeadParameterSpec> aeads = List.of(AES_128_GCM, AES_256_GCM, CHACHA20POLY1305);
225             for (AeadParameterSpec aead : aeads) {
226                 String suiteName
227                         = Hpke.getSuiteName(DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, aead);
228                 assertNotNull(suiteName);
229                 assertNotNull(Hpke.getInstance(suiteName));
230                 // Also check Tink-compatible names
231                 // TODO(prb) enable after https://github.com/google/conscrypt/pull/1258 lands
232                 // String altName = suiteName.replaceAll("/", "_");
233                 // assertNotNull(Hpke.getInstance(altName));
234             }
235         }
236 
237         // Note API test only, implementation tests are in Conscrypt.
238         @Test
export()239         public void export() throws Exception {
240             byte[] context = "Hello".getBytes(StandardCharsets.UTF_8);
241             Hpke hpke = Hpke.getInstance(SUITE_NAME);
242             assertNotNull(hpke);
243             Sender sender = new Sender.Builder(hpke, publicKey).build();
244             Recipient recipient =
245                     new Recipient.Builder(hpke, sender.getEncapsulated(), privateKey).build();
246             assertNotNull(recipient);
247 
248             byte[] senderData = sender.export(EXPORT_LENGTH, context);
249             assertNotNull(senderData);
250             assertEquals(EXPORT_LENGTH, senderData.length);
251             int sum = 0;
252             for (byte b : senderData) {
253                 sum += b;
254             }
255             // Check data isn't all zeroes.
256             assertNotEquals(0, sum);
257 
258             byte[] recipientData = recipient.export(EXPORT_LENGTH, context);
259             assertArrayEquals(senderData, recipientData);
260         }
261 
262         @Test
spiAndProvider()263         public void spiAndProvider() throws Exception{
264             Hpke hpke = Hpke.getInstance(SUITE_NAME);
265             assertNotNull(hpke);
266             assertNotNull(hpke.getProvider());
267 
268             Sender sender = new Sender.Builder(hpke, publicKey).build();
269             Recipient recipient =
270                     new Recipient.Builder(hpke, sender.getEncapsulated(), privateKey).build();
271             assertNotNull(recipient);
272 
273             assertNotNull(sender.getProvider());
274             assertNotNull(sender.getSpi());
275             assertNotNull(recipient.getProvider());
276             assertNotNull(recipient.getSpi());
277         }
278 
279         // Note API test only.  Implementation not yet present.
280         @Test
futureBuilderMethods()281         public void futureBuilderMethods() throws Exception {
282             byte[] appInfo = "App Info".getBytes(StandardCharsets.UTF_8);
283             byte[] psk = "Very Secret Key".getBytes(StandardCharsets.UTF_8);
284             byte[] pskId = "ID".getBytes(StandardCharsets.UTF_8);
285 
286             Hpke hpke = Hpke.getInstance(SUITE_NAME);
287             assertNotNull(hpke);
288 
289             Sender.Builder senderBuilder = new Sender.Builder(hpke, publicKey)
290                     .setApplicationInfo(appInfo)
291                     .setSenderKey(privateKey)
292                     .setPsk(psk, pskId);
293             assertThrows(UnsupportedOperationException.class, senderBuilder::build);
294 
295             Sender sender = new Sender.Builder(hpke, publicKey).build();
296             assertNotNull(sender);
297 
298             Recipient.Builder recipientBuilder =
299                     new Recipient.Builder(hpke, sender.getEncapsulated(), privateKey)
300                             .setApplicationInfo(appInfo)
301                             .setSenderKey(publicKey)
302                             .setPsk(psk, pskId);
303             assertThrows(UnsupportedOperationException.class, recipientBuilder::build);
304         }
305     }
306 }