ios-cert-enrollment 0.0.2
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.
- 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
|
+
|