pindo 5.21.1 → 5.21.3
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: a600e7bb32da7ea1ef2ed73200663b344ad302d9ead4279ed6c5973caa168e4a
|
|
4
|
+
data.tar.gz: f56d3755cf9844773d98afe6778becf92dc8ea40f7c5fc96a55635ac332a079d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5bad05ce63824706c5bd0b1b2a2e127d6122e81d2ba60dcb8d56cee2d25787de2359cdc2b3f389ca10255008c0edc30230ccf4fc58aaf4ff09b7329cf2b98a0b
|
|
7
|
+
data.tar.gz: a7dfdebb7fbc7f0d28de181c4136e30c780da7913c9ed65bfff0bc471d5fd05c6e26f3808fd1238fd36572bd6d73d775599e07f62e46e49b0dfd3b44d93e25ba
|
|
@@ -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 =
|
|
187
|
-
|
|
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
|
-
#
|
|
383
|
+
# 文件密码按当前材料内容指纹查找;nil 表示继续查找钥匙串。
|
|
382
384
|
def p12_export_password(key_path)
|
|
383
385
|
source = ENV['PINDO_P12_PASSWORD_FILE']
|
|
384
|
-
return
|
|
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
|
-
|
|
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
|
|
@@ -417,7 +421,7 @@ module Pindo
|
|
|
417
421
|
end
|
|
418
422
|
|
|
419
423
|
def install_certificate_to_keychain(cert_path:, key_path:, cert_password:)
|
|
420
|
-
P12Material.load(cert_path: cert_path, key_path: key_path, password: cert_password)
|
|
424
|
+
material = P12Material.load(cert_path: cert_path, key_path: key_path, password: cert_password)
|
|
421
425
|
unless Pindo::CertHelper.is_cert_valid?(cert_path)
|
|
422
426
|
raise Informative, "证书已经过期,请重新生成新证书!"
|
|
423
427
|
end
|
|
@@ -427,8 +431,19 @@ module Pindo
|
|
|
427
431
|
return if signing_identity_installed?(cert_path, keychain_path)
|
|
428
432
|
|
|
429
433
|
keychain_password = Pindoconfig.instance.cert_key_password
|
|
430
|
-
|
|
431
|
-
|
|
434
|
+
if material.is_a?(P12Material::PemMaterial)
|
|
435
|
+
# security 需要完整的证书与私钥;在受限目录中将 PEM 封装为 P12。
|
|
436
|
+
Dir.mktmpdir('pindo_pem_import_') do |directory|
|
|
437
|
+
import_path = File.join(directory, 'identity.p12')
|
|
438
|
+
p12 = OpenSSL::PKCS12.create(cert_password, 'pindo', material.key, material.certificate)
|
|
439
|
+
File.open(import_path, File::WRONLY | File::CREAT | File::EXCL, 0o600) { |file| file.write(p12.to_der) }
|
|
440
|
+
KeychainHelper.import_file(import_path, keychain_path,
|
|
441
|
+
keychain_password: keychain_password, certificate_password: cert_password, strict: true)
|
|
442
|
+
end
|
|
443
|
+
else
|
|
444
|
+
KeychainHelper.import_file(key_path, keychain_path,
|
|
445
|
+
keychain_password: keychain_password, certificate_password: cert_password, strict: true)
|
|
446
|
+
end
|
|
432
447
|
unless signing_identity_installed?(cert_path, keychain_path)
|
|
433
448
|
raise Informative, "钥匙串未出现证书与私钥组成的签名身份,请检查钥匙串访问权限"
|
|
434
449
|
end
|
|
@@ -4,8 +4,17 @@ module Pindo
|
|
|
4
4
|
module CertProcess
|
|
5
5
|
# Validate the freshly downloaded material before touching a user's keychain.
|
|
6
6
|
class P12Material
|
|
7
|
+
PemMaterial = Struct.new(:key, :certificate)
|
|
8
|
+
class PasswordError < Informative; end
|
|
9
|
+
|
|
7
10
|
def self.load(cert_path:, key_path:, password:)
|
|
8
|
-
|
|
11
|
+
certificate = OpenSSL::X509::Certificate.new(File.binread(cert_path))
|
|
12
|
+
content = File.binread(key_path)
|
|
13
|
+
material = if content.include?('-----BEGIN ')
|
|
14
|
+
parse_pem(content, password, certificate)
|
|
15
|
+
else
|
|
16
|
+
parse(key_path, password)
|
|
17
|
+
end
|
|
9
18
|
unless material.key
|
|
10
19
|
raise Informative, 'P12 私钥缺失,请重新导出包含私钥的 P12', cause: nil
|
|
11
20
|
end
|
|
@@ -13,9 +22,8 @@ module Pindo
|
|
|
13
22
|
raise Informative, 'P12 证书缺失,请重新导出包含证书的 P12', cause: nil
|
|
14
23
|
end
|
|
15
24
|
|
|
16
|
-
certificate = OpenSSL::X509::Certificate.new(File.binread(cert_path))
|
|
17
25
|
unless material.certificate.to_der == certificate.to_der && certificate.check_private_key(material.key)
|
|
18
|
-
raise Informative, '
|
|
26
|
+
raise Informative, '私钥或证书与下载的 CER 证书不匹配,请更新同一套证书材料', cause: nil
|
|
19
27
|
end
|
|
20
28
|
material
|
|
21
29
|
rescue OpenSSL::X509::CertificateError, OpenSSL::PKey::PKeyError
|
|
@@ -24,13 +32,33 @@ module Pindo
|
|
|
24
32
|
raise Informative, '无法读取证书材料,请重新下载', cause: nil
|
|
25
33
|
end
|
|
26
34
|
|
|
35
|
+
def self.parse_pem(content, password, certificate)
|
|
36
|
+
unless content.match?(/-----BEGIN (?:RSA |EC |DSA |ENCRYPTED )?PRIVATE KEY-----/)
|
|
37
|
+
raise Informative, 'PEM 私钥缺失,请提供包含私钥的 PEM 文件', cause: nil
|
|
38
|
+
end
|
|
39
|
+
key = OpenSSL::PKey.read(content, password.to_s)
|
|
40
|
+
unless key.respond_to?(:private?) && key.private?
|
|
41
|
+
raise Informative, 'PEM 私钥缺失,请提供包含私钥的 PEM 文件', cause: nil
|
|
42
|
+
end
|
|
43
|
+
PemMaterial.new(key, certificate)
|
|
44
|
+
rescue OpenSSL::PKey::PKeyError => error
|
|
45
|
+
if error.message.match?(/unsupported|unknown cipher|unknown pbe|fetch failed|disabled|not supported/i)
|
|
46
|
+
raise Informative, 'PEM 加密算法不兼容当前 OpenSSL,请以现代算法重新导出', cause: nil
|
|
47
|
+
elsif content.include?('ENCRYPTED')
|
|
48
|
+
raise PasswordError, 'PEM 私钥解密失败,请检查导出密码及文件完整性', cause: nil
|
|
49
|
+
else
|
|
50
|
+
raise Informative, 'PEM 私钥格式无效或文件已损坏', cause: nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
private_class_method :parse_pem
|
|
54
|
+
|
|
27
55
|
def self.parse(key_path, password)
|
|
28
56
|
OpenSSL::PKCS12.new(File.binread(key_path), password)
|
|
29
57
|
rescue OpenSSL::PKCS12::PKCS12Error => error
|
|
30
58
|
if error.message.match?(/unsupported|unknown cipher|unknown pbe|fetch failed|disabled|not supported/i)
|
|
31
59
|
raise Informative, 'P12 加密算法不兼容当前 OpenSSL;请使用兼容旧算法的工具读取后,以现代加密算法重新导出 P12', cause: nil
|
|
32
60
|
elsif error.message.match?(/mac verify|invalid password|cipherfinal|bad decrypt/i)
|
|
33
|
-
raise
|
|
61
|
+
raise PasswordError, 'P12 导出密码错误或文件完整性校验失败,请核对该文件对应的导出密码', cause: nil
|
|
34
62
|
else
|
|
35
63
|
raise Informative, 'P12 文件格式无效或已损坏,请重新导出(不能据此判定为密码错误)', cause: nil
|
|
36
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
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.
|
|
4
|
+
version: 5.21.3
|
|
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
|