class FastlaneCore::CertChecker

This class checks if a specific certificate is installed on the current mac

Public Class Methods

install_missing_wwdr_certificates(in_keychain: nil) click to toggle source
# File fastlane_core/lib/fastlane_core/cert_checker.rb, line 140
def self.install_missing_wwdr_certificates(in_keychain: nil)
  # Install all Worldwide Developer Relations Intermediate Certificates listed here: https://www.apple.com/certificateauthority/
  keychain = in_keychain || wwdr_keychain
  missing = WWDRCA_CERTIFICATES.map { |c| c[:alias] } - installed_wwdr_certificates(keychain: keychain)
  missing.each do |cert_alias|
    install_wwdr_certificate(cert_alias, keychain: keychain)
  end
  missing.count
end
install_wwdr_certificate(cert_alias, keychain: nil) click to toggle source
# File fastlane_core/lib/fastlane_core/cert_checker.rb, line 150
def self.install_wwdr_certificate(cert_alias, keychain: nil)
  url = WWDRCA_CERTIFICATES.find { |c| c[:alias] == cert_alias }.fetch(:url)
  file = Tempfile.new([File.basename(url, ".cer"), ".cer"])
  filename = file.path
  keychain ||= wwdr_keychain # backwards compatibility
  keychain = "-k #{keychain.shellescape}" unless keychain.empty?

  # Attempts to fix an issue installing WWDR cert tends to fail on CIs
  # https://github.com/fastlane/fastlane/issues/20960
  curl_extras = ""
  if FastlaneCore::Feature.enabled?('FASTLANE_WWDR_USE_HTTP1_AND_RETRIES')
    curl_extras = "--http1.1 --retry 3 --retry-all-errors "
  end

  import_command = "curl #{curl_extras}-f -o #{filename} #{url} && security import #{filename} #{keychain}"
  UI.verbose("Installing WWDR Cert: #{import_command}")

  require 'open3'
  stdout, stderr, status = Open3.capture3(import_command)
  if FastlaneCore::Globals.verbose?
    UI.command_output(stdout)
    UI.command_output(stderr)
  end

  unless status.success?
    UI.verbose("Failed to install WWDR Certificate, checking output to see why")
    # Check the command output, WWDR might already exist
    unless /The specified item already exists in the keychain./ =~ stderr
      UI.user_error!("Could not install WWDR certificate")
    end
    UI.verbose("WWDR Certificate was already installed")
  end
  return true
end
installed?(path, in_keychain: nil) click to toggle source
# File fastlane_core/lib/fastlane_core/cert_checker.rb, line 39
def self.installed?(path, in_keychain: nil)
  UI.user_error!("Could not find file '#{path}'") unless File.exist?(path)

  in_keychain &&= FastlaneCore::Helper.keychain_path(in_keychain)
  ids = installed_identities(in_keychain: in_keychain)
  ids += installed_installers(in_keychain: in_keychain)
  finger_print = sha1_fingerprint(path)

  return ids.include?(finger_print)
end
installed_identities(in_keychain: nil) click to toggle source
# File fastlane_core/lib/fastlane_core/cert_checker.rb, line 55
def self.installed_identities(in_keychain: nil)
  install_missing_wwdr_certificates(in_keychain: in_keychain)

  available = list_available_identities(in_keychain: in_keychain)
  # Match for this text against word boundaries to avoid edge cases around multiples of 10 identities!
  if /\b0 valid identities found\b/ =~ available
    UI.error([
      "There are no local code signing identities found.",
      "You can run" << " `security find-identity -v -p codesigning #{in_keychain}".rstrip << "` to get this output.",
      "This Stack Overflow thread has more information: https://stackoverflow.com/q/35390072/774.",
      "(Check in Keychain Access for an expired WWDR certificate: https://stackoverflow.com/a/35409835/774 has more info.)"
    ].join("\n"))
  end

  ids = []
  available.split("\n").each do |current|
    next if current.include?("REVOKED")
    begin
      (ids << current.match(/.*\) ([[:xdigit:]]*) \".*/)[1])
    rescue
      # the last line does not match
    end
  end

  return ids
end
installed_installers(in_keychain: nil) click to toggle source
# File fastlane_core/lib/fastlane_core/cert_checker.rb, line 82
def self.installed_installers(in_keychain: nil)
  available = self.list_available_third_party_mac_installer(in_keychain: in_keychain)
  available += self.list_available_developer_id_installer(in_keychain: in_keychain)

  return available.scan(/^SHA-1 hash: ([[:xdigit:]]+)$/).flatten
end
installed_wwdr_certificates(keychain: nil) click to toggle source
# File fastlane_core/lib/fastlane_core/cert_checker.rb, line 115
def self.installed_wwdr_certificates(keychain: nil)
  certificate_name = "Apple Worldwide Developer Relations"
  keychain ||= wwdr_keychain # backwards compatibility

  # Find all installed WWDRCA certificates
  installed_certs = []
  Helper.backticks("security find-certificate -a -c '#{certificate_name}' -p #{keychain.shellescape}", print: false)
        .lines
        .each do |line|
    if line.start_with?('-----BEGIN CERTIFICATE-----')
      installed_certs << line
    else
      installed_certs.last << line
    end
  end

  # Get the alias (see `WWDRCA_CERTIFICATES`) of the installed WWDRCA certificates
  installed_certs
    .map do |pem|
      sha256 = Digest::SHA256.hexdigest(OpenSSL::X509::Certificate.new(pem).to_der)
      WWDRCA_CERTIFICATES.find { |c| c[:sha256].casecmp?(sha256) }&.fetch(:alias)
    end
    .compact
end
is_installed?(path) click to toggle source

Legacy Method, use ‘installed?` instead

# File fastlane_core/lib/fastlane_core/cert_checker.rb, line 51
def self.is_installed?(path)
  installed?(path)
end
list_available_developer_id_installer(in_keychain: nil) click to toggle source
# File fastlane_core/lib/fastlane_core/cert_checker.rb, line 106
def self.list_available_developer_id_installer(in_keychain: nil)
  # -Z  Print SHA-256 (and SHA-1) hash of the certificate
  # -a  Find all matching certificates, not just the first one
  # -c  Match on "name" when searching (optional)
  commands = ['security find-certificate -Z -a -c "Developer ID Installer"']
  commands << in_keychain if in_keychain
  `#{commands.join(' ')}`
end
list_available_identities(in_keychain: nil) click to toggle source
# File fastlane_core/lib/fastlane_core/cert_checker.rb, line 89
def self.list_available_identities(in_keychain: nil)
  # -v  Show valid identities only (default is to show all identities)
  # -p  Specify policy to evaluate
  commands = ['security find-identity -v -p codesigning']
  commands << in_keychain if in_keychain
  `#{commands.join(' ')}`
end
list_available_third_party_mac_installer(in_keychain: nil) click to toggle source
# File fastlane_core/lib/fastlane_core/cert_checker.rb, line 97
def self.list_available_third_party_mac_installer(in_keychain: nil)
  # -Z  Print SHA-256 (and SHA-1) hash of the certificate
  # -a  Find all matching certificates, not just the first one
  # -c  Match on "name" when searching (optional)
  commands = ['security find-certificate -Z -a -c "3rd Party Mac Developer Installer"']
  commands << in_keychain if in_keychain
  `#{commands.join(' ')}`
end
sha1_fingerprint(path) click to toggle source
# File fastlane_core/lib/fastlane_core/cert_checker.rb, line 200
def self.sha1_fingerprint(path)
  file_data = File.read(path.to_s)
  cert = OpenSSL::X509::Certificate.new(file_data)
  return OpenSSL::Digest::SHA1.new(cert.to_der).to_s.upcase
rescue => error
  UI.error(error)
  UI.user_error!("Error parsing certificate '#{path}'")
end
wwdr_keychain() click to toggle source
# File fastlane_core/lib/fastlane_core/cert_checker.rb, line 185
def self.wwdr_keychain
  priority = [
    "security default-keychain -d user",
    "security list-keychains -d user"
  ]
  priority.each do |command|
    keychains = Helper.backticks(command, print: FastlaneCore::Globals.verbose?).split("\n")
    unless keychains.empty?
      # Select first keychain name from returned keychains list
      return keychains[0].strip.tr('"', '')
    end
  end
  return ""
end