1/*
2 * Copyright (c) 2015, 2016, 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
24package sun.security.ssl;
25
26import java.io.IOException;
27import java.util.*;
28import java.nio.ByteBuffer;
29
30/*
31 * Checks that the hash value for a certificate's issuer name is generated
32 * correctly. Requires any certificate that is not self-signed.
33 *
34 * NOTE: this test uses Sun private classes which are subject to change.
35 */
36public class CertStatusReqExtensionTests {
37
38    private static final boolean debug = false;
39
40    // Default status_request extension (type = ocsp, OCSPStatusRequest
41    // with no responder IDs or extensions
42    private static final byte[] CSRE_DEF_OSR = {1, 0, 0, 0, 0};
43
44    // A status_request extension using a user-defined type (0xFF) and
45    // an underlying no-Responder ID/no-extension OCSPStatusRequest
46    private static final byte[] CSRE_TYPE_FF = {-1, 0, 0, 0, 0};
47
48    // A CertStatusReqExtension with 5 ResponderIds and 1 Extension
49    private static final byte[] CSRE_REQ_RID_EXTS = {
50           1,    0,  -13,    0,   59,  -95,   57,   48,
51          55,   49,   16,   48,   14,    6,    3,   85,
52           4,   10,   19,    7,   83,  111,  109,  101,
53          73,  110,   99,   49,   16,   48,   14,    6,
54           3,   85,    4,   11,   19,    7,   83,  111,
55         109,  101,   80,   75,   73,   49,   17,   48,
56          15,    6,    3,   85,    4,    3,   19,    8,
57          83,  111,  109,  101,   79,   67,   83,   80,
58           0,   68,  -95,   66,   48,   64,   49,   13,
59          48,   11,    6,    3,   85,    4,   10,   19,
60           4,   79,  104,   77,  121,   49,   14,   48,
61          12,    6,    3,   85,    4,   11,   19,    5,
62          66,  101,   97,  114,  115,   49,   15,   48,
63          13,    6,    3,   85,    4,   11,   19,    6,
64          84,  105,  103,  101,  114,  115,   49,   14,
65          48,   12,    6,    3,   85,    4,    3,   19,
66           5,   76,  105,  111,  110,  115,    0,   58,
67         -95,   56,   48,   54,   49,   16,   48,   14,
68           6,    3,   85,    4,   10,   19,    7,   67,
69         111,  109,  112,   97,  110,  121,   49,   13,
70          48,   11,    6,    3,   85,    4,   11,   19,
71           4,   87,  101,  115,  116,   49,   19,   48,
72          17,    6,    3,   85,    4,    3,   19,   10,
73          82,  101,  115,  112,  111,  110,  100,  101,
74         114,   49,    0,   24,  -94,   22,    4,   20,
75         -67,  -36,  114,  121,   92,  -79,  116,   -1,
76         102, -107,    7,  -21,   18, -113,   64,   76,
77          96,   -7,  -66,  -63,    0,   24,  -94,   22,
78           4,   20,  -51,  -69,  107,  -82,  -39,  -87,
79          45,   25,   41,   28,  -76,  -68,  -11, -110,
80         -94,  -97,   62,   47,   58, -125,    0,   51,
81          48,   49,   48,   47,    6,    9,   43,    6,
82           1,    5,    5,    7,   48,    1,    2,    4,
83          34,    4,   32,  -26,  -81, -120,  -61, -127,
84         -79,    0,  -39,  -54,   49,    3,  -51,  -57,
85         -85,   19, -126,   94,   -2,   21,   26,   98,
86           6,  105,  -35,  -37,  -29,  -73,  101,   53,
87          44,   15,  -19
88    };
89
90    public static void main(String[] args) throws Exception {
91        Map<String, TestCase> testList =
92                new LinkedHashMap<String, TestCase>() {{
93            put("CTOR (default)", testCtorDefault);
94            put("CTOR (int, StatusRequest)", testCtorStatReqs);
95            put("CTOR (HandshakeInStream, length, getReqType, getRequest)",
96                    testCtorInStream);
97        }};
98
99        TestUtils.runTests(testList);
100    }
101
102    public static final TestCase testCtorDefault = new TestCase() {
103        @Override
104        public Map.Entry<Boolean, String> runTest() {
105            Boolean pass = Boolean.FALSE;
106            String message = null;
107            try {
108                CertStatusReqExtension csreDef = new CertStatusReqExtension();
109                HandshakeOutStream hsout =
110                        new HandshakeOutStream(null);
111                csreDef.send(hsout);
112                TestUtils.valueCheck(wrapExtData(null), hsout.toByteArray());
113
114                // The length should be 4 (2 bytes for the type, 2 for the
115                // encoding of zero-length
116                if (csreDef.length() != 4) {
117                    throw new RuntimeException("Incorrect length from " +
118                            "default object.  Expected 4, got " +
119                            csreDef.length());
120                }
121
122                // Since there's no data, there are no status_type or request
123                // data fields defined.  Both should return null in this case
124                if (csreDef.getType() != null) {
125                    throw new RuntimeException("Default CSRE returned " +
126                            "non-null status_type");
127                } else if (csreDef.getRequest() != null) {
128                    throw new RuntimeException("Default CSRE returned " +
129                            "non-null request object");
130                }
131
132                pass = Boolean.TRUE;
133            } catch (Exception e) {
134                e.printStackTrace(System.out);
135                message = e.getClass().getName();
136            }
137
138            return new AbstractMap.SimpleEntry<>(pass, message);
139        }
140    };
141
142    public static final TestCase testCtorStatReqs = new TestCase() {
143        @Override
144        public Map.Entry<Boolean, String> runTest() {
145            Boolean pass = Boolean.FALSE;
146            String message = null;
147            try {
148                HandshakeOutStream hsout =
149                        new HandshakeOutStream(null);
150                StatusRequest basicStatReq = new OCSPStatusRequest();
151
152                // Create an extension using a default-style OCSPStatusRequest
153                // (no responder IDs, no extensions).
154                CertStatusReqExtension csre1 = new CertStatusReqExtension(
155                        StatusRequestType.OCSP, basicStatReq);
156                csre1.send(hsout);
157                TestUtils.valueCheck(wrapExtData(CSRE_DEF_OSR),
158                        hsout.toByteArray());
159                hsout.reset();
160
161                // Create the extension using a StatusRequestType not already
162                // instantiated as a static StatusRequestType
163                // (e.g. OCSP/OCSP_MULTI)
164                CertStatusReqExtension csre2 =
165                        new CertStatusReqExtension(StatusRequestType.get(-1),
166                                basicStatReq);
167                csre2.send(hsout);
168                TestUtils.valueCheck(wrapExtData(CSRE_TYPE_FF),
169                        hsout.toByteArray());
170
171                // Create the extension using a StatusRequest that
172                // does not match the status_type field
173                // This should throw an IllegalArgumentException
174                try {
175                    CertStatusReqExtension csreBadRequest =
176                            new CertStatusReqExtension(StatusRequestType.OCSP,
177                                    new BogusStatusRequest());
178                    throw new RuntimeException("Constructor accepted a " +
179                            "StatusRequest that is inconsistent with " +
180                            "the status_type");
181                } catch (IllegalArgumentException iae) { }
182
183                // We don't allow a null value for the StatusRequestType
184                // parameter in this constructor.
185                try {
186                    CertStatusReqExtension csreBadRequest =
187                            new CertStatusReqExtension(null, basicStatReq);
188                    throw new RuntimeException("Constructor accepted a " +
189                            "null StatusRequestType");
190                } catch (NullPointerException npe) { }
191
192                // We also don't allow a null value for the StatusRequest
193                // parameter in this constructor.
194                try {
195                    CertStatusReqExtension csreBadRequest =
196                            new CertStatusReqExtension(StatusRequestType.OCSP,
197                                    null);
198                    throw new RuntimeException("Constructor accepted a " +
199                            "null StatusRequest");
200                } catch (NullPointerException npe) { }
201
202                pass = Boolean.TRUE;
203            } catch (Exception e) {
204                e.printStackTrace(System.out);
205                message = e.getClass().getName();
206            }
207
208            return new AbstractMap.SimpleEntry<>(pass, message);
209        }
210    };
211
212    // Test the constructor that builds the ob ject using data from
213    // a HandshakeInStream
214    // This also tests the length, getReqType and getRequest methods
215    public static final TestCase testCtorInStream = new TestCase() {
216        @Override
217        public Map.Entry<Boolean, String> runTest() {
218            Boolean pass = Boolean.FALSE;
219            String message = null;
220            OCSPStatusRequest osr;
221
222            try {
223                // To simulate the extension coming in a ServerHello, the
224                // type and length would already be read by HelloExtensions
225                // and there is no extension data
226                HandshakeInStream hsis = new HandshakeInStream();
227                hsis.incomingRecord(ByteBuffer.wrap(new byte[0]));
228                CertStatusReqExtension csre =
229                        new CertStatusReqExtension(hsis, hsis.available());
230                // Verify length/type/request
231                if (csre.length() != 4) {
232                     throw new RuntimeException("Invalid length: received " +
233                            csre.length() + ", expected 4");
234                } else if (csre.getType() != null) {
235                    throw new RuntimeException("Non-null type from default " +
236                            "extension");
237                } else if (csre.getRequest() != null) {
238                    throw new RuntimeException("Non-null request from default " +
239                            "extension");
240                }
241
242                // Try the an extension with a default OCSPStatusRequest
243                hsis = new HandshakeInStream();
244                hsis.incomingRecord(ByteBuffer.wrap(CSRE_DEF_OSR));
245                csre = new CertStatusReqExtension(hsis, hsis.available());
246                if (csre.length() != (CSRE_DEF_OSR.length + 4)) {
247                    throw new RuntimeException("Invalid length: received " +
248                            csre.length() + ", expected " +
249                            CSRE_DEF_OSR.length + 4);
250                } else if (!csre.getType().equals(StatusRequestType.OCSP)) {
251                    throw new RuntimeException("Unknown status_type: " +
252                            String.format("0x%02X", csre.getType().id));
253                } else {
254                    osr = (OCSPStatusRequest)csre.getRequest();
255                    if (!osr.getResponderIds().isEmpty() ||
256                            !osr.getExtensions().isEmpty()) {
257                        throw new RuntimeException("Non-default " +
258                                "OCSPStatusRequest found in extension");
259                    }
260                }
261
262                // Try with a non-default extension
263                hsis = new HandshakeInStream();
264                hsis.incomingRecord(ByteBuffer.wrap(CSRE_REQ_RID_EXTS));
265                csre = new CertStatusReqExtension(hsis, hsis.available());
266                if (csre.length() != (CSRE_REQ_RID_EXTS.length + 4)) {
267                    throw new RuntimeException("Invalid length: received " +
268                            csre.length() + ", expected " +
269                            CSRE_REQ_RID_EXTS.length + 4);
270                } else if (!(csre.getType().equals(StatusRequestType.OCSP))) {
271                    throw new RuntimeException("Unknown status_type: " +
272                            String.format("0x%02X", csre.getType().id));
273                } else {
274                    osr = (OCSPStatusRequest)csre.getRequest();
275                    if (osr.getResponderIds().size() != 5 ||
276                            osr.getExtensions().size() != 1) {
277                        throw new RuntimeException("Incorrect number of " +
278                                "ResponderIds or Extensions found in " +
279                                "OCSPStatusRequest");
280                    }
281                }
282
283                // Create a CSRE that asserts status_request and has the
284                // proper length, but really is a bunch of random junk inside
285                // In this case, it will create an UnknownStatusRequest to
286                // handle the unparseable data.
287                byte[] junkData = new byte[48];
288                Random r = new Random(System.currentTimeMillis());
289                r.nextBytes(junkData);
290                junkData[0] = 7;        // Ensure it isn't a valid status_type
291                hsis = new HandshakeInStream();
292                hsis.incomingRecord(ByteBuffer.wrap(junkData));
293                csre = new CertStatusReqExtension(hsis, hsis.available());
294                StatusRequest sr = csre.getRequest();
295                if (!(sr instanceof UnknownStatusRequest)) {
296                    throw new RuntimeException("Expected returned status " +
297                            "request to be of type UnknownStatusRequest but " +
298                            "received " + sr.getClass().getName());
299                } else if (csre.length() != (junkData.length + 4)) {
300                    throw new RuntimeException("Invalid length: received " +
301                            csre.length() + ", expected " +
302                            junkData.length + 4);
303                }
304
305                // Set the leading byte to 1 (OCSP type) and run again
306                // It should pass the argument check and fail trying to parse
307                // the underlying StatusRequest.
308                junkData[0] = (byte)StatusRequestType.OCSP.id;
309                hsis = new HandshakeInStream();
310                hsis.incomingRecord(ByteBuffer.wrap(junkData));
311                try {
312                    csre = new CertStatusReqExtension(hsis, hsis.available());
313                    throw new RuntimeException("Expected CTOR exception did " +
314                            "not occur");
315                } catch (IOException ioe) { }
316
317                pass = Boolean.TRUE;
318            } catch (Exception e) {
319                e.printStackTrace(System.out);
320                message = e.getClass().getName();
321            }
322
323            return new AbstractMap.SimpleEntry<>(pass, message);
324        }
325    };
326
327    // Take CSRE extension data and add extension type and length decorations
328    private static byte[] wrapExtData(byte[] extData) {
329        int bufferLen = (extData != null ? extData.length : 0) + 4;
330        ByteBuffer bb = ByteBuffer.allocate(bufferLen);
331        bb.putShort((short)ExtensionType.EXT_STATUS_REQUEST.id);
332        bb.putShort((short)(extData != null ? extData.length: 0));
333        if (extData != null) {
334            bb.put(extData);
335        }
336        return bb.array();
337    }
338}
339