1 /* 2 * Copyright (c) 2003, 2012, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.security.timestamp; 27 28 import java.io.BufferedInputStream; 29 import java.io.DataOutputStream; 30 import java.io.EOFException; 31 import java.io.IOException; 32 import java.net.URI; 33 import java.net.URL; 34 import java.net.HttpURLConnection; 35 import java.util.*; 36 37 import sun.security.util.Debug; 38 39 /** 40 * A timestamper that communicates with a Timestamping Authority (TSA) 41 * over HTTP. 42 * It supports the Time-Stamp Protocol defined in: 43 * <a href="http://www.ietf.org/rfc/rfc3161.txt">RFC 3161</a>. 44 * 45 * @since 1.5 46 * @author Vincent Ryan 47 */ 48 49 public class HttpTimestamper implements Timestamper { 50 51 private static final int CONNECT_TIMEOUT = 15000; // 15 seconds 52 53 // The MIME type for a timestamp query 54 private static final String TS_QUERY_MIME_TYPE = 55 "application/timestamp-query"; 56 57 // The MIME type for a timestamp reply 58 private static final String TS_REPLY_MIME_TYPE = 59 "application/timestamp-reply"; 60 61 private static final Debug debug = Debug.getInstance("ts"); 62 63 /* 64 * HTTP URI identifying the location of the TSA 65 */ 66 private URI tsaURI = null; 67 68 /** 69 * Creates a timestamper that connects to the specified TSA. 70 * 71 * @param tsaURI The location of the TSA. It must be an HTTP or HTTPS URI. 72 * @throws IllegalArgumentException if tsaURI is not an HTTP or HTTPS URI 73 */ 74 public HttpTimestamper(URI tsaURI) { 75 if (!tsaURI.getScheme().equalsIgnoreCase("http") && 76 !tsaURI.getScheme().equalsIgnoreCase("https")) { 77 throw new IllegalArgumentException( 78 "TSA must be an HTTP or HTTPS URI"); 79 } 80 this.tsaURI = tsaURI; 81 } 82 83 /** 84 * Connects to the TSA and requests a timestamp. 85 * 86 * @param tsQuery The timestamp query. 87 * @return The result of the timestamp query. 88 * @throws IOException The exception is thrown if a problem occurs while 89 * communicating with the TSA. 90 */ 91 public TSResponse generateTimestamp(TSRequest tsQuery) throws IOException { 92 93 HttpURLConnection connection = 94 (HttpURLConnection) tsaURI.toURL().openConnection(); 95 connection.setDoOutput(true); 96 connection.setUseCaches(false); // ignore cache 97 connection.setRequestProperty("Content-Type", TS_QUERY_MIME_TYPE); 98 connection.setRequestMethod("POST"); 99 // Avoids the "hang" when a proxy is required but none has been set. 100 connection.setConnectTimeout(CONNECT_TIMEOUT); 101 102 if (debug != null) { 103 Set<Map.Entry<String, List<String>>> headers = 104 connection.getRequestProperties().entrySet(); 105 debug.println(connection.getRequestMethod() + " " + tsaURI + 106 " HTTP/1.1"); 107 for (Map.Entry<String, List<String>> e : headers) { 108 debug.println(" " + e); 109 } 110 debug.println(); 111 } 112 connection.connect(); // No HTTP authentication is performed 113 114 // Send the request 115 DataOutputStream output = null; 116 try { 117 output = new DataOutputStream(connection.getOutputStream()); 118 byte[] request = tsQuery.encode(); 119 output.write(request, 0, request.length); 120 output.flush(); 121 if (debug != null) { 122 debug.println("sent timestamp query (length=" + 123 request.length + ")"); 124 } 125 } finally { 126 if (output != null) { 127 output.close(); 128 } 129 } 130 131 // Receive the reply 132 BufferedInputStream input = null; 133 byte[] replyBuffer = null; 134 try { 135 input = new BufferedInputStream(connection.getInputStream()); 136 if (debug != null) { 137 String header = connection.getHeaderField(0); 138 debug.println(header); 139 int i = 1; 140 while ((header = connection.getHeaderField(i)) != null) { 141 String key = connection.getHeaderFieldKey(i); 142 debug.println(" " + ((key==null) ? "" : key + ": ") + 143 header); 144 i++; 145 } 146 debug.println(); 147 } 148 verifyMimeType(connection.getContentType()); 149 150 int clen = connection.getContentLength(); 151 replyBuffer = input.readAllBytes(); 152 if (clen != -1 && replyBuffer.length != clen) 153 throw new EOFException("Expected:" + clen + 154 ", read:" + replyBuffer.length); 155 156 if (debug != null) { 157 debug.println("received timestamp response (length=" + 158 replyBuffer.length + ")"); 159 } 160 } finally { 161 if (input != null) { 162 input.close(); 163 } 164 } 165 return new TSResponse(replyBuffer); 166 } 167 168 /* 169 * Checks that the MIME content type is a timestamp reply. 170 * 171 * @param contentType The MIME content type to be checked. 172 * @throws IOException The exception is thrown if a mismatch occurs. 173 */ 174 private static void verifyMimeType(String contentType) throws IOException { 175 if (! TS_REPLY_MIME_TYPE.equalsIgnoreCase(contentType)) { 176 throw new IOException("MIME Content-Type is not " + 177 TS_REPLY_MIME_TYPE); 178 } 179 } 180 }