package com.it_nomads.fluttersecurestorage.ciphers;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;

import java.math.BigInteger;
import java.security.Key;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Calendar;

import javax.crypto.Cipher;
import javax.security.auth.x500.X500Principal;

class RSACipher18Implementation {

    private final String KEY_ALIAS;
    private static final String KEYSTORE_PROVIDER_ANDROID = "AndroidKeyStore";
    private static final String TYPE_RSA = "RSA";


    public RSACipher18Implementation(Context context) throws Exception {
        KEY_ALIAS = context.getPackageName() + ".FlutterSecureStoragePluginKey";
        createRSAKeysIfNeeded(context);
    }

    public byte[] wrap(Key key) throws Exception {
        PublicKey publicKey = getEntry().getCertificate().getPublicKey();
        Cipher cipher = getRSACipher();
        cipher.init(Cipher.WRAP_MODE, publicKey);

        return cipher.wrap(key);
    }

    public Key unwrap(byte[] wrappedKey, String algorithm) throws Exception {
        PrivateKey privateKey = getEntry().getPrivateKey();
        Cipher cipher = getRSACipher();
        cipher.init(Cipher.UNWRAP_MODE, privateKey);

        return cipher.unwrap(wrappedKey, algorithm, Cipher.SECRET_KEY);
    }

    public byte[] encrypt(byte[] input) throws Exception {
        PublicKey publicKey = getEntry().getCertificate().getPublicKey();
        Cipher cipher = getRSACipher();
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);

        return cipher.doFinal(input);
    }

    public byte[] decrypt(byte[] input) throws Exception {
        PrivateKey privateKey = getEntry().getPrivateKey();
        Cipher cipher = getRSACipher();
        cipher.init(Cipher.DECRYPT_MODE, privateKey);

        return cipher.doFinal(input);
    }

    private KeyStore.PrivateKeyEntry getEntry() throws Exception {
        KeyStore ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID);
        ks.load(null);

        KeyStore.Entry entry = ks.getEntry(KEY_ALIAS, null);
        if (entry == null) {
            throw new Exception("No key found under alias: " + KEY_ALIAS);
        }

        if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
            throw new Exception("Not an instance of a PrivateKeyEntry");
        }

        return (KeyStore.PrivateKeyEntry) entry;
    }

    private Cipher getRSACipher() throws Exception {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL"); // error in android 6: InvalidKeyException: Need RSA private or public key
        } else {
            return Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidKeyStoreBCWorkaround"); // error in android 5: NoSuchProviderException: Provider not available: AndroidKeyStoreBCWorkaround
        }
    }

    private void createRSAKeysIfNeeded(Context context) throws Exception {
        KeyStore ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID);
        ks.load(null);

        KeyStore.Entry entry = ks.getEntry(KEY_ALIAS, null);
        if (entry == null) {
            createKeys(context);
        }
    }

    @SuppressLint("NewApi")
    @SuppressWarnings("deprecation")
    private void createKeys(Context context) throws Exception {
        Calendar start = Calendar.getInstance();
        Calendar end = Calendar.getInstance();
        end.add(Calendar.YEAR, 25);

        KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance(TYPE_RSA, KEYSTORE_PROVIDER_ANDROID);

        AlgorithmParameterSpec spec;

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            //noinspection deprecation
            spec = new android.security.KeyPairGeneratorSpec.Builder(context)
                    .setAlias(KEY_ALIAS)
                    .setSubject(new X500Principal("CN=" + KEY_ALIAS))
                    .setSerialNumber(BigInteger.valueOf(1))
                    .setStartDate(start.getTime())
                    .setEndDate(end.getTime())
                    .build();
        } else {
            spec = new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
                    .setCertificateSubject(new X500Principal("CN=" + KEY_ALIAS))
                    .setDigests(KeyProperties.DIGEST_SHA256)
                    .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
                    .setCertificateSerialNumber(BigInteger.valueOf(1))
                    .setCertificateNotBefore(start.getTime())
                    .setCertificateNotAfter(end.getTime())
                    .build();
        }
        kpGenerator.initialize(spec);
        kpGenerator.generateKeyPair();
    }

}

@@ -432,24 +432,63 @@
 .PublicKey;%0A
+import java.security.cert.Certificate;%0A
 import java.
@@ -1117,36 +1117,8 @@
 y = 
-getEntry().getCertificate().
 getP
@@ -1366,35 +1366,24 @@
 rivateKey = 
-getEntry().
 getPrivateKe
@@ -1653,36 +1653,8 @@
 y = 
-getEntry().getCertificate().
 getP
@@ -1899,19 +1899,8 @@
 y = 
-getEntry().
 getP
@@ -2064,25 +2064,16 @@
 private 
-KeyStore.
 PrivateK
@@ -2078,21 +2078,21 @@
 eKey
-Entry getEntr
+ getPrivateKe
 y() 
@@ -2216,32 +2216,19 @@
      Key
-Store.Entry entr
+ ke
 y = ks.g
@@ -2221,36 +2221,34 @@
 Key key = ks.get
-Entr
+Ke
 y(KEY_ALIAS, nul
@@ -2255,36 +2255,34 @@
 l);%0A        if (
+k
 e
-ntr
 y == null) %7B%0A   
@@ -2378,20 +2378,18 @@
   if (!(
+k
 e
-ntr
 y instan
@@ -2393,25 +2393,16 @@
 tanceof 
-KeyStore.
 PrivateK
@@ -2403,21 +2403,16 @@
 ivateKey
-Entry
 )) %7B%0A   
@@ -2472,21 +2472,16 @@
 ivateKey
-Entry
 %22);%0A    
@@ -2507,38 +2507,537 @@
 rn (
-KeyStore.PrivateKeyEntry) entr
+PrivateKey) key;%0A    %7D%0A%0A    private PublicKey getPublicKey() throws Exception %7B%0A        KeyStore ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID);%0A        ks.load(null);%0A%0A        Certificate cert = ks.getCertificate(KEY_ALIAS);%0A        if (cert == null) %7B%0A            throw new Exception(%22No certificate found under alias: %22 + KEY_ALIAS);%0A        %7D%0A%0A        PublicKey key = cert.getPublicKey();%0A        if (key == null) %7B%0A            throw new Exception(%22No key found under alias: %22 + KEY_ALIAS);%0A        %7D%0A%0A        return ke
 y;%0A 

Access keys correctly