ios-cert-enrollment 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/ios-cert-enrollment.rb +8 -0
- data/lib/ios-cert-enrollment/certificate.rb +16 -0
- data/lib/ios-cert-enrollment/configuration.rb +29 -0
- data/lib/ios-cert-enrollment/device.rb +15 -0
- data/lib/ios-cert-enrollment/profile.rb +153 -0
- data/lib/ios-cert-enrollment/sign.rb +85 -0
- data/lib/ios-cert-enrollment/ssl.rb +17 -0
- data/lib/ios-cert-enrollment/version.rb +3 -0
- metadata +130 -0
@@ -0,0 +1,8 @@
|
|
1
|
+
require File.expand_path('../ios-cert-enrollment/configuration', __FILE__)
|
2
|
+
require File.expand_path('../ios-cert-enrollment/sign', __FILE__)
|
3
|
+
require File.expand_path('../ios-cert-enrollment/profile', __FILE__)
|
4
|
+
require File.expand_path('../ios-cert-enrollment/device', __FILE__)
|
5
|
+
|
6
|
+
module IOSCertEnrollment
|
7
|
+
extend Configuration
|
8
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path('../configuration', __FILE__)
|
2
|
+
require "openssl"
|
3
|
+
|
4
|
+
module IOSCertEnrollment
|
5
|
+
class Certificate
|
6
|
+
|
7
|
+
attr_accessor :certificate, :mime_type
|
8
|
+
|
9
|
+
def initialize(certificate,mime_type)
|
10
|
+
self.certificate = certificate
|
11
|
+
self.mime_type = mime_type
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path('../version', __FILE__)
|
2
|
+
|
3
|
+
module IOSCertEnrollment
|
4
|
+
# Defines constants and methods related to configuration
|
5
|
+
module Configuration
|
6
|
+
VALID_OPTIONS_KEYS = [
|
7
|
+
:ssl_certificate_path,
|
8
|
+
:ssl_key_path,
|
9
|
+
:base_url,
|
10
|
+
:identifier,
|
11
|
+
:display_name,
|
12
|
+
:organization
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
attr_accessor *VALID_OPTIONS_KEYS
|
16
|
+
|
17
|
+
# Convenience method to allow configuration options to be set in a block
|
18
|
+
def configure
|
19
|
+
yield self
|
20
|
+
end
|
21
|
+
|
22
|
+
# Create a hash of options and their values
|
23
|
+
def options
|
24
|
+
VALID_OPTIONS_KEYS.inject({}) do |option, key|
|
25
|
+
option.merge!(key => send(key))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path('../configuration', __FILE__)
|
2
|
+
require File.expand_path('../certificate', __FILE__)
|
3
|
+
require File.expand_path('../ssl', __FILE__)
|
4
|
+
|
5
|
+
require "plist"
|
6
|
+
|
7
|
+
module IOSCertEnrollment
|
8
|
+
module Device
|
9
|
+
class << self
|
10
|
+
def parse(p7sign)
|
11
|
+
return Plist::parse_xml(p7sign.data)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require File.expand_path('../configuration', __FILE__)
|
2
|
+
require "rubygems"
|
3
|
+
require "uuidtools"
|
4
|
+
require "plist"
|
5
|
+
module IOSCertEnrollment
|
6
|
+
class Profile
|
7
|
+
attr_accessor :url, :identifier, :display_name, :description, :icon, :payload, :organization, :expiration
|
8
|
+
def initialize(url="")
|
9
|
+
self.url = IOSCertEnrollment.base_url + url
|
10
|
+
self.identifier = IOSCertEnrollment.identifier
|
11
|
+
self.display_name = IOSCertEnrollment.display_name
|
12
|
+
self.organization = IOSCertEnrollment.organization
|
13
|
+
self.description = ""
|
14
|
+
self.expiration = nil
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def service
|
19
|
+
payload = general_payload()
|
20
|
+
payload['PayloadType'] = "Profile Service" # do not modify
|
21
|
+
payload['PayloadIdentifier'] = self.identifier+".mobileconfig.profile-service"
|
22
|
+
|
23
|
+
# strings that show up in UI, customizable
|
24
|
+
payload['PayloadDisplayName'] = self.display_name
|
25
|
+
payload['PayloadDescription'] = self.description
|
26
|
+
|
27
|
+
payload_content = Hash.new
|
28
|
+
payload_content['URL'] = self.url
|
29
|
+
payload_content['DeviceAttributes'] = [
|
30
|
+
"UDID",
|
31
|
+
"VERSION",
|
32
|
+
"PRODUCT", # ie. iPhone1,1 or iPod2,1
|
33
|
+
"DEVICE_NAME", # given device name "My iPhone"
|
34
|
+
"MAC_ADDRESS_EN0",
|
35
|
+
"IMEI",
|
36
|
+
"ICCID"
|
37
|
+
];
|
38
|
+
|
39
|
+
payload['PayloadContent'] = payload_content
|
40
|
+
self.payload = Plist::Emit.dump(payload)
|
41
|
+
return self
|
42
|
+
end
|
43
|
+
|
44
|
+
def encrypted_service
|
45
|
+
## ASA encryption_cert_payload
|
46
|
+
payload = general_payload()
|
47
|
+
|
48
|
+
payload['PayloadIdentifier'] = self.identifier+".encrypted-profile-service"
|
49
|
+
payload['PayloadType'] = "Configuration" # do not modify
|
50
|
+
|
51
|
+
# strings that show up in UI, customisable
|
52
|
+
payload['PayloadDisplayName'] = self.display_name
|
53
|
+
payload['PayloadDescription'] = self.description
|
54
|
+
|
55
|
+
payload['PayloadContent'] = [encryption_cert_request("Profile Service")];
|
56
|
+
self.payload = Plist::Emit.dump(payload)
|
57
|
+
return self
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def webclip
|
62
|
+
|
63
|
+
webclip_payload = general_payload()
|
64
|
+
|
65
|
+
webclip_payload['PayloadIdentifier'] = self.identifier+".webclip.tester"
|
66
|
+
webclip_payload['PayloadType'] = "com.apple.webClip.managed" # do not modify
|
67
|
+
|
68
|
+
# strings that show up in UI, customisable
|
69
|
+
webclip_payload['PayloadDisplayName'] = self.display_name
|
70
|
+
webclip_payload['PayloadDescription'] = self.description
|
71
|
+
|
72
|
+
# allow user to remove webclip
|
73
|
+
webclip_payload['IsRemovable'] = true
|
74
|
+
webclip_payload['FullScreen'] = true
|
75
|
+
webclip_payload['Icon'] = self.icon
|
76
|
+
webclip_payload['Precomposed'] = true
|
77
|
+
# the link
|
78
|
+
webclip_payload['Label'] = self.display_name
|
79
|
+
webclip_payload['URL'] = self.url
|
80
|
+
|
81
|
+
#client_cert_payload = scep_cert_payload(request, "Client Authentication", "foo");
|
82
|
+
|
83
|
+
self.payload = Plist::Emit.dump(payload)
|
84
|
+
return self
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
def configuration(encrypted_content)
|
91
|
+
payload = general_payload()
|
92
|
+
payload['PayloadIdentifier'] = self.identifier+".intranet"
|
93
|
+
payload['PayloadType'] = "Configuration" # do not modify
|
94
|
+
|
95
|
+
# strings that show up in UI, customisable
|
96
|
+
payload['PayloadDisplayName'] = self.display_name
|
97
|
+
payload['PayloadDescription'] = self.description
|
98
|
+
payload['PayloadExpirationDate'] = self.expiration || Date.today + (360 * 10) # expire in 10 years
|
99
|
+
|
100
|
+
payload['EncryptedPayloadContent'] = StringIO.new(encrypted_content)
|
101
|
+
self.payload = Plist::Emit.dump(payload)
|
102
|
+
return self
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
def sign
|
107
|
+
signed_profile = OpenSSL::PKCS7.sign(SSL.certificate, SSL.key, self.payload, [], OpenSSL::PKCS7::BINARY)
|
108
|
+
return Certificate.new(signed_profile.to_der, "application/x-apple-aspen-config")
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
def encrypt(certificates)
|
113
|
+
encrypted_profile = OpenSSL::PKCS7.encrypt(certificates, self.payload, OpenSSL::Cipher::Cipher::new("des-ede3-cbc"), OpenSSL::PKCS7::BINARY)
|
114
|
+
return Certificate.new(encrypted_profile.to_der, "application/x-apple-aspen-config")
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
private
|
120
|
+
def encryption_cert_request(purpose)
|
121
|
+
## AKA scep_cert_payload
|
122
|
+
payload = general_payload()
|
123
|
+
|
124
|
+
|
125
|
+
payload['PayloadIdentifier'] = self.identifier+".encryption-cert-request"
|
126
|
+
payload['PayloadType'] = "com.apple.security.scep" # do not modify
|
127
|
+
|
128
|
+
payload['PayloadDisplayName'] = purpose
|
129
|
+
payload['PayloadDescription'] = "Provides device encryption identity"
|
130
|
+
|
131
|
+
payload_content = Hash.new
|
132
|
+
payload_content['URL'] = self.url
|
133
|
+
payload_content['Subject'] = [ [ [ "O", self.organization ] ],
|
134
|
+
[ [ "CN", purpose + " (" + UUIDTools::UUID.random_create().to_s + ")" ] ] ];
|
135
|
+
|
136
|
+
payload_content['Keysize'] = 1024
|
137
|
+
payload_content['Key Type'] = "RSA"
|
138
|
+
payload_content['Key Usage'] = 5 # digital signature (1) | key encipherment (4)
|
139
|
+
payload_content['GetCACaps'] = ["POSTPKIOperation","Renewal","SHA-1"]
|
140
|
+
|
141
|
+
payload['PayloadContent'] = payload_content;
|
142
|
+
payload
|
143
|
+
end
|
144
|
+
|
145
|
+
def general_payload()
|
146
|
+
payload = Hash.new
|
147
|
+
payload['PayloadVersion'] = 1 # do not modify
|
148
|
+
payload['PayloadUUID'] = UUIDTools::UUID.random_create().to_s # should be unique
|
149
|
+
payload['PayloadOrganization'] = self.organization
|
150
|
+
payload
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.expand_path('../configuration', __FILE__)
|
2
|
+
require File.expand_path('../certificate', __FILE__)
|
3
|
+
require File.expand_path('../ssl', __FILE__)
|
4
|
+
|
5
|
+
require "openssl"
|
6
|
+
|
7
|
+
module IOSCertEnrollment
|
8
|
+
module Sign
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def registration_authority
|
12
|
+
scep_certs = OpenSSL::PKCS7.new()
|
13
|
+
scep_certs.type="signed"
|
14
|
+
scep_certs.certificates=[SSL.certificate,SSL.certificate]
|
15
|
+
return Certificate.new(scep_certs.to_der, "application/x-x509-ca-ra-cert")
|
16
|
+
end
|
17
|
+
|
18
|
+
def certificate_authority_caps
|
19
|
+
return "POSTPKIOperation\nSHA-1\nDES3\n"
|
20
|
+
end
|
21
|
+
|
22
|
+
def sign_PKI(data)
|
23
|
+
|
24
|
+
p7sign = OpenSSL::PKCS7.new(data)
|
25
|
+
store = OpenSSL::X509::Store.new
|
26
|
+
p7sign.verify(nil, store, nil, OpenSSL::PKCS7::NOVERIFY)
|
27
|
+
signers = p7sign.signers
|
28
|
+
p7enc = OpenSSL::PKCS7.new(p7sign.data)
|
29
|
+
|
30
|
+
# Certificate Signing Request
|
31
|
+
csr = p7enc.decrypt(SSL.key, SSL.certificate)
|
32
|
+
|
33
|
+
# Signed Certificate
|
34
|
+
cert = self.sign_certificate(csr)
|
35
|
+
|
36
|
+
degenerate_pkcs7 = OpenSSL::PKCS7.new()
|
37
|
+
degenerate_pkcs7.type="signed"
|
38
|
+
degenerate_pkcs7.certificates=[cert]
|
39
|
+
enc_cert = OpenSSL::PKCS7.encrypt(p7sign.certificates, degenerate_pkcs7.to_der,
|
40
|
+
OpenSSL::Cipher::Cipher::new("des-ede3-cbc"), OpenSSL::PKCS7::BINARY)
|
41
|
+
reply = OpenSSL::PKCS7.sign(SSL.certificate, SSL.key, enc_cert.to_der, [], OpenSSL::PKCS7::BINARY)
|
42
|
+
|
43
|
+
return Certificate.new(reply.to_der, "application/x-pki-message")
|
44
|
+
end
|
45
|
+
|
46
|
+
def verify_response(raw_postback_data)
|
47
|
+
p7sign = OpenSSL::PKCS7.new(raw_postback_data)
|
48
|
+
store = OpenSSL::X509::Store.new
|
49
|
+
p7sign.verify(nil, store, nil, OpenSSL::PKCS7::NOVERIFY)
|
50
|
+
return p7sign
|
51
|
+
end
|
52
|
+
def verify_signer(p7sign)
|
53
|
+
signers = p7sign.signers
|
54
|
+
|
55
|
+
return (signers[0].issuer.to_s == SSL.certificate.subject.to_s)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
end
|
60
|
+
private
|
61
|
+
def self.sign_certificate(signing_request)
|
62
|
+
request = OpenSSL::X509::Request.new(signing_request)
|
63
|
+
|
64
|
+
# New Certificate
|
65
|
+
cert = OpenSSL::X509::Certificate.new
|
66
|
+
cert.version = 2
|
67
|
+
cert.serial = 1
|
68
|
+
cert.subject = request.subject
|
69
|
+
cert.issuer = SSL.certificate.subject
|
70
|
+
cert.public_key = request.public_key
|
71
|
+
cert.not_before = Time.now
|
72
|
+
cert.not_after = Time.now+(86400*1)
|
73
|
+
|
74
|
+
# Prepare to sign
|
75
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
76
|
+
ef.subject_certificate = cert
|
77
|
+
ef.issuer_certificate = SSL.certificate
|
78
|
+
cert.add_extension(ef.create_extension("keyUsage", "digitalSignature,keyEncipherment", true))
|
79
|
+
cert.sign(SSL.key, OpenSSL::Digest::SHA1.new)
|
80
|
+
|
81
|
+
return cert
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module IOSCertEnrollment
|
2
|
+
module SSL
|
3
|
+
@@key, @@certificate = nil
|
4
|
+
class << self
|
5
|
+
def key
|
6
|
+
return @@key if @@key
|
7
|
+
return @@key = OpenSSL::PKey::RSA.new(File.read(IOSCertEnrollment.ssl_key_path))
|
8
|
+
end
|
9
|
+
|
10
|
+
def certificate
|
11
|
+
return @@certificate if @@certificate
|
12
|
+
return @@certificate = OpenSSL::X509::Certificate.new(File.read(IOSCertEnrollment.ssl_certificate_path))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ios-cert-enrollment
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Nolan Brown
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-05-10 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rake
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rdoc
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: uuidtools
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :runtime
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: plist
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
type: :runtime
|
75
|
+
version_requirements: *id004
|
76
|
+
description: Easy tools to implement a SCEP server for iOS Configuration Profiles
|
77
|
+
email: nolanbrown@gmail.com
|
78
|
+
executables: []
|
79
|
+
|
80
|
+
extensions: []
|
81
|
+
|
82
|
+
extra_rdoc_files: []
|
83
|
+
|
84
|
+
files:
|
85
|
+
- lib/ios-cert-enrollment.rb
|
86
|
+
- lib/ios-cert-enrollment/certificate.rb
|
87
|
+
- lib/ios-cert-enrollment/configuration.rb
|
88
|
+
- lib/ios-cert-enrollment/device.rb
|
89
|
+
- lib/ios-cert-enrollment/profile.rb
|
90
|
+
- lib/ios-cert-enrollment/sign.rb
|
91
|
+
- lib/ios-cert-enrollment/ssl.rb
|
92
|
+
- lib/ios-cert-enrollment/version.rb
|
93
|
+
homepage: http://github.com/nolanbrown
|
94
|
+
licenses: []
|
95
|
+
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options:
|
98
|
+
- --title
|
99
|
+
- iOS Portal
|
100
|
+
- --main
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
hash: 3
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
version: "0"
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
hash: 3
|
119
|
+
segments:
|
120
|
+
- 0
|
121
|
+
version: "0"
|
122
|
+
requirements: []
|
123
|
+
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 1.8.24
|
126
|
+
signing_key:
|
127
|
+
specification_version: 3
|
128
|
+
summary: SCEP server for iOS Configuration Profiles
|
129
|
+
test_files: []
|
130
|
+
|