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 Unit tests for OCSPNonceExtension objects
28 * @modules java.base/sun.security.provider.certpath
29 *          java.base/sun.security.util
30 *          java.base/sun.security.x509
31 */
32
33import java.security.cert.Extension;
34import java.io.ByteArrayOutputStream;
35import java.io.IOException;
36import java.util.*;
37
38import sun.security.util.DerValue;
39import sun.security.util.DerInputStream;
40import sun.security.util.ObjectIdentifier;
41import sun.security.provider.certpath.OCSPNonceExtension;
42import sun.security.x509.PKIXExtensions;
43
44public class OCSPNonceExtensionTests {
45    public static final boolean DEBUG = true;
46    public static final String OCSP_NONCE_OID = "1.3.6.1.5.5.7.48.1.2";
47    public static final String ELEMENT_NONCE = "nonce";
48    public static final String EXT_NAME = "OCSPNonce";
49
50    // DER encoding for OCSP nonce extension:
51    // OID = 1.3.6.1.5.5.7.48.1.2
52    // Critical = true
53    // 48 bytes of 0xDEADBEEF
54    public static final byte[] OCSP_NONCE_DER = {
55          48,   66,    6,    9,   43,    6,    1,    5,
56           5,    7,   48,    1,    2,    1,    1,   -1,
57           4,   50,    4,   48,  -34,  -83,  -66,  -17,
58         -34,  -83,  -66,  -17,  -34,  -83,  -66,  -17,
59         -34,  -83,  -66,  -17,  -34,  -83,  -66,  -17,
60         -34,  -83,  -66,  -17,  -34,  -83,  -66,  -17,
61         -34,  -83,  -66,  -17,  -34,  -83,  -66,  -17,
62         -34,  -83,  -66,  -17,  -34,  -83,  -66,  -17,
63         -34,  -83,  -66,  -17,
64    };
65
66    // 16 bytes of 0xDEADBEEF
67    public static final byte[] DEADBEEF_16 = {
68         -34,  -83,  -66,  -17,  -34,  -83,  -66,  -17,
69         -34,  -83,  -66,  -17,  -34,  -83,  -66,  -17,
70    };
71
72    // DER encoded extension using 16 bytes of DEADBEEF
73    public static final byte[] OCSP_NONCE_DB16 = {
74          48,   31,    6,    9,   43,    6,    1,    5,
75           5,    7,   48,    1,    2,    4,   18,    4,
76          16,  -34,  -83,  -66,  -17,  -34,  -83,  -66,
77         -17,  -34,  -83,  -66,  -17,  -34,  -83,  -66,
78         -17
79    };
80
81    public static void main(String [] args) throws Exception {
82        Map<String, TestCase> testList =
83                new LinkedHashMap<String, TestCase>() {{
84            put("CTOR Test (provide length)", testCtorByLength);
85            put("CTOR Test (provide nonce bytes)", testCtorByValue);
86            put("CTOR Test (set criticality forms)", testCtorCritForms);
87            put("CTOR Test (provide extension DER encoding)",
88                    testCtorSuperByDerValue);
89            put("Test getName() method", testGetName);
90        }};
91
92        System.out.println("============ Tests ============");
93        int testNo = 0;
94        int numberFailed = 0;
95        Map.Entry<Boolean, String> result;
96        for (String testName : testList.keySet()) {
97            System.out.println("Test " + ++testNo + ": " + testName);
98            result = testList.get(testName).runTest();
99            System.out.print("Result: " + (result.getKey() ? "PASS" : "FAIL"));
100            System.out.println(" " +
101                    (result.getValue() != null ? result.getValue() : ""));
102            System.out.println("-------------------------------------------");
103            if (!result.getKey()) {
104                numberFailed++;
105            }
106        }
107        System.out.println("End Results: " + (testList.size() - numberFailed) +
108                " Passed" + ", " + numberFailed + " Failed.");
109        if (numberFailed > 0) {
110            throw new RuntimeException(
111                    "One or more tests failed, see test output for details");
112        }
113    }
114
115    private static void dumpHexBytes(byte[] data) {
116        if (data != null) {
117            for (int i = 0; i < data.length; i++) {
118                if (i % 16 == 0 && i != 0) {
119                    System.out.print("\n");
120                }
121                System.out.print(String.format("%02X ", data[i]));
122            }
123            System.out.print("\n");
124        }
125    }
126
127    private static void debuglog(String message) {
128        if (DEBUG) {
129            System.out.println(message);
130        }
131    }
132
133    public static void verifyExtStructure(byte[] derData) throws IOException {
134        debuglog("verifyASN1Extension() received " + derData.length + " bytes");
135        DerInputStream dis = new DerInputStream(derData);
136
137        // The sequenceItems array should be either two or three elements
138        // long.  If three, then the criticality bit setting has been asserted.
139        DerValue[] sequenceItems = dis.getSequence(3);
140        debuglog("Found sequence containing " + sequenceItems.length +
141                " elements");
142        if (sequenceItems.length != 2 && sequenceItems.length != 3) {
143            throw new RuntimeException("Incorrect number of items found in " +
144                    "the SEQUENCE (Got " + sequenceItems.length +
145                    ", expected 2 or 3 items)");
146        }
147
148        int seqIndex = 0;
149        ObjectIdentifier extOid = sequenceItems[seqIndex++].getOID();
150        debuglog("Found OID: " + extOid.toString());
151        if (!extOid.equals((Object)PKIXExtensions.OCSPNonce_Id)) {
152            throw new RuntimeException("Incorrect OID (Got " +
153                    extOid.toString() + ", expected " +
154                    PKIXExtensions.OCSPNonce_Id.toString() + ")");
155        }
156
157        if (sequenceItems.length == 3) {
158            // Non-default criticality bit setting should be at index 1
159            boolean isCrit = sequenceItems[seqIndex++].getBoolean();
160            debuglog("Found BOOLEAN (critical): " + isCrit);
161        }
162
163        // The extnValue is an encapsulating OCTET STRING that contains the
164        // extension's value.  For the OCSP Nonce, that value itself is also
165        // an OCTET STRING consisting of the random bytes.
166        DerValue extnValue =
167                new DerValue(sequenceItems[seqIndex++].getOctetString());
168        byte[] nonceData = extnValue.getOctetString();
169        debuglog("Found " + nonceData.length + " bytes of nonce data");
170    }
171
172    public interface TestCase {
173        Map.Entry<Boolean, String> runTest();
174    }
175
176    public static final TestCase testCtorByLength = new TestCase() {
177        @Override
178        public Map.Entry<Boolean, String> runTest() {
179            Boolean pass = Boolean.FALSE;
180            String message = null;
181            try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
182                // Try sending in a negative length
183                try {
184                    Extension negLenNonce = new OCSPNonceExtension(-8);
185                    throw new RuntimeException(
186                            "Accepted a negative length nonce");
187                } catch (IllegalArgumentException iae) { }
188
189                // How about a zero length?
190                try {
191                    Extension zeroLenNonce = new OCSPNonceExtension(0);
192                    throw new RuntimeException("Accepted a zero length nonce");
193                } catch (IllegalArgumentException iae) { }
194
195                // Valid input to constructor
196                Extension nonceByLen = new OCSPNonceExtension(32);
197
198                // Verify overall encoded extension structure
199                nonceByLen.encode(baos);
200                verifyExtStructure(baos.toByteArray());
201
202                // Verify the name, elements, and data conform to
203                // expected values for this specific object.
204                boolean crit = nonceByLen.isCritical();
205                String oid = nonceByLen.getId();
206                DerValue nonceData = new DerValue(nonceByLen.getValue());
207
208                if (crit) {
209                    message = "Extension incorrectly marked critical";
210                } else if (!oid.equals(OCSP_NONCE_OID)) {
211                    message = "Incorrect OID (Got " + oid + ", Expected " +
212                            OCSP_NONCE_OID + ")";
213                } else if (nonceData.getTag() != DerValue.tag_OctetString) {
214                    message = "Incorrect nonce data tag type (Got " +
215                            String.format("0x%02X", nonceData.getTag()) +
216                            ", Expected 0x04)";
217                } else if (nonceData.getOctetString().length != 32) {
218                    message = "Incorrect nonce byte length (Got " +
219                            nonceData.getOctetString().length +
220                            ", Expected 32)";
221                } else {
222                    pass = Boolean.TRUE;
223                }
224            } catch (Exception e) {
225                e.printStackTrace(System.out);
226                message = e.getClass().getName();
227            }
228
229            return new AbstractMap.SimpleEntry<>(pass, message);
230        }
231    };
232
233    public static final TestCase testCtorByValue = new TestCase() {
234        @Override
235        public Map.Entry<Boolean, String> runTest() {
236            Boolean pass = Boolean.FALSE;
237            String message = null;
238            try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
239
240                // Try giving a null value for the nonce
241                try {
242                    Extension nullNonce = new OCSPNonceExtension(null);
243                    throw new RuntimeException("Accepted a null nonce");
244                } catch (NullPointerException npe) { }
245
246                // How about a zero-length byte array?
247                try {
248                    Extension zeroLenNonce =
249                            new OCSPNonceExtension(new byte[0]);
250                    throw new RuntimeException("Accepted a zero length nonce");
251                } catch (IllegalArgumentException iae) { }
252
253                OCSPNonceExtension nonceByValue =
254                        new OCSPNonceExtension(DEADBEEF_16);
255
256                // Verify overall encoded extension structure
257                nonceByValue.encode(baos);
258                verifyExtStructure(baos.toByteArray());
259
260                // Verify the name, elements, and data conform to
261                // expected values for this specific object.
262                boolean crit = nonceByValue.isCritical();
263                String oid = nonceByValue.getId();
264                byte[] nonceData = nonceByValue.getNonceValue();
265
266                if (crit) {
267                    message = "Extension incorrectly marked critical";
268                } else if (!oid.equals(OCSP_NONCE_OID)) {
269                    message = "Incorrect OID (Got " + oid + ", Expected " +
270                            OCSP_NONCE_OID + ")";
271                } else if (!Arrays.equals(nonceData, DEADBEEF_16)) {
272                    message = "Returned nonce value did not match input";
273                } else {
274                    pass = Boolean.TRUE;
275                }
276            } catch (Exception e) {
277                e.printStackTrace(System.out);
278                message = e.getClass().getName();
279            }
280
281            return new AbstractMap.SimpleEntry<>(pass, message);
282        }
283    };
284
285    public static final TestCase testCtorCritForms = new TestCase() {
286        @Override
287        public Map.Entry<Boolean, String> runTest() {
288            Boolean pass = Boolean.FALSE;
289            String message = null;
290            try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
291                Extension nonceByLength = new OCSPNonceExtension(true, 32);
292                Extension nonceByValue =
293                        new OCSPNonceExtension(true, DEADBEEF_16);
294                pass = nonceByLength.isCritical() && nonceByValue.isCritical();
295                if (!pass) {
296                    message = "nonceByLength or nonceByValue was not marked " +
297                            "critical as expected";
298                }
299            }  catch (Exception e) {
300                e.printStackTrace(System.out);
301                message = e.getClass().getName();
302            }
303
304            return new AbstractMap.SimpleEntry<>(pass, message);
305        }
306    };
307
308
309    public static final TestCase testCtorSuperByDerValue = new TestCase() {
310        @Override
311        public Map.Entry<Boolean, String> runTest() {
312            Boolean pass = Boolean.FALSE;
313            String message = null;
314            try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
315                Extension nonceByDer = new sun.security.x509.Extension(
316                        new DerValue(OCSP_NONCE_DER));
317
318                // Verify overall encoded extension structure
319                nonceByDer.encode(baos);
320                verifyExtStructure(baos.toByteArray());
321
322                // Verify the name, elements, and data conform to
323                // expected values for this specific object.
324                boolean crit = nonceByDer.isCritical();
325                String oid = nonceByDer.getId();
326                DerValue nonceData = new DerValue(nonceByDer.getValue());
327
328                if (!crit) {
329                    message = "Extension lacks expected criticality setting";
330                } else if (!oid.equals(OCSP_NONCE_OID)) {
331                    message = "Incorrect OID (Got " + oid + ", Expected " +
332                            OCSP_NONCE_OID + ")";
333                } else if (nonceData.getTag() != DerValue.tag_OctetString) {
334                    message = "Incorrect nonce data tag type (Got " +
335                            String.format("0x%02X", nonceData.getTag()) +
336                            ", Expected 0x04)";
337                } else if (nonceData.getOctetString().length != 48) {
338                    message = "Incorrect nonce byte length (Got " +
339                            nonceData.getOctetString().length +
340                            ", Expected 48)";
341                } else {
342                    pass = Boolean.TRUE;
343                }
344            } catch (Exception e) {
345                e.printStackTrace(System.out);
346                message = e.getClass().getName();
347            }
348
349            return new AbstractMap.SimpleEntry<>(pass, message);
350        }
351    };
352
353    public static final TestCase testGetName = new TestCase() {
354        @Override
355        public Map.Entry<Boolean, String> runTest() {
356            Boolean pass = Boolean.FALSE;
357            String message = null;
358            try {
359                OCSPNonceExtension nonceByLen = new OCSPNonceExtension(32);
360                pass = new Boolean(nonceByLen.getName().equals(EXT_NAME));
361            } catch (Exception e) {
362                e.printStackTrace(System.out);
363                message = e.getClass().getName();
364            }
365
366            return new AbstractMap.SimpleEntry<>(pass, message);
367        }
368    };
369}
370