/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.security.keystore.recovery;

import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;

import java.io.ByteArrayInputStream;
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Objects;

/**
 * The certificate path of the recovery service.
 *
 * @hide
 */
public final class RecoveryCertPath implements Parcelable {

    private static final String CERT_PATH_ENCODING = "PkiPath";

    private final byte[] mEncodedCertPath;

    /**
     * Wraps a {@code CertPath} to create a {@code Parcelable} for Binder calls.
     *
     * @param certPath The certificate path to be wrapped.
     * @throws CertificateException if the given certificate path cannot be encoded properly.
     */
    public static @NonNull RecoveryCertPath createRecoveryCertPath(@NonNull CertPath certPath)
            throws CertificateException {
        // Perform the encoding here to avoid throwing exceptions in writeToParcel
        try {
            return new RecoveryCertPath(encodeCertPath(certPath));
        } catch (CertificateEncodingException e) {
            throw new CertificateException("Failed to encode the given CertPath", e);
        }
    }

    /**
     * Obtains the {@code CertPath} wrapped in the Parcelable.
     *
     * @return the wrapped certificate path.
     * @throws CertificateException if the wrapped certificate path cannot be decoded properly.
     */
    public @NonNull CertPath getCertPath() throws CertificateException {
        // Perform the decoding here to avoid throwing exceptions in createFromParcel
        return decodeCertPath(mEncodedCertPath);
    }

    private RecoveryCertPath(@NonNull byte[] encodedCertPath) {
        mEncodedCertPath = Objects.requireNonNull(encodedCertPath);
    }

    private RecoveryCertPath(Parcel in) {
        mEncodedCertPath = in.createByteArray();
    }

    public static final @NonNull Parcelable.Creator<RecoveryCertPath> CREATOR =
            new Parcelable.Creator<RecoveryCertPath>() {
        public RecoveryCertPath createFromParcel(Parcel in) {
            return new RecoveryCertPath(in);
        }

        public RecoveryCertPath[] newArray(int length) {
            return new RecoveryCertPath[length];
        }
    };

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeByteArray(mEncodedCertPath);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @NonNull
    private static byte[] encodeCertPath(@NonNull CertPath certPath)
            throws CertificateEncodingException {
        Objects.requireNonNull(certPath);
        return certPath.getEncoded(CERT_PATH_ENCODING);
    }

    @NonNull
    private static CertPath decodeCertPath(@NonNull byte[] bytes) throws CertificateException {
        Objects.requireNonNull(bytes);
        CertificateFactory certFactory;
        try {
            certFactory = CertificateFactory.getInstance("X.509");
        } catch (CertificateException e) {
            // Should not happen, as X.509 is mandatory for all providers.
            throw new RuntimeException(e);
        }
        return certFactory.generateCertPath(new ByteArrayInputStream(bytes), CERT_PATH_ENCODING);
    }
}
