/**
 * Created by Chris Renfrow on 12/28/18.
 */

package com.example.brokenbiometric

import android.content.Context
import android.os.Build
import android.security.KeyPairGeneratorSpec
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.annotation.RequiresApi
import java.io.IOException
import java.math.BigInteger
import java.security.*
import java.security.cert.CertificateException
import java.security.spec.ECGenParameterSpec
import java.util.*
import javax.security.auth.x500.X500Principal

object Keystore {

    const val BIOMETRIC = "com.example.brokenbiometric.BIOMETRIC"
    const val KEYSTORE_PROVIDER = "AndroidKeyStore"

    @Throws(Exception::class)
    fun getKeyPair(context: Context, alias: String): KeyPair? {
        val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER)
        keyStore.load(null)

        if (!keyStore.containsAlias(alias)) {
            if (alias == BIOMETRIC && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                generateBiometricKeyPair(alias)
            } else {
                addAlias(context, alias)
            }
        }

        val publicKey = keyStore.getCertificate(alias).publicKey
        val privateKey = keyStore.getKey(alias, null) as PrivateKey

        return KeyPair(publicKey, privateKey)
    }

    @Throws(Exception::class)
    @RequiresApi(Build.VERSION_CODES.M)
    private fun generateBiometricKeyPair(alias: String): KeyPair {
        val keyPairGenerator = KeyPairGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_EC,
            "AndroidKeyStore")

        val builder = KeyGenParameterSpec.Builder(alias,
            KeyProperties.PURPOSE_SIGN)
            .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
            .setDigests(
                KeyProperties.DIGEST_SHA256,
                KeyProperties.DIGEST_SHA384,
                KeyProperties.DIGEST_SHA512)
            .setUserAuthenticationRequired(true)

        keyPairGenerator.initialize(builder.build())

        return keyPairGenerator.generateKeyPair()
    }

    fun addAlias(context: Context, alias: String) {
        try {
            val keystore = KeyStore.getInstance(KEYSTORE_PROVIDER)
            keystore.load(null)

            // Create new key if needed
            if (!keystore.containsAlias(alias)) {
                val start = Calendar.getInstance()
                val end = Calendar.getInstance()
                end.add(Calendar.YEAR, 1)

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    val spec = KeyGenParameterSpec.Builder(alias,
                        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
                        .setCertificateSubject(X500Principal("CN=BrokenBiometric, O=Example"))
                        .setCertificateSerialNumber(BigInteger.ONE)
                        .setKeyValidityStart(start.time)
                        .setKeyValidityEnd(end.time)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
                        .build()
                    val generator = KeyPairGenerator.getInstance("RSA", KEYSTORE_PROVIDER)
                    generator.initialize(spec)
                    generator.generateKeyPair()

                } else {
                    @Suppress("DEPRECATION")
                    val spec = KeyPairGeneratorSpec.Builder(context)
                        .setAlias(alias)
                        .setSubject(X500Principal("CN=BrokenBiometric, O=Example"))
                        .setSerialNumber(BigInteger.ONE)
                        .setStartDate(start.time)
                        .setEndDate(end.time)
                        .build()
                    val generator = KeyPairGenerator.getInstance("RSA", KEYSTORE_PROVIDER)
                    generator.initialize(spec)
                    generator.generateKeyPair()
                }
            }

        } catch (ignored: KeyStoreException) {
        } catch (ignored: CertificateException) {
        } catch (ignored: NoSuchAlgorithmException) {
        } catch (ignored: IOException) {
        } catch (ignored: NoSuchProviderException) {
        } catch (ignored: InvalidAlgorithmParameterException) {
        }

    }
}