diff --git a/lib/run_loop.rb b/lib/run_loop.rb index c0506eb7..4d9a4d77 100644 --- a/lib/run_loop.rb +++ b/lib/run_loop.rb @@ -14,6 +14,7 @@ require 'run_loop/core' require 'run_loop/version' require 'run_loop/plist_buddy' +require "run_loop/codesign" require 'run_loop/app' require 'run_loop/ipa' require 'run_loop/sim_control' diff --git a/lib/run_loop/app.rb b/lib/run_loop/app.rb index 5e2df047..9aa76f91 100644 --- a/lib/run_loop/app.rb +++ b/lib/run_loop/app.rb @@ -95,6 +95,21 @@ def calabash_server_version version end + # @!visibility private + def codesign_info + RunLoop::Codesign.info(path) + end + + # @!visibility private + def developer_signed? + RunLoop::Codesign.developer?(path) + end + + # @!visibility private + def distribution_signed? + RunLoop::Codesign.distribution?(path) + end + # @!visibility private # Collects the paths to executables in the bundle. def executables diff --git a/lib/run_loop/cli/cli.rb b/lib/run_loop/cli/cli.rb index f98af46d..847dc632 100644 --- a/lib/run_loop/cli/cli.rb +++ b/lib/run_loop/cli/cli.rb @@ -4,6 +4,7 @@ require 'run_loop/cli/instruments' require 'run_loop/cli/simctl' require "run_loop/cli/locale" +require "run_loop/cli/codesign" trap 'SIGINT' do puts 'Trapped SIGINT - exiting' @@ -35,6 +36,9 @@ def version desc "locale", "Tools for interacting with locales" subcommand "locale", RunLoop::CLI::Locale + desc "codesign", "Tools for interacting with codesign" + subcommand "codesign", RunLoop::CLI::Codesign + end end end diff --git a/lib/run_loop/cli/codesign.rb b/lib/run_loop/cli/codesign.rb new file mode 100644 index 00000000..b67d82da --- /dev/null +++ b/lib/run_loop/cli/codesign.rb @@ -0,0 +1,24 @@ +require "thor" +require "run_loop" +require "run_loop/cli/errors" + +module RunLoop + module CLI + class Codesign < Thor + + desc "info ARTIFACT", "Print codesign information about ARTIFACT (ipa, app, or library)" + + def info(app_or_ipa) + extension = File.extname(app_or_ipa) + + if extension == ".app" + puts RunLoop::App.new(app_or_ipa).codesign_info + elsif extension == ".ipa" + puts RunLoop::Ipa.new(app_or_ipa).codesign_info + else + puts RunLoop::Codesign.info(app_or_ipa) + end + end + end + end +end diff --git a/lib/run_loop/codesign.rb b/lib/run_loop/codesign.rb new file mode 100644 index 00000000..b11bf04c --- /dev/null +++ b/lib/run_loop/codesign.rb @@ -0,0 +1,76 @@ +module RunLoop + # @!visibility private + # A wrapper around codesign command line tool + class Codesign + + # @!visibility private + DEV_REGEX = /Authority=iPhone Developer:/ + + # @!visibility private + APP_STORE_REGEX = /Authority=Apple iPhone OS Application Signing/ + + # @!visibility private + DISTR_REGEX = /Authority=iPhone Distribution:/ + + # @!visibility private + NOT_SIGNED_REGEX = /code object is not signed at all/ + + # @!visibility private + def self.info(path) + self.expect_path_exists(path) + self.exec(["--display", "--verbose=4", path]) + end + + # @!visibility private + # + # True if the asset is signed. + def self.signed?(path) + info = self.info(path) + info[NOT_SIGNED_REGEX, 0] == nil + end + + # @!visibility private + # + # True if the asset is signed with anything other than a dev cert. + def self.distribution?(path) + info = self.info(path) + + info[NOT_SIGNED_REGEX, 0] == nil && + info[DEV_REGEX, 0] == nil + end + + # @!visibility private + # + # True if the asset is signed with a dev cert + def self.developer?(path) + info = self.info(path) + info[DEV_REGEX, 0] != nil + end + + private + + def self.expect_path_exists(path) + if !File.exist?(path) + raise ArgumentError, +%Q{There is no file or directory at path: + +#{path} +} + end + end + + def self.exec(args) + if !args.is_a?(Array) + raise ArgumentError, "Expected args: '#{args}' to be an Array" + end + + xcrun = RunLoop::Xcrun.new + cmd = ["codesign"] + args + options = {:log_cmd => true} + hash = xcrun.exec(cmd, options) + + hash[:out] + end + end +end + diff --git a/lib/run_loop/ipa.rb b/lib/run_loop/ipa.rb index a1588f55..a04513f9 100644 --- a/lib/run_loop/ipa.rb +++ b/lib/run_loop/ipa.rb @@ -51,6 +51,21 @@ def calabash_server_version app.calabash_server_version end + # @!visibility private + def codesign_info + app.codesign_info + end + + # @!visibility private + def developer_signed? + app.developer_signed? + end + + # @!visibility private + def distribution_signed? + app.distribution_signed? + end + private # @!visibility private diff --git a/scripts/ci/jenkins/run.sh b/scripts/ci/jenkins/run.sh index 541cb2a4..60440b02 100755 --- a/scripts/ci/jenkins/run.sh +++ b/scripts/ci/jenkins/run.sh @@ -26,7 +26,8 @@ $RBENV_EXEC bundle exec rspec \ spec/integration/xcode_spec.rb \ spec/integration/otool_spec.rb \ spec/integration/strings_spec.rb \ - spec/integration/app_spec.rb + spec/integration/app_spec.rb \ + spec/integration/codesign_spec.rb # CLI tests @@ -42,5 +43,6 @@ execute "$RBENV_EXEC bundle exec run-loop version" execute "$RBENV_EXEC bundle exec run-loop help" execute "$RBENV_EXEC bundle exec run-loop instruments help" execute "$RBENV_EXEC bundle exec run-loop simctl help" - +execute "$RBENV_EXEC bundle exec run-loop codesign help" +execute "$RBENV_EXEC bundle exec run-loop codesign info spec/resources/CalSmoke.ipa" diff --git a/spec/integration/codesign_spec.rb b/spec/integration/codesign_spec.rb new file mode 100644 index 00000000..87ac8461 --- /dev/null +++ b/spec/integration/codesign_spec.rb @@ -0,0 +1,41 @@ + +describe RunLoop::Codesign do + let(:distribution) { Resources.shared.wetap_bundle } + let(:developer) do + ipa = RunLoop::Ipa.new(Resources.shared.ipa_path) + ipa.send(:app).send(:path) + end + let(:unsigned) { Resources.shared.app_bundle_path } + + describe ".distribution?" do + it "true" do + expect(RunLoop::Codesign.distribution?(distribution)).to be_truthy + end + + it "false" do + expect(RunLoop::Codesign.distribution?(developer)).to be_falsey + end + end + + describe ".developer?" do + it "true" do + expect(RunLoop::Codesign.developer?(developer)).to be_truthy + end + + it "false" do + expect(RunLoop::Codesign.developer?(distribution)).to be_falsey + end + end + + describe ".signed?" do + it "true" do + expect(RunLoop::Codesign.signed?(developer)).to be_truthy + expect(RunLoop::Codesign.signed?(distribution)).to be_truthy + end + + it "false" do + expect(RunLoop::Codesign.signed?(unsigned)).to be_falsey + end + end +end + diff --git a/spec/lib/app_spec.rb b/spec/lib/app_spec.rb index be0e0fd1..332545a9 100644 --- a/spec/lib/app_spec.rb +++ b/spec/lib/app_spec.rb @@ -132,6 +132,26 @@ end end + describe "codesign" do + it "#codesign_info" do + expect(RunLoop::Codesign).to receive(:info).with(app.path).and_return(:info) + + expect(app.codesign_info).to be == :info + end + + it "#developer_signed?" do + expect(RunLoop::Codesign).to receive(:developer?).with(app.path).and_return(:value) + + expect(app.developer_signed?).to be == :value + end + + it "#distribution_signed?" do + expect(RunLoop::Codesign).to receive(:distribution?).with(app.path).and_return(:value) + + expect(app.distribution_signed?).to be == :value + end + end + it '#sha1' do expect(RunLoop::Directory).to receive(:directory_digest).with(app.path).and_return 'sha1' diff --git a/spec/lib/codesign_spec.rb b/spec/lib/codesign_spec.rb new file mode 100644 index 00000000..bd0f1d96 --- /dev/null +++ b/spec/lib/codesign_spec.rb @@ -0,0 +1,143 @@ + +describe RunLoop::Codesign do + + let(:path) { "/path/to/file" } + let(:not_signed) { "#{path}: code object is not signed at all" } + let(:developer) { "Authority=iPhone Developer: Karl Krukow (XXXXXXXXXX)" } + let(:app_store) { "Authority=Apple iPhone OS Application Signing" } + let(:distribution) { "Authority=iPhone Distribution: Permissions" } + + describe "path exists" do + before do + allow(RunLoop::Codesign).to receive(:expect_path_exists).with(path).and_return(true) + end + + it ".info" do + args = ["--display", "--verbose=4", path] + expect(RunLoop::Codesign).to receive(:exec).with(args).and_return("info") + + expect(RunLoop::Codesign.info(path)).to be == "info" + end + + describe ".signed?" do + describe "true if object is signed" do + it "developer" do + expect(RunLoop::Codesign).to receive(:info).and_return(developer) + + expect(RunLoop::Codesign.signed?(path)).to be_truthy + end + + it "app store" do + expect(RunLoop::Codesign).to receive(:info).and_return(app_store) + + expect(RunLoop::Codesign.signed?(path)).to be_truthy + end + + it "distribution" do + expect(RunLoop::Codesign).to receive(:info).and_return(distribution) + + expect(RunLoop::Codesign.signed?(path)).to be_truthy + end + end + + it "false if object is not signed" do + expect(RunLoop::Codesign).to receive(:info).and_return(not_signed) + + expect(RunLoop::Codesign.signed?(path)).to be_falsey + end + end + + describe ".distribution?" do + + describe "false" do + it "developer" do + expect(RunLoop::Codesign).to receive(:info).and_return(developer) + + expect(RunLoop::Codesign.distribution?(path)).to be_falsey + end + + it "not signed" do + expect(RunLoop::Codesign).to receive(:info).and_return(not_signed) + + expect(RunLoop::Codesign.distribution?(path)).to be_falsey + end + end + + describe "true" do + it "app store" do + expect(RunLoop::Codesign).to receive(:info).and_return(app_store) + + expect(RunLoop::Codesign.distribution?(path)).to be_truthy + end + + it "distribution" do + expect(RunLoop::Codesign).to receive(:info).and_return(distribution) + + expect(RunLoop::Codesign.distribution?(path)).to be_truthy + end + end + end + + describe ".developer?" do + describe "false" do + it "distribution" do + expect(RunLoop::Codesign).to receive(:info).and_return(distribution) + + expect(RunLoop::Codesign.developer?(path)).to be_falsey + end + + it "app store" do + expect(RunLoop::Codesign).to receive(:info).and_return(app_store) + + expect(RunLoop::Codesign.developer?(path)).to be_falsey + end + + it "not signed" do + expect(RunLoop::Codesign).to receive(:info).and_return(not_signed) + + expect(RunLoop::Codesign.developer?(path)).to be_falsey + end + end + + it "true" do + expect(RunLoop::Codesign).to receive(:info).and_return(developer) + + expect(RunLoop::Codesign.developer?(path)).to be_truthy + end + end + end + + describe ".exec" do + it "expects an Array argument" do + expect do + RunLoop::Codesign.send(:exec, "string") + end.to raise_error ArgumentError, /to be an Array/ + end + + it ".exec" do + path = "tmp/file.txt" + FileUtils.touch(path) + args = ["--display", "--verbose=4", path] + actual = RunLoop::Codesign.send(:exec, args) + expected = "tmp/file.txt: code object is not signed at all" + expect(actual).to be == expected + end + end + + describe ".expect_path_exists" do + it "raises ArgumentError" do + expect do + RunLoop::Codesign.send(:expect_path_exists, path) + end.to raise_error ArgumentError, /There is no file or directory at path/ + end + + it "does nothing" do + expect(File).to receive(:exist?).with(path).and_return(true) + + expect do + RunLoop::Codesign.send(:expect_path_exists, path) + end.not_to raise_error ArgumentError + end + end +end + diff --git a/spec/lib/ipa_spec.rb b/spec/lib/ipa_spec.rb index c19ef1c0..b728daed 100644 --- a/spec/lib/ipa_spec.rb +++ b/spec/lib/ipa_spec.rb @@ -42,6 +42,29 @@ expect(version).to be_a_kind_of(RunLoop::Version) end + describe "codesign" do + + let(:app) { ipa.send(:app) } + + it "#codesign_info" do + expect(app).to receive(:codesign_info).and_return(:info) + + expect(ipa.codesign_info).to be == :info + end + + it "#developer_signed?" do + expect(app).to receive(:developer_signed?).and_return(:value) + + expect(ipa.developer_signed?).to be == :value + end + + it "#distribution_signed?" do + expect(app).to receive(:distribution_signed?).and_return(:value) + + expect(ipa.distribution_signed?).to be == :value + end + end + describe 'private' do it '#tmpdir' do tmp_dir = ipa.send(:tmpdir) diff --git a/spec/resources.rb b/spec/resources.rb index 828b9303..7baecac8 100644 --- a/spec/resources.rb +++ b/spec/resources.rb @@ -109,6 +109,10 @@ def app_bundle_path_x86_64 @app_bundle_path_x86_64 ||= File.expand_path(File.join(resources_dir, 'lipo', 'x86_64', 'CalSmoke.app')) end + def wetap_bundle + @wettap_bundle ||= File.join(resources_dir, "wetap.app") + end + def bundle_id @bundle_id = 'com.xamarin.CalSmoke-cal' end diff --git a/spec/resources/wetap.app/Info.plist b/spec/resources/wetap.app/Info.plist new file mode 100644 index 00000000..c306f1cf Binary files /dev/null and b/spec/resources/wetap.app/Info.plist differ diff --git a/spec/resources/wetap.app/wetap b/spec/resources/wetap.app/wetap new file mode 100755 index 00000000..7b3db106 Binary files /dev/null and b/spec/resources/wetap.app/wetap differ