buildRootKeychain.rb [plain text]
require 'FileUtils'
require 'singleton'
class Utilities
include Singleton
def self.bail(reason = nil)
puts "reason" if !reason.nil?
exit(-1)
end
def self.check_path(path, is_dir = true)
Utilities.bail(path + " does not exist") if !FileTest.exists? path
if is_dir
Utilities.bail(path + " is not a directory") if !FileTest.directory? path
end
true
end
def self.quote_str(str)
result = "'" + str + "'"
result
end
def self.hex_to_bin(s)
s.scan(/../).map { |x| x.hex.chr }.join
end
def self.bin_to_hex(s)
s.each_byte.map { |b| b.to_s(16) }.join
end
end
class CertTools
include Singleton
attr_reader :build_dir
attr_reader :project_dir
attr_reader :certificate_dir
attr_reader :distrusted_certs_dir
attr_reader :revoked_certs_dir
attr_reader :root_certs_dir
attr_reader :intermediate_certs_dir
attr_reader :security_tool_path
attr_reader :output_keychain_path
attr_writer :saved_kc_list
def initialize()
@saved_kc_list = nil;
@build_dir = ENV["BUILT_PRODUCTS_DIR"]
@project_dir = ENV["PROJECT_DIR"]
@certificate_dir = File.join(@project_dir, "..")
@distrusted_certs_dir = File.join(certificate_dir, "distrusted")
@revoked_certs_dir = File.join(certificate_dir, "revoked")
@root_certs_dir = File.join(certificate_dir, "roots")
@intermediate_certs_dir = File.join(certificate_dir, "certs")
Utilities.check_path(@distrusted_certs_dir)
Utilities.check_path(@revoked_certs_dir)
Utilities.check_path(@root_certs_dir)
Utilities.check_path(@intermediate_certs_dir)
@security_tool_path = '/usr/bin/security'
Utilities.check_path(@security_tool_path, false)
@output_keychain_path = File.join(@build_dir , "BuiltKeychains")
FileUtils.mkdir_p(@output_keychain_path) if !FileTest.exists? @output_keychain_path
output_variables = false
if output_variables
puts "================================================="
puts "CertTools variables"
puts " "
puts "@build_dir = #{@build_dir}"
puts "@project_dir = #{@project_dir}"
puts "@certificate_dir = #{@certificate_dir}"
puts "@distrusted_certs_dir = #{@distrusted_certs_dir}"
puts "@revoked_certs_dir = #{@revoked_certs_dir}"
puts "@root_certs_dir = #{@root_certs_dir}"
puts "@intermediate_certs_dir = #{@intermediate_certs_dir}"
puts "@security_tool_path = #{@security_tool_path}"
puts "@output_keychain_path = #{@output_keychain_path}"
puts "================================================="
puts " "
end
end
def self.build_dir
CertTools.instance.build_dir
end
def self.certificate_dir
CertTools.instance.certificate_dir
end
def self.distrusted_certs_dir
CertTools.instance.distrusted_certs_dir
end
def self.revoked_certs_dir
CertTools.instance.revoked_certs_dir
end
def self.root_certs_dir
CertTools.instance.root_certs_dir
end
def self.intermediate_certs_dir
CertTools.instance.intermediate_certs_dir
end
def self.security_tool_path
CertTools.instance.security_tool_path
end
def self.output_keychain_path
CertTools.instance.output_keychain_path
end
def self.saveKeychainList()
cmd_str = CertTools.instance.security_tool_path + " list -d user"
temp = `#{cmd_str}`
CertTools.instance.saved_kc_list = temp
$?
end
def self.restoreKeychainList()
return if CertTools.instance.saved_kc_list.nil?
st = CertTools.instance.security_tool_path
cmd_str = "echo -n " + Utilities.quote_str(CertTools.instance.saved_kc_list) + " | xargs " + st + " list -d user -s"
`#{cmd_str}`
$?
end
def self.createKeychain(path, name)
FileUtils.rm_rf(path) if FileTest.exists? path
cmd_str = CertTools.security_tool_path + " create-keychain -p " + Utilities.quote_str(name) + " " + Utilities.quote_str(path)
`#{cmd_str}`
$?
end
end
class BuildRootKeychains
attr_reader :root_cert_file_name
attr_reader :root_cert_kc_path
attr_reader :settings_file_name
attr_reader :setting_file_path
attr_reader :temp_kc_name
attr_reader :temp_kc_path
attr :verbose
def initialize(verbose = true)
@verbose = verbose
@root_cert_file_name = "SystemRootCertificates.keychain"
@root_cert_kc_path = File.join(CertTools.output_keychain_path, @root_cert_file_name)
@settings_file_name = "SystemTrustSettings.plist"
@setting_file_path = File.join(CertTools.output_keychain_path, @settings_file_name)
@temp_kc_name = "SystemTempCertificates.keychain"
@temp_kc_path = File.join(CertTools.build_dir, @temp_kc_name)
end
def create_root_keychain()
puts "Creating empty System Root certificates keychain at #{@root_cert_kc_path}" if @verbose
CertTools.createKeychain(@root_cert_kc_path, @root_cert_file_name)
end
def create_setting_file()
puts "Creating empty Setting file at #{@setting_file_path}" if @verbose
FileUtils.rm_rf(@setting_file_path) if FileTest.exists? @setting_file_path
cmd_str = CertTools.security_tool_path + " add-trusted-cert -o " + Utilities.quote_str(@setting_file_path)
`#{cmd_str}`
$?
end
def add_roots()
puts "Adding root certs to #{@root_cert_file_name}" if @verbose
num_root_certs = 0
Dir.foreach(CertTools.root_certs_dir) do |f|
next if f[0].chr == "."
full_root_path = File.join(CertTools.root_certs_dir, f)
if f == "AppleDEVID.cer"
puts " sipping intermediate #{f} for trust" if @verbose
cmd_str = CertTools.security_tool_path + " -q add-certificates -k " + Utilities.quote_str(@root_cert_kc_path) + " " +
Utilities.quote_str(full_root_path)
`#{cmd_str}`
Utilities.bail("Security tool add-certificates returned an error for #{full_root_path}") if $? != 0
else
cmd_str = CertTools.security_tool_path
cmd_str += " -q add-trusted-cert -i "
cmd_str += Utilities.quote_str(@setting_file_path)
cmd_str += " -o "
cmd_str += Utilities.quote_str(@setting_file_path)
cmd_str += " -k "
cmd_str += Utilities.quote_str(@root_cert_kc_path)
cmd_str += " "
cmd_str += Utilities.quote_str(full_root_path)
cmd_result = `#{cmd_str}`
Utilities.bail("Security tool add-trusted-cer returned an error for #{full_root_path}") if $? != 0
new_num_certs = get_num_root_certs
if new_num_certs <= num_root_certs then
puts "Root #{f} was not added! result = #{cmd_result.to_s}"
puts cmd_str
end
num_root_certs = new_num_certs
end
end
true
end
def create_temp_keychain()
puts "Creating empty temp keychain at #{@temp_kc_path}" if @verbose
CertTools.createKeychain(@temp_kc_path, @temp_kc_name)
end
def delete_temp_keychain()
FileUtils.rm_rf(@temp_kc_path) if FileTest.exists? @temp_kc_path
end
def process_certs(message, dir)
puts message if @verbose
Dir.foreach(dir) do |f|
next if f[0].chr == "."
full_path = File.join(dir, f)
cmd_str = CertTools.security_tool_path
cmd_str += " add-trusted-cert -i "
cmd_str += Utilities.quote_str(@setting_file_path)
cmd_str += " -o "
cmd_str += Utilities.quote_str(@setting_file_path)
cmd_str += " -k "
cmd_str += Utilities.quote_str(@temp_kc_path)
cmd_str += " -r deny "
cmd_str += Utilities.quote_str(full_path)
`#{cmd_str}`
Utilities.bail("Security add-trusted-cert returned an error for #{full_path}") if $? != 0
end
end
def distrust_certs()
process_certs("Explicitly distrusting certs", CertTools.distrusted_certs_dir)
end
def revoked_certs()
process_certs("Explicitly distrusting certs", CertTools.revoked_certs_dir)
end
def get_num_root_certs()
cmd_str = CertTools.security_tool_path + " find-certificate -a " + Utilities.quote_str(@root_cert_kc_path)
cert_str = `#{cmd_str}`
Utilities.bail(" find-certificate failed") if $? != 0
cert_list = cert_str.split
labl_list = cert_list.grep(/issu/)
labl_list.length
end
def check_all_roots_added()
num_items_in_kc = get_num_root_certs
file_system_entries = Dir.entries(CertTools.root_certs_dir)
num_file_system_entries = file_system_entries.length
file_system_entries.each do |f|
if f[0].chr == "."
num_file_system_entries = num_file_system_entries - 1
end
end
puts "num_items_in_kc = #{num_items_in_kc}" if @verbose
puts "num_file_system_entries = #{num_file_system_entries}" if @verbose
num_items_in_kc == num_file_system_entries
end
def set_file_priv()
FileUtils.chmod 0644, @setting_file_path
FileUtils.chmod 0644, @root_cert_kc_path
end
def do_processing()
result = create_root_keychain
Utilities.bail("create_root_keychain failed") if result != 0
Utilities.bail("create_setting_file failed") if create_setting_file != 0
add_roots()
Utilities.bail("create_temp_keychain failed") if create_temp_keychain != 0
distrust_certs()
revoked_certs()
delete_temp_keychain()
Utilities.bail("check_all_roots_added failes") if !check_all_roots_added
set_file_priv()
end
end
class BuildCAKeychain
attr_reader :cert_kc_name
attr_reader :cert_kc_path
attr :verbose
def initialize(verbose = true)
@verbose = verbose
@cert_kc_name = "SystemCACertificates.keychain"
@cert_kc_path = File.join(CertTools.output_keychain_path, @cert_kc_name)
end
def do_processing()
CertTools.createKeychain(@cert_kc_path, @cert_kc_name)
cert_path = CertTools.intermediate_certs_dir
puts "Adding intermediate cderts to #{@cert_kc_path}" if @verbose
puts "Intermediates #{cert_path}" if @verbose
Dir.foreach(cert_path) do |f|
next if f[0].chr == "."
full_path = File.join(cert_path, f)
puts "Processing #{f}" if @verbose
cmd_str = CertTools.security_tool_path
cmd_str += " -q add-certificates "
cmd_str += " -k "
cmd_str += Utilities.quote_str(@cert_kc_path)
cmd_str += " "
cmd_str += Utilities.quote_str(full_path)
`#{cmd_str}`
Utilities.bail("Security add-certificates returned an error for #{full_path}") if $? != 0
end
FileUtils.chmod 0644, @cert_kc_path
end
end
class BuildEVRoots
attr_reader :open_ssl_tool_path
attr_reader :plistbuddy_tool_path
attr_reader :evroots_kc_name
attr_reader :evroots_kc_path
attr_reader :evroots_plist_name
attr_reader :evroots_plist_path
attr_reader :evroots_config_path
attr :verbose
attr :evroots_config_data
def initialize(verbose = true)
@verbose = verbose
@open_ssl_tool_path = "/usr/bin/openssl"
@plistbuddy_tool_path = "/usr/libexec/PlistBuddy"
@evroots_config_path = File.join(CertTools.certificate_dir, "CertificateTool/BuildOSXRootKeychain/evroot.config")
@evroots_config_data = nil
Utilities.check_path(@evroots_config_path, false)
@evroots_kc_name = "EVRoots.keychain"
@evroots_kc_path = File.join(CertTools.build_dir, @evroots_kc_name)
@evroots_plist_name = "EVRoots.plist"
@evroots_plist_path = File.join(CertTools.output_keychain_path, @evroots_plist_name)
end
def get_config_data()
return @evroots_config_data if !@evroots_config_data.nil?
@evroots_config_data = ""
File.open(@evroots_config_path, "r") do |file|
file.each do |line|
line.gsub!(/^ next if line.empty?
line.gsub!(/^\s*\n/, '')
next if line.empty?
@evroots_config_data += line
end
end
@evroots_config_data
end
def get_cert_lines()
lines_str = get_config_data
lines = lines_str.split("\n")
lines
end
def pass_one()
lines = get_cert_lines
lines.each do |line|
items = line.split('"')
items.shift
items.each do |cert_file|
next if cert_file.empty? || cert_file == " "
cert_file.gsub!(/\"/, '')
puts "Adding cert from file #{cert_file}" if @verbose
cert_to_add = File.join(CertTools.root_certs_dir, cert_file)
Utilities.bail("#{cert_to_add} does not exist") if !FileTest.exists?(cert_to_add)
quoted_cert_to_add = Utilities.quote_str(cert_to_add)
cmd_str = CertTools.security_tool_path + " -q add-certificates -k " + @evroots_kc_path + " " + quoted_cert_to_add
`#{cmd_str}`
Utilities.bail("#{cmd_str} failed") if $? != 0 && $? != 256
end end end
def pass_two()
lines = get_cert_lines
lines.sort!
lines.each do |line|
items = line.split('"')
oid_str = items.shift
oid_str.gsub!(/\s/, '')
index = 0
cmd_str = @plistbuddy_tool_path + " -c " + '"' + "add :#{oid_str} array" + '"' + " " + @evroots_plist_path
`#{cmd_str}`
Utilities.bail("#{cmd_str} failed") if $? != 0
items.each do |cert_file|
next if cert_file.empty? || cert_file == " "
cert_file.gsub!(/\"/, '')
cert_to_hash = File.join(CertTools.root_certs_dir, cert_file)
Utilities.bail("#{cert_to_hash} does not exist") if !FileTest.exists?(cert_to_hash)
cmd_str = @open_ssl_tool_path + " x509 -inform DER -in " + Utilities.quote_str(cert_to_hash) + " -fingerprint -noout"
finger_print = `#{cmd_str}`
Utilities.bail("#{cmd_str} failed") if $? != 0
finger_print.gsub!(/SHA1 Fingerprint=/, '')
finger_print.gsub!(/:/,'').chomp!
puts "Certificate fingerprint for #{cert_file} SHA1: #{finger_print}" if @verbose
binary_finger_print = Utilities.hex_to_bin(finger_print)
FileUtils.rm_f "/tmp/certsha1hashtmp"
File.open("/tmp/certsha1hashtmp", "w") { |f| f.write binary_finger_print }
cmd_str = @plistbuddy_tool_path + " -c " + '"' + "add :#{oid_str}:#{index} data" + '"' + " -c " + '"' +
"import :#{oid_str}:#{index} " + "/tmp/certsha1hashtmp" + '"' + " " + @evroots_plist_path
`#{cmd_str}`
Utilities.bail("#{cmd_str} failed") if $? != 0
cmd_str = @plistbuddy_tool_path + " -c " + '"' + "print :#{oid_str}:#{index} data" + '" ' + @evroots_plist_path
file_binary_finger_print = `#{cmd_str}`
Utilities.bail("#{cmd_str} failed") if $? != 0
file_binary_finger_print.chomp!
hex_finger_print = Utilities.bin_to_hex(binary_finger_print)
hex_file_finger_print = Utilities.bin_to_hex(file_binary_finger_print)
if hex_finger_print != hex_file_finger_print
puts "### BUILD FAILED: data verification error"
puts "You likely need to install a newer version of #{@plistbuddy_tool_path} see <rdar://6208924> for details"
CertTools.restoreKeychainList
FileUtils.rm_f @evroots_plist_path
exit 1
end
index += 1
end end end
def do_processing()
CertTools.saveKeychainList
CertTools.createKeychain(@evroots_kc_path, @evroots_kc_name)
pass_one
puts "Removing #{@evroots_plist_path}" if @verbose
FileUtils.rm_f @evroots_plist_path
pass_two
FileUtils.chmod 0644, @evroots_plist_path
puts "Built #{@evroots_plist_path} successfully" if @verbose
end
end
verbose = false;
brkc = BuildRootKeychains.new(verbose)
brkc.do_processing
bcakc = BuildCAKeychain.new(verbose)
bcakc.do_processing
bevr = BuildEVRoots.new(verbose)
bevr.do_processing
x509_anchors_path = File.join(CertTools.certificate_dir, "CertificateTool/BuildOSXRootKeychain/X509Anchors")
output_dir = File.join(CertTools.output_keychain_path, "X509Anchors")
FileUtils.cp x509_anchors_path, output_dir
puts "That's all folks!"