Skip to content

Instantly share code, notes, and snippets.

@bryanjhv
Last active March 2, 2020 07:42
Show Gist options
  • Select an option

  • Save bryanjhv/0cc85f7ad504d261b979ed50035e81b8 to your computer and use it in GitHub Desktop.

Select an option

Save bryanjhv/0cc85f7ad504d261b979ed50035e81b8 to your computer and use it in GitHub Desktop.
/**
* Creates ~/.android/{env}.keystore with given arguments
* Requires bcpkix-jdk15on-156.jar and bcprov-jdk15on-156.jar
* Extracted from com.android.ide.common.signing.KeystoreHelper
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v1CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
public class CreateAndroidKeystore {
public static void main(String[] args) throws Exception {
if (args.length < 1) {
System.out.println("Usage:");
System.out.println("- Env (debug/release)");
System.out.println("- Validity in years [30]");
System.out.println("- Store password [android]");
System.out.println("- Key password [android]");
System.out.println("- Key alias [Android{TYPE}Key]");
return;
}
String storeEnv = args[0].toLowerCase();
String storeEnvCap = storeEnv.substring(0, 1).toUpperCase() + storeEnv.substring(1);
int validityYears = args.length > 1 ? Integer.parseInt(args[1]) : 30;
String storePassword = args.length > 2 ? args[2] : "android";
String keyPassword = args.length > 3 ? args[3] : "android";
String keyAlias = args.length > 4 ? args[4] : "Android" + storeEnvCap + "Key";
String certificateDesc = "CN=Android " + storeEnvCap + ",O=Android,C=US";
String storeLocation = getAndroidFolder() + storeEnv + ".keystore";
File storeFile = new File(storeLocation);
createNewStore(null, storeFile, storePassword, keyPassword, keyAlias, certificateDesc, validityYears);
}
private static String getAndroidFolder() {
String home = System.getProperty("user.home");
if (!home.endsWith(File.separator))
home += File.separator;
home += ".android";
File folder = new File(home);
if (!folder.mkdirs() && !folder.isDirectory())
throw new RuntimeException("Cannot create directory " + folder);
return home + File.separator;
}
public static boolean createNewStore(String storeType, File storeFile, String storePassword, String keyPassword,
String keyAlias, String dn, int validityYears) throws KeytoolException {
if (validityYears < 0)
throw new IllegalArgumentException("validityYears (" + validityYears + ") <= 0");
if (storeType == null) {
storeType = KeyStore.getDefaultType();
if (storeType == null) {
throw new IllegalArgumentException("expected a non-null reference");
}
}
try {
KeyStore ks = KeyStore.getInstance(storeType);
ks.load(null, null);
Object[] generated = generateKeyAndCertificate("RSA", "SHA1withRSA", validityYears, dn);
ks.setKeyEntry(keyAlias, (PrivateKey) generated[0], keyPassword.toCharArray(),
new Certificate[] { (X509Certificate) generated[1] });
FileOutputStream fos = new FileOutputStream(storeFile);
boolean threw = true;
try {
ks.store(fos, storePassword.toCharArray());
threw = false;
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
if (!threw)
throw e;
}
}
}
} catch (KeytoolException e) {
throw e;
} catch (Exception e) {
throw new KeytoolException("Failed to create keystore.", e);
}
return true;
}
private static Object[] generateKeyAndCertificate(String asymmetric, String sign, int validityYears, String dn)
throws KeytoolException {
if (validityYears < 0)
throw new IllegalArgumentException("validityYears <= 0");
KeyPair keyPair;
try {
keyPair = KeyPairGenerator.getInstance(asymmetric).generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new KeytoolException("Failed to generate key and certificate pair for algorithm '" + asymmetric + "'.", e);
}
Date notBefore = new Date(System.currentTimeMillis());
Date notAfter = new Date(System.currentTimeMillis() + validityYears * 365L * 24 * 60 * 60 * 1000);
X500Name issuer = new X500Name(new X500Principal(dn).getName());
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
X509v1CertificateBuilder builder = new X509v1CertificateBuilder(issuer, BigInteger.ONE, notBefore, notAfter, issuer,
publicKeyInfo);
ContentSigner signer;
try {
signer = new JcaContentSignerBuilder(sign).setProvider(new BouncyCastleProvider()).build(keyPair.getPrivate());
} catch (OperatorCreationException e) {
throw new KeytoolException("Failed to build content signer with signature algorithm '" + sign + "'.", e);
}
X509CertificateHolder holder = builder.build(signer);
JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider());
X509Certificate certificate;
try {
certificate = converter.getCertificate(holder);
} catch (CertificateException e) {
throw new KeytoolException("Failed to obtain the self-signed certificate.", e);
}
return new Object[] { keyPair.getPrivate(), certificate };
}
public static class KeytoolException extends Exception {
private static final long serialVersionUID = 1L;
KeytoolException(String message, Throwable t) {
super(message, t);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment