ipa_utilities 0.1.1 → 0.2.0

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.
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