motion-provisioning 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +106 -0
- data/LICENSE.txt +46 -0
- data/README.md +207 -0
- data/Rakefile +71 -0
- data/bin/console +6 -0
- data/bin/setup +8 -0
- data/lib/motion-provisioning.rb +231 -0
- data/lib/motion-provisioning/application.rb +53 -0
- data/lib/motion-provisioning/certificate.rb +278 -0
- data/lib/motion-provisioning/mobileprovision.rb +84 -0
- data/lib/motion-provisioning/provisioning_profile.rb +136 -0
- data/lib/motion-provisioning/spaceship/free_portal_client.rb +123 -0
- data/lib/motion-provisioning/spaceship/portal_client.rb +34 -0
- data/lib/motion-provisioning/tasks.rb +14 -0
- data/lib/motion-provisioning/utils.rb +59 -0
- data/lib/motion-provisioning/version.rb +3 -0
- data/motion-provisioning.gemspec +29 -0
- metadata +192 -0
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'yaml'
|
3
|
+
require 'base64'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
require 'plist'
|
7
|
+
require 'security'
|
8
|
+
require 'spaceship'
|
9
|
+
require 'motion-provisioning/spaceship/portal_client'
|
10
|
+
require 'motion-provisioning/spaceship/free_portal_client'
|
11
|
+
|
12
|
+
require 'motion-provisioning/utils'
|
13
|
+
require 'motion-provisioning/tasks'
|
14
|
+
require 'motion-provisioning/version'
|
15
|
+
require 'motion-provisioning/service'
|
16
|
+
require 'motion-provisioning/certificate'
|
17
|
+
require 'motion-provisioning/application'
|
18
|
+
require 'motion-provisioning/mobileprovision'
|
19
|
+
require 'motion-provisioning/provisioning_profile'
|
20
|
+
|
21
|
+
module MotionProvisioning
|
22
|
+
|
23
|
+
class << self
|
24
|
+
attr_accessor :free, :team
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.client
|
28
|
+
Spaceship::Portal.client ||= begin
|
29
|
+
|
30
|
+
if File.exist?('.gitignore') && File.read('.gitignore').match(/^provisioning$/).nil?
|
31
|
+
answer = Utils.ask("Info", "Do you want to add the 'provisioning' folder fo your '.gitignore' file? (Recommended) (Y/n):")
|
32
|
+
`echo provisioning >> .gitignore` if answer.downcase == 'y'
|
33
|
+
end
|
34
|
+
|
35
|
+
client = if free
|
36
|
+
Spaceship::FreePortalClient.new
|
37
|
+
else
|
38
|
+
Spaceship::PortalClient.new
|
39
|
+
end
|
40
|
+
|
41
|
+
email = ENV['MOTION_PROVISIONING_EMAIL'] || MotionProvisioning.config['email'] || Utils.ask("Info", "Your Apple ID email:")
|
42
|
+
|
43
|
+
config_path = File.expand_path('./provisioning/config.yaml')
|
44
|
+
|
45
|
+
if ENV['MOTION_PROVISIONING_EMAIL'].nil? && !File.exist?(config_path)
|
46
|
+
answer = Utils.ask("Info", "Do you want to save the email to the config file ('provisioning/config.yaml') so you dont have to type it again? (Y/n):")
|
47
|
+
if answer.downcase == 'y'
|
48
|
+
FileUtils.mkdir_p(File.expand_path('./provisioning'))
|
49
|
+
File.write(config_path, { 'email' => email }.to_yaml)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
password = ENV['MOTION_PROVISIONING_PASSWORD']
|
54
|
+
|
55
|
+
server_name = "motionprovisioning.#{email}"
|
56
|
+
item = Security::InternetPassword.find(server: server_name)
|
57
|
+
password ||= item.password if item
|
58
|
+
|
59
|
+
if password.nil?
|
60
|
+
puts "The login information you enter will be stored safely in the macOS keychain."
|
61
|
+
password = Utils.ask_password("Info", "Password for #{email}:")
|
62
|
+
Security::InternetPassword.add(server_name, email, password)
|
63
|
+
end
|
64
|
+
|
65
|
+
Utils.log("Info", "Logging into the Developer Portal with email '#{email}'.")
|
66
|
+
begin
|
67
|
+
client.user = email
|
68
|
+
client.send("do_login", email, password)
|
69
|
+
rescue Spaceship::Client::InvalidUserCredentialsError => ex
|
70
|
+
Utils.log("Error", "There was an error logging into your account. Your password may be wrong.")
|
71
|
+
|
72
|
+
if Utils.ask("Info", 'Do you want to reenter your password? (Y/n):').downcase == 'y'
|
73
|
+
|
74
|
+
# The 'delete' method is very verbose, temporarily disable output
|
75
|
+
orig_stdout = $stdout.dup
|
76
|
+
$stdout.reopen('/dev/null', 'w')
|
77
|
+
Security::InternetPassword.delete(server: server_name)
|
78
|
+
$stdout.reopen(orig_stdout)
|
79
|
+
|
80
|
+
password = Utils.ask_password("Info", "Password for #{email}:")
|
81
|
+
Security::InternetPassword.add(server_name, email, password)
|
82
|
+
retry
|
83
|
+
else
|
84
|
+
abort
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
if self.free
|
89
|
+
client.teams.each do |team|
|
90
|
+
if team['currentTeamMember']['roles'].include?('XCODE_FREE_USER')
|
91
|
+
client.team_id = team['teamId']
|
92
|
+
self.team = team
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
if client.team_id.nil?
|
97
|
+
raise "could not find free team"
|
98
|
+
end
|
99
|
+
else
|
100
|
+
if team_id = MotionProvisioning.config['team_id'] || ENV['MOTION_PROVISIONING_TEAM_ID']
|
101
|
+
found = false
|
102
|
+
client.teams.each do |team|
|
103
|
+
if team_id == team['teamId']
|
104
|
+
client.team_id = team_id
|
105
|
+
self.team = team
|
106
|
+
found = true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
if found == false
|
111
|
+
raise "The current user does not belong to team with ID '#{team_id}' selected in config.yml."
|
112
|
+
end
|
113
|
+
else
|
114
|
+
|
115
|
+
team_id = client.select_team
|
116
|
+
|
117
|
+
if File.exist?(config_path) && ENV['MOTION_PROVISIONING_TEAM_ID'].nil?
|
118
|
+
answer = Utils.ask("Info", "Do you want to save the team id (#{team_id}) in the config file ('provisioning/config.yaml') so you dont have to select it again? (Y/n):")
|
119
|
+
if answer.downcase == 'y'
|
120
|
+
config = YAML.load(File.read(config_path))
|
121
|
+
config['team_id'] = team_id
|
122
|
+
File.write(config_path, config.to_yaml)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
self.team = client.teams.detect { |team| team['teamId'] == team_id }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
Utils.log("Info", "Selected team '#{self.team['name']} (#{self.team['teamId']})'.")
|
131
|
+
|
132
|
+
Spaceship::App.set_client(client)
|
133
|
+
Spaceship::AppGroup.set_client(client)
|
134
|
+
Spaceship::Device.set_client(client)
|
135
|
+
Spaceship::Certificate.set_client(client)
|
136
|
+
Spaceship::ProvisioningProfile.set_client(client)
|
137
|
+
|
138
|
+
client
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.config
|
143
|
+
return @config if @config
|
144
|
+
config_path = File.expand_path('./provisioning/config.yaml')
|
145
|
+
if File.exist?(config_path)
|
146
|
+
@config = YAML.load(File.read(config_path)) || {}
|
147
|
+
else
|
148
|
+
@config = {}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.services
|
153
|
+
@services ||= []
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.services=(services)
|
157
|
+
@services = services
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.entitlements
|
161
|
+
self.services.map(&:to_hash).inject({}, &:merge!)
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.certificate(opts = {})
|
165
|
+
unless opts[:platform]
|
166
|
+
Utils.log("Error", "Certificate 'platform' is required")
|
167
|
+
exit(1)
|
168
|
+
end
|
169
|
+
|
170
|
+
unless opts[:type]
|
171
|
+
Utils.log("Error", "Certificate 'type' is required")
|
172
|
+
exit(1)
|
173
|
+
end
|
174
|
+
|
175
|
+
if opts[:free] == true && opts[:type] != :development
|
176
|
+
Utils.log("Error", "You can only create a 'free' certificate for type 'development'. You selected type '#{opts[:type].to_s}'")
|
177
|
+
exit(1)
|
178
|
+
end
|
179
|
+
|
180
|
+
opts[:platform] = :ios if opts[:platform] == :tvos
|
181
|
+
|
182
|
+
supported_platforms = [:ios, :mac]
|
183
|
+
unless supported_platforms.include?(opts[:platform])
|
184
|
+
Utils.log("Error", "Invalid value'#{opts[:platform]}'for 'platorm'. Supported values: #{supported_platforms}")
|
185
|
+
exit(1)
|
186
|
+
end
|
187
|
+
|
188
|
+
supported_types = [:distribution, :development, :developer_id]
|
189
|
+
unless supported_types.include?(opts[:type])
|
190
|
+
Utils.log("Error", "Invalid value '#{opts[:type]}'for 'type'. Supported values: #{supported_types}")
|
191
|
+
exit(1)
|
192
|
+
end
|
193
|
+
|
194
|
+
MotionProvisioning.free = opts[:free]
|
195
|
+
Certificate.new.certificate_name(opts[:type], opts[:platform])
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
def self.profile(opts = {})
|
200
|
+
unless opts[:bundle_identifier]
|
201
|
+
Utils.log("Error", "'bundle_identifier' is required")
|
202
|
+
exit(1)
|
203
|
+
end
|
204
|
+
|
205
|
+
unless opts[:app_name]
|
206
|
+
Utils.log("Error", "'app_name' is required")
|
207
|
+
exit(1)
|
208
|
+
end
|
209
|
+
|
210
|
+
supported_platforms = [:ios, :tvos, :mac]
|
211
|
+
unless supported_platforms.include?(opts[:platform])
|
212
|
+
Utils.log("Error", "Invalid value'#{opts[:platform]}'for 'platorm'. Supported values: #{supported_platforms}")
|
213
|
+
exit(1)
|
214
|
+
end
|
215
|
+
|
216
|
+
supported_types = [:distribution, :adhoc, :development]
|
217
|
+
unless supported_types.include?(opts[:type])
|
218
|
+
Utils.log("Error", "Invalid value '#{opts[:type]}'for 'type'. Supported values: #{supported_types}")
|
219
|
+
exit(1)
|
220
|
+
end
|
221
|
+
|
222
|
+
if opts[:free] == true && opts[:type] != :development
|
223
|
+
Utils.log("Error", "You can only create a 'free' provisioning profile for type 'development'. You selected type '#{opts[:type].to_s}'")
|
224
|
+
exit(1)
|
225
|
+
end
|
226
|
+
|
227
|
+
MotionProvisioning.free = opts[:free]
|
228
|
+
ProvisioningProfile.new.provisioning_profile(opts[:bundle_identifier], opts[:app_name], opts[:platform], opts[:type])
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module MotionProvisioning
|
2
|
+
class Application
|
3
|
+
|
4
|
+
# Finds or create app for the given bundle id and name
|
5
|
+
def self.find_or_create(bundle_id: nil, name: nil, mac: mac = false)
|
6
|
+
app = Spaceship::Portal::App.find(bundle_id, mac: mac)
|
7
|
+
if app
|
8
|
+
app = app.details if app.features.nil?
|
9
|
+
else
|
10
|
+
begin
|
11
|
+
app = Spaceship::Portal::App.create!(bundle_id: bundle_id, name: name, mac: mac)
|
12
|
+
app = app.details if app.features.nil?
|
13
|
+
rescue Spaceship::Client::UnexpectedResponse => e
|
14
|
+
if e.to_s.include?("is not a valid identifier")
|
15
|
+
Utils.log("Error", "'#{bundle_id}' is not a valid identifier for an app. Please choose an identifier containing only alphanumeric characters, dots and asterisk")
|
16
|
+
exit 1
|
17
|
+
elsif e.to_s.include?("is not available")
|
18
|
+
Utils.log("Error", "'#{bundle_id}' has already been taken. Please enter a different string.")
|
19
|
+
exit 1
|
20
|
+
else
|
21
|
+
raise e
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# services = MotionProvisioning.services
|
27
|
+
|
28
|
+
# Disable all app services not enabled via entitlements
|
29
|
+
# app.enabled_features.each do |feature_id|
|
30
|
+
# # These services are always enabled and cannot be disabled
|
31
|
+
# next if ['inAppPurchase', 'gameCenter', 'push'].include?(feature_id)
|
32
|
+
# service = services.detect { |s| s.identifier == feature_id }
|
33
|
+
# if service.nil?
|
34
|
+
# Utils.log('Info', "Disabling unused app service '#{feature_id}' for '#{bundle_id}'")
|
35
|
+
# # To disable Data Protection we need to send an empty string as value
|
36
|
+
# value = feature_id == 'dataProtection' ? '' : false
|
37
|
+
# app.update_service(Spaceship::Portal::AppService.new(feature_id, value))
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
|
41
|
+
# # Enable all app services enabled via entitlements (or which have a different value)
|
42
|
+
# services.each do |service|
|
43
|
+
# value = service.identifier == 'dataProtection' ? 'complete' : true
|
44
|
+
# if app.features[service.identifier] != value
|
45
|
+
# Utils.log('Info', "Enabling app service '#{service.name.split("::").last}' for '#{bundle_id}'")
|
46
|
+
# app.update_service(Spaceship::Portal::AppService.new(service.identifier, value))
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
|
50
|
+
app
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
module Spaceship
|
2
|
+
module Portal
|
3
|
+
class Certificate
|
4
|
+
# The PLIST request for the free certificate returns the certificate content
|
5
|
+
# in the certContent variable. It's stored in this attribute for later use.
|
6
|
+
attr_accessor :motionprovisioning_certContent,
|
7
|
+
:motionprovisioning_serialNumber
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module MotionProvisioning
|
13
|
+
class Certificate
|
14
|
+
|
15
|
+
attr_accessor :type, :output_path, :platform
|
16
|
+
|
17
|
+
def client
|
18
|
+
MotionProvisioning.client
|
19
|
+
end
|
20
|
+
|
21
|
+
def mac?
|
22
|
+
self.platform == :mac
|
23
|
+
end
|
24
|
+
|
25
|
+
def certificate_name(type, platform)
|
26
|
+
self.type = type
|
27
|
+
self.platform = platform
|
28
|
+
self.output_path = File.expand_path('./provisioning')
|
29
|
+
certificate_path = File.expand_path("./provisioning/#{platform}_#{type}_certificate.cer")
|
30
|
+
private_key_path = File.expand_path("./provisioning/#{platform}_#{type}_private_key.p12")
|
31
|
+
|
32
|
+
# First check if there is a certificate and key file, and if it is installed
|
33
|
+
identities = available_identities
|
34
|
+
if File.exist?(certificate_path) && File.exist?(private_key_path) && ENV['recreate_certificate'].nil?
|
35
|
+
fingerprint = sha1_fingerprint(certificate_path)
|
36
|
+
installed_cert = identities.detect { |e| e[:fingerprint] == fingerprint }
|
37
|
+
if installed_cert
|
38
|
+
Utils.log("Info", "Using certificate '#{installed_cert[:name]}'.")
|
39
|
+
return installed_cert[:name]
|
40
|
+
else
|
41
|
+
# The certificate is not installed, so we install the cert and the key
|
42
|
+
import_file(private_key_path)
|
43
|
+
import_file(certificate_path)
|
44
|
+
name = common_name(certificate_path)
|
45
|
+
Utils.log("Info", "Using certificate '#{name}'.")
|
46
|
+
return name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Create the folder to store the certs
|
51
|
+
FileUtils.mkdir_p(File.expand_path('./provisioning'))
|
52
|
+
|
53
|
+
# Make sure a client is created and logged in
|
54
|
+
client
|
55
|
+
|
56
|
+
# All the certificates for the specified type
|
57
|
+
user_certificates = certificates
|
58
|
+
|
59
|
+
# Lets see if any of the user certificates is in the keychain
|
60
|
+
installed_certificate = nil
|
61
|
+
if !certificates.empty?
|
62
|
+
installed_certs_sha1 = identities.map { |e| e[:fingerprint] }
|
63
|
+
installed_certificate = certificates.detect do |certificate|
|
64
|
+
sha1 = OpenSSL::Digest::SHA1.new(certificate.motionprovisioning_certContent || certificate.download_raw)
|
65
|
+
installed_certs_sha1.include?(sha1.to_s.upcase)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# There are no certificates in the server so we create a new one
|
70
|
+
if user_certificates.empty?
|
71
|
+
Utils.log("Warning", "Couldn't find any existing certificates... creating a new one.")
|
72
|
+
if certificate = create_certificate
|
73
|
+
return common_name(certificate)
|
74
|
+
else
|
75
|
+
Utils.log("Error", "Something went wrong when trying to create a new certificate.")
|
76
|
+
abort
|
77
|
+
end
|
78
|
+
# There are certificates in the server, but none is installed locally. Revoke all and create a new one.
|
79
|
+
elsif installed_certificate.nil?
|
80
|
+
Utils.log("Error", "None of the available certificates (#{user_certificates.count}) is installed on the local machine. Revoking...")
|
81
|
+
|
82
|
+
# For distribution, ask before revoking
|
83
|
+
if self.type == :distribution && !$test_mode
|
84
|
+
answer = Utils.ask("Info", "There are #{user_certificates.count} distribution certificates in your account, but none installed locally.\n" \
|
85
|
+
"Before revoking and creating a new one, ask other team members who might have them installed to share them with you.\n" \
|
86
|
+
"Do you want to continue revoking the certificates? (Y/n):")
|
87
|
+
abort if answer.downcase != "y"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Revoke all and create new one
|
91
|
+
if MotionProvisioning.free
|
92
|
+
user_certificates.each do |certificate|
|
93
|
+
client.revoke_development_certificate(certificate.motionprovisioning_serialNumber)
|
94
|
+
end
|
95
|
+
else
|
96
|
+
user_certificates.each(&:revoke!)
|
97
|
+
end
|
98
|
+
|
99
|
+
if certificate = create_certificate
|
100
|
+
return common_name(certificate)
|
101
|
+
else
|
102
|
+
Utils.log("Error", "Something went wrong when trying to create a new certificate...")
|
103
|
+
abort
|
104
|
+
end
|
105
|
+
# There are certificates on the server, and one of them is installed locally.
|
106
|
+
else
|
107
|
+
path = store_certificate_raw(installed_certificate.motionprovisioning_certContent ||installed_certificate.download_raw)
|
108
|
+
private_key_path = File.expand_path(File.join(output_path, "#{installed_certificate.id}.p12"))
|
109
|
+
|
110
|
+
# This certificate is installed on the local machine
|
111
|
+
Utils.log("Info", "Using certificate '#{installed_certificate.name}'.")
|
112
|
+
|
113
|
+
return common_name(path)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# All certificates of this type
|
118
|
+
def certificates
|
119
|
+
@certificates ||= begin
|
120
|
+
if MotionProvisioning.free
|
121
|
+
client.development_certificates(mac: mac?).map do |cert|
|
122
|
+
certificate = Spaceship::Portal::Certificate.factory(cert)
|
123
|
+
certificate.motionprovisioning_certContent = cert['certContent'].read
|
124
|
+
certificate.motionprovisioning_serialNumber = cert['serialNumber']
|
125
|
+
certificate
|
126
|
+
end
|
127
|
+
else
|
128
|
+
certificates = certificate_type.all
|
129
|
+
# Filter out development certificates belonging to other team members
|
130
|
+
if self.type == :development
|
131
|
+
user_id = MotionProvisioning.team['currentTeamMember']['teamMemberId']
|
132
|
+
certificates.select! { |c| c.owner_id == user_id }
|
133
|
+
end
|
134
|
+
certificates
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# The kind of certificate we're interested in
|
140
|
+
def certificate_type
|
141
|
+
cert_type = nil
|
142
|
+
case platform
|
143
|
+
when :ios, :tvos
|
144
|
+
cert_type = Spaceship.certificate.production
|
145
|
+
cert_type = Spaceship.certificate.in_house if Spaceship.client.in_house?
|
146
|
+
cert_type = Spaceship.certificate.development if self.type == :development
|
147
|
+
when :mac
|
148
|
+
cert_type = Spaceship.certificate.mac_development
|
149
|
+
cert_type = Spaceship.certificate.mac_app_distribution if self.type == :distribution
|
150
|
+
cert_type = Spaceship.certificate.developer_i_d_application if self.type == :developer_id
|
151
|
+
end
|
152
|
+
cert_type
|
153
|
+
end
|
154
|
+
|
155
|
+
def create_certificate_signing_request
|
156
|
+
$motion_provisioninig_csr || Spaceship.certificate.create_certificate_signing_request
|
157
|
+
end
|
158
|
+
|
159
|
+
def create_certificate
|
160
|
+
# Create a new certificate signing request
|
161
|
+
csr, pkey = create_certificate_signing_request
|
162
|
+
|
163
|
+
# Store all that onto the filesystem
|
164
|
+
request_path = File.expand_path(File.join(self.output_path, "#{platform}_#{type}.certSigningRequest"))
|
165
|
+
File.write(request_path, csr.to_pem)
|
166
|
+
|
167
|
+
private_key_path = File.expand_path(File.join(self.output_path, "#{platform}_#{type}_private_key.p12"))
|
168
|
+
File.write(private_key_path, pkey.export)
|
169
|
+
|
170
|
+
# Use the signing request to create a new distribution certificate
|
171
|
+
begin
|
172
|
+
certificate = nil
|
173
|
+
certificate_attrs = nil
|
174
|
+
if MotionProvisioning.free
|
175
|
+
certificate_attrs = client.create_development_certificate(csr.to_pem)
|
176
|
+
# Fetch the certificate again because the response does not contain
|
177
|
+
# the certContent key
|
178
|
+
certificate_attrs = client.development_certificates(mac: mac?).detect do |cert|
|
179
|
+
cert['certificateId'] == certificate_attrs['certificateId']
|
180
|
+
end
|
181
|
+
certificate_attrs['certificateTypeDisplayId'] = certificate_attrs['certificateType']['certificateTypeDisplayId']
|
182
|
+
certificate = Spaceship::Portal::Certificate.factory(certificate_attrs)
|
183
|
+
certificate.motionprovisioning_certContent = certificate_attrs['certContent'].read
|
184
|
+
certificate
|
185
|
+
else
|
186
|
+
certificate = certificate_type.create!(csr: csr)
|
187
|
+
end
|
188
|
+
rescue => ex
|
189
|
+
if ex.to_s.include?("You already have a current")
|
190
|
+
FileUtils.rm(private_key_path)
|
191
|
+
FileUtils.rm(request_path)
|
192
|
+
Utils.log("Error", "Could not create another certificate, reached the maximum number of available certificates. Manually revoke certificates in the Developer Portal.")
|
193
|
+
abort
|
194
|
+
end
|
195
|
+
raise ex
|
196
|
+
end
|
197
|
+
Utils.log("Info", "Successfully created certificate.")
|
198
|
+
|
199
|
+
cert_path = store_certificate_raw(certificate.motionprovisioning_certContent || certificate.download_raw)
|
200
|
+
|
201
|
+
# Import all the things into the Keychain
|
202
|
+
import_file(private_key_path)
|
203
|
+
import_file(cert_path)
|
204
|
+
Utils.log("Info", "Successfully installed certificate.")
|
205
|
+
|
206
|
+
if self.type == :distribution
|
207
|
+
Utils.log("Warning", "You have just created a distribution certificate. These certificates must be shared with other team members by sending them the private key (.p12) and certificate (.cer) files in your /provisioning folder and install them in the keychain.")
|
208
|
+
else
|
209
|
+
Utils.log("Warning", "You have just created a development certificate. If you want to use this certificate on another machine, transfer the private key (.p12) and certificate (.cer) files in your /provisioning folder and install them in the keychain.")
|
210
|
+
end
|
211
|
+
Utils.ask("Info", "Press any key to continue...") if !$test_mode
|
212
|
+
|
213
|
+
return cert_path
|
214
|
+
end
|
215
|
+
|
216
|
+
def store_certificate_raw(raw_data)
|
217
|
+
path = File.expand_path(File.join(self.output_path, "#{platform}_#{type}_certificate.cer"))
|
218
|
+
File.write(path, raw_data)
|
219
|
+
path
|
220
|
+
end
|
221
|
+
|
222
|
+
def available_identities
|
223
|
+
ids = []
|
224
|
+
keychain = $keychain || "#{Dir.home}/Library/Keychains/login.keychain"
|
225
|
+
available = `security find-identity -v -p codesigning #{keychain}`
|
226
|
+
available.split("\n").each do |current|
|
227
|
+
next if current.include? "REVOKED"
|
228
|
+
begin
|
229
|
+
id = current.match(/.*\) (.*) \"(.*)\"/)
|
230
|
+
ids << {
|
231
|
+
fingerprint: id[1],
|
232
|
+
name: id[2]
|
233
|
+
}
|
234
|
+
rescue
|
235
|
+
# the last line does not match
|
236
|
+
end
|
237
|
+
end
|
238
|
+
ids
|
239
|
+
end
|
240
|
+
|
241
|
+
def sha1_fingerprint(path)
|
242
|
+
result = `openssl x509 -in "#{path}" -inform der -noout -sha1 -fingerprint`
|
243
|
+
begin
|
244
|
+
result = result.match(/SHA1 Fingerprint=(.*)/)[1]
|
245
|
+
result.delete!(':')
|
246
|
+
return result
|
247
|
+
rescue
|
248
|
+
puts result
|
249
|
+
Utils.log("Error", "Error parsing certificate '#{path}'")
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def common_name(path)
|
254
|
+
result = `openssl x509 -in "#{path}" -inform der -noout -sha1 -subject`
|
255
|
+
begin
|
256
|
+
return result.match(/\/CN=(.*?)\//)[1]
|
257
|
+
rescue
|
258
|
+
puts result
|
259
|
+
Utils.log("Error", "Error parsing certificate '#{path}'")
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def import_file(path)
|
264
|
+
unless File.exist?(path)
|
265
|
+
Utils.log("Error", "Could not find file '#{path}'")
|
266
|
+
abort
|
267
|
+
end
|
268
|
+
|
269
|
+
keychain = $keychain || "#{Dir.home}/Library/Keychains/login.keychain"
|
270
|
+
|
271
|
+
command = "security import #{path.shellescape} -k '#{keychain}' -P ''"
|
272
|
+
command << " -T /usr/bin/codesign"
|
273
|
+
command << " -T /usr/bin/security"
|
274
|
+
|
275
|
+
`#{command} 2>&1`
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|