Skip to content

Instantly share code, notes, and snippets.

@denisoster
Last active September 11, 2023 02:48
Show Gist options
  • Save denisoster/808c9965ce9cc4bca3233d491c9a3e61 to your computer and use it in GitHub Desktop.
Save denisoster/808c9965ce9cc4bca3233d491c9a3e61 to your computer and use it in GitHub Desktop.
Resign ipa
bundle exec fastlane resign_ipa ipa:Provenance-tvOS.ipa provision:myprovision.mobileprovision
# frozen_string_literal: true
lane :resign_ipa do |options|
resign_ipa_os(options[:provision], options[:ipa], options[:identity]) { |plist| }
end
def resign_ipa_os(provision, ipa_path, identity, &block)
provision = verify_provision(provision)
ipa_path = verify_ipa_path(ipa_path)
puts "Using IPA #{ipa_path}"
puts "Mobile Provisioning #{provision}"
ipa_update = update_ipa(provision, ipa_path, &block)
puts "Modify IPA before resign #{ipa_update}"
resign_ipa(provision, ipa_update, identity)
end
def update_ipa(provision, ipa_path, &block)
ipa_path_update_dir = result_dir()
ipa_path_update = "#{ipa_path_update_dir}/#{ipa_path.split('/').last }"
unpack_name = "Payload"
sh "yes | unzip -q #{ipa_path}"
provision_content_path = "./provision.plist"
sh "security cms -D -i #{provision} > #{provision_content_path}"
raw_bundle_id = sh "/usr/libexec/PlistBuddy -c \"PRINT :Entitlements:application-identifier\" #{provision_content_path}"
bundle_id = raw_bundle_id.split(".").drop(1).join(".").gsub("\n", '')
sh "rm #{provision_content_path}"
app_name = Dir.children("./#{unpack_name}").select{|file| file.end_with?(".app") }[0]
plist_path = "./#{unpack_name}/#{app_name}/Info.plist"
original_bundle_id_raw = sh "/usr/libexec/PlistBuddy -c \"PRINT :CFBundleIdentifier\" #{plist_path}"
original_bundle_id = original_bundle_id_raw.gsub("\n", '')
puts "Original IPA BundleID: #{original_bundle_id}"
puts "Replace to IPA BundleID: #{bundle_id}"
# update bundle id
sh "/usr/libexec/PlistBuddy -c \"Set :CFBundleIdentifier #{bundle_id}\" #{plist_path}"
# hand to the caller to hanlde plist if needed
block.call(plist_path)
# remove plugins as not needed for base functioning.
sh "rm -fr ./#{unpack_name}/#{app_name}/PlugIns"
if Dir.exist?(ipa_path_update)
File.delete(ipa_path_update)
end
sh "zip -rq #{ipa_path_update} ./#{unpack_name}"
sh "rm -fr ./#{unpack_name}"
ipa_path_update
end
def resign_ipa(provision, ipa_path, identity)
if identity == nil
identity = find_identity(provision)
end
puts "using resign identity: #{identity}"
resign(
ipa: ipa_path,
signing_identity: identity,
provisioning_profile: provision
)
end
def find_identity(provision)
# Extract all the system certificates for identity search.
identities_list = (sh "security find-certificate -p -Z -a").split("-----END CERTIFICATE-----\n")
identities = {}
identities_list.each do |item|
lines = item.split("\n")
# key -> identity SHA-1, value -> certificate in PEM format
identities.store(lines[1].split(" ").last, lines.drop(3).join())
end
# Decode mobileprovision.
xml = sh "security cms -D -i #{provision}"
parsed_info = Nokogiri::XML(xml)
identity = ""
# Find a match for mobileprovision and keychain certificates.
parsed_info.search('data').map(&:text).each do |pem|
identities.each do |key, value|
if value == pem
identity = key
end
end
end
identity
end
def result_dir
dir = "#{Dir.pwd}/resign"
Dir.mkdir(dir) unless Dir.exist?(dir)
dir
end
def verify_provision(provision)
verify_path(provision, "`provision` key with a full path to *.mobileprovision file must be provided.") { |path|
path.end_with?(".mobileprovision")
}
end
def verify_ipa_path(ipa_path)
verify_path(ipa_path, "ipa full file path must be provided.") { |path|
path.end_with?(".ipa")
}
end
def verify_path(path, error)
if path == nil || File.exists?(path) == false || path.start_with?("/") == false || yield(path) == false
UI.user_error!(error)
end
path
end
# frozen_string_literal: true
source "https://rubygems.org"
gem 'fastlane', '~> 2.214'
gem 'nokogiri', '~> 1.15', '>= 1.15.4'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment