fastlane-plugin-match_keystore 0.1.9 → 0.1.14

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42c7915b7d46b87470d41acfd251257c04ce89460ac60a08318404530a1fbd24
4
- data.tar.gz: 07f280f892d41adee35f025218da2a1eeb345f17c9cf27d2d027246f4b248940
3
+ metadata.gz: d5b1878b6e0f6453f4c62eab321b2080210dcaa93fafcb04b14ac9819e7f996e
4
+ data.tar.gz: 18748d40e3d7c6dbe63179c8bbc726b5284049c25349d28c5ad1e76d529c21de
5
5
  SHA512:
6
- metadata.gz: 85ff5b2dda6f5a38714a09aea05fb279ec87015e970860ac3238f10280d233195936b84ef617d6bfafca36ee9d164f4a1c6267d8ea76e75305a40f4d44b55627
7
- data.tar.gz: f8e3fba2336ef34b35300d38535d2f5fbb5798af9efc50f268fa33a7a05282157f1a46755953eac2d99b362642560b8dc9e7b6255377a13e0fae9b5ebcfcd60d
6
+ metadata.gz: 31e657e39efa81b9141182a75f8df3ee5cc1704a6b1ed2b8526d50ee1e361aaf7d74ed3b8a04771e323a215850051eba4b3f6f932c9808ec8cf57bc70ad894f7
7
+ data.tar.gz: 1dce9a11c73f7921cea57a5953b7b64b2497bb401864cc906386617f342f3792a8c360d81d6709c3538b4282b6e9462c3fb7d5a87b215cebaffc950448bf6d76
data/README.md CHANGED
@@ -24,13 +24,18 @@ The keystore properties are encrypted with AES in order to secure sensitive data
24
24
 
25
25
  ```ruby
26
26
  lane :release_and_sign do |options|
27
- gradle(task: "clean")
27
+ gradle(task: "clean")
28
28
  gradle(task: 'assemble', build_type: 'Release')
29
29
 
30
30
  signed_apk_path = match_keystore(
31
31
  git_url: "https://github.com/<GITHUB_USERNAME>/keystores.git", # Please use a private Git repository !
32
32
  package_name: "com.your.package.name",
33
33
  apk_path: "/app/build/outputs/apk/app-release.apk" # Or path without APK: /app/build/outputs/apk/
34
+ # Optional:
35
+ match_secret: "A-very-str0ng-password!", # The secret use to encrypt/decrypt Keystore passwords on Git repo (for CI)
36
+ existing_keystore: "assets/existing-keystore.jks", # Optional, if needed to import an existing keystore
37
+ override_keystore: true, # Optional, override an existing Keystore on Git repo
38
+ keystore_data: "assets/keystore.json" # Optional, all data required to create a new Keystore (use to bypass prompt)
34
39
  )
35
40
 
36
41
  # Return the path of signed APK (useful for other lanes such as `publish_to_firebase`, `upload_to_play_store`)
@@ -1,6 +1,8 @@
1
1
  require 'fastlane/action'
2
2
  require 'fileutils'
3
3
  require 'os'
4
+ require 'json'
5
+ require 'digest'
4
6
  require_relative '../helper/match_keystore_helper'
5
7
 
6
8
  module Fastlane
@@ -13,6 +15,17 @@ module Fastlane
13
15
 
14
16
  class MatchKeystoreAction < Action
15
17
 
18
+ def self.to_md5(value)
19
+ hash_value = Digest::MD5.hexdigest value
20
+ hash_value
21
+ end
22
+
23
+ def self.load_json(json_path)
24
+ file = File.read(json_path)
25
+ data_hash = JSON.parse(file)
26
+ data_hash
27
+ end
28
+
16
29
  def self.load_properties(properties_filename)
17
30
  properties = {}
18
31
  File.open(properties_filename, 'r') do |properties_file|
@@ -42,7 +55,7 @@ module Fastlane
42
55
 
43
56
  def self.get_build_tools
44
57
  android_home = self.get_android_home()
45
- build_tools_root = android_home + '/build-tools'
58
+ build_tools_root = File.join(android_home, '/build-tools')
46
59
 
47
60
  sub_dirs = Dir.glob(File.join(build_tools_root, '*', ''))
48
61
  build_tools_last_version = ''
@@ -62,40 +75,45 @@ module Fastlane
62
75
  end
63
76
 
64
77
  def self.gen_key(key_path, password)
65
- `rm -f #{key_path}`
66
- `echo "#{password}" | openssl dgst -sha512 | awk '{print $2}' | cut -c1-128 > #{key_path}`
78
+ `rm -f '#{key_path}'`
79
+ `echo "#{password}" | openssl dgst -sha512 | awk '{print $2}' | cut -c1-128 > '#{key_path}'`
67
80
  end
68
81
 
69
82
  def self.encrypt_file(clear_file, encrypt_file, key_path)
70
- `rm -f #{encrypt_file}`
71
- `openssl enc -aes-256-cbc -salt -pbkdf2 -in #{clear_file} -out #{encrypt_file} -pass file:#{key_path}`
83
+ `rm -f '#{encrypt_file}'`
84
+ `openssl enc -aes-256-cbc -salt -pbkdf2 -in '#{clear_file}' -out '#{encrypt_file}' -pass file:'#{key_path}'`
72
85
  end
73
86
 
74
87
  def self.decrypt_file(encrypt_file, clear_file, key_path)
75
- `rm -f #{clear_file}`
76
- `openssl enc -d -aes-256-cbc -pbkdf2 -in #{encrypt_file} -out #{clear_file} -pass file:#{key_path}`
88
+ `rm -f '#{clear_file}'`
89
+ `openssl enc -d -aes-256-cbc -pbkdf2 -in '#{encrypt_file}' -out '#{clear_file}' -pass file:'#{key_path}'`
77
90
  end
78
91
 
79
92
  def self.sign_apk(apk_path, keystore_path, key_password, alias_name, alias_password, zip_align)
80
93
 
81
94
  build_tools_path = self.get_build_tools()
95
+ UI.message("Build-tools path: #{build_tools_path}")
82
96
 
83
97
  # https://developer.android.com/studio/command-line/zipalign
84
98
  if zip_align == true
85
99
  apk_path_aligned = apk_path.gsub(".apk", "-aligned.apk")
86
- `rm -f #{apk_path_aligned}`
87
- `#{build_tools_path}zipalign 4 #{apk_path} #{apk_path_aligned}`
100
+ `rm -f '#{apk_path_aligned}'`
101
+ UI.message("Aligning APK (zipalign): #{apk_path_aligned}")
102
+ `#{build_tools_path}zipalign -f -v 4 '#{apk_path}' '#{apk_path_aligned}'`
88
103
  else
104
+ UI.message("No zip align!")
89
105
  apk_path_aligned = apk_path
90
106
  end
107
+ apk_path_signed = apk_path.gsub(".apk", "-signed.apk")
108
+ apk_path_signed = apk_path_signed.gsub("unsigned", "")
109
+ apk_path_signed = apk_path_signed.gsub("--", "-")
91
110
 
92
111
  # https://developer.android.com/studio/command-line/apksigner
93
- apk_path_signed = apk_path.gsub(".apk", "-signed.apk")
94
- `rm -f #{apk_path_signed}`
95
- `#{build_tools_path}apksigner sign --ks #{keystore_path} --ks-key-alias '#{alias_name}' --ks-pass pass:'#{alias_password}' --key-pass pass:'#{key_password}' --v1-signing-enabled true --v2-signing-enabled true --out #{apk_path_signed} #{apk_path_aligned}`
96
-
97
- `#{build_tools_path}apksigner verify #{apk_path_signed}`
98
- `rm -f #{apk_path_aligned}`
112
+ `rm -f '#{apk_path_signed}'`
113
+ `#{build_tools_path}apksigner sign --ks '#{keystore_path}' --ks-key-alias '#{alias_name}' --ks-pass pass:'#{key_password}' --key-pass pass:'#{alias_password}' --v1-signing-enabled true --v2-signing-enabled true --out '#{apk_path_signed}' '#{apk_path_aligned}'`
114
+
115
+ `#{build_tools_path}apksigner verify '#{apk_path_signed}'`
116
+ `rm -f '#{apk_path_aligned}'`
99
117
 
100
118
  apk_path_signed
101
119
  end
@@ -107,6 +125,11 @@ module Fastlane
107
125
 
108
126
  def self.resolve_apk_path(apk_path)
109
127
 
128
+ # Set default APK path if not set:
129
+ if apk_path.to_s.strip.empty?
130
+ apk_path = '/app/build/outputs/apk/'
131
+ end
132
+
110
133
  if !apk_path.to_s.end_with?(".apk")
111
134
 
112
135
  if !File.directory?(apk_path)
@@ -134,15 +157,28 @@ module Fastlane
134
157
  apk_path
135
158
  end
136
159
 
160
+ def self.prompt2(params)
161
+ # UI.message("prompt2: #{params[:value]}")
162
+ if params[:value].to_s.empty?
163
+ return_value = other_action.prompt(text: params[:text], secure_text: params[:secure_text], ci_input: params[:ci_input])
164
+ else
165
+ return_value = params[:value]
166
+ end
167
+ return_value
168
+ end
169
+
137
170
  def self.run(params)
138
171
 
172
+ # Get input parameters:
139
173
  git_url = params[:git_url]
140
174
  package_name = params[:package_name]
141
175
  apk_path = params[:apk_path]
142
176
  existing_keystore = params[:existing_keystore]
143
- ci_password = params[:ci_password]
177
+ match_secret = params[:match_secret]
144
178
  override_keystore = params[:override_keystore]
179
+ keystore_data = params[:keystore_data]
145
180
 
181
+ # Init constants:
146
182
  keystore_name = 'keystore.jks'
147
183
  properties_name = 'keystore.properties'
148
184
  keystore_info_name = 'keystore.txt'
@@ -158,52 +194,83 @@ module Fastlane
158
194
  # Check OpenSSL:
159
195
  self.check_openssl_version
160
196
 
197
+ # Init workign local directory:
161
198
  dir_name = ENV['HOME'] + '/.match_keystore'
162
199
  unless File.directory?(dir_name)
163
200
  UI.message("Creating '.match_keystore' working directory...")
164
201
  FileUtils.mkdir_p(dir_name)
165
202
  end
166
203
 
167
- key_path = dir_name + '/key.hex'
204
+ # Init 'security password' for AES encryption:
205
+ key_name = "#{self.to_md5(git_url)}.hex"
206
+ key_path = File.join(dir_name, key_name)
207
+ # UI.message(key_path)
168
208
  if !File.file?(key_path)
169
- security_password = other_action.prompt(text: "Security password: ", secure_text: true, ci_input: ci_password)
209
+ security_password = self.prompt2(text: "Security password: ", secure_text: true, value: match_secret)
170
210
  if security_password.to_s.strip.empty?
171
- raise "Security password is not defined! Please use 'ci_password' parameter for CI."
211
+ raise "Security password is not defined! Please use 'match_secret' parameter for CI."
172
212
  end
173
- UI.message "Generating security key..."
213
+ UI.message "Generating security key '#{key_name}'..."
174
214
  self.gen_key(key_path, security_password)
175
215
  end
176
216
 
217
+ # Check is 'security password' is well initialized:
177
218
  tmpkey = self.get_file_content(key_path).strip
178
219
  if tmpkey.length == 128
179
- UI.message "Security key initialized"
220
+ UI.message "Security key '#{key_name}' initialized"
180
221
  else
181
- raise "The security key format is invalid, or not initialized!"
222
+ raise "The security key '#{key_name}' is malformed, or not initialized!"
182
223
  end
183
224
 
184
- repo_dir = dir_name + '/repo'
225
+ # Create repo directory to sync remote Keystores repository:
226
+ repo_dir = File.join(dir_name, self.to_md5(git_url))
227
+ # UI.message(repo_dir)
185
228
  unless File.directory?(repo_dir)
186
229
  UI.message("Creating 'repo' directory...")
187
230
  FileUtils.mkdir_p(repo_dir)
188
231
  end
189
232
 
190
- gitDir = repo_dir + '/.git'
191
- unless File.directory?(gitDir)
233
+ # Cloning/pulling GIT remote repository:
234
+ gitDir = File.join(repo_dir, '/.git')
235
+ if !File.directory?(gitDir)
192
236
  UI.message("Cloning remote Keystores repository...")
193
237
  puts ''
194
238
  `git clone #{git_url} #{repo_dir}`
195
239
  puts ''
240
+ else
241
+ UI.message("Pulling remote Keystores repository...")
242
+ puts ''
243
+ `cd #{repo_dir} && git pull`
244
+ puts ''
196
245
  end
197
246
 
198
- keystoreAppDir = repo_dir + '/' + package_name
247
+ # Create sub-directory for Android app:
248
+ if package_name.to_s.strip.empty?
249
+ raise "Package name is not defined!"
250
+ end
251
+ keystoreAppDir = File.join(repo_dir, package_name)
199
252
  unless File.directory?(keystoreAppDir)
200
253
  UI.message("Creating '#{package_name}' keystore directory...")
201
254
  FileUtils.mkdir_p(keystoreAppDir)
202
255
  end
203
256
 
204
- keystore_path = keystoreAppDir + '/' + keystore_name
205
- properties_path = keystoreAppDir + '/' + properties_name
206
- properties_encrypt_path = keystoreAppDir + '/' + properties_encrypt_name
257
+ keystore_path = File.join(keystoreAppDir, keystore_name)
258
+ properties_path = File.join(keystoreAppDir, properties_name)
259
+ properties_encrypt_path = File.join(keystoreAppDir, properties_encrypt_name)
260
+
261
+ # Load parameters from JSON for CI or Unit Tests:
262
+ if keystore_data != nil && File.file?(keystore_data)
263
+ data_json = self.load_json(keystore_data)
264
+ data_key_password = data_json['key_password']
265
+ data_alias_name = data_json['alias_name']
266
+ data_alias_password = data_json['alias_password']
267
+ data_full_name = data_json['full_name']
268
+ data_org_unit = data_json['org_unit']
269
+ data_org = data_json['org']
270
+ data_city_locality = data_json['city_locality']
271
+ data_state_province = data_json['state_province']
272
+ data_country = data_json['country']
273
+ end
207
274
 
208
275
  # Create keystore with command
209
276
  override_keystore = !existing_keystore.to_s.strip.empty? && File.file?(existing_keystore)
@@ -213,15 +280,15 @@ module Fastlane
213
280
  FileUtils.remove_dir(keystore_path)
214
281
  end
215
282
 
216
- key_password = other_action.prompt(text: "Keystore Password: ")
283
+ key_password = self.prompt2(text: "Keystore Password: ", value: data_key_password)
217
284
  if key_password.to_s.strip.empty?
218
285
  raise "Keystore Password is not definined!"
219
286
  end
220
- alias_name = other_action.prompt(text: "Keystore Alias name: ")
287
+ alias_name = self.prompt2(text: "Keystore Alias name: ", value: data_alias_name)
221
288
  if alias_name.to_s.strip.empty?
222
289
  raise "Keystore Alias name is not definined!"
223
290
  end
224
- alias_password = other_action.prompt(text: "Keystore Alias password: ")
291
+ alias_password = self.prompt2(text: "Keystore Alias password: ", value: data_alias_password)
225
292
  if alias_password.to_s.strip.empty?
226
293
  raise "Keystore Alias password is not definined!"
227
294
  end
@@ -230,20 +297,20 @@ module Fastlane
230
297
  if !File.file?(existing_keystore)
231
298
  UI.message("Generating Android Keystore...")
232
299
 
233
- full_name = other_action.prompt(text: "Certificate First and Last Name: ")
234
- org_unit = other_action.prompt(text: "Certificate Organisation Unit: ")
235
- org = other_action.prompt(text: "Certificate Organisation: ")
236
- city_locality = other_action.prompt(text: "Certificate City or Locality: ")
237
- state_province = other_action.prompt(text: "Certificate State or Province: ")
238
- country = other_action.prompt(text: "Certificate Country Code (XX): ")
300
+ full_name = self.prompt2(text: "Certificate First and Last Name: ", value: data_full_name)
301
+ org_unit = self.prompt2(text: "Certificate Organisation Unit: ", value: data_org_unit)
302
+ org = self.prompt2(text: "Certificate Organisation: ", value: data_org)
303
+ city_locality = self.prompt2(text: "Certificate City or Locality: ", value: data_city_locality)
304
+ state_province = self.prompt2(text: "Certificate State or Province: ", value: data_state_province)
305
+ country = self.prompt2(text: "Certificate Country Code (XX): ", value: data_country)
239
306
 
240
307
  keytool_parts = [
241
308
  "keytool -genkey -v",
242
- "-keystore #{keystore_path}",
243
- "-alias #{alias_name}",
309
+ "-keystore '#{keystore_path}'",
310
+ "-alias '#{alias_name}'",
244
311
  "-keyalg RSA -keysize 2048 -validity 10000",
245
- "-storepass #{alias_password} ",
246
- "-keypass #{key_password}",
312
+ "-storepass '#{alias_password}'",
313
+ "-keypass '#{key_password}'",
247
314
  "-dname \"CN=#{full_name}, OU=#{org_unit}, O=#{org}, L=#{city_locality}, S=#{state_province}, C=#{country}\"",
248
315
  ]
249
316
  sh keytool_parts.join(" ")
@@ -258,6 +325,7 @@ module Fastlane
258
325
  FileUtils.remove_dir(properties_path)
259
326
  end
260
327
 
328
+ # Build URL:
261
329
  store_file = git_url + '/' + package_name + '/' + keystore_name
262
330
 
263
331
  out_file = File.new(properties_path, "w")
@@ -271,15 +339,17 @@ module Fastlane
271
339
  File.delete(properties_path)
272
340
 
273
341
  # Print Keystore data in repo:
274
- keystore_info_path = keystoreAppDir + '/' + keystore_info_name
275
- `yes "" | keytool -list -v -keystore #{keystore_path} > #{keystore_info_path}`
342
+ keystore_info_path = File.join(keystoreAppDir, keystore_info_name)
343
+ `yes "" | keytool -list -v -keystore '#{keystore_path}' -storepass '#{key_password}' > '#{keystore_info_path}'`
276
344
 
277
345
  UI.message("Upload new Keystore to remote repository...")
278
- `cd #{repo_dir} && git add .`
279
- `cd #{repo_dir} && git commit -m "[ADD] Keystore for app '#{package_name}'."`
280
- `cd #{repo_dir} && git push`
346
+ puts ''
347
+ `cd '#{repo_dir}' && git add .`
348
+ `cd '#{repo_dir}' && git commit -m "[ADD] Keystore for app '#{package_name}'."`
349
+ `cd '#{repo_dir}' && git push`
350
+ puts ''
281
351
 
282
- else
352
+ else
283
353
  UI.message "Keystore file already exists, continue..."
284
354
 
285
355
  self.decrypt_file(properties_encrypt_path, properties_path, key_path)
@@ -290,37 +360,40 @@ module Fastlane
290
360
  alias_password = properties['aliasPassword']
291
361
 
292
362
  File.delete(properties_path)
293
-
294
363
  end
295
364
 
365
+ # Resolve path to the APK to sign:
296
366
  output_signed_apk = ''
297
367
  apk_path = self.resolve_apk_path(apk_path)
298
368
 
369
+ # Sign APK:
299
370
  if File.file?(apk_path)
300
371
  UI.message("APK to sign: " + apk_path)
301
372
 
302
373
  if File.file?(keystore_path)
303
374
 
304
375
  UI.message("Signing the APK...")
376
+ puts ''
305
377
  output_signed_apk = self.sign_apk(
306
378
  apk_path,
307
379
  keystore_path,
308
380
  key_password,
309
381
  alias_name,
310
382
  alias_password,
311
- true
383
+ true # Zip align
312
384
  )
385
+ puts ''
313
386
  end
314
387
  else
315
388
  UI.message("No APK file found to sign!")
316
389
  end
317
390
 
391
+ # Prepare contect shared values for next lanes:
318
392
  Actions.lane_context[SharedValues::MATCH_KEYSTORE_PATH] = keystore_path
319
393
  Actions.lane_context[SharedValues::MATCH_KEYSTORE_ALIAS_NAME] = alias_name
320
394
  Actions.lane_context[SharedValues::MATCH_KEYSTORE_APK_SIGNED] = output_signed_apk
321
395
 
322
396
  output_signed_apk
323
-
324
397
  end
325
398
 
326
399
  def self.description
@@ -363,11 +436,11 @@ module Fastlane
363
436
  FastlaneCore::ConfigItem.new(key: :apk_path,
364
437
  env_name: "MATCH_KEYSTORE_APK_PATH",
365
438
  description: "Path of the APK file to sign",
366
- optional: false,
439
+ optional: true,
367
440
  type: String),
368
- FastlaneCore::ConfigItem.new(key: :ci_password,
369
- env_name: "MATCH_KEYSTORE_CI_PASSWORD",
370
- description: "Password to decrypt keystore.properties file (CI)",
441
+ FastlaneCore::ConfigItem.new(key: :match_secret,
442
+ env_name: "MATCH_KEYSTORE_SECRET",
443
+ description: "Secret to decrypt keystore.properties file (CI)",
371
444
  optional: true,
372
445
  type: String),
373
446
  FastlaneCore::ConfigItem.new(key: :existing_keystore,
@@ -379,7 +452,12 @@ module Fastlane
379
452
  env_name: "MATCH_KEYSTORE_OVERRIDE",
380
453
  description: "Override an existing Keystore (false by default)",
381
454
  optional: true,
382
- type: Boolean)
455
+ type: Boolean),
456
+ FastlaneCore::ConfigItem.new(key: :keystore_data,
457
+ env_name: "MATCH_KEYSTORE_JSON_PATH",
458
+ description: "Required data to import an existing keystore, or create a new one",
459
+ optional: true,
460
+ type: String)
383
461
  ]
384
462
  end
385
463
 
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module MatchKeystore
3
- VERSION = "0.1.9"
3
+ VERSION = "0.1.14"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-match_keystore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher NEY
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-11 00:00:00.000000000 Z
11
+ date: 2020-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -167,7 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
167
  - !ruby/object:Gem::Version
168
168
  version: '0'
169
169
  requirements: []
170
- rubygems_version: 3.0.6
170
+ rubygems_version: 3.0.3
171
171
  signing_key:
172
172
  specification_version: 4
173
173
  summary: Easily sync your Android keystores across your team