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