/*
 * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.timestamp;

import java.io.IOException;
import sun.security.pkcs.PKCS7;
import sun.security.util.Debug;
import sun.security.util.DerValue;

/**
 * This class provides the response corresponding to a timestamp request,
 * as defined in
 * <a href="http://www.ietf.org/rfc/rfc3161.txt">RFC 3161</a>.
 *
 * The TimeStampResp ASN.1 type has the following definition:
 * <pre>
 *
 *     TimeStampResp ::= SEQUENCE {
 *         status            PKIStatusInfo,
 *         timeStampToken    TimeStampToken OPTIONAL ]
 *
 *     PKIStatusInfo ::= SEQUENCE {
 *         status        PKIStatus,
 *         statusString  PKIFreeText OPTIONAL,
 *         failInfo      PKIFailureInfo OPTIONAL }
 *
 *     PKIStatus ::= INTEGER {
 *         granted                (0),
 *           -- when the PKIStatus contains the value zero a TimeStampToken, as
 *           -- requested, is present.
 *         grantedWithMods        (1),
 *           -- when the PKIStatus contains the value one a TimeStampToken,
 *           -- with modifications, is present.
 *         rejection              (2),
 *         waiting                (3),
 *         revocationWarning      (4),
 *           -- this message contains a warning that a revocation is
 *           -- imminent
 *         revocationNotification (5)
 *           -- notification that a revocation has occurred }
 *
 *     PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String
 *           -- text encoded as UTF-8 String (note:  each UTF8String SHOULD
 *           -- include an RFC 1766 language tag to indicate the language
 *           -- of the contained text)
 *
 *     PKIFailureInfo ::= BIT STRING {
 *         badAlg              (0),
 *           -- unrecognized or unsupported Algorithm Identifier
 *         badRequest          (2),
 *           -- transaction not permitted or supported
 *         badDataFormat       (5),
 *           -- the data submitted has the wrong format
 *         timeNotAvailable    (14),
 *           -- the TSA's time source is not available
 *         unacceptedPolicy    (15),
 *           -- the requested TSA policy is not supported by the TSA
 *         unacceptedExtension (16),
 *           -- the requested extension is not supported by the TSA
 *         addInfoNotAvailable (17)
 *           -- the additional information requested could not be understood
 *           -- or is not available
 *         systemFailure       (25)
 *           -- the request cannot be handled due to system failure }
 *
 *     TimeStampToken ::= ContentInfo
 *         -- contentType is id-signedData
 *         -- content is SignedData
 *         -- eContentType within SignedData is id-ct-TSTInfo
 *         -- eContent within SignedData is TSTInfo
 *
 * </pre>
 *
 * @since 1.5
 * @author Vincent Ryan
 * @see Timestamper
 */

public final class TSResponse {

    // Status codes (from RFC 3161)

    /**
     * The requested timestamp was granted.
     */
    public static final int GRANTED = 0;

    /**
     * The requested timestamp was granted with some modifications.
     */
    public static final int GRANTED_WITH_MODS = 1;

    /**
     * The requested timestamp was not granted.
     */
    public static final int REJECTION = 2;

    /**
     * The requested timestamp has not yet been processed.
     */
    public static final int WAITING = 3;

    /**
     * A warning that a certificate revocation is imminent.
     */
    public static final int REVOCATION_WARNING = 4;

    /**
     * Notification that a certificate revocation has occurred.
     */
    public static final int REVOCATION_NOTIFICATION = 5;

    // Failure codes (from RFC 3161)

    /**
     * Unrecognized or unsupported algorithm identifier.
     */
    public static final int BAD_ALG = 0;

    /**
     * The requested transaction is not permitted or supported.
     */
    public static final int BAD_REQUEST = 2;

    /**
     * The data submitted has the wrong format.
     */
    public static final int BAD_DATA_FORMAT = 5;

    /**
     * The TSA's time source is not available.
     */
    public static final int TIME_NOT_AVAILABLE = 14;

    /**
     * The requested TSA policy is not supported by the TSA.
     */
    public static final int UNACCEPTED_POLICY = 15;

    /**
     * The requested extension is not supported by the TSA.
     */
    public static final int UNACCEPTED_EXTENSION = 16;

    /**
     * The additional information requested could not be understood or is not
     * available.
     */
    public static final int ADD_INFO_NOT_AVAILABLE = 17;

    /**
     * The request cannot be handled due to system failure.
     */
    public static final int SYSTEM_FAILURE = 25;

    private static final Debug debug = Debug.getInstance("ts");

    private int status;

    private String[] statusString = null;

    private boolean[] failureInfo = null;

    private byte[] encodedTsToken = null;

    private PKCS7 tsToken = null;

    private TimestampToken tstInfo;

    /**
     * Constructs an object to store the response to a timestamp request.
     *
     * @param tsReply A buffer containing the ASN.1 BER encoded response.
     * @throws IOException The exception is thrown if a problem is encountered
     *         parsing the timestamp response.
     */
    TSResponse(byte[] tsReply) throws IOException {
        parse(tsReply);
    }

    /**
     * Retrieve the status code returned by the TSA.
     */
    public int getStatusCode() {
        return status;
    }

    /**
     * Retrieve the status messages returned by the TSA.
     *
     * @return If null then no status messages were received.
     */
    public String[] getStatusMessages() {
        return statusString;
    }

    /**
     * Retrieve the failure info returned by the TSA.
     *
     * @return the failure info, or null if no failure code was received.
     */
    public boolean[] getFailureInfo() {
        return failureInfo;
    }

    public String getStatusCodeAsText() {

        switch (status)  {
        case GRANTED:
            return "the timestamp request was granted.";

        case GRANTED_WITH_MODS:
            return
                "the timestamp request was granted with some modifications.";

        case REJECTION:
            return "the timestamp request was rejected.";

        case WAITING:
            return "the timestamp request has not yet been processed.";

        case REVOCATION_WARNING:
            return "warning: a certificate revocation is imminent.";

        case REVOCATION_NOTIFICATION:
            return "notification: a certificate revocation has occurred.";

        default:
            return ("unknown status code " + status + ".");
        }
    }

    private boolean isSet(int position) {
        return failureInfo[position];
    }

    public String getFailureCodeAsText() {

        if (failureInfo == null) {
            return "";
        }

        try {
            if (isSet(BAD_ALG))
                return "Unrecognized or unsupported algorithm identifier.";
            if (isSet(BAD_REQUEST))
                return "The requested transaction is not permitted or " +
                       "supported.";
            if (isSet(BAD_DATA_FORMAT))
                return "The data submitted has the wrong format.";
            if (isSet(TIME_NOT_AVAILABLE))
                return "The TSA's time source is not available.";
            if (isSet(UNACCEPTED_POLICY))
                return "The requested TSA policy is not supported by the TSA.";
            if (isSet(UNACCEPTED_EXTENSION))
                return "The requested extension is not supported by the TSA.";
            if (isSet(ADD_INFO_NOT_AVAILABLE))
                return "The additional information requested could not be " +
                       "understood or is not available.";
            if (isSet(SYSTEM_FAILURE))
                return "The request cannot be handled due to system failure.";
        } catch (ArrayIndexOutOfBoundsException ex) {}

        return ("unknown failure code");
    }

    /**
     * Retrieve the timestamp token returned by the TSA.
     *
     * @return If null then no token was received.
     */
    public PKCS7 getToken() {
        return tsToken;
    }

    public TimestampToken getTimestampToken() {
        return tstInfo;
    }

    /**
     * Retrieve the ASN.1 BER encoded timestamp token returned by the TSA.
     *
     * @return If null then no token was received.
     */
    public byte[] getEncodedToken() {
        return encodedTsToken;
    }

    /*
     * Parses the timestamp response.
     *
     * @param status A buffer containing the ASN.1 BER encoded response.
     * @throws IOException The exception is thrown if a problem is encountered
     *         parsing the timestamp response.
     */
    private void parse(byte[] tsReply) throws IOException {
        // Decode TimeStampResp

        DerValue derValue = new DerValue(tsReply);
        if (derValue.tag != DerValue.tag_Sequence) {
            throw new IOException("Bad encoding for timestamp response");
        }

        // Parse status

        DerValue statusInfo = derValue.data.getDerValue();
        this.status = statusInfo.data.getInteger();
        if (debug != null) {
            debug.println("timestamp response: status=" + this.status);
        }
        // Parse statusString, if present
        if (statusInfo.data.available() > 0) {
            byte tag = (byte)statusInfo.data.peekByte();
            if (tag == DerValue.tag_SequenceOf) {
                DerValue[] strings = statusInfo.data.getSequence(1);
                statusString = new String[strings.length];
                for (int i = 0; i < strings.length; i++) {
                    statusString[i] = strings[i].getUTF8String();
                    if (debug != null) {
                        debug.println("timestamp response: statusString=" +
                                      statusString[i]);
                    }
                }
            }
        }
        // Parse failInfo, if present
        if (statusInfo.data.available() > 0) {
            this.failureInfo
                = statusInfo.data.getUnalignedBitString().toBooleanArray();
        }

        // Parse timeStampToken, if present
        if (derValue.data.available() > 0) {
            DerValue timestampToken = derValue.data.getDerValue();
            encodedTsToken = timestampToken.toByteArray();
            tsToken = new PKCS7(encodedTsToken);
            tstInfo = new TimestampToken(tsToken.getContentInfo().getData());
        }

        // Check the format of the timestamp response
        if (this.status == 0 || this.status == 1) {
            if (tsToken == null) {
                throw new TimestampException(
                    "Bad encoding for timestamp response: " +
                    "expected a timeStampToken element to be present");
            }
        } else if (tsToken != null) {
            throw new TimestampException(
                "Bad encoding for timestamp response: " +
                "expected no timeStampToken element to be present");
        }
    }

    static final class TimestampException extends IOException {
        @java.io.Serial
        private static final long serialVersionUID = -1631631794891940953L;

        TimestampException(String message) {
            super(message);
        }
    }
}
