1/*
2 * Copyright (c) 2003, 2011, 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
26package sun.security.timestamp;
27
28import java.io.IOException;
29import sun.security.pkcs.PKCS7;
30import sun.security.util.Debug;
31import sun.security.util.DerValue;
32
33/**
34 * This class provides the response corresponding to a timestamp request,
35 * as defined in
36 * <a href="http://www.ietf.org/rfc/rfc3161.txt">RFC 3161</a>.
37 *
38 * The TimeStampResp ASN.1 type has the following definition:
39 * <pre>
40 *
41 *     TimeStampResp ::= SEQUENCE {
42 *         status            PKIStatusInfo,
43 *         timeStampToken    TimeStampToken OPTIONAL ]
44 *
45 *     PKIStatusInfo ::= SEQUENCE {
46 *         status        PKIStatus,
47 *         statusString  PKIFreeText OPTIONAL,
48 *         failInfo      PKIFailureInfo OPTIONAL }
49 *
50 *     PKIStatus ::= INTEGER {
51 *         granted                (0),
52 *           -- when the PKIStatus contains the value zero a TimeStampToken, as
53 *           -- requested, is present.
54 *         grantedWithMods        (1),
55 *           -- when the PKIStatus contains the value one a TimeStampToken,
56 *           -- with modifications, is present.
57 *         rejection              (2),
58 *         waiting                (3),
59 *         revocationWarning      (4),
60 *           -- this message contains a warning that a revocation is
61 *           -- imminent
62 *         revocationNotification (5)
63 *           -- notification that a revocation has occurred }
64 *
65 *     PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String
66 *           -- text encoded as UTF-8 String (note:  each UTF8String SHOULD
67 *           -- include an RFC 1766 language tag to indicate the language
68 *           -- of the contained text)
69 *
70 *     PKIFailureInfo ::= BIT STRING {
71 *         badAlg              (0),
72 *           -- unrecognized or unsupported Algorithm Identifier
73 *         badRequest          (2),
74 *           -- transaction not permitted or supported
75 *         badDataFormat       (5),
76 *           -- the data submitted has the wrong format
77 *         timeNotAvailable    (14),
78 *           -- the TSA's time source is not available
79 *         unacceptedPolicy    (15),
80 *           -- the requested TSA policy is not supported by the TSA
81 *         unacceptedExtension (16),
82 *           -- the requested extension is not supported by the TSA
83 *         addInfoNotAvailable (17)
84 *           -- the additional information requested could not be understood
85 *           -- or is not available
86 *         systemFailure       (25)
87 *           -- the request cannot be handled due to system failure }
88 *
89 *     TimeStampToken ::= ContentInfo
90 *         -- contentType is id-signedData
91 *         -- content is SignedData
92 *         -- eContentType within SignedData is id-ct-TSTInfo
93 *         -- eContent within SignedData is TSTInfo
94 *
95 * </pre>
96 *
97 * @since 1.5
98 * @author Vincent Ryan
99 * @see Timestamper
100 */
101
102public class TSResponse {
103
104    // Status codes (from RFC 3161)
105
106    /**
107     * The requested timestamp was granted.
108     */
109    public static final int GRANTED = 0;
110
111    /**
112     * The requested timestamp was granted with some modifications.
113     */
114    public static final int GRANTED_WITH_MODS = 1;
115
116    /**
117     * The requested timestamp was not granted.
118     */
119    public static final int REJECTION = 2;
120
121    /**
122     * The requested timestamp has not yet been processed.
123     */
124    public static final int WAITING = 3;
125
126    /**
127     * A warning that a certificate revocation is imminent.
128     */
129    public static final int REVOCATION_WARNING = 4;
130
131    /**
132     * Notification that a certificate revocation has occurred.
133     */
134    public static final int REVOCATION_NOTIFICATION = 5;
135
136    // Failure codes (from RFC 3161)
137
138    /**
139     * Unrecognized or unsupported algorithm identifier.
140     */
141    public static final int BAD_ALG = 0;
142
143    /**
144     * The requested transaction is not permitted or supported.
145     */
146    public static final int BAD_REQUEST = 2;
147
148    /**
149     * The data submitted has the wrong format.
150     */
151    public static final int BAD_DATA_FORMAT = 5;
152
153    /**
154     * The TSA's time source is not available.
155     */
156    public static final int TIME_NOT_AVAILABLE = 14;
157
158    /**
159     * The requested TSA policy is not supported by the TSA.
160     */
161    public static final int UNACCEPTED_POLICY = 15;
162
163    /**
164     * The requested extension is not supported by the TSA.
165     */
166    public static final int UNACCEPTED_EXTENSION = 16;
167
168    /**
169     * The additional information requested could not be understood or is not
170     * available.
171     */
172    public static final int ADD_INFO_NOT_AVAILABLE = 17;
173
174    /**
175     * The request cannot be handled due to system failure.
176     */
177    public static final int SYSTEM_FAILURE = 25;
178
179    private static final Debug debug = Debug.getInstance("ts");
180
181    private int status;
182
183    private String[] statusString = null;
184
185    private boolean[] failureInfo = null;
186
187    private byte[] encodedTsToken = null;
188
189    private PKCS7 tsToken = null;
190
191    private TimestampToken tstInfo;
192
193    /**
194     * Constructs an object to store the response to a timestamp request.
195     *
196     * @param status A buffer containing the ASN.1 BER encoded response.
197     * @throws IOException The exception is thrown if a problem is encountered
198     *         parsing the timestamp response.
199     */
200    TSResponse(byte[] tsReply) throws IOException {
201        parse(tsReply);
202    }
203
204    /**
205     * Retrieve the status code returned by the TSA.
206     */
207    public int getStatusCode() {
208        return status;
209    }
210
211    /**
212     * Retrieve the status messages returned by the TSA.
213     *
214     * @return If null then no status messages were received.
215     */
216    public String[] getStatusMessages() {
217        return statusString;
218    }
219
220    /**
221     * Retrieve the failure info returned by the TSA.
222     *
223     * @return the failure info, or null if no failure code was received.
224     */
225    public boolean[] getFailureInfo() {
226        return failureInfo;
227    }
228
229    public String getStatusCodeAsText() {
230
231        switch (status)  {
232        case GRANTED:
233            return "the timestamp request was granted.";
234
235        case GRANTED_WITH_MODS:
236            return
237                "the timestamp request was granted with some modifications.";
238
239        case REJECTION:
240            return "the timestamp request was rejected.";
241
242        case WAITING:
243            return "the timestamp request has not yet been processed.";
244
245        case REVOCATION_WARNING:
246            return "warning: a certificate revocation is imminent.";
247
248        case REVOCATION_NOTIFICATION:
249            return "notification: a certificate revocation has occurred.";
250
251        default:
252            return ("unknown status code " + status + ".");
253        }
254    }
255
256    private boolean isSet(int position) {
257        return failureInfo[position];
258    }
259
260    public String getFailureCodeAsText() {
261
262        if (failureInfo == null) {
263            return "";
264        }
265
266        try {
267            if (isSet(BAD_ALG))
268                return "Unrecognized or unsupported algorithm identifier.";
269            if (isSet(BAD_REQUEST))
270                return "The requested transaction is not permitted or " +
271                       "supported.";
272            if (isSet(BAD_DATA_FORMAT))
273                return "The data submitted has the wrong format.";
274            if (isSet(TIME_NOT_AVAILABLE))
275                return "The TSA's time source is not available.";
276            if (isSet(UNACCEPTED_POLICY))
277                return "The requested TSA policy is not supported by the TSA.";
278            if (isSet(UNACCEPTED_EXTENSION))
279                return "The requested extension is not supported by the TSA.";
280            if (isSet(ADD_INFO_NOT_AVAILABLE))
281                return "The additional information requested could not be " +
282                       "understood or is not available.";
283            if (isSet(SYSTEM_FAILURE))
284                return "The request cannot be handled due to system failure.";
285        } catch (ArrayIndexOutOfBoundsException ex) {}
286
287        return ("unknown failure code");
288    }
289
290    /**
291     * Retrieve the timestamp token returned by the TSA.
292     *
293     * @return If null then no token was received.
294     */
295    public PKCS7 getToken() {
296        return tsToken;
297    }
298
299    public TimestampToken getTimestampToken() {
300        return tstInfo;
301    }
302
303    /**
304     * Retrieve the ASN.1 BER encoded timestamp token returned by the TSA.
305     *
306     * @return If null then no token was received.
307     */
308    public byte[] getEncodedToken() {
309        return encodedTsToken;
310    }
311
312    /*
313     * Parses the timestamp response.
314     *
315     * @param status A buffer containing the ASN.1 BER encoded response.
316     * @throws IOException The exception is thrown if a problem is encountered
317     *         parsing the timestamp response.
318     */
319    private void parse(byte[] tsReply) throws IOException {
320        // Decode TimeStampResp
321
322        DerValue derValue = new DerValue(tsReply);
323        if (derValue.tag != DerValue.tag_Sequence) {
324            throw new IOException("Bad encoding for timestamp response");
325        }
326
327        // Parse status
328
329        DerValue statusInfo = derValue.data.getDerValue();
330        this.status = statusInfo.data.getInteger();
331        if (debug != null) {
332            debug.println("timestamp response: status=" + this.status);
333        }
334        // Parse statusString, if present
335        if (statusInfo.data.available() > 0) {
336            byte tag = (byte)statusInfo.data.peekByte();
337            if (tag == DerValue.tag_SequenceOf) {
338                DerValue[] strings = statusInfo.data.getSequence(1);
339                statusString = new String[strings.length];
340                for (int i = 0; i < strings.length; i++) {
341                    statusString[i] = strings[i].getUTF8String();
342                    if (debug != null) {
343                        debug.println("timestamp response: statusString=" +
344                                      statusString[i]);
345                    }
346                }
347            }
348        }
349        // Parse failInfo, if present
350        if (statusInfo.data.available() > 0) {
351            this.failureInfo
352                = statusInfo.data.getUnalignedBitString().toBooleanArray();
353        }
354
355        // Parse timeStampToken, if present
356        if (derValue.data.available() > 0) {
357            DerValue timestampToken = derValue.data.getDerValue();
358            encodedTsToken = timestampToken.toByteArray();
359            tsToken = new PKCS7(encodedTsToken);
360            tstInfo = new TimestampToken(tsToken.getContentInfo().getData());
361        }
362
363        // Check the format of the timestamp response
364        if (this.status == 0 || this.status == 1) {
365            if (tsToken == null) {
366                throw new TimestampException(
367                    "Bad encoding for timestamp response: " +
368                    "expected a timeStampToken element to be present");
369            }
370        } else if (tsToken != null) {
371            throw new TimestampException(
372                "Bad encoding for timestamp response: " +
373                "expected no timeStampToken element to be present");
374        }
375    }
376
377    static final class TimestampException extends IOException {
378        private static final long serialVersionUID = -1631631794891940953L;
379
380        TimestampException(String message) {
381            super(message);
382        }
383    }
384}
385