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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87a32db98559102adaf6fd2b1d7da31143fe593fa0ed170865a1da8b3ef5d4d8
|
4
|
+
data.tar.gz: 4c3b0130a705055ad7d9e6c51f8ac1a19c2a4a2df96ddca7011d72275487bb89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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): #{
|
102
|
-
`#{build_tools_path}zipalign -
|
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
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
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.
|
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
|
-
#
|
400
|
+
# Clear repo Keystore (local) - mostly for testing:
|
226
401
|
repo_dir = File.join(dir_name, self.to_md5(git_url))
|
227
|
-
|
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
|
|
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.
|
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-
|
11
|
+
date: 2020-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|