1 /*
2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /*
25  * @test
26  * @bug 8184359
27  * @summary Standard tests on KeySpec, KeyFactory, KeyPairs and Keys.
28  *  Arguments order <KeyExchangeAlgorithm> <Provider> <KeyGenAlgorithm> <Curve*>
29  * @run main KeySpecTest DiffieHellman SunJCE DiffieHellman
30  * @run main KeySpecTest ECDH SunEC EC
31  * @run main KeySpecTest XDH SunEC XDH X25519
32  * @run main KeySpecTest XDH SunEC XDH X448
33  */
34 package test.java.security.KeyAgreement;
35 
36 import java.io.ByteArrayInputStream;
37 import java.io.ByteArrayOutputStream;
38 import java.io.IOException;
39 import java.io.ObjectInputStream;
40 import java.io.ObjectOutputStream;
41 import java.security.KeyFactory;
42 import java.security.KeyPair;
43 import java.security.KeyPairGenerator;
44 import java.security.PrivateKey;
45 import java.security.PublicKey;
46 import java.security.spec.ECPrivateKeySpec;
47 import java.security.spec.ECPublicKeySpec;
48 import java.security.spec.PKCS8EncodedKeySpec;
49 import java.security.spec.X509EncodedKeySpec;
50 import java.security.spec.NamedParameterSpec;
51 import java.security.spec.XECPublicKeySpec;
52 import java.security.spec.XECPrivateKeySpec;
53 import java.security.spec.KeySpec;
54 import java.util.ArrayList;
55 import java.util.List;
56 import java.util.Arrays;
57 import javax.crypto.KeyAgreement;
58 import javax.crypto.spec.DHPrivateKeySpec;
59 import javax.crypto.spec.DHPublicKeySpec;
60 
61 import org.testng.annotations.Test;
62 import static org.junit.Assert.fail;
63 
64 public class KeySpecTest {
65 
66     @Test
testDHKeySpecs()67     public void testDHKeySpecs() throws Exception {
68         KeyPair kp = genKeyPair("BC","DiffieHellman",
69                 "DiffieHellman");
70         testKeySpecs("BC", "DiffieHellman", "DiffieHellman", kp);
71         testEncodedKeySpecs("BC", "DiffieHellman", "DiffieHellman", kp);
72     }
73 
74     @Test
testECDHKeySpecs()75     public void testECDHKeySpecs() throws Exception {
76         KeyPair kp = genKeyPair("AndroidOpenSSL","EC",
77                 "EC");
78         testKeySpecs("AndroidOpenSSL", "ECDH", "EC", kp);
79         testEncodedKeySpecs("AndroidOpenSSL", "ECDH", "EC", kp);
80     }
81 
82     // BEGIN Android-removed: XDH is not yet supported
83     /*
84     @Test
85     public void testXDHKeySpecs() throws Exception {
86         KeyPair kp1 = genKeyPair("AndroidOpenSSL","XDH",
87                 "X25519");
88         KeyPair kp2 = genKeyPair("AndroidOpenSSL","XDH",
89                 "X25519");
90         testKeySpecs("AndroidOpenSSL", "XDH", "XDH", kp1);
91         testEncodedKeySpecs("AndroidOpenSSL", "XDH", "XDH", kp1);
92         testKeySpecs("AndroidOpenSSL", "XDH", "XDH", kp2);
93         testEncodedKeySpecs("AndroidOpenSSL", "XDH", "XDH", kp2);
94     }
95      */
96     // END Android-removed: XDH is not yet supported
97 
98     /**
99      * Generate keyPair based on KeyPairGenerator algorithm.
100      */
genKeyPair(String provider, String kpgAlgo, String kpgInit)101     private static KeyPair genKeyPair(String provider, String kpgAlgo,
102             String kpgInit) throws Exception {
103 
104         KeyPairGenerator kpg = KeyPairGenerator.getInstance(kpgAlgo, provider);
105         switch (kpgInit) {
106             case "DiffieHellman":
107                 kpg.initialize(512);
108                 break;
109             case "EC":
110                 kpg.initialize(256);
111                 break;
112             case "X25519":
113                 kpg.initialize(255);
114                 break;
115             case "X448":
116                 kpg.initialize(448);
117                 break;
118             default:
119                 fail("Invalid Algo name " + kpgInit);
120         }
121         return kpg.generateKeyPair();
122     }
123 
124     /**
125      * Standard Test with Keys and the one generated through Spec.
126      */
testKeySpecs(String provider, String kaAlgo, String kpgAlgo, KeyPair kp)127     private static void testKeySpecs(String provider, String kaAlgo,
128             String kpgAlgo, KeyPair kp) throws Exception {
129 
130         KeyFactory kf = KeyFactory.getInstance(kpgAlgo, provider);
131         // For each public KeySpec supported by KeyPairGenerator
132         for (Class pubSpecType
133                 : getCompatibleKeySpecs(kpgAlgo, KeyType.PUBLIC)) {
134             //For each private KeySpec supported by KeyPairGenerator
135             for (Class priSpecType
136                     : getCompatibleKeySpecs(kpgAlgo, KeyType.PRIVATE)) {
137                 // Transform original PublicKey through KeySpec
138                 KeySpec pubSpec = kf.getKeySpec(kp.getPublic(), pubSpecType);
139                 PublicKey pubKey = kf.generatePublic(pubSpec);
140                 // Transform original PrivateKey through KeySpec
141                 KeySpec priSpec = kf.getKeySpec(kp.getPrivate(), priSpecType);
142                 PrivateKey priKey = kf.generatePrivate(priSpec);
143 
144                 // Test the keys are equal after transformation through KeySpec.
145                 testKeyEquals(kp, pubKey, priKey);
146                 // Test the keys are serializable.
147                 testSerialize(kp);
148                 // Test Parameter name.
149                 if (!kaAlgo.equals("DiffieHellman")) {
150                     testParamName(priSpec, pubSpec);
151                 }
152                 // Compare KeyAgreement secret generated from original keys
153                 // and by the keys after transformed through KeySpec.
154                 if (!Arrays.equals(getKeyAgreementSecret(provider, kaAlgo,
155                         kp.getPublic(), kp.getPrivate()),
156                         getKeyAgreementSecret(provider, kaAlgo,
157                                 pubKey, priKey))) {
158                     fail("KeyAgreement secret mismatch.");
159                 }
160             }
161         }
162     }
163 
164     /**
165      * Standard Test with Keys and the one generated from encoded bytes.
166      */
testEncodedKeySpecs(String provider, String kaAlgo, String kpgAlgo, KeyPair kp)167     private static void testEncodedKeySpecs(String provider, String kaAlgo,
168             String kpgAlgo, KeyPair kp) throws Exception {
169 
170         KeyFactory kf = KeyFactory.getInstance(kpgAlgo, provider);
171         PKCS8EncodedKeySpec priSpec
172                 = new PKCS8EncodedKeySpec(kp.getPrivate().getEncoded());
173         PrivateKey priKey = kf.generatePrivate(priSpec);
174 
175         X509EncodedKeySpec pubSpec
176                 = new X509EncodedKeySpec(kp.getPublic().getEncoded());
177         PublicKey pubKey = kf.generatePublic(pubSpec);
178 
179         // Test the keys are equal after transformation through KeySpec.
180         testKeyEquals(kp, pubKey, priKey);
181         // Test the keys are serializable.
182         testSerialize(kp);
183         // Test Parameter name.
184         if (!kaAlgo.equals("DiffieHellman")) {
185             testParamName(priSpec, pubSpec);
186         }
187         // Compare KeyAgreement secret generated from original keys
188         // and by the keys after transformed through KeySpec.
189         if (!Arrays.equals(getKeyAgreementSecret(provider, kaAlgo,
190                 kp.getPublic(), kp.getPrivate()),
191                 getKeyAgreementSecret(provider, kaAlgo, pubKey, priKey))) {
192             fail("KeyAgreement secret mismatch.");
193         }
194     }
195 
196     private enum KeyType {
197         PUBLIC, PRIVATE;
198     }
199 
200     /**
201      * Provides Compatible KeySpec Type list for KeyPairGenerator algorithm.
202      */
getCompatibleKeySpecs(String kpgAlgo, KeyType type)203     private static List<Class> getCompatibleKeySpecs(String kpgAlgo,
204             KeyType type) {
205 
206         List<Class> specs = new ArrayList<>();
207         switch (kpgAlgo) {
208             case "DiffieHellman":
209                 if (type == KeyType.PUBLIC) {
210                     return Arrays.asList(X509EncodedKeySpec.class,
211                             DHPublicKeySpec.class);
212                 } else {
213                     return Arrays.asList(PKCS8EncodedKeySpec.class,
214                             DHPrivateKeySpec.class);
215                 }
216             case "EC":
217                 if (type == KeyType.PUBLIC) {
218                     return Arrays.asList(X509EncodedKeySpec.class,
219                             ECPublicKeySpec.class);
220                 } else {
221                     return Arrays.asList(PKCS8EncodedKeySpec.class,
222                             ECPrivateKeySpec.class);
223                 }
224             case "XDH":
225                 if (type == KeyType.PUBLIC) {
226                     return Arrays.asList(X509EncodedKeySpec.class,
227                             XECPublicKeySpec.class);
228                 } else {
229                     return Arrays.asList(PKCS8EncodedKeySpec.class,
230                             XECPrivateKeySpec.class);
231                 }
232         }
233         return specs;
234     }
235 
236     /**
237      * Generate KeyAgreement Secret.
238      */
getKeyAgreementSecret(String provider, String kaAlgo, PublicKey pubKey, PrivateKey priKey)239     private static byte[] getKeyAgreementSecret(String provider, String kaAlgo,
240             PublicKey pubKey, PrivateKey priKey) throws Exception {
241 
242         KeyAgreement ka = KeyAgreement.getInstance(kaAlgo, provider);
243         ka.init(priKey);
244         ka.doPhase(pubKey, true);
245         return ka.generateSecret();
246     }
247 
248     /**
249      * Compare original KeyPair with transformed ones.
250      */
testKeyEquals(KeyPair kp, PublicKey pubKey, PrivateKey priKey)251     private static void testKeyEquals(KeyPair kp, PublicKey pubKey,
252             PrivateKey priKey) {
253 
254         if (!kp.getPrivate().equals(priKey)
255                 && kp.getPrivate().hashCode() != priKey.hashCode()) {
256             fail("PrivateKey is not equal with PrivateKey"
257                     + " generated through KeySpec");
258         }
259         if (!kp.getPublic().equals(pubKey)
260                 && kp.getPublic().hashCode() != pubKey.hashCode()) {
261             fail("PublicKey is not equal with PublicKey"
262                     + " generated through KeySpec");
263         }
264     }
265 
266     /**
267      * Compare the parameter names of Public/Private KeySpec from a pair.
268      */
testParamName(KeySpec priSpec, KeySpec pubSpec)269     private static void testParamName(KeySpec priSpec, KeySpec pubSpec) {
270 
271         if (priSpec instanceof XECPrivateKeySpec
272                 && pubSpec instanceof XECPublicKeySpec) {
273             if (((NamedParameterSpec) ((XECPrivateKeySpec) priSpec)
274                     .getParams()).getName()
275                     != ((NamedParameterSpec) ((XECPublicKeySpec) pubSpec)
276                     .getParams()).getName()) {
277                 fail("Curve name mismatch found");
278             }
279         }
280     }
281 
282     /**
283      * Test serialization of KeyPair and Keys it holds.
284      */
testSerialize(KeyPair keyPair)285     private static void testSerialize(KeyPair keyPair) throws Exception {
286 
287         // Verify Serialized PrivateKey instance.
288         if (!keyPair.getPrivate().equals(
289                 deserializedCopy(keyPair.getPrivate(), PrivateKey.class))) {
290             fail("PrivateKey is not equal with PrivateKey"
291                     + " generated through Serialization");
292         }
293         // Verify Serialized PublicKey instance.
294         if (!keyPair.getPublic().equals(
295                 deserializedCopy(keyPair.getPublic(), PublicKey.class))) {
296             fail("PublicKey is not equal with PublicKey"
297                     + " generated through Serialization");
298         }
299         // Verify Serialized KeyPair instance.
300         KeyPair copy = deserializedCopy(keyPair, KeyPair.class);
301         if (!keyPair.getPrivate().equals(copy.getPrivate())) {
302             fail("PrivateKey is not equal with PrivateKey"
303                     + " generated through Serialized KeyPair");
304         }
305         if (!keyPair.getPublic().equals(copy.getPublic())) {
306             fail("PublicKey is not equal with PublicKey"
307                     + " generated through Serialized KeyPair");
308         }
309     }
310 
deserializedCopy(T orig, Class<T> type)311     private static <T extends Object> T deserializedCopy(T orig, Class<T> type)
312             throws IOException, ClassNotFoundException {
313         return deserialize(serialize(orig), type);
314     }
315 
316     /**
317      * Deserialize the Key object.
318      */
deserialize(byte[] serialized, Class<T> type)319     private static <T extends Object> T deserialize(byte[] serialized,
320             Class<T> type) throws IOException, ClassNotFoundException {
321 
322         T key = null;
323         try (ByteArrayInputStream bis = new ByteArrayInputStream(serialized);
324              ObjectInputStream ois = new ObjectInputStream(bis)) {
325             key = (T) ois.readObject();
326         }
327         return key;
328     }
329 
330     /**
331      * Serialize the given Key object.
332      */
serialize(T key)333     private static <T extends Object> byte[] serialize(T key)
334             throws IOException {
335 
336         try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
337              ObjectOutputStream oos = new ObjectOutputStream(bos)) {
338             oos.writeObject(key);
339             return bos.toByteArray();
340         }
341     }
342 }