Skip to content

Commit 48cd0ba

Browse files
committed
Hygiene: use repo keystore for debug, dev, & alpha
* Add a public keystore and properties to repo. The name "repo" is used to avoid confusion with build flavors, build types, and key types. i.e., the repo keystore is applicable to more than the dev flavor, so don't call it "dev", more than the debug build type, so don't call it "debug", and like any keystore, contains both public and private keys so don't call it "public". Properties could be hardcoded in Groovy, but a properties file allows for homogeneous handling of both repo and prod signing configurations. * Change signing configurations: * Override signingConfigs.debug to use repo. It is a little confusing to call the repo signing configuration "debug" but we don't want the implicit debug configuration leaking in. The alternative is to have a dummy or redundant debig configuration, each of which is unhappy. * Always use repo keystore for debug build types, and dev and alpha flavors. Always use prod keystore for all other release flavors. For dev and alpha builds, that means signatures are tied to package names not build types. e.g., you can install a dev release build on top of a dev debug build without uninstalling. A dev keystore is recommend by Jake Wharton[0]. Using this keystore for alpha builds as well keeps security concerns out of our Jenkins alpha job. [0] https://twitter.com/jakewharton/status/554242089236828160 * Dev and alpha builds have a new signature. Previously distributed dev and alpha builds must be uninstalled which will cause data loss. * Everyone can install each others' apps without uninstalling. * Use lowercase.dot instead of UPPERCASE_SNAKE for keystore pre-dex properties. UPPERCASE_SNAKE seems unconventional[0]. [0] https://android.googlesource.com/platform/build/+/master/tools/buildinfo.sh TODOs once merged: * Send courtesy announcement alerting users that an alpha uninstall is necessary. * Update locale ~/.sign/signing.properties. Notes on keystore generation # Create a new keystore. Note: some tools guard against usage of # keystores without passwords. keytool \ -keystore repo.keystore \ -keyalg RSA \ -genkeypair \ -alias repo \ -keypass android \ -storepass android \ -dname 'CN=Wikimedia Foundation, OU=Mobile, O=Wikimedia Foundation, L=San Francisco, ST=California, C=US' \ -validity 36524 Notes on keystore validation # Convert to intermediate PKCS12. keytool \ -importkeystore \ -srckeystore repo.keystore \ -destkeystore repo.p12 \ -deststoretype PKCS12 \ -srcstorepass android \ -storepass android \ -keypass android # Convert to PEM for comparison with known good keys. openssl \ pkcs12 \ -in repo.p12 \ -out repo.pem \ -nodes \ -passin pass:android # Do some diffs against a standard Android debug keystores like # Telecine[0] and the release keystore. # [0] https://github.com/JakeWharton/Telecine/blob/master/debug.keystore Notes on signing configuration validation # Build all variants. ./gradlew -Ppre.dex=false clean assemble # Verify APK signature. # $1 keystore # $2 key alias # $3 apk verify() { jarsigner \ -verify \ -strict \ -sigalg MD5withRSA \ -digestalg SHA1 \ -keystore "$1" \ "$3" \ "$2" } alias verify-prod='verify prod.keystore prod' alias verify-repo='verify repo.keystore repo' # Verify all repo variants. ls -1 app/build/outputs/apk/{*-debug*,*-dev-*,*-alpha-*} | sort -u | while IFS= read -r -d $'\n' i; do echo "$i" && verify-repo "$i" done # Verify all prod variants. ls -1 app/build/outputs/apk/* | grep -Ev 'debug|dev|alpha' | while IFS= read -r -d $'\n' i; do echo "$i" && verify-prod "$i" done # Explicitly verify release prod variant. verify-prod app/build/outputs/apk/app-prod-release.apk && ! verify-repo app/build/outputs/apk/app-prod-release.apk # Be paranoid. Download known good APK and verify. scp \ caesium:/srv/org/wikimedia/releases/mobile/android/wikipedia/stable/wikipedia-2.0.108-releasesprod-2015-08-04.apk \ . && verify-prod wikipedia-2.0.108-releasesprod-2015-08-04.apk && ! verify-repo wikipedia-2.0.108-releasesprod-2015-08-04.apk # Verify app installs and launches devDebyg variant from IDE. # Verify IDE respects repo key configuration. adb install -r app/build/outputs/apk/app-dev-release.apk # Test no prod keystore with dev and alpha debug and release. ./gradlew -Ppre.dex=false clean assembleDebug assemble{Dev,Alpha}Release && ls app/build/outputs/apk Change-Id: I7d562413e9f13be1ea514e47ffb8af94858e47aa
1 parent 7662379 commit 48cd0ba

File tree

4 files changed

+53
-29
lines changed

4 files changed

+53
-29
lines changed

app/build.gradle

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,18 @@ apply from: '../config/quality.gradle'
55

66
import com.android.ddmlib.DdmPreferences
77

8-
final int ADB_TIMEOUT = 5 * 60 * 1000
8+
import java.util.concurrent.TimeUnit
9+
10+
// Copy the signing.properties.sample file to ~/.sign/signing.properties and adjust the values.
11+
final File PROD_PROPS_FILE = new File(System.getProperty('user.home'), '.sign/signing.properties')
12+
final File REPO_PROPS_FILE = new File('repo.properties')
13+
final Properties PROD_PROPS = loadProperties(PROD_PROPS_FILE)
14+
final Properties REPO_PROPS = loadProperties(REPO_PROPS_FILE)
15+
16+
final int ADB_TIMEOUT = TimeUnit.MINUTES.toMillis(5)
917
final boolean continuousIntegrationBuild = System.getenv('JENKINS_HOME') != null
10-
final boolean preDexEnabled = hasProperty('preDex') ?
11-
Boolean.valueOf(preDex.toString()) :
18+
final boolean preDexEnabled = hasProperty('pre.dex') ?
19+
Boolean.valueOf(getProperty('pre.dex').toString()) :
1220
!continuousIntegrationBuild
1321
if (!preDexEnabled) {
1422
println 'Pre-dexing disabled.'
@@ -52,43 +60,57 @@ android {
5260
versionCode 108
5361
testApplicationId 'org.wikipedia.test'
5462
}
63+
5564
signingConfigs {
56-
release
65+
prod {
66+
setSigningConfigKey(prod, PROD_PROPS)
67+
}
68+
debug {
69+
setSigningConfigKey(debug, REPO_PROPS)
70+
}
5771
}
72+
5873
buildTypes {
5974
release {
6075
minifyEnabled true
6176
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
62-
signingConfig signingConfigs.release
6377
}
6478
}
79+
6580
productFlavors {
6681
dev {
6782
versionName computeVersionName("dev")
6883
applicationId 'org.wikipedia.dev'
84+
signingConfig signingConfigs.debug
6985
}
7086
prod {
7187
versionName computeVersionName("r")
88+
signingConfig signingConfigs.prod
7289
}
7390
releasesprod {
7491
versionName computeVersionName("releasesprod")
92+
signingConfig signingConfigs.prod
7593
}
7694
alpha {
7795
versionName computeVersionName("alpha")
7896
applicationId 'org.wikipedia.alpha'
97+
signingConfig signingConfigs.debug
7998
}
8099
beta {
81100
versionName computeVersionName("beta")
82101
applicationId 'org.wikipedia.beta'
102+
signingConfig signingConfigs.prod
83103
}
84104
amazon {
85105
versionName computeVersionName("amazon")
106+
signingConfig signingConfigs.prod
86107
}
87108
custom {
88109
versionName computeVersionName(customChannel)
89110
applicationId getProperty('customApplicationId')
90111
// next line is for injecting a custom channel value into the custom/AndroidManifest.xml
91112
manifestPlaceholders = [customChannel:getProperty('customChannel').toString()]
113+
signingConfig signingConfigs.prod
92114
}
93115
}
94116
// while we still have lint errors; remove once those are fixed
@@ -136,25 +158,23 @@ dependencies {
136158
testCompile 'com.squareup.okhttp:mockwebserver:2.4.0'
137159
}
138160

139-
// The next block is for setting the release signing config from a file outside the git repo
140-
// To make release builds work:
141-
// Copy the signing.properties.sample file to ~/.sign/signing.properties and adjust the values.
142-
def Properties props = new Properties()
143-
def propFile = new File(System.getProperty('user.home'), '.sign/signing.properties')
144-
if (propFile.canRead()) {
145-
props.load(new FileInputStream(propFile))
146-
147-
if (props != null && props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&
148-
props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {
149-
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
150-
android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
151-
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
152-
android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
161+
private setSigningConfigKey(config, Properties props) {
162+
if (props) {
163+
config.storeFile = props['keystore'] == null ? null : file(props['keystore'])
164+
config.storePassword = props['store.pass']
165+
config.keyAlias = props['key.alias']
166+
config.keyPassword = props['key.pass']
167+
}
168+
return config
169+
}
170+
171+
private @Nullable Properties loadProperties(File file) {
172+
Properties props = null
173+
if (file.canRead()) {
174+
props = new Properties()
175+
props.load(new FileInputStream(file))
153176
} else {
154-
System.err.println propFile.toString() + ' found but some entries are missing'
155-
android.buildTypes.release.signingConfig = null
177+
System.err.println "\"${file}\" not found"
156178
}
157-
} else {
158-
System.err.println propFile.toString() + ' not found'
159-
android.buildTypes.release.signingConfig = null
160-
}
179+
return props
180+
}

app/signing.properties.sample

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
STORE_FILE=/path/to/your.keystore
2-
STORE_PASSWORD=yourkeystorepass
3-
KEY_ALIAS=projectkeyalias
4-
KEY_PASSWORD=keyaliaspassword
1+
keystore=/path/to/your.keystore
2+
store.pass=KeystorePassword
3+
key.alias=KeyName
4+
key.pass=KeyPassword

repo.keystore

2.25 KB
Binary file not shown.

repo.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
keystore=../repo.keystore
2+
store.pass=android
3+
key.alias=repo
4+
key.pass=android

0 commit comments

Comments
 (0)