fastlane-plugin-match_keystore 0.1.14 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5b1878b6e0f6453f4c62eab321b2080210dcaa93fafcb04b14ac9819e7f996e
4
- data.tar.gz: 18748d40e3d7c6dbe63179c8bbc726b5284049c25349d28c5ad1e76d529c21de
3
+ metadata.gz: 87a32db98559102adaf6fd2b1d7da31143fe593fa0ed170865a1da8b3ef5d4d8
4
+ data.tar.gz: 4c3b0130a705055ad7d9e6c51f8ac1a19c2a4a2df96ddca7011d72275487bb89
5
5
  SHA512:
6
- metadata.gz: 31e657e39efa81b9141182a75f8df3ee5cc1704a6b1ed2b8526d50ee1e361aaf7d74ed3b8a04771e323a215850051eba4b3f6f932c9808ec8cf57bc70ad894f7
7
- data.tar.gz: 1dce9a11c73f7921cea57a5953b7b64b2497bb401864cc906386617f342f3792a8c360d81d6709c3538b4282b6e9462c3fb7d5a87b215cebaffc950448bf6d76
6
+ metadata.gz: 9522a73a0ca9b71adcc82f8d986c39a68d2c7b64f4273df5736933d1a0db43c4e9f6aa3414cfe538c99c513f2d3eeb06efb93e2624844ca9fac045b62f7a989d
7
+ data.tar.gz: 0f7fe8a732b905f6bf751d42e87c622412e1e446561e2e8b0ee534997d562246afbe5deef77827002f8ca3198cf07a490603aef684815ec06f38d9de836a0f6c
@@ -15,11 +15,21 @@ module Fastlane
15
15
 
16
16
  class MatchKeystoreAction < Action
17
17
 
18
+ def self.openssl_path
19
+ path = "/usr/local/opt/openssl@1.1/bin"
20
+ path
21
+ end
22
+
18
23
  def self.to_md5(value)
19
24
  hash_value = Digest::MD5.hexdigest value
20
25
  hash_value
21
26
  end
22
27
 
28
+ def self.sha512(value)
29
+ hash_value = Digest::SHA512.hexdigest value
30
+ hash_value
31
+ end
32
+
23
33
  def self.load_json(json_path)
24
34
  file = File.read(json_path)
25
35
  data_hash = JSON.parse(file)
@@ -66,27 +76,159 @@ module Fastlane
66
76
  build_tools_last_version
67
77
  end
68
78
 
69
- def self.check_openssl_version
70
- output = `openssl version`
71
- if !output.start_with?("OpenSSL")
72
- raise "Please install OpenSSL 1.1.1 at least https://www.openssl.org/"
79
+ def self.check_ssl_version(forceOpenSSL)
80
+ libressl_min = '2.9'
81
+ openssl_min = '1.1.1'
82
+
83
+ openssl = self.openssl(forceOpenSSL)
84
+ output = `#{openssl} version`
85
+ if !output.start_with?("LibreSSL") && !output.start_with?("OpenSSL")
86
+ raise "Please install OpenSSL '#{openssl_min}' at least OR LibreSSL #{libressl_min}' at least"
87
+ end
88
+ UI.message("SSL/TLS protocol library: '#{output.strip!}'")
89
+
90
+ # Check minimum verion:
91
+ vesion = output.to_str.scan(/[0-9\.]{1,}/).first
92
+ UI.message("SSL/TLS protocol version: '#{vesion}'")
93
+ if self.is_libre_ssl(forceOpenSSL)
94
+ if Gem::Version.new(vesion) < Gem::Version.new(libressl_min)
95
+ raise "Minimum version for LibreSSL is '#{libressl_min}', please update it. Use homebrew is your are Mac user, and update ~/.bah_profile or ~/.zprofile"
96
+ end
97
+ else
98
+ if Gem::Version.new(vesion) > Gem::Version.new(openssl_min)
99
+ raise "Minimum version for OpenSSL is '#{openssl_min}' please update it. Use homebrew is your are Mac user, and update ~/.bah_profile or ~/.zprofile"
100
+ end
73
101
  end
74
- UI.message("OpenSSL version: " + output.strip)
102
+
103
+ output.strip
104
+ end
105
+
106
+ def self.openssl(forceOpenSSL)
107
+ if forceOpenSSL
108
+ path = openssl_path
109
+ output = "#{path}/openssl"
110
+ else
111
+ output = "openssl"
112
+ end
113
+ output
114
+ end
115
+
116
+ def self.is_libre_ssl(forceOpenSSL)
117
+ result = false
118
+ openssl = self.openssl(forceOpenSSL)
119
+ output = `#{openssl} version`
120
+ if output.start_with?("LibreSSL")
121
+ result = true
122
+ end
123
+ result
75
124
  end
76
125
 
77
126
  def self.gen_key(key_path, password)
78
127
  `rm -f '#{key_path}'`
79
- `echo "#{password}" | openssl dgst -sha512 | awk '{print $2}' | cut -c1-128 > '#{key_path}'`
128
+ shaValue = self.sha512(password)
129
+ `echo "#{shaValue}" > '#{key_path}'`
80
130
  end
81
131
 
82
- def self.encrypt_file(clear_file, encrypt_file, key_path)
132
+ def self.encrypt_file(clear_file, encrypt_file, key_path, forceOpenSSL)
83
133
  `rm -f '#{encrypt_file}'`
84
- `openssl enc -aes-256-cbc -salt -pbkdf2 -in '#{clear_file}' -out '#{encrypt_file}' -pass file:'#{key_path}'`
134
+ libre_ssl = self.is_libre_ssl(forceOpenSSL)
135
+ openssl_bin = self.openssl(forceOpenSSL)
136
+ `#{openssl_bin} enc -aes-256-cbc -salt -pbkdf2 -in '#{clear_file}' -out '#{encrypt_file}' -pass file:'#{key_path}'`
85
137
  end
86
138
 
87
- def self.decrypt_file(encrypt_file, clear_file, key_path)
139
+ def self.decrypt_file(encrypt_file, clear_file, key_path, forceOpenSSL)
88
140
  `rm -f '#{clear_file}'`
89
- `openssl enc -d -aes-256-cbc -pbkdf2 -in '#{encrypt_file}' -out '#{clear_file}' -pass file:'#{key_path}'`
141
+ libre_ssl = self.is_libre_ssl(forceOpenSSL)
142
+ openssl_bin = self.openssl(forceOpenSSL)
143
+ `#{openssl_bin} enc -d -aes-256-cbc -pbkdf2 -in '#{encrypt_file}' -out '#{clear_file}' -pass file:'#{key_path}'`
144
+ end
145
+
146
+ def self.assert_equals(test_name, excepted, value)
147
+ puts "Unit Test: #{test_name}"
148
+ if value != excepted
149
+ puts " - Excepted: #{excepted}"
150
+ puts " - Returned: #{value}"
151
+ raise "Unit Test - #{test_name} error!"
152
+ else
153
+ puts " - OK"
154
+ end
155
+ end
156
+
157
+ def self.test_security
158
+
159
+ self.check_ssl_version(false)
160
+
161
+ # Clear temp files
162
+ temp_dir = File.join(Dir.pwd, '/temp/')
163
+ FileUtils.rm_rf(temp_dir)
164
+ Dir.mkdir(temp_dir)
165
+
166
+ fakeValue = "4esfsf4dsfds!efs5ZDOJF"
167
+ # Check MD5
168
+ md5value = self.to_md5(fakeValue)
169
+ excepted = "1c815cd208fe08076c9e7b6595d121d1"
170
+ self.assert_equals("MD5", excepted, md5value)
171
+
172
+ # Check SHA-512
173
+ shaValue = self.sha512(fakeValue)
174
+ excepted = "cc6a7b0d89cc61c053f7018a305672bdb82bc07e5015f64bb063d9662be4ec81ec8afa819b009de266482b6bd56b7068def2524c32f5b5d4d9db49ee4578499d"
175
+ self.assert_equals("SHA-512", excepted, shaValue)
176
+
177
+ # Check SHA-512-File
178
+ key_path = File.join(Dir.pwd, '/temp/key.txt')
179
+ self.gen_key(key_path, fakeValue)
180
+ shaValue = self.get_file_content(key_path).strip!
181
+ excepted = "cc6a7b0d89cc61c053f7018a305672bdb82bc07e5015f64bb063d9662be4ec81ec8afa819b009de266482b6bd56b7068def2524c32f5b5d4d9db49ee4578499d"
182
+ self.assert_equals("SHA-512-File", excepted, shaValue)
183
+
184
+
185
+ # Check LibreSSL
186
+ result = self.is_libre_ssl(false)
187
+ self.assert_equals("Is-LibreSSL", true, result)
188
+ result = self.is_libre_ssl(true)
189
+ self.assert_equals("Is-LibreSSL", false, result)
190
+
191
+ # Encrypt OpenSSL
192
+ clear_file = File.join(Dir.pwd, '/temp/clear.txt')
193
+ openssl_encrypt_file = File.join(Dir.pwd, '/temp/openssl_encrypted.txt')
194
+ self.content_to_file(clear_file, fakeValue)
195
+ self.encrypt_file(clear_file, openssl_encrypt_file, key_path, true)
196
+ result = File.file?(openssl_encrypt_file) && File.size(openssl_encrypt_file) > 10
197
+ self.assert_equals("Encrypt-OpenSSL", true, result)
198
+
199
+ # Encrypt LibreSSL
200
+ encrypt_file_libre = File.join(Dir.pwd, '/temp/libressl_encrypted.txt')
201
+ self.content_to_file(clear_file, fakeValue)
202
+ self.encrypt_file(clear_file, encrypt_file_libre, key_path, false)
203
+ result = File.file?(encrypt_file_libre) && File.size(encrypt_file_libre) > 10
204
+ self.assert_equals("Encrypt-LibreSSL", true, result)
205
+
206
+ # exit!
207
+
208
+ # Decrypt OpenSSL (from OpenSSL)
209
+ openssl_clear_file = File.join(Dir.pwd, '/temp/openssl_clear.txt')
210
+ self.decrypt_file(openssl_encrypt_file, openssl_clear_file, key_path, true)
211
+ decrypted = self.get_file_content(openssl_clear_file).strip!
212
+ self.assert_equals("Decrypt-OpenSSL", fakeValue, decrypted)
213
+
214
+ # Decrypt LibreSSL (from LibreSSL)
215
+ libressl_clear_file = File.join(Dir.pwd, '/temp/libressl_clear.txt')
216
+ self.decrypt_file(encrypt_file_libre, libressl_clear_file, key_path, false)
217
+ decrypted = self.get_file_content(libressl_clear_file).strip!
218
+ self.assert_equals("Decrypt-LibreSSL", fakeValue, decrypted)
219
+
220
+ # Decrypt LibreSSL (from OpenSSL)
221
+ libressl_clear_file = File.join(Dir.pwd, '/temp/libressl_from_openssl_clear.txt')
222
+ self.decrypt_file(openssl_encrypt_file, libressl_clear_file, key_path, false)
223
+ decrypted = self.get_file_content(libressl_clear_file).strip!
224
+ self.assert_equals("Decrypt-LibreSSL-from-OpenSSL", fakeValue, decrypted)
225
+
226
+ # Decrypt OpenSSL (from LibreSSL)
227
+ openssl_clear_file = File.join(Dir.pwd, '/temp/openssl_from_libressl_clear.txt')
228
+ self.decrypt_file(encrypt_file_libre, openssl_clear_file, key_path, true)
229
+ decrypted = self.get_file_content(openssl_clear_file).strip!
230
+ self.assert_equals("Decrypt-OpenSSL-from-LibreSSL", fakeValue, decrypted)
231
+
90
232
  end
91
233
 
92
234
  def self.sign_apk(apk_path, keystore_path, key_password, alias_name, alias_password, zip_align)
@@ -98,8 +240,15 @@ module Fastlane
98
240
  if zip_align == true
99
241
  apk_path_aligned = apk_path.gsub(".apk", "-aligned.apk")
100
242
  `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}'`
243
+ UI.message("Aligning APK (zipalign): #{apk_path}")
244
+ output = `#{build_tools_path}zipalign -v 4 '#{apk_path}' '#{apk_path_aligned}'`
245
+ puts ""
246
+ puts output
247
+
248
+ if !File.file?(apk_path_aligned)
249
+ raise "Aligned APK not exists!"
250
+ end
251
+
103
252
  else
104
253
  UI.message("No zip align!")
105
254
  apk_path_aligned = apk_path
@@ -110,12 +259,36 @@ module Fastlane
110
259
 
111
260
  # https://developer.android.com/studio/command-line/apksigner
112
261
  `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}'`
262
+ UI.message("Signing APK: #{apk_path_aligned}")
263
+ output = `#{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 --v4-signing-enabled false --out '#{apk_path_signed}' '#{apk_path_aligned}'`
264
+ puts ""
265
+ puts output
266
+
267
+ UI.message("Verifing APK signature: #{apk_path_signed}")
268
+ output = `#{build_tools_path}apksigner verify '#{apk_path_signed}'`
269
+ puts ""
270
+ puts output
116
271
  `rm -f '#{apk_path_aligned}'`
117
272
 
118
273
  apk_path_signed
274
+ end
275
+
276
+ def self.resolve_dir(path)
277
+ if !File.directory?(path)
278
+ path = File.join(Dir.pwd, path)
279
+ end
280
+ path
281
+ end
282
+
283
+ def self.resolve_file(path)
284
+ if !File.file?(path)
285
+ path = File.join(Dir.pwd, path)
286
+ end
287
+ path
288
+ end
289
+
290
+ def self.content_to_file(file_path, content)
291
+ `echo #{content} > #{file_path}`
119
292
  end
120
293
 
121
294
  def self.get_file_content(file_path)
@@ -132,9 +305,7 @@ module Fastlane
132
305
 
133
306
  if !apk_path.to_s.end_with?(".apk")
134
307
 
135
- if !File.directory?(apk_path)
136
- apk_path = File.join(Dir.pwd, apk_path)
137
- end
308
+ apk_path = self.resolve_dir(apk_path)
138
309
 
139
310
  pattern = File.join(apk_path, '*.apk')
140
311
  files = Dir[pattern]
@@ -147,11 +318,7 @@ module Fastlane
147
318
  end
148
319
 
149
320
  else
150
-
151
- if !File.file?(apk_path)
152
- apk_path = File.join(Dir.pwd, apk_path)
153
- end
154
-
321
+ apk_path = self.resolve_file(apk_path)
155
322
  end
156
323
 
157
324
  apk_path
@@ -177,6 +344,14 @@ module Fastlane
177
344
  match_secret = params[:match_secret]
178
345
  override_keystore = params[:override_keystore]
179
346
  keystore_data = params[:keystore_data]
347
+ clear_keystore = params[:clear_keystore]
348
+ unit_test = params[:unit_test]
349
+
350
+ # Test OpenSSL/LibreSSL
351
+ if unit_test
352
+ result_test = self.test_security
353
+ exit!
354
+ end
180
355
 
181
356
  # Init constants:
182
357
  keystore_name = 'keystore.jks'
@@ -192,7 +367,7 @@ module Fastlane
192
367
  end
193
368
 
194
369
  # Check OpenSSL:
195
- self.check_openssl_version
370
+ self.check_ssl_version(false)
196
371
 
197
372
  # Init workign local directory:
198
373
  dir_name = ENV['HOME'] + '/.match_keystore'
@@ -222,14 +397,30 @@ module Fastlane
222
397
  raise "The security key '#{key_name}' is malformed, or not initialized!"
223
398
  end
224
399
 
225
- # Create repo directory to sync remote Keystores repository:
400
+ # Clear repo Keystore (local) - mostly for testing:
226
401
  repo_dir = File.join(dir_name, self.to_md5(git_url))
227
- # UI.message(repo_dir)
402
+ if clear_keystore && File.directory?(repo_dir)
403
+ FileUtils.rm_rf(repo_dir)
404
+ UI.message("Local repo keystore (#{repo_dir}) directory deleted!")
405
+ end
406
+
407
+ # Create repo directory to sync remote Keystores repository:
228
408
  unless File.directory?(repo_dir)
229
409
  UI.message("Creating 'repo' directory...")
230
410
  FileUtils.mkdir_p(repo_dir)
231
411
  end
232
412
 
413
+ # Check if package name defined:
414
+ if package_name.to_s.strip.empty?
415
+ raise "Package name is not defined!"
416
+ end
417
+
418
+ # Define paths:
419
+ keystoreAppDir = File.join(repo_dir, package_name)
420
+ keystore_path = File.join(keystoreAppDir, keystore_name)
421
+ properties_path = File.join(keystoreAppDir, properties_name)
422
+ properties_encrypt_path = File.join(keystoreAppDir, properties_encrypt_name)
423
+
233
424
  # Cloning/pulling GIT remote repository:
234
425
  gitDir = File.join(repo_dir, '/.git')
235
426
  if !File.directory?(gitDir)
@@ -244,20 +435,6 @@ module Fastlane
244
435
  puts ''
245
436
  end
246
437
 
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)
252
- unless File.directory?(keystoreAppDir)
253
- UI.message("Creating '#{package_name}' keystore directory...")
254
- FileUtils.mkdir_p(keystoreAppDir)
255
- end
256
-
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
438
  # Load parameters from JSON for CI or Unit Tests:
262
439
  if keystore_data != nil && File.file?(keystore_data)
263
440
  data_json = self.load_json(keystore_data)
@@ -274,6 +451,7 @@ module Fastlane
274
451
 
275
452
  # Create keystore with command
276
453
  override_keystore = !existing_keystore.to_s.strip.empty? && File.file?(existing_keystore)
454
+ UI.message("Existing Keystore: #{existing_keystore}")
277
455
  if !File.file?(keystore_path) || override_keystore
278
456
 
279
457
  if File.file?(keystore_path)
@@ -294,7 +472,7 @@ module Fastlane
294
472
  end
295
473
 
296
474
  # https://developer.android.com/studio/publish/app-signing
297
- if !File.file?(existing_keystore)
475
+ if existing_keystore.to_s.strip.empty? || !File.file?(existing_keystore)
298
476
  UI.message("Generating Android Keystore...")
299
477
 
300
478
  full_name = self.prompt2(text: "Certificate First and Last Name: ", value: data_full_name)
@@ -335,7 +513,7 @@ module Fastlane
335
513
  out_file.puts("aliasPassword=#{alias_password}")
336
514
  out_file.close
337
515
 
338
- self.encrypt_file(properties_path, properties_encrypt_path, key_path)
516
+ self.encrypt_file(properties_path, properties_encrypt_path, key_path, false)
339
517
  File.delete(properties_path)
340
518
 
341
519
  # Print Keystore data in repo:
@@ -352,7 +530,7 @@ module Fastlane
352
530
  else
353
531
  UI.message "Keystore file already exists, continue..."
354
532
 
355
- self.decrypt_file(properties_encrypt_path, properties_path, key_path)
533
+ self.decrypt_file(properties_encrypt_path, properties_path, key_path, false)
356
534
 
357
535
  properties = self.load_properties(properties_path)
358
536
  key_password = properties['keyPassword']
@@ -457,7 +635,17 @@ module Fastlane
457
635
  env_name: "MATCH_KEYSTORE_JSON_PATH",
458
636
  description: "Required data to import an existing keystore, or create a new one",
459
637
  optional: true,
460
- type: String)
638
+ type: String),
639
+ FastlaneCore::ConfigItem.new(key: :clear_keystore,
640
+ env_name: "MATCH_KEYSTORE_CLEAR",
641
+ description: "Clear the local keystore (false by default)",
642
+ optional: true,
643
+ type: Boolean),
644
+ FastlaneCore::ConfigItem.new(key: :unit_test,
645
+ env_name: "MATCH_KEYSTORE_UNIT_TESTS",
646
+ description: "launch Unit Tests (false by default)",
647
+ optional: true,
648
+ type: Boolean)
461
649
  ]
462
650
  end
463
651
 
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module MatchKeystore
3
- VERSION = "0.1.14"
3
+ VERSION = "0.1.15"
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.14
4
+ version: 0.1.15
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-09-07 00:00:00.000000000 Z
11
+ date: 2020-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry