motion-provisioning 0.0.1
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 +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
|