<commit_before>package io.keybase.android;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;

import org.msgpack.MessagePack;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyStore.Entry;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Iterator;

import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import go.keybase.Keybase;
import io.keybase.android.keystore.KeyStoreHelper;

public class KeyStore extends Keybase.ExternalKeyStore.Stub {
    private final Context context;
    private final SharedPreferences prefs;
    private final java.security.KeyStore ks;

    private static final String PREFS_KEY = "wrappedKey_";
    private static final String KEY_ALIAS = "keybase-rsa-wrapper_";

    public KeyStore(final Context context, final SharedPreferences prefs) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
        this.context = context;
        this.prefs = prefs;

        ks = java.security.KeyStore.getInstance("AndroidKeyStore");
        ks.load(null);
    }


    @SuppressLint("CommitPrefEdits")
    @Override
    public void ClearSecret(final String username) throws Exception {
        prefs.edit().remove(PREFS_KEY + username).commit();
    }

    @Override
    public byte[] GetUsersWithStoredSecretsMsgPack() throws Exception {
        final Iterator<String> keyIterator = prefs.getAll().keySet().iterator();
        final ArrayList<String> userNames = new ArrayList<>();

        while (keyIterator.hasNext()) {
            final String key = keyIterator.next();
            if (key.indexOf(PREFS_KEY) == 0) {
                userNames.add(key.substring(PREFS_KEY.length()));
            }
        }

        MessagePack msgpack = new MessagePack();
        return msgpack.write(userNames);
    }

    @Override
    public synchronized byte[] RetrieveSecret(final String username) throws Exception {
        final byte[] wrappedSecret = readWrappedSecret(prefs, PREFS_KEY + username);
        Entry entry;
        try {
            entry = ks.getEntry(KEY_ALIAS, null);
        } catch (Exception e) {
            return null;
        }

        if (wrappedSecret == null) {
            return null;
        }

        if (entry == null || !(entry instanceof PrivateKeyEntry)){
            return null;
        }

        return unwrapSecret((PrivateKeyEntry) entry, wrappedSecret).getEncoded();
    }

    @Override
    public String GetTerminalPrompt() {
        return "Store secret in Android's KeyStore?";
    }

    @Override
    public synchronized void SetupKeyStore(final String username) throws Exception {
        if (!ks.containsAlias(KEY_ALIAS)) {
            KeyStoreHelper.generateRSAKeyPair(context, KEY_ALIAS);
        }

        // Try to read the entry from the keystore.
        // The entry may exists, but it may not be readable by us.
        // (this happens when the app is uninstalled and reinstalled)
        // In that case, lets' delete the entry and recreate it.
        // Note we are purposely not recursing to avoid a state where we
        // Constantly try to generate new RSA keys (which is slow)
        try {
            final Entry entry = ks.getEntry(KEY_ALIAS, null);
            if (entry == null) {
                throw new NullPointerException("Null Entry");
            }
        } catch (Exception e) {
            ks.deleteEntry(KEY_ALIAS);
            KeyStoreHelper.generateRSAKeyPair(context, KEY_ALIAS);
        }
    }

    @Override
    public synchronized void StoreSecret(final String username, final byte[] bytes) throws Exception {
        Entry entry = null;

        try {
            entry = ks.getEntry(KEY_ALIAS, null);
        } catch (Exception e) {
            throw new KeyStoreException("Failed to get the RSA keys from the keystore");
        }

        if (entry == null) {
            throw new KeyStoreException("No RSA keys in the keystore");
        }

        final byte[] wrappedSecret = wrapSecret((PrivateKeyEntry) entry, new SecretKeySpec(bytes, "SECRETBOX"));

        if (wrappedSecret == null) {
            throw new IOException("Null return when wrapping secret");
        }

        saveWrappedSecret(prefs, PREFS_KEY + username, wrappedSecret);
    }

    private static void saveWrappedSecret(SharedPreferences prefs, String prefsKey, byte[] wrappedSecret) {
        prefs.edit().putString(prefsKey, Base64.encodeToString(wrappedSecret, Base64.NO_WRAP)).apply();
    }


    private static byte[] readWrappedSecret(SharedPreferences prefs, String prefsKey) {
        final String wrappedKey = prefs.getString(prefsKey,"");
        if (wrappedKey.isEmpty()) {
            return null;
        }
        return Base64.decode(wrappedKey, Base64.NO_WRAP);
    }

    /**
     * Similar to Android's example Vault https://github.com/android/platform_development/tree/master/samples/Vault
     */
    private static byte[] wrapSecret(PrivateKeyEntry entry, SecretKey key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, NoSuchProviderException {
        KeyPair mPair = new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey());
        // This is the only cipher that's supported by AndroidKeystore (api version +18)
        // The padding makes sure this encryption isn't deterministic
        Cipher mCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        mCipher.init(Cipher.WRAP_MODE, mPair.getPublic());
        return mCipher.wrap(key);
    }

    private SecretKey unwrapSecret(PrivateKeyEntry entry, byte[] wrappedSecretKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, NoSuchProviderException {
        KeyPair mPair = new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey());<commit_msg>Add missing synchronized<commit_after>package io.keybase.android;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;

import org.msgpack.MessagePack;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyStore.Entry;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Iterator;

import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import go.keybase.Keybase;
import io.keybase.android.keystore.KeyStoreHelper;

public class KeyStore extends Keybase.ExternalKeyStore.Stub {
    private final Context context;
    private final SharedPreferences prefs;
    private final java.security.KeyStore ks;

    private static final String PREFS_KEY = "wrappedKey_";
    private static final String KEY_ALIAS = "keybase-rsa-wrapper_";

    public KeyStore(final Context context, final SharedPreferences prefs) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
        this.context = context;
        this.prefs = prefs;

        ks = java.security.KeyStore.getInstance("AndroidKeyStore");
        ks.load(null);
    }


    @SuppressLint("CommitPrefEdits")
    @Override
    public void ClearSecret(final String username) throws Exception {
        prefs.edit().remove(PREFS_KEY + username).commit();
    }

    @Override
    public synchronized byte[] GetUsersWithStoredSecretsMsgPack() throws Exception {
        final Iterator<String> keyIterator = prefs.getAll().keySet().iterator();
        final ArrayList<String> userNames = new ArrayList<>();

        while (keyIterator.hasNext()) {
            final String key = keyIterator.next();
            if (key.indexOf(PREFS_KEY) == 0) {
                userNames.add(key.substring(PREFS_KEY.length()));
            }
        }

        MessagePack msgpack = new MessagePack();
        return msgpack.write(userNames);
    }

    @Override
    public synchronized byte[] RetrieveSecret(final String username) throws Exception {
        final byte[] wrappedSecret = readWrappedSecret(prefs, PREFS_KEY + username);
        Entry entry;
        try {
            entry = ks.getEntry(KEY_ALIAS, null);
        } catch (Exception e) {
            return null;
        }

        if (wrappedSecret == null) {
            return null;
        }

        if (entry == null || !(entry instanceof PrivateKeyEntry)){
            return null;
        }

        return unwrapSecret((PrivateKeyEntry) entry, wrappedSecret).getEncoded();
    }

    @Override
    public String GetTerminalPrompt() {
        return "Store secret in Android's KeyStore?";
    }

    @Override
    public synchronized void SetupKeyStore(final String username) throws Exception {
        if (!ks.containsAlias(KEY_ALIAS)) {
            KeyStoreHelper.generateRSAKeyPair(context, KEY_ALIAS);
        }

        // Try to read the entry from the keystore.
        // The entry may exists, but it may not be readable by us.
        // (this happens when the app is uninstalled and reinstalled)
        // In that case, lets' delete the entry and recreate it.
        // Note we are purposely not recursing to avoid a state where we
        // Constantly try to generate new RSA keys (which is slow)
        try {
            final Entry entry = ks.getEntry(KEY_ALIAS, null);
            if (entry == null) {
                throw new NullPointerException("Null Entry");
            }
        } catch (Exception e) {
            ks.deleteEntry(KEY_ALIAS);
            KeyStoreHelper.generateRSAKeyPair(context, KEY_ALIAS);
        }
    }

    @Override
    public synchronized void StoreSecret(final String username, final byte[] bytes) throws Exception {
        Entry entry = null;

        try {
            entry = ks.getEntry(KEY_ALIAS, null);
        } catch (Exception e) {
            throw new KeyStoreException("Failed to get the RSA keys from the keystore");
        }

        if (entry == null) {
            throw new KeyStoreException("No RSA keys in the keystore");
        }

        final byte[] wrappedSecret = wrapSecret((PrivateKeyEntry) entry, new SecretKeySpec(bytes, "SECRETBOX"));

        if (wrappedSecret == null) {
            throw new IOException("Null return when wrapping secret");
        }

        saveWrappedSecret(prefs, PREFS_KEY + username, wrappedSecret);
    }

    private static void saveWrappedSecret(SharedPreferences prefs, String prefsKey, byte[] wrappedSecret) {
        prefs.edit().putString(prefsKey, Base64.encodeToString(wrappedSecret, Base64.NO_WRAP)).apply();
    }


    private static byte[] readWrappedSecret(SharedPreferences prefs, String prefsKey) {
        final String wrappedKey = prefs.getString(prefsKey,"");
        if (wrappedKey.isEmpty()) {
            return null;
        }
        return Base64.decode(wrappedKey, Base64.NO_WRAP);
    }

    /**
     * Similar to Android's example Vault https://github.com/android/platform_development/tree/master/samples/Vault
     */
    private static byte[] wrapSecret(PrivateKeyEntry entry, SecretKey key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, NoSuchProviderException {
        KeyPair mPair = new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey());
        // This is the only cipher that's supported by AndroidKeystore (api version +18)
        // The padding makes sure this encryption isn't deterministic
        Cipher mCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        mCipher.init(Cipher.WRAP_MODE, mPair.getPublic());
        return mCipher.wrap(key);
    }

    private static SecretKey unwrapSecret(PrivateKeyEntry entry, byte[] wrappedSecretKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, NoSuchProviderException {
        KeyPair mPair = new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey());
{'fraction_non_alphanumeric': 0.09015682874931043, 'fraction_numerical': 0.002206635668689416, 'mean_word_length': 3.1054674862504044, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '680400', 'n_tokens_mistral': 3435, 'n_tokens_neox': 3308, 'n_words': 899}