pindo 5.21.2 → 5.21.4

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: 8fc2d66a2010bba4e00fe508252a291bac2efda4623ded2135bf707b6f93ade0
4
- data.tar.gz: cfca3acb943122590ffeba33d95249c2d73143b7e51ff87e74ef5342e44dd67f
3
+ metadata.gz: 9f3b56fbb1ab0c039204dd6a338a884ba5a6b0cc014d4abe0356a18bd92b9277
4
+ data.tar.gz: 5f1c9874bfb6ed50da8c7f02d5dc2a8fc93a15bd59eb59a6be5e904222a0fe42
5
5
  SHA512:
6
- metadata.gz: 652bf861a3896a3763e38ff0f7142128dace9d221350db4a353b1f6ad22fd19dc5661ac926825438cf5dae7fdf287221b3888b8b84d6dccf6d54be024bac759b
7
- data.tar.gz: bfd8a3891dc78f37fa1bccec6a67eb9dd7f6f8a4f4cac86d95788d0d64b63e938dd6385579ac3d0bc50141629bc236f2bf4a16d69f597b7036f8a1b497c03cb4
6
+ metadata.gz: dc8a61f2d93aae23c9f6e2986d962cf15d25dfe3bf5d88c02035f36a08dd4b04d98d1ce2f911ada41b79361fa9f6ff8b19a4c46751dce5fcbb5d30b7c87d08a5
7
+ data.tar.gz: b1f52d2f1e22c7c5b2d0d06ba51c57f65e240f2305dd8c4c72ad535708d27dd7f08e7dee84ea7f28a7a527930b55f20dd580ee20db69f732e9908771c595d371
@@ -34,7 +34,6 @@ module Pindo
34
34
  if thrd.value.success?
35
35
  # puts "安装成功!"
36
36
  unless skip_set_partition_list
37
- keychain_password ||= resolve_keychain_password(keychain_path)
38
37
  set_partition_list(path, keychain_path, keychain_password: keychain_password, output: output, strict: strict)
39
38
  end
40
39
  else
@@ -59,100 +58,56 @@ module Pindo
59
58
  return result
60
59
  end
61
60
 
61
+ # 与 Match 一致:配置密码优先,否则从钥匙串读取或询问并缓存。
62
62
  def set_partition_list(path, keychain_path, keychain_password: nil, output: false, strict: false)
63
- # When security supports partition lists, also add the partition IDs
64
- # See https://openradar.appspot.com/28524119
65
- if help_backticks('security -h | grep set-key-partition-list', print: false).length > 0
66
- password_part = " -k #{keychain_password.to_s.shellescape}"
67
-
68
- command = "security set-key-partition-list"
69
- command << " -S apple-tool:,apple:,codesign:"
70
- command << " -s" # This is a needed in Catalina to prevent "security: SecKeychainItemCopyAccess: A missing value was detected."
71
- command << password_part
72
- command << " #{keychain_path.shellescape}"
73
- command << " 1> /dev/null" # always disable stdout. This can be very verbose, and leak potentially sensitive info
74
-
75
- # Showing loading indicator as this can take some time if a lot of keys installed
76
- # Helper.show_loading_indicator("Setting key partition list... (this can take a minute if there are a lot of keys installed)")
77
-
78
- # Strip keychain password from command output
79
- # sensitive_command = command.gsub(password_part, " -k ********")
80
- # puts command
81
- # UI.command(sensitive_command) if output
82
- Open3.popen3(command) do |stdin, stdout, stderr, thrd|
83
- unless thrd.value.success?
84
- err = stderr.read.to_s.strip
85
- raise Informative, "钥匙串访问配置失败,请检查 macOS 钥匙串密码", cause: nil if strict
86
-
87
- # Inform user when no/wrong password was used as its needed to prevent UI permission popup from Xcode when signing
88
- if err.include?("SecKeychainItemSetAccessWithPassword")
89
- keychain_name = File.basename(keychain_path, ".*")
90
- # puts "password #{keychain_password}"
91
- # puts "keychain #{keychain_name}"
92
- # puts "security #{server_name(keychain_name)}"
93
- Security::InternetPassword.delete(server: server_name(keychain_name))
94
-
95
- # UI.important("")
96
- # UI.important("Could not configure imported keychain item (certificate) to prevent UI permission popup when code signing\n" \
97
- # "Check if you supplied the correct `keychain_password` for keychain: `#{keychain_path}`\n" \
98
- # "#{err}")
99
- # UI.important("")
100
- # UI.important("Please look at the following docs to see how to set a keychain password:")
101
- # UI.important(" - https://docs.fastlane.tools/actions/sync_code_signing")
102
- # UI.important(" - https://docs.fastlane.tools/actions/get_certificates")
103
- # puts "证书密码错误!"
104
- else
105
- puts err
106
- end
63
+ warning = '未配置免弹窗签名权限;后续签名时 macOS 可能弹出系统对话框,请按提示授权。'
64
+ return unless help_backticks('security -h | grep set-key-partition-list', print: false).length > 0
65
+ keychain_password = resolve_keychain_password(keychain_path) if keychain_password.to_s.empty?
66
+
67
+ command = "security set-key-partition-list -S apple-tool:,apple:,codesign: -s"
68
+ command << " -k #{keychain_password.shellescape} #{keychain_path.shellescape} 1> /dev/null"
69
+ Open3.popen3(command) do |stdin, stdout, stderr, thread|
70
+ # 消费错误输出但不打印,避免泄露敏感信息。
71
+ error = stderr.read
72
+ next if thread.value.success?
73
+
74
+ if error.include?('SecKeychainItemSetAccessWithPassword')
75
+ # Match 会清除被拒绝的缓存;捕获输出以安静处理条目不存在的情况。
76
+ _output, _error, status = Open3.capture3('security', 'delete-internet-password', '-s',
77
+ server_name(keychain_path), '-a', '')
78
+ unless status.success? || status.exitstatus == 44 # errSecItemNotFound
79
+ FastlaneCore::UI.important('未能清除失效的钥匙串密码缓存,请检查钥匙串访问权限;下次可能仍读取旧密码。')
107
80
  end
108
81
  end
109
-
110
- # Hiding after Open3 finishes
111
- # Helper.hide_loading_indicator
112
-
82
+ FastlaneCore::UI.important(warning)
113
83
  end
114
84
  end
115
85
 
116
- # https://github.com/fastlane/fastlane/issues/14196
117
- # Keychain password is needed to set the partition list to
118
- # prevent Xcode from prompting dialog for keychain password when signing
119
- # 1. Uses keychain password from login keychain if found
120
- # 2. Prompts user for keychain password and stores it in login keychain for user later
121
86
  def resolve_keychain_password(keychain_path)
122
- keychain_name = File.basename(keychain_path, ".*")
123
- server = server_name(keychain_name)
124
-
125
- # Attempt to find password in keychain for keychain
87
+ server = server_name(keychain_path)
126
88
  item = Security::InternetPassword.find(server: server)
127
- if item
128
- keychain_password = item.password
129
- # UI.important("Using keychain password from keychain item #{server} in #{keychain_path}")
130
- end
89
+ return item.password if item && !item.password.to_s.empty?
131
90
 
132
- if keychain_password.nil?
133
- # if UI.interactive?
134
- # UI.important("Enter the password for #{keychain_path}")
135
- # UI.important("This passphrase will be stored in your local keychain with the name #{server} and used in future runs")
136
- # UI.important("This prompt can be avoided by specifying the 'keychain_password' option or 'MATCH_KEYCHAIN_PASSWORD' environment variable")
137
- # 收起 spinner,否则密码提示会被动画行覆盖,终端看起来像卡死
138
- keychain_password = Funlog.with_paused_spinner do
139
- FastlaneCore::Helper.ask_password(message: "Password for #{keychain_name} keychain: ", confirm: true, confirmation_message: "Type password for #{keychain_name} keychain again: ")
140
- end
141
- Security::InternetPassword.add(server, "", keychain_password)
142
- # else
143
- # UI.important("Keychain password for #{keychain_path} was not specified and not found in your keychain. Specify the 'keychain_password' option to prevent the UI permission popup when code signing")
144
- # keychain_password = ""
145
- # end
91
+ unless $stdin.tty? && FastlaneCore::UI.interactive?
92
+ FastlaneCore::UI.important('未配置或缓存 macOS 钥匙串密码;签名时可能需要系统授权。')
93
+ return ''
146
94
  end
147
95
 
148
- return keychain_password
96
+ name = File.basename(keychain_path, '.*')
97
+ password = Funlog.with_paused_spinner do
98
+ FastlaneCore::Helper.ask_password(
99
+ message: "请输入 macOS #{name} 钥匙串密码(非证书导出密码,将保存到 #{server}):",
100
+ confirm: true, confirmation_message: "请再次输入 #{name} 钥匙串密码:")
101
+ end
102
+ unless Security::InternetPassword.add(server, '', password)
103
+ FastlaneCore::UI.important('未能缓存钥匙串密码,下次运行可能需要重新输入。')
104
+ end
105
+ password
149
106
  end
150
107
 
151
- # server name used for accessing the macOS keychain
152
- def server_name(keychain_name)
153
- ["pindo", "keychain", keychain_name].join("_")
108
+ def server_name(keychain_path)
109
+ ['pindo', 'keychain', File.basename(keychain_path, '.*')].join('_')
154
110
  end
155
-
156
111
  private :server_name
157
112
  end
158
113
  end
@@ -9,6 +9,7 @@ require 'jpsclient'
9
9
  require 'json'
10
10
  require 'digest'
11
11
  require 'pindo/module/cert/p12_material'
12
+ require 'pindo/module/cert/p12_password_resolver'
12
13
 
13
14
  module Pindo
14
15
  module CertProcess
@@ -183,8 +184,9 @@ module Pindo
183
184
  download_file_from_url(url: cert_p12_url, dest_path: key_path)
184
185
  Funlog.instance.info("私钥文件路径: #{key_path}") if debug_mode?
185
186
 
186
- cert_password = p12_export_password(key_path)
187
- material = P12Material.load(cert_path: cert_path, key_path: key_path, password: cert_password)
187
+ cert_password, material = P12PasswordResolver.resolve(
188
+ cert_path: cert_path, key_path: key_path, file_password: p12_export_password(key_path)
189
+ )
188
190
 
189
191
  # 安装证书到 Keychain(明文,无需 AES 解密)
190
192
  install_certificate_to_keychain(cert_path: cert_path, key_path: key_path, cert_password: cert_password)
@@ -378,10 +380,10 @@ module Pindo
378
380
  raise Informative, "证书材料下载失败,请检查下载权限与网络", cause: nil
379
381
  end
380
382
 
381
- # 密码只按当前 P12 的内容指纹查找;不缓存、不读取 API 猜测字段。
383
+ # 文件密码按当前材料内容指纹查找;nil 表示继续查找钥匙串。
382
384
  def p12_export_password(key_path)
383
385
  source = ENV['PINDO_P12_PASSWORD_FILE']
384
- return '' if source.nil? || source.empty?
386
+ return nil if source.nil? || source.empty?
385
387
 
386
388
  File.open(source, File::RDONLY | File::NOFOLLOW) do |file|
387
389
  stat = file.stat
@@ -390,7 +392,9 @@ module Pindo
390
392
  end
391
393
  passwords = JSON.parse(file.read)
392
394
  fingerprint = Digest::SHA256.file(key_path).hexdigest
393
- password = passwords.fetch(fingerprint, '')
395
+ raise TypeError unless passwords.is_a?(Hash)
396
+ return nil unless passwords.key?(fingerprint)
397
+ password = passwords.fetch(fingerprint)
394
398
  raise TypeError unless password.is_a?(String)
395
399
  password
396
400
  end
@@ -5,6 +5,7 @@ module Pindo
5
5
  # Validate the freshly downloaded material before touching a user's keychain.
6
6
  class P12Material
7
7
  PemMaterial = Struct.new(:key, :certificate)
8
+ class PasswordError < Informative; end
8
9
 
9
10
  def self.load(cert_path:, key_path:, password:)
10
11
  certificate = OpenSSL::X509::Certificate.new(File.binread(cert_path))
@@ -44,7 +45,7 @@ module Pindo
44
45
  if error.message.match?(/unsupported|unknown cipher|unknown pbe|fetch failed|disabled|not supported/i)
45
46
  raise Informative, 'PEM 加密算法不兼容当前 OpenSSL,请以现代算法重新导出', cause: nil
46
47
  elsif content.include?('ENCRYPTED')
47
- raise Informative, 'PEM 私钥解密失败,请检查导出密码及文件完整性', cause: nil
48
+ raise PasswordError, 'PEM 私钥解密失败,请检查导出密码及文件完整性', cause: nil
48
49
  else
49
50
  raise Informative, 'PEM 私钥格式无效或文件已损坏', cause: nil
50
51
  end
@@ -57,7 +58,7 @@ module Pindo
57
58
  if error.message.match?(/unsupported|unknown cipher|unknown pbe|fetch failed|disabled|not supported/i)
58
59
  raise Informative, 'P12 加密算法不兼容当前 OpenSSL;请使用兼容旧算法的工具读取后,以现代加密算法重新导出 P12', cause: nil
59
60
  elsif error.message.match?(/mac verify|invalid password|cipherfinal|bad decrypt/i)
60
- raise Informative, 'P12 导出密码错误或文件完整性校验失败,请核对该文件对应的导出密码', cause: nil
61
+ raise PasswordError, 'P12 导出密码错误或文件完整性校验失败,请核对该文件对应的导出密码', cause: nil
61
62
  else
62
63
  raise Informative, 'P12 文件格式无效或已损坏,请重新导出(不能据此判定为密码错误)', cause: nil
63
64
  end
@@ -0,0 +1,110 @@
1
+ require 'digest'
2
+ require 'open3'
3
+ require 'pindo/base/funlog'
4
+ require 'pindo/module/cert/p12_material'
5
+
6
+ module Pindo
7
+ module CertProcess
8
+ # Capture both output streams: security gem's system calls can expose raw errors.
9
+ class P12ExportPasswordStore
10
+ Item = Struct.new(:password)
11
+
12
+ def self.find(server:)
13
+ output, _error, status = Open3.capture3('security', 'find-internet-password', '-w', '-s', server, '-a', '')
14
+ return nil if status.exitstatus == 44
15
+ fail_access! unless status.success?
16
+
17
+ Item.new(output.sub(/\n\z/, ''))
18
+ rescue Informative
19
+ raise
20
+ rescue ::StandardError
21
+ fail_access!
22
+ end
23
+
24
+ def self.add(server, account, password)
25
+ _output, _error, status = Open3.capture3('security', 'add-internet-password', '-s', server, '-a', account, '-w', password)
26
+ fail_access! unless status.success?
27
+ true
28
+ rescue Informative
29
+ raise
30
+ rescue ::StandardError
31
+ fail_access!
32
+ end
33
+
34
+ def self.delete(server:)
35
+ _output, _error, status = Open3.capture3('security', 'delete-internet-password', '-s', server, '-a', '')
36
+ fail_access! unless status.success?
37
+ true
38
+ rescue Informative
39
+ raise
40
+ rescue ::StandardError
41
+ fail_access!
42
+ end
43
+
44
+ def self.fail_access!
45
+ raise Informative, '无法访问私钥导出密码钥匙串条目,请检查 macOS 钥匙串权限', cause: nil
46
+ end
47
+ private_class_method :fail_access!
48
+ end
49
+
50
+ # Export passwords are scoped to the exact downloaded material, not a keychain password.
51
+ class P12PasswordResolver
52
+ def self.resolve(cert_path:, key_path:, file_password:)
53
+ resolver = new(cert_path, key_path)
54
+ resolver.resolve(file_password)
55
+ end
56
+
57
+ def initialize(cert_path, key_path)
58
+ @cert_path = cert_path
59
+ @key_path = key_path
60
+ @server = "pindo_p12_export_#{Digest::SHA256.file(key_path).hexdigest}"
61
+ end
62
+
63
+ def resolve(file_password)
64
+ return validate(file_password) unless file_password.nil?
65
+
66
+ item = keychain { P12ExportPasswordStore.find(server: @server) }
67
+ begin
68
+ return validate(item ? keychain { item.password } : '')
69
+ rescue P12Material::PasswordError
70
+ keychain { P12ExportPasswordStore.delete(server: @server) } if item
71
+ end
72
+
73
+ password = prompt
74
+ result = validate(password)
75
+ keychain { P12ExportPasswordStore.add(@server, '', password) }
76
+ result
77
+ end
78
+
79
+ private
80
+
81
+ def validate(password)
82
+ material = P12Material.load(cert_path: @cert_path, key_path: @key_path, password: password)
83
+ [password, material]
84
+ end
85
+
86
+ def prompt
87
+ unless $stdin.tty? && $stdout.tty?
88
+ raise Informative, '非交互环境缺少有效的私钥导出密码,请配置 PINDO_P12_PASSWORD_FILE 或预先保存到钥匙串', cause: nil
89
+ end
90
+ password = Funlog.with_paused_spinner do
91
+ FastlaneCore::Helper.ask_password(message: '请输入当前证书材料的导出密码(非 macOS 钥匙串密码):')
92
+ end
93
+ unless password.is_a?(String)
94
+ raise Informative, '未输入有效的私钥导出密码', cause: nil
95
+ end
96
+ password
97
+ rescue Informative
98
+ raise
99
+ rescue ::StandardError
100
+ raise Informative, '无法读取控制台导出密码', cause: nil
101
+ end
102
+
103
+ def keychain
104
+ yield
105
+ rescue ::StandardError
106
+ raise Informative, '无法读取或保存私钥导出密码,请检查 macOS 钥匙串访问权限', cause: nil
107
+ end
108
+ end
109
+ end
110
+ end
data/lib/pindo/version.rb CHANGED
@@ -6,7 +6,7 @@ require 'time'
6
6
 
7
7
  module Pindo
8
8
 
9
- VERSION = "5.21.2"
9
+ VERSION = "5.21.4"
10
10
 
11
11
  class VersionCheck
12
12
  RUBYGEMS_API = 'https://rubygems.org/api/v1/gems/pindo.json'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pindo
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.21.2
4
+ version: 5.21.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - wade
@@ -405,6 +405,7 @@ files:
405
405
  - lib/pindo/module/cert/mode/custom_https_cert_operator.rb
406
406
  - lib/pindo/module/cert/mode/match_git_cert_operator.rb
407
407
  - lib/pindo/module/cert/p12_material.rb
408
+ - lib/pindo/module/cert/p12_password_resolver.rb
408
409
  - lib/pindo/module/cert/pem_helper.rb
409
410
  - lib/pindo/module/cert/provisioning_helper.rb
410
411
  - lib/pindo/module/pgyer/media_url_helper.rb