app_signer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. data/README.md +52 -0
  2. data/bin/app_signer +25 -0
  3. data/lib/app_signer.rb +189 -0
  4. metadata +85 -0
@@ -0,0 +1,52 @@
1
+ # app_signer
2
+
3
+ app_signer is a tool to re-sign iOS .app files to create a new .ipa with a given
4
+ provisioning profile and certificate.
5
+
6
+ ## Usage
7
+
8
+ ### Explanation of Parameters
9
+
10
+ * `app_path` - Path to the iOS .app file to be signed.
11
+ * `provisioning_profile_path` - Path to the provisioning profile to be used to
12
+ sign the ipa.
13
+ * `signing_identity` - Common name in the certificate to be used to sign the
14
+ ipa. This can be found by opening the keychain, right clicking the certificate,
15
+ and clicking 'get info'.
16
+ * `signing_identity_SHA1` - SHA1 of the certificate to be used to sign the app.
17
+ This can be found by opening the keychain, right clicking the certificate,
18
+ and clicking 'get info'. This is needed because it's possible to have two
19
+ certificates with the same common name.
20
+ * `generated_ipa_name` - Name to use for the generated ipa file
21
+ * `bundle_id` - Used to changed the bundle id during re-signing. *(optional)*
22
+
23
+ ### In a Ruby Script
24
+
25
+ ```ruby
26
+ require 'app_signer'
27
+
28
+ # Create signer
29
+ signer = AppSigner::Signer.new
30
+
31
+ # Set needed params
32
+ signer.app_path = # path to .app
33
+ signer.provisioning_profile_path = # path to provisioning profile
34
+ signer.signing_identity = # signing identity common name
35
+ signer.signing_identity_SHA1 = # signing identity SHA1
36
+ signer.generated_ipa_name = # name for generated ipa
37
+ signer.bundle_id = # optional new bundle id to use in info plist
38
+
39
+ # Create new ipa
40
+ signer.sign
41
+ ```
42
+
43
+ ### From the Command Line
44
+
45
+ ```bash
46
+ app_signer --app-path PATH_TO_APP\
47
+ --profile-path PATH_TO_PROVISIONING_PROFILE\
48
+ --signing-identity SIGNING_IDENTITY\
49
+ --signing-identity-SHA1 SIGNING_IDENTITY_SHA1\
50
+ --ipa-name IPA_NAME\
51
+ --bundle-id OPTIONAL_NEW_BUNDLE_ID
52
+ ```
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'app_signer'
4
+ require 'fileutils'
5
+ require 'pathname'
6
+ require 'trollop'
7
+
8
+ opts = Trollop::options do
9
+ opt :app_path, "Path to .app file", :type => :string, :required => true
10
+ opt :profile_path, "Path to provisioning profile", :type => :string, :required => true
11
+ opt :signing_identity, "Signing Identity", :type => :string, :required => true
12
+ opt :signing_identity_SHA1, "SHA1 of Signing Identity", :type => :string, :required => true
13
+ opt :ipa_name, "Name of signed IPA", :type => :string, :required => true
14
+ opt :bundle_id, "bundle id to be used in signed IPA", :type => :string
15
+ end
16
+
17
+ signer = AppSigner::Signer.new
18
+ signer.app_path = opts[:app_path]
19
+ signer.provisioning_profile_path = opts[:profile_path]
20
+ signer.signing_identity = opts[:signing_identity]
21
+ signer.signing_identity_SHA1 = opts[:signing_identity_SHA1]
22
+ signer.generated_ipa_name = opts[:ipa_name]
23
+ signer.bundle_id = opts[:bundle_id]
24
+
25
+ signer.sign
@@ -0,0 +1,189 @@
1
+ require 'plist'
2
+ require 'openssl'
3
+
4
+ # AppSigner is an easy way to re-sign iOS apps. This module contains only one
5
+ # class {AppSigner::Signer} which contains all functionality.
6
+ module AppSigner
7
+
8
+ # Used to re-sign iOS .app files. Generates a .ipa based upon the set
9
+ # attributes when calling {AppSigner::Signer#sign}
10
+ class Signer
11
+
12
+ # @!attribute app_path
13
+ # @return [String] path to the .app file to be re-signed
14
+ attr_accessor :app_path
15
+
16
+ # @!attribute provisioning_profile_path
17
+ # @return [String] path to provisioning profile to be used during signing
18
+ attr_accessor :provisioning_profile_path
19
+
20
+ # @!attribute signing_identity
21
+ # @return [String] signing identity of the certificate to be used to sign
22
+ attr_accessor :signing_identity
23
+
24
+ # @!attribute bundle_id
25
+ # @return [String] bundle id to set in the re-signed ipa's info plist
26
+ attr_accessor :bundle_id
27
+
28
+ # @!attribute signing_identity_SHA1
29
+ # @return [String] SHA1 of certificate to be used during signing
30
+ attr_reader :signing_identity_SHA1
31
+
32
+ # @!attribute signing_identity_SHA1
33
+ # @return [String] name tp be used for the generated .ipa
34
+ attr_accessor :generated_ipa_name
35
+
36
+ # Sets signing_identity_SHA1 by removing spaces in the giving hash
37
+ def signing_identity_SHA1=(value)
38
+ @signing_identity_SHA1 = value.gsub(" ", "")
39
+ end
40
+
41
+ # Extracts XML from provisioning profile to be used to for obtaining proper
42
+ # entitlements
43
+ #
44
+ # @param signed_data [String] contents of provisioing profile
45
+ # @return [String]
46
+ def unwrap_signed_data(signed_data)
47
+ pkcs7 = OpenSSL::PKCS7.new(signed_data)
48
+ store = OpenSSL::X509::Store.new
49
+ flags = OpenSSL::PKCS7::NOVERIFY
50
+ pkcs7.verify([], store, nil, flags) # VERIFY IT SO WE CAN PULL OUT THE DATA
51
+ return pkcs7.data
52
+ end
53
+
54
+ # Checks to see if a givin certificate exists in the keychain by looking it
55
+ # up by the name and SHA1
56
+ #
57
+ # @param common_name [String] common name used in the certificate
58
+ # @param sha1 [String] SHA1 of the certificate
59
+ # @return [Boolean]
60
+ def cert_valid?(common_name, sha1)
61
+ certs = `security find-certificate -c "#{common_name}" -Z -p -a`
62
+ end_certificate = "-----END CERTIFICATE-----"
63
+ state = :searching
64
+ cert = ""
65
+
66
+ certs.lines.each do |line|
67
+ case state
68
+ when :searching
69
+
70
+ if line.include? sha1
71
+ state = :found_hash
72
+ end
73
+
74
+ when :found_hash
75
+ cert << line
76
+ if line.include? end_certificate
77
+ state = :did_end
78
+ end
79
+ when :did_end
80
+ end
81
+ end
82
+
83
+ if cert.empty?
84
+ throw 'Failed to find Signing Certificate'
85
+ end
86
+
87
+ File.open("pem", 'w') {|f| f.write(cert) }
88
+ system("security verify-cert -c \"pem\"")
89
+ File.unlink("pem")
90
+ return $?.success?
91
+ end
92
+
93
+ # Returns a PList by parsing the provisioning profile at the given path
94
+ #
95
+ # @param path [String] path to plist to parse
96
+ # @return [PList]
97
+ def parse_provisioning_proflie(path)
98
+ # Read provisioning profile into signedData
99
+ signed_data=File.read(path)
100
+ # Parse profile
101
+ r = Plist::parse_xml(unwrap_signed_data(signed_data))
102
+ return r
103
+ end
104
+
105
+ # Raises an exception if attributes needed for signing aren't set
106
+ def validate_attributes
107
+ if self.app_path.nil?
108
+ raise 'app_path required to sign'
109
+ end
110
+
111
+ if self.provisioning_profile_path.nil?
112
+ raise 'provisioning_profile_path required to sign'
113
+ end
114
+
115
+ if self.signing_identity.nil?
116
+ raise 'signing_identity required to sign'
117
+ end
118
+
119
+ if self.signing_identity_SHA1.nil?
120
+ raise 'signing_identity_SHA1 required to sign'
121
+ end
122
+
123
+ if self.generated_ipa_name.nil?
124
+ raise 'generated_ipa_name required to sign'
125
+ end
126
+ end
127
+
128
+ # Creates an ipa by using the signing information given in the set
129
+ # attributes
130
+ def sign
131
+
132
+ validate_attributes
133
+
134
+ # Parse profile
135
+ r = parse_provisioning_proflie(self.provisioning_profile_path)
136
+
137
+ # Grab entitlements
138
+ entitlements=r['Entitlements']
139
+
140
+ if self.bundle_id
141
+ # Update info plist to have bundle id specified in the config
142
+ info_plist_path="#{app_path}/Info.plist"
143
+ system("plutil -convert xml1 \"#{info_plist_path}\"")
144
+ file_data=File.read(info_plist_path)
145
+ info_plist=Plist::parse_xml(file_data)
146
+ info_plist['CFBundleIdentifier']=self.bundle_id
147
+
148
+ # Save updated info plist and entitlements plist
149
+ info_plist.save_plist info_plist_path
150
+ end
151
+
152
+ entitlements.save_plist("#{app_path}/Entitlements.plist")
153
+
154
+ # Remove old embedded provisioning profile
155
+ File.unlink("#{app_path}/embedded.mobileprovision") if File.exists? "#{app_path}/embedded.mobileprovision"
156
+
157
+ # Embed new profile
158
+ FileUtils.cp_r(self.provisioning_profile_path,"#{app_path}/embedded.mobileprovision")
159
+
160
+ puts 'Verifying Signing Certificate'
161
+ unless cert_valid?(self.signing_identity, self.signing_identity_SHA1)
162
+ throw 'Failed to verify Signing Certificate'
163
+ end
164
+ puts 'Certificate Valid'
165
+
166
+ # Re-sign application using correct profile and entitlements
167
+ $stderr.puts "running /usr/bin/codesign -f -s \"#{self.signing_identity}\" --resource-rules=\"#{app_path}/ResourceRules.plist\" \"#{app_path}\""
168
+ result=system("/usr/bin/codesign -f -s \"#{self.signing_identity_SHA1}\" --resource-rules=\"#{app_path}/ResourceRules.plist\" --entitlements=\"#{app_path}/Entitlements.plist\" \"#{app_path}\"")
169
+
170
+ $stderr.puts "codesigning returned #{result}"
171
+ throw 'Codesigning failed' if !result
172
+
173
+ # Create temporary folder to zip up the application
174
+ app_folder=Pathname.new(app_path).dirname.to_s
175
+ temp_folder="#{app_folder}/temp_#{generated_ipa_name}"
176
+ Dir.mkdir(temp_folder)
177
+ Dir.mkdir("#{temp_folder}/Payload")
178
+ FileUtils.cp_r(app_path,"#{temp_folder}/Payload")
179
+
180
+ # Zip it up into the correct directory
181
+ system("pushd \"#{temp_folder}\" && /usr/bin/zip -r \"../#{generated_ipa_name}.ipa\" Payload")
182
+
183
+ # Remove temporary folder
184
+ FileUtils.rm_rf(temp_folder)
185
+ end
186
+
187
+ end
188
+
189
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: app_signer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Andrew Carter
9
+ - WillowTree Apps
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2014-05-15 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: plist
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - '='
21
+ - !ruby/object:Gem::Version
22
+ version: 3.1.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - '='
29
+ - !ruby/object:Gem::Version
30
+ version: 3.1.0
31
+ - !ruby/object:Gem::Dependency
32
+ name: trollop
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - '='
37
+ - !ruby/object:Gem::Version
38
+ version: '2.0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - '='
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ description: Sign an iOS .app package with a given provisioning profile.
48
+ email:
49
+ - andrew.carter@willowtreeapps.com
50
+ - andrew.carter@gmail.com
51
+ executables:
52
+ - app_signer
53
+ extensions: []
54
+ extra_rdoc_files: []
55
+ files:
56
+ - lib/app_signer.rb
57
+ - README.md
58
+ - bin/app_signer
59
+ homepage: https://github.com/willowtreeapps/app_signer
60
+ licenses:
61
+ - MIT
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 1.8.23
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Easily sign an iOS .app package.
84
+ test_files: []
85
+ has_rdoc: yard