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 
  26 package sun.security.timestamp;
  27 
  28 import java.io.IOException;
  29 import sun.security.pkcs.PKCS7;
  30 import sun.security.util.Debug;
  31 import 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 
 102 public 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     final static class TimestampException extends IOException {
 378         private static final long serialVersionUID = -1631631794891940953L;
 379 
 380         TimestampException(String message) {
 381             super(message);
 382         }
 383     }
 384 }