/*
 * 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;

import android.os.Parcel;
import android.os.Parcelable;

import java.math.BigInteger;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.Date;

import javax.security.auth.x500.X500Principal;

/**
 * A parcelable version of KeyGenParameterSpec
 * @hide only used for communicating with the DPMS.
 */
public final class ParcelableKeyGenParameterSpec implements Parcelable {
    private static final int ALGORITHM_PARAMETER_SPEC_NONE = 1;
    private static final int ALGORITHM_PARAMETER_SPEC_RSA = 2;
    private static final int ALGORITHM_PARAMETER_SPEC_EC = 3;

    private final KeyGenParameterSpec mSpec;

    public ParcelableKeyGenParameterSpec(
            KeyGenParameterSpec spec) {
        mSpec = spec;
    }

    public int describeContents() {
        return 0;
    }

    private static void writeOptionalDate(Parcel out, Date date) {
        if (date != null) {
            out.writeBoolean(true);
            out.writeLong(date.getTime());
        } else {
            out.writeBoolean(false);
        }
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeString(mSpec.getKeystoreAlias());
        out.writeInt(mSpec.getPurposes());
        out.writeInt(mSpec.getUid());
        out.writeInt(mSpec.getKeySize());

        // Only needs to support RSAKeyGenParameterSpec and ECGenParameterSpec.
        AlgorithmParameterSpec algoSpec = mSpec.getAlgorithmParameterSpec();
        if (algoSpec == null) {
            out.writeInt(ALGORITHM_PARAMETER_SPEC_NONE);
        } else if (algoSpec instanceof RSAKeyGenParameterSpec) {
            RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) algoSpec;
            out.writeInt(ALGORITHM_PARAMETER_SPEC_RSA);
            out.writeInt(rsaSpec.getKeysize());
            out.writeByteArray(rsaSpec.getPublicExponent().toByteArray());
        } else if (algoSpec instanceof ECGenParameterSpec) {
            ECGenParameterSpec ecSpec = (ECGenParameterSpec) algoSpec;
            out.writeInt(ALGORITHM_PARAMETER_SPEC_EC);
            out.writeString(ecSpec.getName());
        } else {
            throw new IllegalArgumentException(
                    String.format("Unknown algorithm parameter spec: %s", algoSpec.getClass()));
        }
        out.writeByteArray(mSpec.getCertificateSubject().getEncoded());
        out.writeByteArray(mSpec.getCertificateSerialNumber().toByteArray());
        out.writeLong(mSpec.getCertificateNotBefore().getTime());
        out.writeLong(mSpec.getCertificateNotAfter().getTime());
        writeOptionalDate(out, mSpec.getKeyValidityStart());
        writeOptionalDate(out, mSpec.getKeyValidityForOriginationEnd());
        writeOptionalDate(out, mSpec.getKeyValidityForConsumptionEnd());
        if (mSpec.isDigestsSpecified()) {
            out.writeStringArray(mSpec.getDigests());
        } else {
            out.writeStringArray(null);
        }
        out.writeStringArray(mSpec.getEncryptionPaddings());
        out.writeStringArray(mSpec.getSignaturePaddings());
        out.writeStringArray(mSpec.getBlockModes());
        out.writeBoolean(mSpec.isRandomizedEncryptionRequired());
        out.writeBoolean(mSpec.isUserAuthenticationRequired());
        out.writeInt(mSpec.getUserAuthenticationValidityDurationSeconds());
        out.writeInt(mSpec.getUserAuthenticationType());
        out.writeBoolean(mSpec.isUserPresenceRequired());
        out.writeByteArray(mSpec.getAttestationChallenge());
        out.writeBoolean(mSpec.isUniqueIdIncluded());
        out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody());
        out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment());
        out.writeBoolean(mSpec.isStrongBoxBacked());
        out.writeBoolean(mSpec.isUserConfirmationRequired());
        out.writeBoolean(mSpec.isUnlockedDeviceRequired());
        out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
    }

    private static Date readDateOrNull(Parcel in) {
        boolean hasDate = in.readBoolean();
        if (hasDate) {
            return new Date(in.readLong());
        } else {
            return null;
        }
    }

    private ParcelableKeyGenParameterSpec(Parcel in) {
        final String keystoreAlias = in.readString();
        final int purposes = in.readInt();
        final int uid = in.readInt();
        final int keySize = in.readInt();

        final int keySpecType = in.readInt();
        AlgorithmParameterSpec algorithmSpec = null;
        if (keySpecType == ALGORITHM_PARAMETER_SPEC_NONE) {
            algorithmSpec = null;
        } else if (keySpecType == ALGORITHM_PARAMETER_SPEC_RSA) {
            int rsaKeySize = in.readInt();
            BigInteger publicExponent = new BigInteger(in.createByteArray());
            algorithmSpec = new RSAKeyGenParameterSpec(rsaKeySize, publicExponent);
        } else if (keySpecType == ALGORITHM_PARAMETER_SPEC_EC) {
            String stdName = in.readString();
            algorithmSpec = new ECGenParameterSpec(stdName);
        } else {
            throw new IllegalArgumentException(
                    String.format("Unknown algorithm parameter spec: %d", keySpecType));
        }

        final X500Principal certificateSubject = new X500Principal(in.createByteArray());
        final BigInteger certificateSerialNumber = new BigInteger(in.createByteArray());
        final Date certificateNotBefore = new Date(in.readLong());
        final Date certificateNotAfter = new Date(in.readLong());
        final Date keyValidityStartDate = readDateOrNull(in);
        final Date keyValidityForOriginationEnd = readDateOrNull(in);
        final Date keyValidityForConsumptionEnd = readDateOrNull(in);
        final String[] digests = in.createStringArray();
        final String[] encryptionPaddings = in.createStringArray();
        final String[] signaturePaddings = in.createStringArray();
        final String[] blockModes = in.createStringArray();
        final boolean randomizedEncryptionRequired = in.readBoolean();
        final boolean userAuthenticationRequired = in.readBoolean();
        final int userAuthenticationValidityDurationSeconds = in.readInt();
        final int userAuthenticationTypes = in.readInt();
        final boolean userPresenceRequired = in.readBoolean();
        final byte[] attestationChallenge = in.createByteArray();
        final boolean uniqueIdIncluded = in.readBoolean();
        final boolean userAuthenticationValidWhileOnBody = in.readBoolean();
        final boolean invalidatedByBiometricEnrollment = in.readBoolean();
        final boolean isStrongBoxBacked = in.readBoolean();
        final boolean userConfirmationRequired = in.readBoolean();
        final boolean unlockedDeviceRequired = in.readBoolean();
        final boolean criticalToDeviceEncryption = in.readBoolean();
        // The KeyGenParameterSpec is intentionally not constructed using a Builder here:
        // The intention is for this class to break if new parameters are added to the
        // KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
        mSpec = new KeyGenParameterSpec(
                keystoreAlias,
                uid,
                keySize,
                algorithmSpec,
                certificateSubject,
                certificateSerialNumber,
                certificateNotBefore,
                certificateNotAfter,
                keyValidityStartDate,
                keyValidityForOriginationEnd,
                keyValidityForConsumptionEnd,
                purposes,
                digests,
                encryptionPaddings,
                signaturePaddings,
                blockModes,
                randomizedEncryptionRequired,
                userAuthenticationRequired,
                userAuthenticationValidityDurationSeconds,
                userAuthenticationTypes,
                userPresenceRequired,
                attestationChallenge,
                uniqueIdIncluded,
                userAuthenticationValidWhileOnBody,
                invalidatedByBiometricEnrollment,
                isStrongBoxBacked,
                userConfirmationRequired,
                unlockedDeviceRequired,
                criticalToDeviceEncryption);
    }

    public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
        @Override
        public ParcelableKeyGenParameterSpec createFromParcel(Parcel in) {
            return new ParcelableKeyGenParameterSpec(in);
        }

        @Override
        public ParcelableKeyGenParameterSpec[] newArray(int size) {
            return new ParcelableKeyGenParameterSpec[size];
        }
    };

    public KeyGenParameterSpec getSpec() {
        return mSpec;
    }
}
