142 lines
4.8 KiB
Java
142 lines
4.8 KiB
Java
package org.signal.signing;
|
|
|
|
import com.android.apksig.ApkSigner;
|
|
import com.android.apksig.apk.ApkFormatException;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileReader;
|
|
import java.io.IOException;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.KeyStore;
|
|
import java.security.KeyStoreException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.PrivateKey;
|
|
import java.security.Provider;
|
|
import java.security.Security;
|
|
import java.security.SignatureException;
|
|
import java.security.UnrecoverableKeyException;
|
|
import java.security.cert.Certificate;
|
|
import java.security.cert.CertificateException;
|
|
import java.security.cert.X509Certificate;
|
|
import java.util.Collections;
|
|
import java.util.Enumeration;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
|
|
public class ApkSignerUtil {
|
|
|
|
private final String providerClass;
|
|
|
|
private final String providerArgument;
|
|
|
|
private final String keyStoreType;
|
|
|
|
private final String keyStorePassword;
|
|
|
|
|
|
public ApkSignerUtil(String providerClass, String providerArgument, String keyStoreType, String keyStorePassword) {
|
|
this.providerClass = providerClass;
|
|
this.providerArgument = providerArgument;
|
|
this.keyStoreType = keyStoreType;
|
|
this.keyStorePassword = keyStorePassword;
|
|
}
|
|
|
|
public void calculateSignature(String inputApkFile, String outputApkFile)
|
|
throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, ApkFormatException, InvalidKeyException, SignatureException
|
|
{
|
|
System.out.println("Running calculateSignature()...");
|
|
|
|
if (providerClass != null) {
|
|
installProvider(providerClass, providerArgument);
|
|
}
|
|
|
|
ApkSigner apkSigner = new ApkSigner.Builder(Collections.singletonList(loadKeyStore(keyStoreType, keyStorePassword)))
|
|
.setV1SigningEnabled(true)
|
|
.setV2SigningEnabled(true)
|
|
.setInputApk(new File(inputApkFile))
|
|
.setOutputApk(new File(outputApkFile))
|
|
.setOtherSignersSignaturesPreserved(false)
|
|
.build();
|
|
|
|
apkSigner.sign();
|
|
}
|
|
|
|
private void installProvider(String providerName, String providerArgument) {
|
|
try {
|
|
Class<?> providerClass = Class.forName(providerName);
|
|
|
|
if (!Provider.class.isAssignableFrom(providerClass)) {
|
|
throw new IllegalArgumentException("JCA Provider class " + providerClass + " not subclass of " + Provider.class.getName());
|
|
}
|
|
|
|
Provider provider;
|
|
|
|
if (providerArgument != null) {
|
|
provider = (Provider) providerClass.getConstructor(String.class).newInstance(providerArgument);
|
|
} else {
|
|
provider = (Provider) providerClass.getConstructor().newInstance();
|
|
}
|
|
|
|
Security.addProvider(provider);
|
|
} catch (ClassNotFoundException | InstantiationException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
|
throw new IllegalArgumentException(e);
|
|
}
|
|
}
|
|
|
|
private ApkSigner.SignerConfig loadKeyStore(String keyStoreType, String keyStorePassword) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
|
KeyStore keyStoreEntity = KeyStore.getInstance(keyStoreType == null ? KeyStore.getDefaultType() : keyStoreType);
|
|
char[] password = getPassword(keyStorePassword);
|
|
keyStoreEntity.load(null, password);
|
|
|
|
Enumeration<String> aliases = keyStoreEntity.aliases();
|
|
String keyAlias = null;
|
|
|
|
while (aliases != null && aliases.hasMoreElements()) {
|
|
String alias = aliases.nextElement();
|
|
if (keyStoreEntity.isKeyEntry(alias)) {
|
|
keyAlias = alias;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (keyAlias == null) {
|
|
throw new IllegalArgumentException("Keystore has no key entries!");
|
|
}
|
|
|
|
PrivateKey privateKey = (PrivateKey) keyStoreEntity.getKey(keyAlias, password);
|
|
Certificate[] certificates = keyStoreEntity.getCertificateChain(keyAlias);
|
|
|
|
if (certificates == null || certificates.length == 0) {
|
|
throw new IllegalArgumentException("Unable to load certificates!");
|
|
}
|
|
|
|
List<X509Certificate> results = new LinkedList<>();
|
|
|
|
for (Certificate certificate : certificates) {
|
|
results.add((X509Certificate)certificate);
|
|
}
|
|
|
|
|
|
return new ApkSigner.SignerConfig.Builder("Signal Signer", privateKey, results).build();
|
|
}
|
|
|
|
private char[] getPassword(String encoded) throws IOException {
|
|
if (encoded.startsWith("file:")) {
|
|
String name = encoded.substring("file:".length());
|
|
BufferedReader reader = new BufferedReader(new FileReader(new File(name)));
|
|
String password = reader.readLine();
|
|
|
|
if (password.length() == 0) {
|
|
throw new IOException("Failed to read password from file: " + name);
|
|
}
|
|
|
|
return password.toCharArray();
|
|
} else {
|
|
return encoded.toCharArray();
|
|
}
|
|
}
|
|
|
|
}
|