/*
 * Copyright (c) 2015, 2017, 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.provider.certpath;

import java.util.Arrays;
import java.io.IOException;
import java.security.PublicKey;
import javax.security.auth.x500.X500Principal;
import sun.security.x509.KeyIdentifier;
import sun.security.util.DerValue;

/**
 * Class for ResponderId entities as described in RFC6960.  ResponderId objects
 * are used to uniquely identify OCSP responders.
 * <p>
 * The RFC 6960 defines a ResponderID structure as:
 * <pre>
 * ResponderID ::= CHOICE {
 *      byName              [1] Name,
 *      byKey               [2] KeyHash }
 *
 * KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
 * (excluding the tag and length fields)
 *
 * Name is defined in RFC 5280.
 * </pre>
 *
 * @see ResponderId.Type
 * @since 9
 */
public final class ResponderId {

    /**
     * A {@code ResponderId} enumeration describing the accepted forms for a
     * {@code ResponderId}.
     *
     * @see ResponderId
     * @since 9
     */
    public static enum Type {
        /**
         * A BY_NAME {@code ResponderId} will be built from a subject name,
         * either as an {@code X500Principal} or a DER-encoded byte array.
         */
        BY_NAME(1, "byName"),

        /**
         * A BY_KEY {@code ResponderId} will be built from a public key
         * identifier, either derived from a {@code PublicKey} or directly
         * from a DER-encoded byte array containing the key identifier.
         */
        BY_KEY(2, "byKey");

        private final int tagNumber;
        private final String ridTypeName;

        private Type(int value, String name) {
            this.tagNumber = value;
            this.ridTypeName = name;
        }

        public int value() {
            return tagNumber;
        }

        @Override
        public String toString() {
            return ridTypeName;
        }
    }

    private Type type;
    private X500Principal responderName;
    private KeyIdentifier responderKeyId;
    private byte[] encodedRid;

    /**
     * Constructs a {@code ResponderId} object using an {@code X500Principal}.
     * When encoded in DER this object will use the BY_NAME option.
     *
     * @param subjectName the subject name of the certificate used
     * to sign OCSP responses.
     *
     * @throws IOException if the internal DER-encoding of the
     *      {@code X500Principal} fails.
     */
    public ResponderId(X500Principal subjectName) throws IOException {
        responderName = subjectName;
        responderKeyId = null;
        encodedRid = principalToBytes();
        type = Type.BY_NAME;
    }

    /**
     * Constructs a {@code ResponderId} object using a {@code PublicKey}.
     * When encoded in DER this object will use the byKey option, a
     * SHA-1 hash of the responder's public key.
     *
     * @param pubKey the the OCSP responder's public key
     *
     * @throws IOException if the internal DER-encoding of the
     *      {@code KeyIdentifier} fails.
     */
    public ResponderId(PublicKey pubKey) throws IOException {
        responderKeyId = new KeyIdentifier(pubKey);
        responderName = null;
        encodedRid = keyIdToBytes();
        type = Type.BY_KEY;
    }

    /**
     * Constructs a {@code ResponderId} object from its DER-encoding.
     *
     * @param encodedData the DER-encoded bytes
     *
     * @throws IOException if the encodedData is not properly DER encoded
     */
    public ResponderId(byte[] encodedData) throws IOException {
        DerValue outer = new DerValue(encodedData);

        if (outer.isContextSpecific((byte)Type.BY_NAME.value())
                && outer.isConstructed()) {
            // Use the X500Principal constructor as a way to sanity
            // check the incoming data.
            responderName = new X500Principal(outer.getDataBytes());
            encodedRid = principalToBytes();
            type = Type.BY_NAME;
        } else if (outer.isContextSpecific((byte)Type.BY_KEY.value())
                && outer.isConstructed()) {
            // Use the KeyIdentifier constructor as a way to sanity
            // check the incoming data.
            responderKeyId =
                new KeyIdentifier(new DerValue(outer.getDataBytes()));
            encodedRid = keyIdToBytes();
            type = Type.BY_KEY;
        } else {
            throw new IOException("Invalid ResponderId content");
        }
    }

    /**
     * Encode a {@code ResponderId} in DER form
     *
     * @return a byte array containing the DER-encoded representation for this
     *      {@code ResponderId}
     */
    public byte[] getEncoded() {
        return encodedRid.clone();
    }

    /**
     * Return the type of {@ResponderId}
     *
     * @return a number corresponding to the context-specific tag number
     *      used in the DER-encoding for a {@code ResponderId}
     */
    public ResponderId.Type getType() {
        return type;
    }

    /**
     * Get the length of the encoded {@code ResponderId} (including the tag and
     * length of the explicit tagging from the outer ASN.1 CHOICE).
     *
     * @return the length of the encoded {@code ResponderId}
     */
    public int length() {
        return encodedRid.length;
    }

    /**
     * Obtain the underlying {@code X500Principal} from a {@code ResponderId}
     *
     * @return the {@code X500Principal} for this {@code ResponderId} if it
     *      is a BY_NAME variant.  If the {@code ResponderId} is a BY_KEY
     *      variant, this routine will return {@code null}.
     */
    public X500Principal getResponderName() {
        return responderName;
    }

    /**
     * Obtain the underlying key identifier from a {@code ResponderId}
     *
     * @return the {@code KeyIdentifier} for this {@code ResponderId} if it
     *      is a BY_KEY variant.  If the {@code ResponderId} is a BY_NAME
     *      variant, this routine will return {@code null}.
     */
    public KeyIdentifier getKeyIdentifier() {
        return responderKeyId;
    }

    /**
     * Compares the specified object with this {@code ResponderId} for equality.
     * A ResponderId will only be considered equivalent if both the type and
     * data value are equal.  Two ResponderIds initialized by name and
     * key ID, respectively, will not be equal even if the
     * ResponderId objects are created from the same source certificate.
     *
     * @param obj the object to be compared against
     *
     * @return true if the specified object is equal to this {@code Responderid}
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if (this == obj) {
            return true;
        }

        if (obj instanceof ResponderId) {
            ResponderId respObj = (ResponderId)obj;
                return Arrays.equals(encodedRid, respObj.getEncoded());
        }

        return false;
    }

    /**
     * Returns the hash code value for this {@code ResponderId}
     *
     * @return the hash code value for this {@code ResponderId}
     */
    @Override
    public int hashCode() {
        return Arrays.hashCode(encodedRid);
    }

    /**
     * Create a String representation of this {@code ResponderId}
     *
     * @return a String representation of this {@code ResponderId}
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        switch (type) {
            case BY_NAME:
                sb.append(type).append(": ").append(responderName);
                break;
            case BY_KEY:
                sb.append(type).append(": ");
                for (byte keyIdByte : responderKeyId.getIdentifier()) {
                    sb.append(String.format("%02X", keyIdByte));
                }
                break;
            default:
                sb.append("Unknown ResponderId Type: ").append(type);
        }
        return sb.toString();
    }

    /**
     * Convert the responderName data member into its DER-encoded form
     *
     * @return the DER encoding for a responder ID byName option, including
     *      explicit context-specific tagging.
     *
     * @throws IOException if any encoding error occurs
     */
    private byte[] principalToBytes() throws IOException {
        DerValue dv = new DerValue(DerValue.createTag(DerValue.TAG_CONTEXT,
                true, (byte)Type.BY_NAME.value()),
                responderName.getEncoded());
        return dv.toByteArray();
    }

    /**
     * Convert the responderKeyId data member into its DER-encoded form
     *
     * @return the DER encoding for a responder ID byKey option, including
     *      explicit context-specific tagging.
     *
     * @throws IOException if any encoding error occurs
     */
    private byte[] keyIdToBytes() throws IOException {
        // Place the KeyIdentifier bytes into an OCTET STRING
        DerValue inner = new DerValue(DerValue.tag_OctetString,
                responderKeyId.getIdentifier());

        // Mark the OCTET STRING-wrapped KeyIdentifier bytes
        // as EXPLICIT CONTEXT 2
        DerValue outer = new DerValue(DerValue.createTag(DerValue.TAG_CONTEXT,
                true, (byte)Type.BY_KEY.value()), inner.toByteArray());

        return outer.toByteArray();
    }

}