app_signer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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