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 +4 -4
- data/Gemfile.lock +3 -3
- data/bin/ipa_utilities +150 -157
- data/lib/ipa_utilities.rb +3 -2
- data/lib/ipa_utilities/code_signer.rb +66 -0
- data/lib/ipa_utilities/ipa_parser.rb +112 -0
- data/lib/ipa_utilities/parsers.rb +151 -0
- data/lib/ipa_utilities/version.rb +1 -1
- metadata +4 -3
- data/lib/ipa_utilities/IpaUtilities.rb +0 -63
- data/lib/ipa_utilities/Parsers.rb +0 -138
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ba9f6d65508ffbc1081de8722d3221acac0fb04
|
4
|
+
data.tar.gz: cc6b0c445643c336bf89807db765b68eb8c5bb30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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.
|
15
|
-
commander (4.2.
|
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 = '
|
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', '
|
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
|
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
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
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.
|
45
|
+
ipa.cleanup
|
122
46
|
end
|
123
47
|
end
|
124
48
|
end
|
125
49
|
|
126
50
|
command :convert do |c|
|
127
|
-
c.syntax = '
|
51
|
+
c.syntax = 'ipa_utilities convert p12_path [...]'
|
128
52
|
c.summary = 'Convert a p12 to PEM'
|
129
53
|
|
130
|
-
c.example 'description', '
|
131
|
-
c.option '-o', '--out
|
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 =
|
59
|
+
path = parse_path(args, "p12")
|
137
60
|
exit unless path
|
138
61
|
|
139
|
-
|
62
|
+
output_path = options.out || "~/Desktop/out.pem"
|
140
63
|
|
141
64
|
begin
|
142
65
|
|
143
|
-
puts
|
144
|
-
|
145
|
-
|
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 = '
|
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', '
|
80
|
+
c.example 'description', 'ipa_utilities certificate ipa_path (WIP)'
|
159
81
|
|
160
82
|
c.action do |args, options|
|
161
83
|
|
162
|
-
path =
|
84
|
+
path = parse_path args, "ipa"
|
163
85
|
exit unless path
|
164
86
|
|
165
87
|
begin
|
166
|
-
|
167
|
-
puts
|
168
|
-
ipa = IpaUtilities.new path
|
169
|
-
ipa.unzipAndParse
|
170
|
-
parser = ipa.provisionParser
|
88
|
+
ipa = IpaParser.new path
|
171
89
|
|
172
|
-
|
173
|
-
|
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
|
-
|
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.
|
103
|
+
ipa.cleanup
|
183
104
|
end
|
184
105
|
end
|
185
106
|
end
|
186
107
|
|
187
108
|
command :resign do |c|
|
188
|
-
c.syntax = '
|
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', '
|
192
|
-
c.option '-p', '--profile
|
193
|
-
c.option '-
|
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
|
-
|
122
|
+
raise "identity name is required with -i" unless options.identity
|
198
123
|
|
199
|
-
|
200
|
-
|
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
|
-
|
203
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
142
|
+
path
|
143
|
+
end
|
213
144
|
|
214
|
-
|
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
|
-
|
217
|
-
ipa.deleteOldSignature
|
152
|
+
end
|
218
153
|
|
219
|
-
|
154
|
+
def verify_certificates(provision_profile, apns_certificate_path)
|
155
|
+
puts "\n______Checking certificates______"
|
220
156
|
|
221
|
-
|
222
|
-
system "cp \"#{profile}\" \"Payload/#{ipa.bundleName}/embedded.mobileprovision\""
|
157
|
+
apns_certificate = SigningIdentity.new(apns_certificate_path)
|
223
158
|
|
224
|
-
|
225
|
-
|
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
|
-
|
228
|
-
|
164
|
+
output("Certificate bundleId identical to App bundleId: ", "Yes", "No",
|
165
|
+
apns_certificate.bundle_id == provision_profile.bundle_id)
|
229
166
|
|
230
|
-
|
231
|
-
|
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
|
-
|
234
|
-
|
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
|
240
|
-
|
241
|
-
|
242
|
-
|
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
|
-
|
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
|
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
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
-
|
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
@@ -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
|
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.
|
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/
|
84
|
-
- "./lib/ipa_utilities/
|
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
|