Update release config
This commit is contained in:
parent
eeb2e2e3af
commit
4063ef39a4
6 changed files with 210 additions and 63 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -24,3 +24,4 @@ test/androidTestEspresso/res/values/arrays.xml
|
||||||
obj/
|
obj/
|
||||||
jni/libspeex/.deps/
|
jni/libspeex/.deps/
|
||||||
*.sh
|
*.sh
|
||||||
|
pkcs11.password
|
||||||
|
|
|
@ -4,7 +4,7 @@ RUN dpkg --add-architecture i386 && \
|
||||||
apt-get update -y && \
|
apt-get update -y && \
|
||||||
apt-get install -y software-properties-common && \
|
apt-get install -y software-properties-common && \
|
||||||
apt-get update -y && \
|
apt-get update -y && \
|
||||||
apt-get install -y libc6:i386=2.26-0ubuntu2.1 libncurses5:i386=6.0+20160625-1ubuntu1 libstdc++6:i386=7.2.0-8ubuntu3.2 lib32z1=1:1.2.11.dfsg-0ubuntu2 wget openjdk-8-jdk=8u171-b11-0ubuntu0.17.10.1 git unzip && \
|
apt-get install -y libc6:i386=2.26-0ubuntu2.1 libncurses5:i386=6.0+20160625-1ubuntu1 libstdc++6:i386=7.2.0-8ubuntu3.2 lib32z1=1:1.2.11.dfsg-0ubuntu2 wget openjdk-8-jdk=8u171-b11-0ubuntu0.17.10.1 git unzip opensc pcscd && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
apt-get autoremove -y && \
|
apt-get autoremove -y && \
|
||||||
apt-get clean
|
apt-get clean
|
||||||
|
|
115
build.gradle
115
build.gradle
|
@ -1,3 +1,5 @@
|
||||||
|
import org.signal.signing.ApkSignerUtil
|
||||||
|
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
|
@ -321,10 +323,6 @@ android {
|
||||||
exclude 'META-INF/proguard/androidx-annotations.pro'
|
exclude 'META-INF/proguard/androidx-annotations.pro'
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
|
||||||
release
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
|
@ -354,7 +352,6 @@ android {
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles = buildTypes.debug.proguardFiles
|
proguardFiles = buildTypes.debug.proguardFiles
|
||||||
signingConfig signingConfigs.release
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,47 +403,66 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task assembleWebsiteDescriptor << {
|
def assembleWebsiteDescriptor = { variant, file ->
|
||||||
android.applicationVariants.all { variant ->
|
if (file.exists()) {
|
||||||
if (variant.name.equals("websiteDebug") ||
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
variant.name.equals("websiteRelease"))
|
file.eachByte 4096, {bytes, size ->
|
||||||
{
|
md.update(bytes, 0, size);
|
||||||
File file = new File(variant.outputs[0].outputFile.path)
|
|
||||||
|
|
||||||
if (file.exists()) {
|
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
|
||||||
file.eachByte 4096, {bytes, size ->
|
|
||||||
md.update(bytes, 0, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
String digest = md.digest().collect {String.format "%02x", it}.join();
|
|
||||||
String url = variant.productFlavors.get(0).ext.websiteUpdateUrl
|
|
||||||
String apkName = variant.outputs[0].outputFile.name
|
|
||||||
|
|
||||||
String descriptor = "{" +
|
|
||||||
"\"versionCode\" : $project.android.defaultConfig.versionCode," +
|
|
||||||
"\"versionName\" : \"$project.android.defaultConfig.versionName\"," +
|
|
||||||
"\"sha256sum\" : \"$digest\"," +
|
|
||||||
"\"url\" : \"$url/$apkName\"" +
|
|
||||||
"}"
|
|
||||||
|
|
||||||
File descriptorFile = new File(variant.outputs[0].outputFile.parent, apkName.replace(".apk", ".json"))
|
|
||||||
|
|
||||||
descriptorFile.write(descriptor)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String digest = md.digest().collect {String.format "%02x", it}.join();
|
||||||
|
String url = variant.productFlavors.get(0).ext.websiteUpdateUrl
|
||||||
|
String apkName = file.getName()
|
||||||
|
|
||||||
|
String descriptor = "{" +
|
||||||
|
"\"versionCode\" : $project.android.defaultConfig.versionCode," +
|
||||||
|
"\"versionName\" : \"$project.android.defaultConfig.versionName\"," +
|
||||||
|
"\"sha256sum\" : \"$digest\"," +
|
||||||
|
"\"url\" : \"$url/$apkName\"" +
|
||||||
|
"}"
|
||||||
|
|
||||||
|
File descriptorFile = new File(file.getParent(), apkName.replace(".apk", ".json"))
|
||||||
|
|
||||||
|
descriptorFile.write(descriptor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def signProductionRelease = { variant ->
|
||||||
|
String apkName = variant.outputs[0].outputFile.name
|
||||||
|
File inputFile = new File(variant.outputs[0].outputFile.path);
|
||||||
|
File outputFile = new File(variant.outputs[0].outputFile.parent, apkName.replace("-unsigned", ""));
|
||||||
|
|
||||||
|
new ApkSignerUtil("sun.security.pkcs11.SunPKCS11",
|
||||||
|
"pkcs11.config",
|
||||||
|
"PKCS11",
|
||||||
|
"file:pkcs11.password").calculateSignature(inputFile.getAbsolutePath(),
|
||||||
|
outputFile.getAbsolutePath());
|
||||||
|
|
||||||
|
inputFile.delete();
|
||||||
|
return outputFile
|
||||||
|
}
|
||||||
|
|
||||||
|
task signProductionPlayRelease << {
|
||||||
|
signProductionRelease(android.applicationVariants.find({ it.name.equals("playRelease") }))
|
||||||
|
}
|
||||||
|
|
||||||
|
task signProductionWebsiteRelease << {
|
||||||
|
def variant = android.applicationVariants.find({ it.name.equals("websiteRelease") })
|
||||||
|
File signedRelease = signProductionRelease(variant)
|
||||||
|
assembleWebsiteDescriptor(variant, signedRelease);
|
||||||
|
}
|
||||||
|
|
||||||
tasks.whenTaskAdded { task ->
|
tasks.whenTaskAdded { task ->
|
||||||
if (task.name.equals("lint")) {
|
if (task.name.equals("lint")) {
|
||||||
task.enabled = false
|
task.enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task.name.equals("assembleWebsiteDebug") ||
|
if (task.name.equals("assemblePlayRelease")) {
|
||||||
task.name.equals("assembleWebsiteRelease"))
|
task.finalizedBy signProductionPlayRelease
|
||||||
{
|
}
|
||||||
task.finalizedBy assembleWebsiteDescriptor
|
|
||||||
|
if (task.name.equals("assembleWebsiteRelease")) {
|
||||||
|
task.finalizedBy signProductionWebsiteRelease
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,28 +478,3 @@ def getLastCommitTimestamp() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def Properties props = new Properties()
|
|
||||||
def propFile = new File('signing.properties')
|
|
||||||
|
|
||||||
if (propFile.canRead()){
|
|
||||||
props.load(new FileInputStream(propFile))
|
|
||||||
|
|
||||||
if (props !=null &&
|
|
||||||
props.containsKey('STORE_FILE') &&
|
|
||||||
props.containsKey('STORE_PASSWORD') &&
|
|
||||||
props.containsKey('KEY_ALIAS') &&
|
|
||||||
props.containsKey('KEY_PASSWORD'))
|
|
||||||
{
|
|
||||||
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
|
|
||||||
android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
|
|
||||||
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
|
|
||||||
android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
|
|
||||||
} else {
|
|
||||||
println 'signing.properties found but some entries are missing'
|
|
||||||
android.buildTypes.release.signingConfig = null
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
println 'signing.properties not found'
|
|
||||||
android.buildTypes.release.signingConfig = null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
9
buildSrc/build.gradle
Normal file
9
buildSrc/build.gradle
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
apply plugin: 'java-gradle-plugin'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile group: 'com.android.tools.build', name: 'apksig', version: '2.3.0'
|
||||||
|
}
|
141
buildSrc/src/main/java/org/signal/signing/ApkSignerUtil.java
Normal file
141
buildSrc/src/main/java/org/signal/signing/ApkSignerUtil.java
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
5
pkcs11.config
Normal file
5
pkcs11.config
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
name = OpenSC-PKCS11
|
||||||
|
description = SunPKCS11 via OpenSC
|
||||||
|
library = /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
|
||||||
|
slotListIndex = 0
|
||||||
|
|
Loading…
Add table
Reference in a new issue