/*
 * 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.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;

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.getNamespace());
        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);
        }
        if (mSpec.isMgf1DigestsSpecified()) {
            out.writeStringList(List.copyOf(mSpec.getMgf1Digests()));
        } else {
            out.writeStringList(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.isDevicePropertiesAttestationIncluded());
        out.writeIntArray(mSpec.getAttestationIds());
        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());
        out.writeInt(mSpec.getMaxUsageCount());
        out.writeString(mSpec.getAttestKeyAlias());
        out.writeLong(mSpec.getBoundToSpecificSecureUserId());
    }

    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 namespace = 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 ArrayList<String> mgf1Digests = in.createStringArrayList();
        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 devicePropertiesAttestationIncluded = in.readBoolean();
        final int[] attestationIds = in.createIntArray();
        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();
        final int maxUsageCount = in.readInt();
        final String attestKeyAlias = in.readString();
        final long boundToSecureUserId = in.readLong();
        // 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,
                namespace,
                keySize,
                algorithmSpec,
                certificateSubject,
                certificateSerialNumber,
                certificateNotBefore,
                certificateNotAfter,
                keyValidityStartDate,
                keyValidityForOriginationEnd,
                keyValidityForConsumptionEnd,
                purposes,
                digests,
                mgf1Digests != null ? Set.copyOf(mgf1Digests) : Collections.emptySet(),
                encryptionPaddings,
                signaturePaddings,
                blockModes,
                randomizedEncryptionRequired,
                userAuthenticationRequired,
                userAuthenticationValidityDurationSeconds,
                userAuthenticationTypes,
                userPresenceRequired,
                attestationChallenge,
                devicePropertiesAttestationIncluded,
                attestationIds,
                uniqueIdIncluded,
                userAuthenticationValidWhileOnBody,
                invalidatedByBiometricEnrollment,
                isStrongBoxBacked,
                userConfirmationRequired,
                unlockedDeviceRequired,
                criticalToDeviceEncryption,
                maxUsageCount,
                attestKeyAlias,
                boundToSecureUserId);
    }

    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;
    }
}
