Module: Pindo::KeychainHelper

Defined in:
lib/pindo/module/cert/keychainhelper.rb

Class Method Summary collapse

Class Method Details

.help_backticks(command, print: true) ⇒ Object



51
52
53
54
55
56
# File 'lib/pindo/module/cert/keychainhelper.rb', line 51

def self.help_backticks(command, print: true)
  # UI.command(command) if print
  result = `#{command}`
  # UI.command_output(result) if print
  return result
end

.import_file(path, keychain_path, keychain_password: nil, certificate_password: "", skip_set_partition_list: false, output: false) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/pindo/module/cert/keychainhelper.rb', line 9

def self.import_file(path, keychain_path, keychain_password: nil, certificate_password: "", skip_set_partition_list: false, output: false)
  # puts path

  # puts "Could not find file '#{path}'" unless File.exist?(path)

  password_part = " -P #{certificate_password.shellescape}"

  command = "security import #{path.shellescape} -k '#{keychain_path.shellescape}'"
  command << password_part
  command << " -T /usr/bin/codesign" # to not be asked for permission when running a tool like `gym` (before Sierra)
  command << " -T /usr/bin/security"
  command << " -T /usr/bin/productbuild" # to not be asked for permission when using an installer cert for macOS
  command << " -T /usr/bin/productsign"  # to not be asked for permission when using an installer cert for macOS
  command << " 1> /dev/null" unless output

  # puts command
  # sensitive_command = command.gsub(password_part, " -P ********")

  # UI.command(sensitive_command) if output
  Open3.popen3(command) do |stdin, stdout, stderr, thrd|
    # UI.command_output(stdout.read.to_s) if output
    # puts "123"
    # Set partition list only if success since it can be a time consuming process if a lot of keys are installed
    if thrd.value.success? && !skip_set_partition_list
      # puts "安装成功!"
      keychain_password ||= resolve_keychain_password(keychain_path)
      set_partition_list(path, keychain_path, keychain_password: keychain_password, output: output)
    else
      # Output verbose if file is already installed since not an error otherwise we will show the whole error
      err = stderr.read.to_s.strip

      if err.include?("SecKeychainItemImport") && err.include?("The specified item already exists in the keychain")
        # UI.verbose("'}' is already installed on this machine")
        # puts "证书key已经存在,安装完成!"
      else
        # puts err
        # raise Informative, err
      end
    end
  end
end

.resolve_keychain_password(keychain_path) ⇒ Object

github.com/fastlane/fastlane/issues/14196 Keychain password is needed to set the partition list to prevent Xcode from prompting dialog for keychain password when signing

  1. Uses keychain password from login keychain if found

  2. Prompts user for keychain password and stores it in login keychain for user later



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/pindo/module/cert/keychainhelper.rb', line 116

def self.resolve_keychain_password(keychain_path)
  keychain_name = File.basename(keychain_path, ".*")
  server = server_name(keychain_name)

  # Attempt to find password in keychain for keychain
  item = Security::InternetPassword.find(server: server)
  if item
    keychain_password = item.password
    # UI.important("Using keychain password from keychain item #{server} in #{keychain_path}")
  end

  if keychain_password.nil?
    # if UI.interactive?
      # UI.important("Enter the password for #{keychain_path}")
      # UI.important("This passphrase will be stored in your local keychain with the name #{server} and used in future runs")
      # UI.important("This prompt can be avoided by specifying the 'keychain_password' option or 'MATCH_KEYCHAIN_PASSWORD' environment variable")
      keychain_password = FastlaneCore::Helper.ask_password(message: "Password for #{keychain_name} keychain: ", confirm: true, confirmation_message: "Type password for #{keychain_name} keychain again: ")
      Security::InternetPassword.add(server, "", keychain_password)
    # else
      # 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")
      # keychain_password = ""
    # end
  end

  return keychain_password
end

.set_partition_list(path, keychain_path, keychain_password: nil, output: false) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/pindo/module/cert/keychainhelper.rb', line 58

def self.set_partition_list(path, keychain_path, keychain_password: nil, output: false)
  # When security supports partition lists, also add the partition IDs
  # See https://openradar.appspot.com/28524119
  if help_backticks('security -h | grep set-key-partition-list', print: false).length > 0
    password_part = " -k #{keychain_password.to_s.shellescape}"

    command = "security set-key-partition-list"
    command << " -S apple-tool:,apple:,codesign:"
    command << " -s" # This is a needed in Catalina to prevent "security: SecKeychainItemCopyAccess: A missing value was detected."
    command << password_part
    command << " #{keychain_path.shellescape}"
    command << " 1> /dev/null" # always disable stdout. This can be very verbose, and leak potentially sensitive info

    # Showing loading indicator as this can take some time if a lot of keys installed
    # Helper.show_loading_indicator("Setting key partition list... (this can take a minute if there are a lot of keys installed)")

    # Strip keychain password from command output
    # sensitive_command = command.gsub(password_part, " -k ********")
    # puts command
    # UI.command(sensitive_command) if output
    Open3.popen3(command) do |stdin, stdout, stderr, thrd|
      unless thrd.value.success?
        err = stderr.read.to_s.strip

        # Inform user when no/wrong password was used as its needed to prevent UI permission popup from Xcode when signing
        if err.include?("SecKeychainItemSetAccessWithPassword")
          keychain_name = File.basename(keychain_path, ".*")
          # puts "password #{keychain_password}"
          # puts "keychain #{keychain_name}"
          # puts "security #{server_name(keychain_name)}"
          Security::InternetPassword.delete(server: server_name(keychain_name))

          # UI.important("")
          # UI.important("Could not configure imported keychain item (certificate) to prevent UI permission popup when code signing\n" \
          #          "Check if you supplied the correct `keychain_password` for keychain: `#{keychain_path}`\n" \
          #          "#{err}")
          # UI.important("")
          # UI.important("Please look at the following docs to see how to set a keychain password:")
          # UI.important(" - https://docs.fastlane.tools/actions/sync_code_signing")
          # UI.important(" - https://docs.fastlane.tools/actions/get_certificates")
          # puts "证书密码错误!"
        else
          puts err
        end
      end
    end

    # Hiding after Open3 finishes
    # Helper.hide_loading_indicator

  end
end