gt:             KeyPair kp = getKerplappKeypair();
context: package com.phonemetra.turbo.store.localrepo;
  
  import android.content.Context;
  import android.util.Log;
  
  import com.phonemetra.turbo.store.StoreApp;
  import com.phonemetra.turbo.store.Utils;
  import org.spongycastle.asn1.ASN1Sequence;
  import org.spongycastle.asn1.x500.X500Name;
  import org.spongycastle.asn1.x509.GeneralName;
  import org.spongycastle.asn1.x509.GeneralNames;
  import org.spongycastle.asn1.x509.SubjectPublicKeyInfo;
  import org.spongycastle.asn1.x509.Time;
  import org.spongycastle.asn1.x509.X509Extension;
  import org.spongycastle.cert.X509CertificateHolder;
  import org.spongycastle.cert.X509v3CertificateBuilder;
  import org.spongycastle.cert.jcajce.JcaX509CertificateConverter;
  import org.spongycastle.operator.ContentSigner;
  import org.spongycastle.operator.OperatorCreationException;
  import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.math.BigInteger;
  import java.net.Socket;
  import java.security.GeneralSecurityException;
  import java.security.Key;
  import java.security.KeyPair;
  import java.security.KeyPairGenerator;
  import java.security.KeyStore;
  import java.security.KeyStoreException;
  import java.security.NoSuchAlgorithmException;
  import java.security.Principal;
  import java.security.PrivateKey;
  import java.security.PublicKey;
  import java.security.SecureRandom;
  import java.security.UnrecoverableKeyException;
  import java.security.cert.Certificate;
  import java.security.cert.CertificateException;
  import java.security.cert.X509Certificate;
  import java.util.Calendar;
  import java.util.Date;
  import java.util.GregorianCalendar;
  import java.util.Locale;
  
  import javax.net.ssl.KeyManager;
  import javax.net.ssl.KeyManagerFactory;
  import javax.net.ssl.X509KeyManager;
  
  import kellinwood.security.zipsigner.ZipSigner;
  
  // TODO Address exception handling in a uniform way throughout
  
  public class LocalRepoKeyStore {
  
      private static final String TAG = "LocalRepoKeyStore";
  
      public static final String INDEX_CERT_ALIAS = "fdroid";
      public static final String HTTP_CERT_ALIAS = "https";
  
      private static final String DEFAULT_SIG_ALG = "SHA1withRSA";
      private static final String DEFAULT_KEY_ALGO = "RSA";
      private static final int DEFAULT_KEY_BITS = 2048;
  
      private static final String DEFAULT_INDEX_CERT_INFO = "O=Kerplapp,OU=GuardianProject";
  
      private static LocalRepoKeyStore localRepoKeyStore;
      private KeyStore keyStore;
      private KeyManager[] keyManagers;
      private File keyStoreFile;
  
      public static LocalRepoKeyStore get(Context context) throws InitException {
          if (localRepoKeyStore == null)
              localRepoKeyStore = new LocalRepoKeyStore(context);
          return localRepoKeyStore;
      }
  
      @SuppressWarnings("serial")
      public static class InitException extends Exception {
          public InitException(String detailMessage) {
              super(detailMessage);
          }
      }
  
      private LocalRepoKeyStore(Context context) throws InitException {
          try {
              File appKeyStoreDir = context.getDir("keystore", Context.MODE_PRIVATE);
  
              Utils.DebugLog(TAG, "Generating LocalRepoKeyStore instance: " + appKeyStoreDir.getAbsolutePath());
              this.keyStoreFile = new File(appKeyStoreDir, "kerplapp.bks");
  
              Utils.DebugLog(TAG, "Using default KeyStore type: " + KeyStore.getDefaultType());
              this.keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
  
              if (keyStoreFile.exists()) {
                  try {
                      Utils.DebugLog(TAG, "Keystore already exists, loading...");
                      keyStore.load(new FileInputStream(keyStoreFile), "".toCharArray());
                  } catch (IOException e) {
                      Log.e(TAG, "Error while loading existing keystore. Will delete and create a new one.");
  
                      // NOTE: Could opt to delete and then re-create the keystore here, but that may
                      // be undesirable. For example - if you were to re-connect to an existing device
                      // that you have swapped apps with in the past, then you would really want the
                      // signature to be the same as last time.
                      throw new InitException("Could not initialize local repo keystore: " + e);
                  }
              }
  
              if (!keyStoreFile.exists()) {
                  // If there isn't a persisted BKS keystore on disk we need to
                  // create a new empty keystore
                  // Init a new keystore with a blank passphrase
                  Utils.DebugLog(TAG, "Keystore doesn't exist, creating...");
                  keyStore.load(null, "".toCharArray());
              }
  
              /*
               * If the keystore we loaded doesn't have an INDEX_CERT_ALIAS entry
               * we need to generate a new random keypair and a self signed
               * certificate for this slot.
               */
              if (keyStore.getKey(INDEX_CERT_ALIAS, "".toCharArray()) == null) {
                  /*
                   * Generate a random key pair to associate with the
                   * INDEX_CERT_ALIAS certificate in the keystore. This keypair
                   * will be used for the HTTPS cert as well.
                   */
                  KeyPair rndKeys = generateRandomKeypair();
  
                  /*
                   * Generate a self signed certificate for signing the index.jar
                   * We can't generate the HTTPS certificate until we know what
                   * the IP address will be to use for the CN field.
                   */
                  X500Name subject = new X500Name(DEFAULT_INDEX_CERT_INFO);
                  Certificate indexCert = generateSelfSignedCertChain(rndKeys, subject);
  
                  addToStore(INDEX_CERT_ALIAS, rndKeys, indexCert);
              }
  
              /*
               * Kerplapp uses its own KeyManager to to ensure the correct
               * keystore alias is used for the correct purpose. With the default
               * key manager it is not possible to specify that HTTP_CERT_ALIAS
               * should be used for TLS and INDEX_CERT_ALIAS for signing the
               * index.jar.
               */
              KeyManagerFactory keyManagerFactory = KeyManagerFactory
                      .getInstance(KeyManagerFactory.getDefaultAlgorithm());
  
              keyManagerFactory.init(keyStore, "".toCharArray());
              KeyManager defaultKeyManager = keyManagerFactory.getKeyManagers()[0];
              KeyManager wrappedKeyManager = new KerplappKeyManager(
                      (X509KeyManager) defaultKeyManager);
              keyManagers = new KeyManager[] {
                      wrappedKeyManager
              };
          } catch (UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException | CertificateException | OperatorCreationException | IOException e) {
              Log.e(TAG, "Error loading keystore", e);
          // TODO: Remove once we have a proper fix for #334
          } catch (IllegalArgumentException e) {
              Log.e(TAG, "Error loading keystore", e);
              Log.e(TAG, "See https://gitlab.com/fdroid/fdroidclient/issues/334");
          }
      }
  
      public void setupHTTPSCertificate() {
          try {
              // Get the existing private/public keypair to use for the HTTPS cert
              KeyPair kerplappKeypair = getKerplappKeypair();
  
              /*
               * Once we have an IP address, that can be used as the hostname. We
               * can generate a self signed cert with a valid CN field to stash
               * into the keystore in a predictable place. If the IP address
               * changes we should run this method again to stomp old
               * HTTPS_CERT_ALIAS entries.
               */
              X500Name subject = new X500Name("CN=" + StoreApp.ipAddressString);
              Certificate indexCert = generateSelfSignedCertChain(kerplappKeypair, subject,
                      StoreApp.ipAddressString);
              addToStore(HTTP_CERT_ALIAS, kerplappKeypair, indexCert);
          } catch (Exception e) {
              Log.e(TAG, "Failed to setup HTTPS certificate", e);
          }
      }
  
      public File getKeyStoreFile() {
          return keyStoreFile;
      }
  
      public KeyStore getKeyStore() {
          return keyStore;
      }
  
      public KeyManager[] getKeyManagers() {
          return keyManagers;
      }
  
      public void signZip(File input, File output) {
          try {
              ZipSigner zipSigner = new ZipSigner();
  
              X509Certificate cert = (X509Certificate) keyStore.getCertificate(INDEX_CERT_ALIAS);
  
  