1 /*
2  * Copyright (c) 2015, 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 8046321
27  * @summary OCSP Stapling for TLS (ResponderId tests)
28  * @modules java.base/sun.security.provider.certpath
29  *          java.base/sun.security.x509
30  */
31 
32 import java.io.*;
33 import java.security.cert.*;
34 import java.security.KeyPair;
35 import java.security.KeyPairGenerator;
36 import java.util.AbstractMap;
37 import java.util.Arrays;
38 import java.util.Map;
39 import java.util.List;
40 import java.util.ArrayList;
41 import javax.security.auth.x500.X500Principal;
42 import sun.security.x509.KeyIdentifier;
43 import sun.security.provider.certpath.ResponderId;
44 
45 /*
46  * NOTE: this test uses Sun private classes which are subject to change.
47  */
48 public class ResponderIdTests {
49 
50     private static final boolean debug = true;
51 
52     // Source certificate created with the following command:
53     // keytool -genkeypair -alias test1 -keyalg rsa -keysize 2048 \
54     //   -validity 7300 -keystore test1.jks \
55     //   -dname "CN=SelfSignedResponder, OU=Validation Services, O=FakeCompany"
56     private static final String RESP_CERT_1 =
57         "-----BEGIN CERTIFICATE-----\n" +
58         "MIIDQzCCAiugAwIBAgIEXTqCCjANBgkqhkiG9w0BAQsFADBSMRQwEgYDVQQKEwtG\n" +
59         "YWtlQ29tcGFueTEcMBoGA1UECxMTVmFsaWRhdGlvbiBTZXJ2aWNlczEcMBoGA1UE\n" +
60         "AxMTU2VsZlNpZ25lZFJlc3BvbmRlcjAeFw0xNDA4MTcwNDM2MzBaFw0zNDA4MTIw\n" +
61         "NDM2MzBaMFIxFDASBgNVBAoTC0Zha2VDb21wYW55MRwwGgYDVQQLExNWYWxpZGF0\n" +
62         "aW9uIFNlcnZpY2VzMRwwGgYDVQQDExNTZWxmU2lnbmVkUmVzcG9uZGVyMIIBIjAN\n" +
63         "BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApt2Cmw2k9tviLxaxE8aWNuoosWKL\n" +
64         "h+K4mNcDGKSoiChsqRqeJEnOxijDZqyFwfkaXvpAduFqYjz+Lij2HumvAjHDTui6\n" +
65         "bGcbsndRDPjvVo1S7f1oWsg7oiA8Lzmjl452S7UNBsDX5Dt1e84Xxwi40B1J2y8D\n" +
66         "FRPfYRWRlC1Z4kzqkBBa7JhANS+W8KDstFZxL4AwWH/byNwB5dl2j04ohg/Ar54e\n" +
67         "mu08PIH3hmi0pAu5wn9ariA7UA5lFWRJzvgGXV5J+QVEFuvKmeJ/Q6tU5OBJGw98\n" +
68         "zjd7F5B0iE+rJHTNF1aGaQfIorz04onV2WjH2VZA18AaMwqlY2br1SBdTQIDAQAB\n" +
69         "oyEwHzAdBgNVHQ4EFgQUG09HasSTYaTIh/CxxV/rcJV1LvowDQYJKoZIhvcNAQEL\n" +
70         "BQADggEBAIcUomNpZxGkocIzzybLyeyC6vLF1k0/unuPAHZLDP3o2JTstPhLHOCg\n" +
71         "FYw1VG2i23pjwKK2x/o80tJAOmW6vowbAPnNmtNIYO3gB/ZGiKeORoGKBCRDNvFa\n" +
72         "6ZrWxwTzT3EpVwRe7ameES0uP8+S4q2P5LhwMIMw7vGHoOQJgkAh/NUiCli1qRnJ\n" +
73         "FYd6cHMJJK5gF2FqQ7tdbA26pS06bkIEvil2M5wyKKWOydOa/pr1LgMf9KxljJ8J\n" +
74         "XlAOO/mGZGkYmWnQaQuBIDyWunWYlhsyCXMa8AScgs0uUeQp19tO7R0f03q/JXoZ\n" +
75         "1At1gZiMS7SdQaRWP5q+FunAeFWjsFE=\n" +
76         "-----END CERTIFICATE-----";
77 
78     private static final String RESP_CERT_1_SUBJ =
79         "CN=SelfSignedResponder, OU=Validation Services, O=FakeCompany";
80 
81     private static X509Certificate cert = null;
82 
83     // The expected DER-encoding for a byName ResponderId derived
84     // from RESP_CERT_1
85     private static final byte[] EXP_NAME_ID_BYTES = {
86         -95,   84,   48,   82,   49,   20,   48,   18,
87           6,    3,   85,    4,   10,   19,   11,   70,
88          97,  107,  101,   67,  111,  109,  112,   97,
89         110,  121,   49,   28,   48,   26,    6,    3,
90          85,    4,   11,   19,   19,   86,   97,  108,
91         105,  100,   97,  116,  105,  111,  110,   32,
92          83,  101,  114,  118,  105,   99,  101,  115,
93          49,   28,   48,   26,    6,    3,   85,    4,
94           3,   19,   19,   83,  101,  108,  102,   83,
95         105,  103,  110,  101,  100,   82,  101,  115,
96         112,  111,  110,  100,  101,  114
97     };
98 
99     // The expected DER-encoding for a byKey ResponderId derived
100     // from RESP_CERT_1
101     private static final byte[] EXP_KEY_ID_BYTES = {
102          -94,   22,    4,   20,   27,   79,   71,  106,
103          -60, -109,   97,  -92,  -56, -121,  -16,  -79,
104          -59,   95,  -21,  112, -107,  117,   46,   -6
105     };
106 
107     // The DER encoding of a byKey ResponderId, but using an
108     // incorrect explicit tagging (CONTEXT CONSTRUCTED 3)
109     private static final byte[] INV_EXPLICIT_TAG_KEY_ID = {
110          -93,   22,    4,   20,   27,   79,   71,  106,
111          -60, -109,   97,  -92,  -56, -121,  -16,  -79,
112          -59,   95,  -21,  112, -107,  117,   46,   -6
113     };
114 
115     // These two ResponderId objects will have objects attached to them
116     // after the pos_CtorByName and pos_CtorByKeyId tests run.  Those
117     // two tests should always be the first two that run.
118     public static ResponderId respByName;
119     public static ResponderId respByKeyId;
120 
main(String[] args)121     public static void main(String[] args) throws Exception {
122         List<TestCase> testList = new ArrayList<>();
123 
124         testList.add(pos_CtorByName);
125         testList.add(pos_CtorByKeyId);
126         testList.add(pos_CtorByEncoding);
127         testList.add(neg_CtorByEncoding);
128         testList.add(pos_Equality);
129         testList.add(pos_GetEncoded);
130         testList.add(pos_GetRespName);
131         testList.add(pos_GetRespKeyId);
132 
133         // Load the certificate object we can use for subsequent tests
134         CertificateFactory cf = CertificateFactory.getInstance("X.509");
135         cert = (X509Certificate)cf.generateCertificate(
136                 new ByteArrayInputStream(RESP_CERT_1.getBytes()));
137 
138         System.out.println("============ Tests ============");
139         int testNo = 0;
140         int numberFailed = 0;
141         Map.Entry<Boolean, String> result;
142         for (TestCase test : testList) {
143             System.out.println("Test " + ++testNo + ": " + test.getName());
144             result = test.runTest();
145             System.out.print("Result: " + (result.getKey() ? "PASS" : "FAIL"));
146             System.out.println(" " +
147                     (result.getValue() != null ? result.getValue() : ""));
148             System.out.println("-------------------------------------------");
149             if (!result.getKey()) {
150                 numberFailed++;
151             }
152         }
153         System.out.println("End Results: " + (testList.size() - numberFailed) +
154                 " Passed" + ", " + numberFailed + " Failed.");
155         if (numberFailed > 0) {
156             throw new RuntimeException(
157                     "One or more tests failed, see test output for details");
158         }
159     }
160 
dumpHexBytes(byte[] data)161     private static void dumpHexBytes(byte[] data) {
162         if (data != null) {
163             for (int i = 0; i < data.length; i++) {
164                 if (i % 16 == 0 && i != 0) {
165                     System.out.print("\n");
166                 }
167                 System.out.print(String.format("%02X ", data[i]));
168             }
169             System.out.print("\n");
170         }
171     }
172 
173     public interface TestCase {
getName()174         String getName();
runTest()175         Map.Entry<Boolean, String> runTest();
176     }
177 
178     public static final TestCase pos_CtorByName = new TestCase() {
179         @Override
180         public String getName() {
181             return "CTOR Test (by-name)";
182         }
183 
184         @Override
185         public Map.Entry<Boolean, String> runTest() {
186             Boolean pass = Boolean.FALSE;
187             String message = null;
188             try {
189                 respByName = new ResponderId(cert.getSubjectX500Principal());
190                 pass = Boolean.TRUE;
191             } catch (Exception e) {
192                 e.printStackTrace(System.out);
193                 message = e.getClass().getName();
194             }
195 
196             return new AbstractMap.SimpleEntry<>(pass, message);
197         }
198     };
199 
200     public static final TestCase pos_CtorByKeyId = new TestCase() {
201         @Override
202         public String getName() {
203             return "CTOR Test (by-keyID)";
204         }
205 
206         @Override
207         public Map.Entry<Boolean, String> runTest() {
208             Boolean pass = Boolean.FALSE;
209             String message = null;
210             try {
211                 respByKeyId = new ResponderId(cert.getPublicKey());
212                 pass = Boolean.TRUE;
213             } catch (Exception e) {
214                 e.printStackTrace(System.out);
215                 message = e.getClass().getName();
216             }
217 
218             return new AbstractMap.SimpleEntry<>(pass, message);
219         }
220     };
221 
222     public static final TestCase pos_CtorByEncoding = new TestCase() {
223         @Override
224         public String getName() {
225             return "CTOR Test (encoded bytes)";
226         }
227 
228         @Override
229         public Map.Entry<Boolean, String> runTest() {
230             Boolean pass = Boolean.FALSE;
231             String message = null;
232             try {
233                 ResponderId ridByNameBytes = new ResponderId(EXP_NAME_ID_BYTES);
234                 ResponderId ridByKeyIdBytes = new ResponderId(EXP_KEY_ID_BYTES);
235 
236                 if (!ridByNameBytes.equals(respByName)) {
237                     throw new RuntimeException(
238                             "Equals failed: respNameFromBytes vs. respByName");
239                 } else if (!ridByKeyIdBytes.equals(respByKeyId)) {
240                     throw new RuntimeException(
241                             "Equals failed: respKeyFromBytes vs. respByKeyId");
242                 }
243                 pass = Boolean.TRUE;
244             } catch (Exception e) {
245                 e.printStackTrace(System.out);
246                 message = e.getClass().getName();
247             }
248 
249             return new AbstractMap.SimpleEntry<>(pass, message);
250         }
251     };
252 
253     public static final TestCase neg_CtorByEncoding = new TestCase() {
254         @Override
255         public String getName() {
256             return "CTOR Test (by encoding, unknown explicit tag)";
257         }
258 
259         @Override
260         public Map.Entry<Boolean, String> runTest() {
261             Boolean pass = Boolean.FALSE;
262             String message = null;
263             try {
264                 ResponderId ridByKeyIdBytes =
265                         new ResponderId(INV_EXPLICIT_TAG_KEY_ID);
266                 throw new RuntimeException("Expected IOException not thrown");
267             } catch (IOException ioe) {
268                 // Make sure it's the IOException we're looking for
269                 if (ioe.getMessage().contains("Invalid ResponderId content")) {
270                     pass = Boolean.TRUE;
271                 } else {
272                     ioe.printStackTrace(System.out);
273                     message = ioe.getClass().getName();
274                 }
275             } catch (Exception e) {
276                 e.printStackTrace(System.out);
277                 message = e.getClass().getName();
278             }
279 
280             return new AbstractMap.SimpleEntry<>(pass, message);
281         }
282     };
283 
284 
285     public static final TestCase pos_Equality = new TestCase() {
286         @Override
287         public String getName() {
288             return "Simple Equality Test";
289         }
290 
291         @Override
292         public Map.Entry<Boolean, String> runTest() {
293             Boolean pass = Boolean.FALSE;
294             String message = null;
295 
296             try {
297                 // byName ResponderId equality test
298                 ResponderId compName =
299                     new ResponderId(new X500Principal(RESP_CERT_1_SUBJ));
300                 if (!respByName.equals(compName)) {
301                     message = "ResponderId mismatch in byName comparison";
302                 } else if (respByKeyId.equals(compName)) {
303                     message = "Invalid ResponderId match in byKeyId comparison";
304                 } else {
305                     pass = Boolean.TRUE;
306                 }
307             } catch (Exception e) {
308                 e.printStackTrace(System.out);
309                 message = e.getClass().getName();
310             }
311 
312             return new AbstractMap.SimpleEntry<>(pass, message);
313         }
314     };
315 
316     public static final TestCase pos_GetEncoded = new TestCase() {
317         @Override
318         public String getName() {
319             return "Get Encoded Value";
320         }
321 
322         @Override
323         public Map.Entry<Boolean, String> runTest() {
324             Boolean pass = Boolean.FALSE;
325             String message = null;
326 
327             try {
328                 // Pull out byName and byKey encodings, they should match
329                 // the expected values
330                 if (!Arrays.equals(respByName.getEncoded(), EXP_NAME_ID_BYTES)) {
331                     message = "ResponderId byName encoding did not " +
332                             "match expected value";
333                 } else if (!Arrays.equals(respByKeyId.getEncoded(), EXP_KEY_ID_BYTES)) {
334                     message = "ResponderId byKeyId encoding did not " +
335                             "match expected value";
336                 } else {
337                     pass = Boolean.TRUE;
338                 }
339             } catch (Exception e) {
340                 e.printStackTrace(System.out);
341                 message = e.getClass().getName();
342             }
343 
344             return new AbstractMap.SimpleEntry<>(pass, message);
345         }
346     };
347 
348     public static final TestCase pos_GetRespName = new TestCase() {
349         @Override
350         public String getName() {
351             return "Get Underlying Responder Name";
352         }
353 
354         @Override
355         public Map.Entry<Boolean, String> runTest() {
356             Boolean pass = Boolean.FALSE;
357             String message = null;
358 
359             try {
360                 // Test methods for pulling out the underlying
361                 // X500Principal object
362                 X500Principal testPrincipal =
363                         new X500Principal(RESP_CERT_1_SUBJ);
364                 if (!respByName.getResponderName().equals(testPrincipal)) {
365                     message = "ResponderId Name did not match expected value";
366                 } else if (respByKeyId.getResponderName() != null) {
367                     message = "Non-null responder name returned from " +
368                             "ResponderId constructed byKey";
369                 } else {
370                     pass = Boolean.TRUE;
371                 }
372             } catch (Exception e) {
373                 e.printStackTrace(System.out);
374                 message = e.getClass().getName();
375             }
376 
377             return new AbstractMap.SimpleEntry<>(pass, message);
378         }
379     };
380 
381     public static final TestCase pos_GetRespKeyId = new TestCase() {
382         @Override
383         public String getName() {
384             return "Get Underlying Responder Key ID";
385         }
386 
387         @Override
388         public Map.Entry<Boolean, String> runTest() {
389             Boolean pass = Boolean.FALSE;
390             String message = null;
391 
392             try {
393                 // Test methods for pulling out the underlying
394                 // KeyIdentifier object.  Note: There is a minute chance that
395                 // an RSA public key, once hashed into a key ID might collide
396                 // with the one extracted from the certificate used to create
397                 // respByKeyId.  This is so unlikely to happen it is considered
398                 // virtually impossible.
399                 KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
400                 kpg.initialize(2048);
401                 KeyPair rsaKey = kpg.generateKeyPair();
402                 KeyIdentifier testKeyId = new KeyIdentifier(rsaKey.getPublic());
403 
404                 if (respByKeyId.getKeyIdentifier().equals(testKeyId)) {
405                     message = "Unexpected match in ResponderId Key ID";
406                 } else if (respByName.getKeyIdentifier() != null) {
407                     message = "Non-null key ID returned from " +
408                             "ResponderId constructed byName";
409                 } else {
410                     pass = Boolean.TRUE;
411                 }
412             } catch (Exception e) {
413                 e.printStackTrace(System.out);
414                 message = e.getClass().getName();
415             }
416 
417             return new AbstractMap.SimpleEntry<>(pass, message);
418         }
419     };
420 
421 }
422