ipa_utilities 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9e13259f8182417c57a41a855b35bee4057c4f3c
4
- data.tar.gz: c1f39537267fef3e907a225600b3cc8be3ef20e2
3
+ metadata.gz: 1ba9f6d65508ffbc1081de8722d3221acac0fb04
4
+ data.tar.gz: cc6b0c445643c336bf89807db765b68eb8c5bb30
5
5
  SHA512:
6
- metadata.gz: 77a59e4dd5264869c433135378d000bd680b6af3bdee03fdd77ab7ea8d5fbbfab134ae41b7e5a2bfab3e98d7e8301206d94f701a96211066cb5f6af5a9686044
7
- data.tar.gz: b8e511c4c71fb3888075dfa13d54d34d8e45072d21a7af788fa44c42a571c47aeea7d37f6bf92c26381af7c1ae9f29628a6263e4da959195b5c09fc6680f3d87
6
+ metadata.gz: 3eae0dfbcad611559f0cf5b4ead71f77985ec3ee9eb68c2e028d3b0de9341d424a3d40633c5ca991fbe4ccc5f27b6cc3aab7a094f87c263237302f98869decb3
7
+ data.tar.gz: 76b01af89335e76caef632bfb6a2c1d7fde9ab91fd276ef7c2a2764f9ac55c9f2ab547f0fb1455d77df791c7020bcd7a4a56a5a718c6ccf5424d43903680c21b
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ipa_utilities (0.0.1)
4
+ ipa_utilities (0.2.0)
5
5
  CFPropertyList (~> 2.2)
6
6
  colorize (~> 0.7)
7
7
  commander (~> 4.1)
@@ -11,8 +11,8 @@ GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
13
  CFPropertyList (2.2.8)
14
- colorize (0.7.3)
15
- commander (4.2.0)
14
+ colorize (0.7.5)
15
+ commander (4.2.1)
16
16
  highline (~> 1.6.11)
17
17
  highline (1.6.21)
18
18
  json (1.8.1)
data/bin/ipa_utilities CHANGED
@@ -7,6 +7,10 @@ require 'ipa_utilities'
7
7
  HighLine.track_eof = false
8
8
  Signal.trap("INT") {}
9
9
 
10
+ $verbose = false
11
+ global_option('--verbose') { $verbose = true }
12
+
13
+
10
14
  program :version, IpaVersion::VERSION
11
15
  program :description, 'A command-line interface for dealing with ipas'
12
16
 
@@ -14,136 +18,54 @@ program :help, 'Author', 'Omar Abdelhafith <o.arrabi@me.com>'
14
18
  program :help, 'Website', 'http://nsomar.com'
15
19
  program :help_formatter, :compact
16
20
 
17
- global_option('--verbose') { $verbose = true }
18
- $verbose = true
19
-
20
21
  default_command :help
21
22
 
22
23
  command :verify do |c|
23
- c.syntax = 'ipa_utils verify ipa_path [...]'
24
+ c.syntax = 'ipa_utilities verify ipa_path [...]'
24
25
  c.summary = 'Verifies the ipa provision and signature information'
25
26
 
26
- c.example 'description', 'ipa_utils verify ipa_path'
27
+ c.example 'description', 'ipa_utilities verify ipa_path'
27
28
  c.option '-c', '--certificate certificate', 'Path of the push notification PEM certificate'
28
- c.option '-d', '--device udid', 'UDID of device to check if its included in embedded provision profile'
29
+ c.option '-d', '--device UDID', 'UDID of device to check if its included in embedded provision profile'
30
+ c.option '--devices', 'Show all the devices included in the provision profile'
29
31
 
30
32
  c.action do |args, options|
31
-
32
- path = checkArgs args, "ipa"
33
- exit unless path
34
-
35
- certificate = options.certificate
36
- device = options.device
37
-
38
33
  begin
39
- errors = 0
40
- puts
41
-
42
- ipa = IpaUtilities.new path
43
- ipa.unzipAndParse
44
-
45
- parser = ipa.provisionParser
46
-
47
- puts "Reading general information"
48
- puts "Application Bundle ID " + parser.appBundleID.green
49
- puts "APNS Enviroment: " + parser.apnsEnviroment.green
50
- puts "App Enviroment: " + parser.buildEnviroment.green
51
- puts
52
-
53
- puts "Verifying bundle signature " + ipa.verifyCodeSign
54
-
55
- status = parser.isAPNSandAppSameEnviroment ? "Yes".green : "No".red
56
- puts "Checking embedde provision profile APNS Entitlement vs App enviroments"
57
- puts "Is App and APNS on same enviroment: " + status
58
-
59
- if parser.isAPNSandAppSameEnviroment
60
- gateway = parser.isAPNSProduction ? "gateway.push.apple.com:2195".green : "gateway.sandbox.push.apple.com:2195".green
61
- puts "APNS connection gateway: " + gateway
62
- else
63
- appStatus = parser.isBuildRelease ? "false (Release)" : "true (debug)"
64
- apnStatus = parser.apnsEnviroment
65
- puts "The application was build with get-task-allow set to #{appStatus} while the aps-environment is set to #{apnStatus}, To fix this issue regenerated the provision profile from apple developer then rebuild the app using it".red
66
- errors += 1
67
- end
68
-
69
- if certificate
70
- puts
71
- puts "Checking certificates"
72
- # puts parser.signingIdentities
73
- pem = PemParser.new certificate
74
-
75
- if pem.isAPNS
76
- puts "Certificate Name " + pem.name.green
77
- puts "Certificate Enviroment: " + "#{pem.enviroment}".green
78
- puts "Certificate Bundle ID: " + "#{pem.bundleID}".green
79
-
80
- status = parser.appBundleID == pem.bundleID ? "Yes".green : "No".red
81
- errors += 1 if parser.appBundleID != pem.bundleID
82
-
83
- puts "Certificate bundleId identical to app #{status}"
84
-
85
- status = pem.isProduction == parser.isAPNSProduction ? "Yes".green : "No".red
86
- puts "Is provided certificate correct for passed ipa: " + status
87
-
88
- if pem.isProduction != parser.isAPNSProduction
89
- puts "The application was build with a provision profile containing aps-environment in #{apnStatus} enviroment while the passed certificate environment is set to #{pem.enviroment}\nTo fix this issue either export the correct iOS Push #{pem.enviroment} certificate from keychain or rebuild your app with the correct provision profile".red
90
- errors += 1
91
- end
92
- else
93
- apnStatus = parser.apnsEnviroment
94
- puts "The passed certificate is not an APNS certificate".red
95
- errors += 1
96
- end
97
-
98
- end
99
-
100
- if device
101
- puts
102
- puts "Checking provisioned devices"
103
-
104
- if parser.isBuildDistro
105
- puts "Distribution build do not contain provisioned devices".red
106
- errors += 1
107
- else
108
- puts "Embedded profile contains " + "#{parser.provisionedDevices.count}".green + " devices"
109
- status = parser.provisionedDevices.include?(device) ? "Device with UDID #{device} found".green :
110
- "Device with UDID #{device} not found".red
111
- puts status
112
- errors += 1 if !parser.provisionedDevices.include?(device)
113
- end
114
- end
34
+ ipa = IpaParser.new(parse_path(args, "ipa"))
115
35
 
116
- puts
117
- puts "No errors encountered".green if errors == 0
118
- puts "#{errors} errors encountered!".red if errors > 0
36
+ print_app_bundle(ipa)
37
+ print_provision_profile(ipa.provision_profile, ipa.info_plist)
38
+ verify_certificates(ipa.provision_profile, options.certificate) if options.certificate
39
+ print_provisioned_devices(ipa.provision_profile) if options.devices
40
+ search_udid(ipa.provision_profile, options.device) if options.device
119
41
 
42
+ rescue Exception => err
43
+ say_error err.message
120
44
  ensure
121
- ipa.cleanUp
45
+ ipa.cleanup
122
46
  end
123
47
  end
124
48
  end
125
49
 
126
50
  command :convert do |c|
127
- c.syntax = 'ipa_utils convert p12_path [...]'
51
+ c.syntax = 'ipa_utilities convert p12_path [...]'
128
52
  c.summary = 'Convert a p12 to PEM'
129
53
 
130
- c.example 'description', 'ipa_utils convert p12_file_path'
131
- c.option '-o', '--out outpath', 'Out put file for the Pem file'
132
- # c.option '-d', '--device udid', 'UDID of device to check if its included in embedded provision profile'
54
+ c.example 'description', 'ipa_utilities convert p12_file_path'
55
+ c.option '-o', '--out output_path', 'Out put file for the Pem file'
133
56
 
134
57
  c.action do |args, options|
135
58
 
136
- path = checkArgs args, "p12"
59
+ path = parse_path(args, "p12")
137
60
  exit unless path
138
61
 
139
- outpath = options.out || "~/Desktop/out.pem"
62
+ output_path = options.out || "~/Desktop/out.pem"
140
63
 
141
64
  begin
142
65
 
143
- puts
144
- puts "Converting P12 to Pem"
145
- system "openssl pkcs12 -in #{path} -out #{outpath} -nodes -clcerts"
146
- puts "Pem saved at " + outpath.green
66
+ puts "\nConverting P12 to Pem"
67
+ system "openssl pkcs12 -in #{path} -out #{output_path} -nodes -clcerts"
68
+ output("Pem saved at ", output_path)
147
69
 
148
70
  ensure
149
71
 
@@ -152,105 +74,176 @@ command :convert do |c|
152
74
  end
153
75
 
154
76
  command :certificate do |c|
155
- c.syntax = 'ipa_utils certificate ipa [...]'
77
+ c.syntax = 'ipa_utilities certificate ipa [...]'
156
78
  c.summary = 'fetch the correct push identity from the provided ipa (WIP)'
157
79
 
158
- c.example 'description', 'ipa_utils certificate ipa_path (WIP)'
80
+ c.example 'description', 'ipa_utilities certificate ipa_path (WIP)'
159
81
 
160
82
  c.action do |args, options|
161
83
 
162
- path = checkArgs args, "ipa"
84
+ path = parse_path args, "ipa"
163
85
  exit unless path
164
86
 
165
87
  begin
166
- #Todo
167
- puts
168
- ipa = IpaUtilities.new path
169
- ipa.unzipAndParse
170
- parser = ipa.provisionParser
88
+ ipa = IpaParser.new path
171
89
 
172
- apnsEnviroment = parser.isAPNSProduction ? "Production" : "Development"
173
- identityName = "Apple #{apnsEnviroment} IOS Push Services: #{parser.appBundleID}"
90
+ apns_environment = ipa.provision_profile.production_apns? ? "Production" : "Development"
91
+ identity_name = "Apple #{apns_environment} IOS Push Services: #{ipa.provision_profile.bundle_id}"
174
92
 
175
- puts "Searching Keychain for identity " + identityName.green
93
+ output("Searching Keychain for identity ", identity_name)
176
94
 
177
95
  identities = `security find-identity -v -p ssl-client`
178
- puts "Item found please export it from your keychain".green if identities.lines.index{|s| s.include?(identityName)}
179
- puts "Item couldnt be found in your keychain".red if !identities.lines.index{|s| s.include?(identityName)}
180
96
 
97
+ if identities.lines.index { |s| s.include?(identity_name) }
98
+ puts "Item found please export it from your keychain".green
99
+ else
100
+ puts "Item couldn't be found in your keychain".red
101
+ end
181
102
  ensure
182
- ipa.cleanUp
103
+ ipa.cleanup
183
104
  end
184
105
  end
185
106
  end
186
107
 
187
108
  command :resign do |c|
188
- c.syntax = 'ipa_utils resign ipa -p new_profile'
109
+ c.syntax = 'ipa_utilities resign ipa -p new_profile'
189
110
  c.summary = 'Resigns the passed ipa to the new passed profile'
190
111
 
191
- c.example 'description', 'ipa_utils certificate ipa_path -p profile'
192
- c.option '-p', '--profile profile', 'Path of the provision profile to use'
193
- c.option '-o', '--out outpath', 'Out put file for the Pem file'
112
+ c.example 'description', 'ipa_utilities certificate ipa_path -p profile'
113
+ c.option '-p', '--profile profile_path', 'Path of the provision profile to use'
114
+ c.option '-i', '--identity Identity_name', 'The identity name'
115
+ c.option '-b', '--bundle bundle_identifier', 'The new bundle identifier'
116
+ c.option '-o', '--out output_path', 'Out put file for the Pem file'
194
117
 
195
118
  c.action do |args, options|
119
+ begin
120
+ path = parse_path(args, "ipa")
196
121
 
197
- puts
122
+ raise "identity name is required with -i" unless options.identity
198
123
 
199
- path = checkArgs args, "ipa"
200
- exit unless path
124
+ signer = CodeSigner.new(ipa_path: path,
125
+ identity: options.identity,
126
+ profile: options.profile,
127
+ bundle_id: options.bundle,
128
+ output_path: options.out)
129
+ signer.resign
201
130
 
202
- profile = options.profile
203
- if !profile
204
- say_error "pass a profile with -p profile-path"
205
- exit
131
+ rescue Exception => err
132
+ say_error err.message
206
133
  end
134
+ end
135
+ end
207
136
 
208
- exit unless checkFileExists "provision profile", profile
209
-
210
- outpath = options.out || "~/Desktop/resigned.ipa"
137
+ def parse_path(args, title)
138
+ raise "Path to #{title} is required" if args.nil? || args.empty?
139
+ path = args.first
140
+ raise "Couldn't find #{title} with path #{path}" unless File.exist?(path)
211
141
 
212
- begin
142
+ path
143
+ end
213
144
 
214
- ipa = IpaUtilities.new path
145
+ def print_app_bundle(ipa)
146
+ puts "______Bundle information______"
147
+ output("App bundle name: ", ipa.bundle_name)
148
+ output("App display name: ", ipa.info_plist.display_name)
149
+ output("Verifying app bundle signature: ", "Signature Valid", "Signature Not Valid",
150
+ CodeSigner.signature_valid?(ipa))
215
151
 
216
- ipa.unzipAndParse
217
- ipa.deleteOldSignature
152
+ end
218
153
 
219
- parser = ipa.provisionParser
154
+ def verify_certificates(provision_profile, apns_certificate_path)
155
+ puts "\n______Checking certificates______"
220
156
 
221
- puts "Copying the new provision profile to app bundle"
222
- system "cp \"#{profile}\" \"Payload/#{ipa.bundleName}/embedded.mobileprovision\""
157
+ apns_certificate = SigningIdentity.new(apns_certificate_path)
223
158
 
224
- puts "Writing Entitlements.plist"
225
- File.write "Entitlements.plist", parser.entitlementForSigning
159
+ if apns_certificate.apns?
160
+ output("Certificate name: ", apns_certificate.name)
161
+ output("Certificate environment: ", apns_certificate.environment)
162
+ output("Certificate bundle id: ", apns_certificate.bundle_id)
226
163
 
227
- buildName = parser.isBuildRelease ? "Distribution" : "Development"
228
- system "codesign -s \"iPhone #{buildName}: #{parser.teamName} (#{parser.teamIdentifier})\" --entitlements Entitlements.plist \"Payload/DummyApp.app\" -f"
164
+ output("Certificate bundleId identical to App bundleId: ", "Yes", "No",
165
+ apns_certificate.bundle_id == provision_profile.bundle_id)
229
166
 
230
- puts
231
- ipa.zip outpath
167
+ same_environment = apns_certificate.production? == provision_profile.production_apns?
168
+ output("Provided certificate correct for the ipa: ", "Yes", "No", same_environment)
232
169
 
233
- ensure
234
- ipa.cleanUp
170
+ unless same_environment
171
+ puts %{\
172
+ The application was build with a provision profile containing aps-environment \
173
+ in `#{provision_profile.environment}` environment while the passed certificate environment is set \
174
+ to `#{apns_certificate.environment}`\
175
+ \nTo fix this issue either export the correct \
176
+ iOS Push `#{apns_certificate.environment}` certificate from keychain or rebuild your app \
177
+ with the correct provision profile}.red
235
178
  end
179
+ else
180
+ puts "The passed certificate is not an APNS certificate".red
236
181
  end
237
182
  end
238
183
 
239
- def checkArgs args, title
240
- if args.nil? || args.empty?
241
- say_error "Path to #{title} is required"
242
- return nil
184
+ def search_udid(provision_profile, device_udid)
185
+ puts "\n______Searching provisioned device______"
186
+
187
+ if provision_profile.app_store_build?
188
+ puts "Distribution build do not contain provisioned devices".red
189
+ else
190
+ output("Number of embedded devices: ", provision_profile.provisioned_devices.count.to_s)
191
+ output("Device with UDID `#{device_udid}`: ", "Yes", "No",
192
+ provision_profile.provisioned_devices.include?(device_udid))
243
193
  end
194
+ end
244
195
 
245
- checkFileExists title, args.first
196
+ def print_provisioned_devices(provision_profile)
197
+ puts "\n______Provisioned devices______"
198
+
199
+ output("Number of embedded devices: ", provision_profile.provisioned_devices.count.to_s)
200
+ puts provision_profile.provisioned_devices
246
201
  end
247
202
 
248
- def checkFileExists title, path
203
+ def check_bundle_id(provision_profile, info_plist)
204
+ output("InfoPlist App bundle id: ", info_plist.bundle_id)
205
+ output("Profile App bundle id: ", provision_profile.bundle_id)
206
+ output("InfoPlist bundle id matches Profile bundle id: ", "Yes", "No",
207
+ info_plist.bundle_id == provision_profile.bundle_id)
208
+ end
249
209
 
250
- if !File.exist?path
251
- say_error "Couldn't find #{title} with path #{path}"
252
- return nil
253
- end
210
+ def print_provision_profile(provision_profile, info_plist)
211
+ puts "\n______Provision profile information______"
212
+ output("Team name: ", provision_profile.team_name)
213
+ output("Profile display name: ", provision_profile.display_name)
214
+ check_bundle_id(provision_profile, info_plist)
254
215
 
255
- path
216
+ puts "\n______Build environments______"
217
+ output("App environment: ", provision_profile.build_environment)
218
+ output("APNS environment: ", provision_profile.apns_environment)
219
+
220
+ output("App and APNS on same environment: ", "Yes", "No",
221
+ provision_profile.apns_and_app_same_environment?)
222
+
223
+ output("APNS connection gateway: ", provision_profile.apns_gateway)
224
+
225
+ unless provision_profile.apns_and_app_same_environment?
226
+ puts %{\
227
+ The application was build with get-task-allow set to `#{provision_profile.task_allow?}` while \
228
+ the aps-environment is set to `#{provision_profile.apns_environment}`, To fix this issue regenerated \
229
+ the provision profile from apple developer then rebuild the app using it}.red
230
+ end
256
231
  end
232
+
233
+ def output(*args)
234
+ label = args.first
235
+
236
+ if args.count == 2
237
+ success_string = failure_string = args[1]
238
+ status = true
239
+ elsif args.count == 3
240
+ success_string = failure_string = args[1]
241
+ status = args[2]
242
+ elsif args.count == 4
243
+ success_string = args[1]
244
+ failure_string = args[2]
245
+ status = args[3]
246
+ end
247
+
248
+ puts label + (status ? success_string.green : failure_string.red)
249
+ end
data/lib/ipa_utilities.rb CHANGED
@@ -1,3 +1,4 @@
1
- require "ipa_utilities/IpaUtilities"
2
- require "ipa_utilities/Parsers"
1
+ require "ipa_utilities/ipa_parser"
2
+ require "ipa_utilities/parsers"
3
3
  require "ipa_utilities/version"
4
+ require "ipa_utilities/code_signer"
@@ -0,0 +1,66 @@
1
+ class CodeSigner
2
+
3
+ def initialize(options)
4
+ @ipa_path = check_file_exist(options[:ipa_path], "Cannot find file at '%s'",)
5
+ @profile = check_file_exist(options[:profile], "Cannot find file at '%s'") if options[:profile]
6
+
7
+ @output_path = options[:output_path] || "~/Desktop/resigned.ipa"
8
+
9
+ @identity = check_non_nil(options[:identity], "Identity cannot be nil")
10
+ @bundle_id = options[:bundle_id]
11
+ end
12
+
13
+ def self.signature_valid?(ipa)
14
+ system("codesign -v #{ipa.app_path} 2>&1")
15
+ $?.exitstatus == 0
16
+ end
17
+
18
+ def resign
19
+ ipa = IpaParser.new(@ipa_path)
20
+ @app_path = ipa.app_path
21
+
22
+ delete_old_signature
23
+ embed_profile
24
+ update_bundle_id(ipa.info_plist)
25
+
26
+ cmd = "codesign -s '#{@identity}' '#{ipa.app_path}' -f"
27
+ puts cmd if $verbose
28
+ system(cmd)
29
+ ipa.zip(@output_path)
30
+ ipa.cleanup
31
+ end
32
+
33
+ def update_bundle_id(info_plist)
34
+ return unless @bundle_id
35
+
36
+ puts "Applying new bundle id '#{@bundle_id.green}'"
37
+ info_plist.bundle_id = @bundle_id
38
+ info_plist.save
39
+ end
40
+
41
+ private
42
+
43
+ def embed_profile
44
+ return unless @profile
45
+
46
+ puts "Copying the new provision profile to app bundle"
47
+ system "cp \"#{@profile}\" \"#{@app_path}/embedded.mobileprovision\""
48
+ end
49
+
50
+ def delete_old_signature
51
+ system "rm -rf #{@app_path}/_CodeSignature"
52
+ puts "Deleting old code sign file" if $verbose
53
+ end
54
+
55
+ def check_non_nil(string, error)
56
+ raise error unless string
57
+ string
58
+ end
59
+
60
+ def check_file_exist(file, error)
61
+ file_path = file || ""
62
+ raise error%file_path unless File.exist?(file_path)
63
+ file
64
+ end
65
+
66
+ end
@@ -0,0 +1,112 @@
1
+ require 'cfpropertylist'
2
+ require 'base64'
3
+ require 'colorize'
4
+ require 'ipa_utilities/parsers'
5
+ require 'tmpdir'
6
+
7
+ class IpaParser
8
+
9
+ attr_reader :provision_profile, :info_plist, :ipa_path
10
+
11
+ def initialize(ipa_path)
12
+ @ipa_path = ipa_path
13
+ unzip_and_parse
14
+ end
15
+
16
+ def bundle_name
17
+ File.basename(app_path)
18
+ end
19
+
20
+ def app_path
21
+ Dir["Payload/*.app"].last
22
+ end
23
+
24
+ def info_plist_path
25
+ app_path + "/Info.plist"
26
+ end
27
+
28
+ def zip(path)
29
+ say "Zipping " + @ipa_path.green if $verbose
30
+ system "zip -qr \"_new.ipa\" Payload"
31
+ system "cp _new.ipa #{path}"
32
+ say "Resigned ipa saved at " + path.green
33
+ end
34
+
35
+ def cleanup
36
+ system "rm -rf #{zip_out_path}"
37
+ say "Deleting directory #{zip_out_path}" if $verbose
38
+ end
39
+
40
+ private
41
+
42
+ def generate_entitlements(new_bundle_id = nil)
43
+ puts "\nGenerating Entitlements.plist"
44
+ File.write("Entitlements.plist", provision_profile.signing_entitlement(new_bundle_id))
45
+ end
46
+
47
+ def unzip_and_parse
48
+ unzip
49
+ parse
50
+ end
51
+
52
+ def unzip
53
+ say "Unzipping '#{@ipa_path.green}' to '#{zip_out_path}'" if $verbose
54
+ system "unzip #{@ipa_path} -d #{zip_out_path} | logger -t ipa_utilities"
55
+
56
+ change_directory
57
+ end
58
+
59
+ def change_directory
60
+ puts "Changing directory: " + "'#{zip_out_path}'\n".green if $verbose
61
+ FileUtils.chdir(zip_out_path)
62
+ end
63
+
64
+ def parse
65
+ provision_path = "Payload/#{bundle_name}/embedded.mobileprovision"
66
+ say "Reading provision profile: "+ provision_path.green + "\n" if $verbose
67
+
68
+ @provision_profile = ProvisionProfile.new(provision_path)
69
+ @info_plist = InfoPlist.new(info_plist_path)
70
+ end
71
+
72
+ def zip_out_path
73
+ @zip_out_path ||= Dir.mktmpdir
74
+ end
75
+
76
+ def base_path
77
+ File.dirname(@ipa_path)
78
+ end
79
+
80
+ def ipa_name
81
+ File.basename(@ipa_path)
82
+ end
83
+
84
+ end
85
+
86
+ class InfoPlist
87
+
88
+ def initialize(path)
89
+ @path = path
90
+ plist = CFPropertyList::List.new(:file => path)
91
+ @data = CFPropertyList.native_types(plist.value)
92
+ end
93
+
94
+ def bundle_id
95
+ @data["CFBundleIdentifier"]
96
+ end
97
+
98
+ def display_name
99
+ @data["CFBundleDisplayName"]
100
+ end
101
+
102
+ def bundle_id=(bundle)
103
+ @data["CFBundleIdentifier"] = bundle
104
+ end
105
+
106
+ def save
107
+ plist = CFPropertyList::List.new(:file => @path)
108
+ plist.value = CFPropertyList.guess(@data)
109
+ plist.save(@path, CFPropertyList::List::FORMAT_BINARY)
110
+ end
111
+
112
+ end
@@ -0,0 +1,151 @@
1
+ class SigningIdentity
2
+
3
+ def initialize(name)
4
+ @identity = name
5
+ end
6
+
7
+ def self.from_base64(base64)
8
+ string = "-----BEGIN CERTIFICATE-----\n"
9
+ string += base64
10
+ string += "-----END CERTIFICATE-----"
11
+
12
+ File.write("cer.pem", string)
13
+
14
+ pem = `openssl x509 -text -in cer.pem`
15
+
16
+ system "rm -rf cer.pem"
17
+
18
+ SigningIdentity.new(pem[/CN=(.*?),/, 1])
19
+ end
20
+
21
+ def self.from_file(file)
22
+ pem = `openssl x509 -text -in #{file}`
23
+ SigningIdentity.new(pem[/CN=(.*?),/, 1])
24
+ end
25
+
26
+ def name
27
+ @identity
28
+ end
29
+
30
+ def apns?
31
+ @identity.include?("IOS Push Services")
32
+ end
33
+
34
+ def production?
35
+ !@identity[/[Development|Developer]/]
36
+ end
37
+
38
+ def environment
39
+ if apns?
40
+ production? ? "Production" : "Development (Sandbox)"
41
+ else
42
+ production? ? "Production" : "Development"
43
+ end
44
+ end
45
+
46
+ def display_name
47
+ @identity[/: (.*?)$/, 1]
48
+ end
49
+
50
+ end
51
+
52
+ class ProvisionProfile
53
+
54
+ def initialize(provision_path)
55
+ @provision_path = provision_path
56
+ @data = CFPropertyList.native_types(read_profile)
57
+ end
58
+
59
+ def uuid
60
+ @data["UUID"]
61
+ end
62
+
63
+ def signing_identities
64
+ certificates.map { |identity| SigningIdentity.from_base64(Base64.encode64(identity)) }
65
+ end
66
+
67
+ def certificates
68
+ @data["DeveloperCertificates"]
69
+ end
70
+
71
+ def provisioned_devices
72
+ @data["ProvisionedDevices"]
73
+ end
74
+
75
+ def production_apns?
76
+ @data["Entitlements"]["aps-environment"] == "production"
77
+ end
78
+
79
+ def release_build?
80
+ !@data["Entitlements"]["get-task-allow"]
81
+ end
82
+
83
+ def task_allow?
84
+ @data["Entitlements"]["get-task-allow"]
85
+ end
86
+
87
+ def app_store_build?
88
+ provisioned_devices.nil?
89
+ end
90
+
91
+ def apns_and_app_same_environment?
92
+ release_build? == production_apns?
93
+ end
94
+
95
+ def bundle_id
96
+ identifier = @data["Entitlements"]["application-identifier"]
97
+ identifier[/#{team_identifier}\.(.*)/, 1]
98
+ end
99
+
100
+ def team_name
101
+ @data["TeamName"]
102
+ end
103
+
104
+ def display_name
105
+ @data["Name"]
106
+ end
107
+
108
+ def team_identifier
109
+ @data["Entitlements"]["com.apple.developer.team-identifier"]
110
+ end
111
+
112
+ def apns_environment
113
+ production_apns? ? "Production" : "Development (Sandbox)"
114
+ end
115
+
116
+ def apns_gateway
117
+ production_apns? ? "gateway.push.apple.com:2195" : "gateway.sandbox.push.apple.com:2195"
118
+ end
119
+
120
+ def build_environment
121
+ if release_build?
122
+ app_store_build? ? "Distribution" : "AdHoc"
123
+ else
124
+ "Development"
125
+ end
126
+ end
127
+
128
+ def signing_entitlement(new_bundle_id)
129
+ bundle_to_write = "#{team_identifier}.#{new_bundle_id || bundle_id}"
130
+ puts "Bundle identifier: #{bundle_to_write}"
131
+
132
+ file_path = File.expand_path("#{__FILE__}/../../resources/Original.Entitlements.plist")
133
+ file = File.read(file_path)
134
+ file.sub!("BUNDLE_ID", bundle_to_write)
135
+ file.sub!("GET_TASK_ALLOW", release_build? ? "false" : "true")
136
+ end
137
+
138
+ private
139
+
140
+ def read_profile
141
+ cmd = "security cms -D -i #{@provision_path} > tmp.plist"
142
+ say cmd if $verbose
143
+ system(cmd)
144
+
145
+ # Get info from plist
146
+ plist = CFPropertyList::List.new(:file => "tmp.plist")
147
+ system("rm tmp.plist")
148
+
149
+ plist.value
150
+ end
151
+ end
@@ -1,3 +1,3 @@
1
1
  module IpaVersion
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ipa_utilities
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Omar Abdelhafith
@@ -80,8 +80,9 @@ files:
80
80
  - "./ipa_utilities-0.0.2.gem"
81
81
  - "./ipa_utilities.gemspec"
82
82
  - "./lib/ipa_utilities.rb"
83
- - "./lib/ipa_utilities/IpaUtilities.rb"
84
- - "./lib/ipa_utilities/Parsers.rb"
83
+ - "./lib/ipa_utilities/code_signer.rb"
84
+ - "./lib/ipa_utilities/ipa_parser.rb"
85
+ - "./lib/ipa_utilities/parsers.rb"
85
86
  - "./lib/ipa_utilities/version.rb"
86
87
  - "./lib/resources/Original.Entitlements.plist"
87
88
  - bin/ipa_utilities
@@ -1,63 +0,0 @@
1
- require 'cfpropertylist'
2
- require 'pathname'
3
- require 'base64'
4
- require 'colorize'
5
- require 'ipa_utilities/Parsers'
6
-
7
- class IpaUtilities
8
- attr :provisionParser
9
-
10
- def initialize ipaPath
11
- pn = Pathname.new(ipaPath)
12
- @ipaPath = pn.dirname
13
- @ipaName = pn.basename
14
- @fullPath = ipaPath
15
- end
16
-
17
- def unzip
18
- say "Unzipping " + @fullPath.green if $verbose
19
- system "unzip #{@fullPath} > log.txt"
20
- end
21
-
22
- def zip path
23
- say "Zipping " + @fullPath.green if $verbose
24
- system "zip -qr \"_new.ipa\" Payload"
25
- system "cp _new.ipa #{path}"
26
- say "Resigned ipa saved at " + path.green if $verbose
27
- end
28
-
29
- def bundleName
30
- Dir.entries("Payload").last
31
- end
32
-
33
- def verifyCodeSign
34
- result = `codesign -v Payload/#{bundleName} 2>&1`
35
- result.empty? ? "Signature Valid\n".green : "Signature Not Valid\n".red + result.red if $verbose
36
- end
37
-
38
- def parse
39
- @provisionPath = "Payload/#{bundleName}/embedded.mobileprovision"
40
- @provisionParser = ProvisionParser.new @provisionPath
41
- end
42
-
43
- def unzipAndParse
44
- unzip
45
- puts "App bundle name is " + bundleName.green if $verbose
46
-
47
- parse
48
- say "Reading provision profile at "+ @provisionPath.green if $verbose
49
- puts if $verbose
50
- end
51
-
52
- def deleteOldSignature
53
- system "rm -rf Payload/*.app/_CodeSignature"
54
- puts "Deleting old code sign file" if $verbose
55
- end
56
-
57
- def cleanUp
58
- system "rm -rf Payload"
59
- system "rm -rf tmp.plist"
60
- system "rm -rf Entitlements.plist"
61
- system "rm -rf _new.ipa"
62
- end
63
- end
@@ -1,138 +0,0 @@
1
- class PemParser
2
-
3
- def initialize file
4
- @identity = PemParser.signingIdentitiesWithFile(file).first
5
- end
6
-
7
- def name
8
- @identity
9
- end
10
-
11
- def isAPNS
12
- @identity.include?("IOS Push Services")
13
- end
14
-
15
- def isProduction
16
- !@identity.include?("Development")
17
- end
18
-
19
- def enviroment
20
- isProduction ? "Production" : "Development (Sandbox)"
21
- end
22
-
23
- def bundleID
24
- /: (.*?)$/.match(@identity).captures.first
25
- end
26
-
27
- def self.signingIdentitiesWithBase64 base64
28
- string = "-----BEGIN CERTIFICATE-----\n"
29
- string += base64
30
- string += "-----END CERTIFICATE-----"
31
-
32
- File.write("cer.pem", string)
33
-
34
- pem = `openssl x509 -text -in cer.pem`
35
-
36
- system "rm -rf cer.pem"
37
-
38
- identity = /CN=(.*?),/.match(pem).captures
39
- identity
40
- end
41
-
42
- def self.signingIdentitiesWithFile file
43
- pem = `openssl x509 -text -in #{file}`
44
- identity = /CN=(.*?),/.match(pem).captures
45
- identity
46
- end
47
- end
48
-
49
- class ProvisionParser
50
-
51
- def initialize provisionPath
52
- @provisionPath = provisionPath
53
- parse
54
- end
55
-
56
- def parse
57
- # read mobileprovision and convert it to plist
58
- `security cms -D -i #{@provisionPath} > tmp.plist`
59
-
60
- # Get info from plist
61
- plist = CFPropertyList::List.new
62
- plist = CFPropertyList::List.new(:file => "tmp.plist")
63
- @data = CFPropertyList.native_types(plist.value)
64
- end
65
-
66
- def uuid
67
- @data["UUID"]
68
- end
69
-
70
- def signingIdentities
71
-
72
- arr = []
73
-
74
- certificates.each do |var|
75
- arr << PemParser.signingIdentitiesWithBase64(Base64.encode64(var))
76
- end
77
-
78
- arr
79
- end
80
-
81
- def certificates
82
- @data["DeveloperCertificates"]
83
- end
84
-
85
- def provisionedDevices
86
- @data["ProvisionedDevices"]
87
- end
88
-
89
- def isAPNSProduction
90
- @data["Entitlements"]["aps-environment"] == "production"
91
- end
92
-
93
- def isBuildRelease
94
- @data["Entitlements"]["get-task-allow"] == false
95
- end
96
-
97
- def isBuildDistro
98
- @data["ProvisionedDevices"].nil?
99
- end
100
-
101
- def isAPNSandAppSameEnviroment
102
- isBuildRelease == isAPNSProduction
103
- end
104
-
105
- def appBundleID
106
- var = @data["Entitlements"]["application-identifier"]
107
- var.slice!(@data["TeamIdentifier"].first + ".")
108
- var
109
- end
110
-
111
- def teamName
112
- @data["TeamName"]
113
- end
114
-
115
- def teamIdentifier
116
- @data["Entitlements"]["com.apple.developer.team-identifier"]
117
- end
118
-
119
- def apnsEnviroment
120
- isAPNSProduction ? "Production" : "Development (Sandbox)"
121
- end
122
-
123
- def buildEnviroment
124
- if isBuildRelease
125
- isBuildDistro ? "Distribution" : "AdHoc"
126
- else
127
- "Development"
128
- end
129
- end
130
-
131
- def entitlementForSigning
132
- filePath = File.expand_path "#{__FILE__}/../../resources/Original.Entitlements.plist"
133
- file = File.read filePath
134
- file.sub! "BUNDLE_ID", "#{teamIdentifier}.#{appBundleID}"
135
- file.sub! "GET_TASK_ALLOW", isBuildRelease ? "false" : "true"
136
- end
137
-
138
- end