Last active
March 2, 2020 07:42
-
-
Save bryanjhv/0cc85f7ad504d261b979ed50035e81b8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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