TimestampCheck.java revision 9330:8b1f1c2a400f
1241862Seadler/*
2241862Seadler * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
3241862Seadler * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4241862Seadler *
5241862Seadler * This code is free software; you can redistribute it and/or modify it
6241862Seadler * 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
24import com.sun.net.httpserver.*;
25import java.io.BufferedReader;
26import java.io.ByteArrayOutputStream;
27import java.io.File;
28import java.io.FileInputStream;
29import java.io.FileOutputStream;
30import java.io.IOException;
31import java.io.InputStreamReader;
32import java.io.OutputStream;
33import java.math.BigInteger;
34import java.net.InetSocketAddress;
35import java.security.KeyStore;
36import java.security.PrivateKey;
37import java.security.Signature;
38import java.security.cert.Certificate;
39import java.security.cert.X509Certificate;
40import java.util.Calendar;
41import sun.security.pkcs.ContentInfo;
42import sun.security.pkcs.PKCS7;
43import sun.security.pkcs.SignerInfo;
44import sun.security.util.DerOutputStream;
45import sun.security.util.DerValue;
46import sun.security.util.ObjectIdentifier;
47import sun.security.x509.AlgorithmId;
48import sun.security.x509.X500Name;
49
50public class TimestampCheck {
51    static final String TSKS = "tsks";
52    static final String JAR = "old.jar";
53
54    static class Handler implements HttpHandler {
55        public void handle(HttpExchange t) throws IOException {
56            int len = 0;
57            for (String h: t.getRequestHeaders().keySet()) {
58                if (h.equalsIgnoreCase("Content-length")) {
59                    len = Integer.valueOf(t.getRequestHeaders().get(h).get(0));
60                }
61            }
62            byte[] input = new byte[len];
63            t.getRequestBody().read(input);
64
65            try {
66                int path = 0;
67                if (t.getRequestURI().getPath().length() > 1) {
68                    path = Integer.parseInt(
69                            t.getRequestURI().getPath().substring(1));
70                }
71                byte[] output = sign(input, path);
72                Headers out = t.getResponseHeaders();
73                out.set("Content-Type", "application/timestamp-reply");
74
75                t.sendResponseHeaders(200, output.length);
76                OutputStream os = t.getResponseBody();
77                os.write(output);
78            } catch (Exception e) {
79                e.printStackTrace();
80                t.sendResponseHeaders(500, 0);
81            }
82            t.close();
83        }
84
85        /**
86         * @param input The data to sign
87         * @param path different cases to simulate, impl on URL path
88         * 0: normal
89         * 1: Missing nonce
90         * 2: Different nonce
91         * 3: Bad digets octets in messageImprint
92         * 4: Different algorithmId in messageImprint
93         * 5: whole chain in cert set
94         * 6: extension is missing
95         * 7: extension is non-critical
96         * 8: extension does not have timestamping
97         * @returns the signed
98         */
99        byte[] sign(byte[] input, int path) throws Exception {
100            // Read TSRequest
101            DerValue value = new DerValue(input);
102            System.err.println("\nIncoming Request\n===================");
103            System.err.println("Version: " + value.data.getInteger());
104            DerValue messageImprint = value.data.getDerValue();
105            AlgorithmId aid = AlgorithmId.parse(
106                    messageImprint.data.getDerValue());
107            System.err.println("AlgorithmId: " + aid);
108
109            BigInteger nonce = null;
110            while (value.data.available() > 0) {
111                DerValue v = value.data.getDerValue();
112                if (v.tag == DerValue.tag_Integer) {
113                    nonce = v.getBigInteger();
114                    System.err.println("nonce: " + nonce);
115                } else if (v.tag == DerValue.tag_Boolean) {
116                    System.err.println("certReq: " + v.getBoolean());
117                }
118            }
119
120            // Write TSResponse
121            System.err.println("\nResponse\n===================");
122            KeyStore ks = KeyStore.getInstance("JKS");
123            ks.load(new FileInputStream(TSKS), "changeit".toCharArray());
124
125            String alias = "ts";
126            if (path == 6) alias = "tsbad1";
127            if (path == 7) alias = "tsbad2";
128            if (path == 8) alias = "tsbad3";
129
130            DerOutputStream statusInfo = new DerOutputStream();
131            statusInfo.putInteger(0);
132
133            DerOutputStream token = new DerOutputStream();
134            AlgorithmId[] algorithms = {aid};
135            Certificate[] chain = ks.getCertificateChain(alias);
136            X509Certificate[] signerCertificateChain = null;
137            X509Certificate signer = (X509Certificate)chain[0];
138            if (path == 5) {   // Only case 5 uses full chain
139                signerCertificateChain = new X509Certificate[chain.length];
140                for (int i=0; i<chain.length; i++) {
141                    signerCertificateChain[i] = (X509Certificate)chain[i];
142                }
143            } else if (path == 9) {
144                signerCertificateChain = new X509Certificate[0];
145            } else {
146                signerCertificateChain = new X509Certificate[1];
147                signerCertificateChain[0] = (X509Certificate)chain[0];
148            }
149
150            DerOutputStream tst = new DerOutputStream();
151
152            tst.putInteger(1);
153            tst.putOID(new ObjectIdentifier("1.2.3.4"));    // policy
154
155            if (path != 3 && path != 4) {
156                tst.putDerValue(messageImprint);
157            } else {
158                byte[] data = messageImprint.toByteArray();
159                if (path == 4) {
160                    data[6] = (byte)0x01;
161                } else {
162                    data[data.length-1] = (byte)0x01;
163                    data[data.length-2] = (byte)0x02;
164                    data[data.length-3] = (byte)0x03;
165                }
166                tst.write(data);
167            }
168
169            tst.putInteger(1);
170
171            Calendar cal = Calendar.getInstance();
172            tst.putGeneralizedTime(cal.getTime());
173
174            if (path == 2) {
175                tst.putInteger(1234);
176            } else if (path == 1) {
177                // do nothing
178            } else {
179                tst.putInteger(nonce);
180            }
181
182            DerOutputStream tstInfo = new DerOutputStream();
183            tstInfo.write(DerValue.tag_Sequence, tst);
184
185            DerOutputStream tstInfo2 = new DerOutputStream();
186            tstInfo2.putOctetString(tstInfo.toByteArray());
187
188            Signature sig = Signature.getInstance("SHA1withRSA");
189            sig.initSign((PrivateKey)(ks.getKey(
190                    alias, "changeit".toCharArray())));
191            sig.update(tstInfo.toByteArray());
192
193            ContentInfo contentInfo = new ContentInfo(new ObjectIdentifier(
194                    "1.2.840.113549.1.9.16.1.4"),
195                    new DerValue(tstInfo2.toByteArray()));
196
197            System.err.println("Signing...");
198            System.err.println(new X500Name(signer
199                    .getIssuerX500Principal().getName()));
200            System.err.println(signer.getSerialNumber());
201
202            SignerInfo signerInfo = new SignerInfo(
203                    new X500Name(signer.getIssuerX500Principal().getName()),
204                    signer.getSerialNumber(),
205                    aid, AlgorithmId.get("RSA"), sig.sign());
206
207            SignerInfo[] signerInfos = {signerInfo};
208            PKCS7 p7 =
209                    new PKCS7(algorithms, contentInfo, signerCertificateChain,
210                    signerInfos);
211            ByteArrayOutputStream p7out = new ByteArrayOutputStream();
212            p7.encodeSignedData(p7out);
213
214            DerOutputStream response = new DerOutputStream();
215            response.write(DerValue.tag_Sequence, statusInfo);
216            response.putDerValue(new DerValue(p7out.toByteArray()));
217
218            DerOutputStream out = new DerOutputStream();
219            out.write(DerValue.tag_Sequence, response);
220
221            return out.toByteArray();
222        }
223    }
224
225    public static void main(String[] args) throws Exception {
226
227        Handler h = new Handler();
228        HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
229        int port = server.getAddress().getPort();
230        HttpContext ctx = server.createContext("/", h);
231        server.start();
232
233        String cmd = null;
234        // Use -J-Djava.security.egd=file:/dev/./urandom to speed up
235        // nonce generation in timestamping request. Not avaibale on
236        // Windows and defaults to thread seed generator, not too bad.
237        if (System.getProperty("java.home").endsWith("jre")) {
238            cmd = System.getProperty("java.home") + "/../bin/jarsigner" +
239                " -J-Djava.security.egd=file:/dev/./urandom" +
240                " -debug -keystore " + TSKS + " -storepass changeit" +
241                " -tsa http://localhost:" + port + "/%d" +
242                " -signedjar new_%d.jar " + JAR + " old";
243        } else {
244            cmd = System.getProperty("java.home") + "/bin/jarsigner" +
245                " -J-Djava.security.egd=file:/dev/./urandom" +
246                " -debug -keystore " + TSKS + " -storepass changeit" +
247                " -tsa http://localhost:" + port + "/%d" +
248                " -signedjar new_%d.jar " + JAR + " old";
249        }
250
251        try {
252            if (args.length == 0) {         // Run this test
253                jarsigner(cmd, 0, true);    // Success, normal call
254                jarsigner(cmd, 1, false);   // These 4 should fail
255                jarsigner(cmd, 2, false);
256                jarsigner(cmd, 3, false);
257                jarsigner(cmd, 4, false);
258                jarsigner(cmd, 5, true);    // Success, 6543440 solved.
259                jarsigner(cmd, 6, false);   // tsbad1
260                jarsigner(cmd, 7, false);   // tsbad2
261                jarsigner(cmd, 8, false);   // tsbad3
262                jarsigner(cmd, 9, false);   // no cert in timestamp
263                jarsigner(cmd + " -tsapolicyid 1.2.3.4", 0, true);
264                jarsigner(cmd + " -tsapolicyid 1.2.3.5", 0, false);
265            } else {                        // Run as a standalone server
266                System.err.println("Press Enter to quit server");
267                System.in.read();
268            }
269        } finally {
270            server.stop(0);
271            new File("x.jar").delete();
272        }
273    }
274
275    /**
276     * @param cmd the command line (with a hole to plug in)
277     * @param path the path in the URL, i.e, http://localhost/path
278     * @param expected if this command should succeed
279     */
280    static void jarsigner(String cmd, int path, boolean expected)
281            throws Exception {
282        System.err.println("Test " + path);
283        Process p = Runtime.getRuntime().exec(String.format(cmd, path, path));
284        BufferedReader reader = new BufferedReader(
285                new InputStreamReader(p.getErrorStream()));
286        while (true) {
287            String s = reader.readLine();
288            if (s == null) break;
289            System.err.println(s);
290        }
291
292        // Will not see noTimestamp warning
293        boolean seeWarning = false;
294        reader = new BufferedReader(
295                new InputStreamReader(p.getInputStream()));
296        while (true) {
297            String s = reader.readLine();
298            if (s == null) break;
299            System.err.println(s);
300            if (s.indexOf("Warning:") >= 0) {
301                seeWarning = true;
302            }
303        }
304        int result = p.waitFor();
305        if (expected && result != 0 || !expected && result == 0) {
306            throw new Exception("Failed");
307        }
308        if (seeWarning) {
309            throw new Exception("See warning");
310        }
311    }
312}
313