001: /*
002: * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.security.timestamp;
027:
028: import java.io.BufferedInputStream;
029: import java.io.DataOutputStream;
030: import java.io.IOException;
031: import java.net.URL;
032: import java.net.HttpURLConnection;
033: import java.util.Iterator;
034: import java.util.Set;
035:
036: import sun.security.pkcs.*;
037:
038: /**
039: * A timestamper that communicates with a Timestamping Authority (TSA)
040: * over HTTP.
041: * It supports the Time-Stamp Protocol defined in:
042: * <a href="http://www.ietf.org/rfc/rfc3161.txt">RFC 3161</a>.
043: *
044: * @since 1.5
045: * @version 1.10, 05/05/07
046: * @author Vincent Ryan
047: */
048:
049: public class HttpTimestamper implements Timestamper {
050:
051: private static final int CONNECT_TIMEOUT = 15000; // 15 seconds
052:
053: // The MIME type for a timestamp query
054: private static final String TS_QUERY_MIME_TYPE = "application/timestamp-query";
055:
056: // The MIME type for a timestamp reply
057: private static final String TS_REPLY_MIME_TYPE = "application/timestamp-reply";
058:
059: private static final boolean DEBUG = false;
060:
061: /*
062: * HTTP URL identifying the location of the TSA
063: */
064: private String tsaUrl = null;
065:
066: /**
067: * Creates a timestamper that connects to the specified TSA.
068: *
069: * @param tsa The location of the TSA. It must be an HTTP URL.
070: */
071: public HttpTimestamper(String tsaUrl) {
072: this .tsaUrl = tsaUrl;
073: }
074:
075: /**
076: * Connects to the TSA and requests a timestamp.
077: *
078: * @param tsQuery The timestamp query.
079: * @return The result of the timestamp query.
080: * @throws IOException The exception is thrown if a problem occurs while
081: * communicating with the TSA.
082: */
083: public TSResponse generateTimestamp(TSRequest tsQuery)
084: throws IOException {
085:
086: HttpURLConnection connection = (HttpURLConnection) new URL(
087: tsaUrl).openConnection();
088: connection.setDoOutput(true);
089: connection.setUseCaches(false); // ignore cache
090: connection.setRequestProperty("Content-Type",
091: TS_QUERY_MIME_TYPE);
092: connection.setRequestMethod("POST");
093: // Avoids the "hang" when a proxy is required but none has been set.
094: connection.setConnectTimeout(CONNECT_TIMEOUT);
095:
096: if (DEBUG) {
097: Set headers = connection.getRequestProperties().entrySet();
098: System.out.println(connection.getRequestMethod() + " "
099: + tsaUrl + " HTTP/1.1");
100: for (Iterator i = headers.iterator(); i.hasNext();) {
101: System.out.println(" " + i.next());
102: }
103: System.out.println();
104: }
105: connection.connect(); // No HTTP authentication is performed
106:
107: // Send the request
108: DataOutputStream output = null;
109: try {
110: output = new DataOutputStream(connection.getOutputStream());
111: byte[] request = tsQuery.encode();
112: output.write(request, 0, request.length);
113: output.flush();
114: if (DEBUG) {
115: System.out.println("sent timestamp query (length="
116: + request.length + ")");
117: }
118: } finally {
119: if (output != null) {
120: output.close();
121: }
122: }
123:
124: // Receive the reply
125: BufferedInputStream input = null;
126: byte[] replyBuffer = null;
127: try {
128: input = new BufferedInputStream(connection.getInputStream());
129: if (DEBUG) {
130: String header = connection.getHeaderField(0);
131: System.out.println(header);
132: int i = 1;
133: while ((header = connection.getHeaderField(i)) != null) {
134: String key = connection.getHeaderFieldKey(i);
135: System.out.println(" "
136: + ((key == null) ? "" : key + ": ")
137: + header);
138: i++;
139: }
140: System.out.println();
141: }
142: int contentLength = connection.getContentLength();
143: if (contentLength == -1) {
144: contentLength = Integer.MAX_VALUE;
145: }
146: verifyMimeType(connection.getContentType());
147:
148: replyBuffer = new byte[contentLength];
149: int total = 0;
150: int count = 0;
151: while (count != -1 && total < contentLength) {
152: count = input.read(replyBuffer, total,
153: replyBuffer.length - total);
154: total += count;
155: }
156: if (DEBUG) {
157: System.out
158: .println("received timestamp response (length="
159: + replyBuffer.length + ")");
160: }
161: } finally {
162: if (input != null) {
163: input.close();
164: }
165: }
166: return new TSResponse(replyBuffer);
167: }
168:
169: /*
170: * Checks that the MIME content type is a timestamp reply.
171: *
172: * @param contentType The MIME content type to be checked.
173: * @throws IOException The exception is thrown if a mismatch occurs.
174: */
175: private static void verifyMimeType(String contentType)
176: throws IOException {
177: if (!TS_REPLY_MIME_TYPE.equalsIgnoreCase(contentType)) {
178: throw new IOException("MIME Content-Type is not "
179: + TS_REPLY_MIME_TYPE);
180: }
181: }
182: }
|