certificate_authority 0.1.3 → 0.1.4
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/README.rdoc +92 -86
- data/VERSION.yml +2 -2
- data/certificate_authority.gemspec +9 -7
- data/lib/certificate_authority.rb +1 -1
- data/lib/certificate_authority/certificate.rb +60 -38
- data/lib/certificate_authority/certificate_revocation_list.rb +12 -12
- data/lib/certificate_authority/distinguished_name.rb +33 -14
- data/lib/certificate_authority/extensions.rb +77 -63
- data/lib/certificate_authority/key_material.rb +48 -15
- data/lib/certificate_authority/ocsp_handler.rb +24 -24
- data/lib/certificate_authority/pkcs11_key_material.rb +13 -13
- data/lib/certificate_authority/serial_number.rb +3 -3
- data/lib/certificate_authority/signing_entity.rb +5 -7
- data/spec/units/certificate_revocation_list_spec.rb +16 -16
- data/spec/units/certificate_spec.rb +149 -84
- data/spec/units/distinguished_name_spec.rb +26 -5
- data/spec/units/extensions_spec.rb +72 -10
- data/spec/units/key_material_spec.rb +69 -65
- data/spec/units/ocsp_handler_spec.rb +20 -20
- data/spec/units/pkcs11_key_material_spec.rb +41 -0
- data/spec/units/serial_number_spec.rb +4 -4
- metadata +50 -53
@@ -1,31 +1,31 @@
|
|
1
1
|
module CertificateAuthority
|
2
2
|
class CertificateRevocationList
|
3
3
|
include ActiveModel::Validations
|
4
|
-
|
4
|
+
|
5
5
|
attr_accessor :certificates
|
6
6
|
attr_accessor :parent
|
7
7
|
attr_accessor :crl_body
|
8
8
|
attr_accessor :next_update
|
9
|
-
|
9
|
+
|
10
10
|
validate do |crl|
|
11
11
|
errors.add :next_update, "Next update must be a positive value" if crl.next_update < 0
|
12
12
|
errors.add :parent, "A parent entity must be set" if crl.parent.nil?
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def initialize
|
16
16
|
self.certificates = []
|
17
17
|
self.next_update = 60 * 60 * 4 # 4 hour default
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def <<(cert)
|
21
21
|
raise "Only revoked certificates can be added to a CRL" unless cert.revoked?
|
22
22
|
self.certificates << cert
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def sign!
|
26
26
|
raise "No parent entity has been set!" if self.parent.nil?
|
27
27
|
raise "Invalid CRL" unless self.valid?
|
28
|
-
|
28
|
+
|
29
29
|
revocations = self.certificates.collect do |certificate|
|
30
30
|
revocation = OpenSSL::X509::Revoked.new
|
31
31
|
x509_cert = OpenSSL::X509::Certificate.new(certificate.to_pem)
|
@@ -33,27 +33,27 @@ module CertificateAuthority
|
|
33
33
|
revocation.time = certificate.revoked_at
|
34
34
|
revocation
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
crl = OpenSSL::X509::CRL.new
|
38
38
|
revocations.each do |revocation|
|
39
39
|
crl.add_revoked(revocation)
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
crl.version = 1
|
43
43
|
crl.last_update = Time.now
|
44
44
|
crl.next_update = Time.now + self.next_update
|
45
|
-
|
45
|
+
|
46
46
|
signing_cert = OpenSSL::X509::Certificate.new(self.parent.to_pem)
|
47
47
|
digest = OpenSSL::Digest::Digest.new("SHA512")
|
48
48
|
crl.issuer = signing_cert.subject
|
49
49
|
self.crl_body = crl.sign(self.parent.key_material.private_key, digest)
|
50
|
-
|
50
|
+
|
51
51
|
self.crl_body
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
def to_pem
|
55
55
|
raise "No signed CRL body" if self.crl_body.nil?
|
56
56
|
self.crl_body.to_pem
|
57
57
|
end
|
58
58
|
end#CertificateRevocationList
|
59
|
-
end
|
59
|
+
end
|
@@ -1,39 +1,58 @@
|
|
1
1
|
module CertificateAuthority
|
2
2
|
class DistinguishedName
|
3
3
|
include ActiveModel::Validations
|
4
|
-
|
4
|
+
|
5
5
|
validates_presence_of :common_name
|
6
|
-
|
6
|
+
|
7
7
|
attr_accessor :common_name
|
8
8
|
alias :cn :common_name
|
9
|
-
|
9
|
+
|
10
10
|
attr_accessor :locality
|
11
11
|
alias :l :locality
|
12
|
-
|
12
|
+
|
13
13
|
attr_accessor :state
|
14
14
|
alias :s :state
|
15
|
-
|
15
|
+
|
16
16
|
attr_accessor :country
|
17
17
|
alias :c :country
|
18
|
-
|
18
|
+
|
19
19
|
attr_accessor :organization
|
20
20
|
alias :o :organization
|
21
|
-
|
21
|
+
|
22
22
|
attr_accessor :organizational_unit
|
23
23
|
alias :ou :organizational_unit
|
24
|
-
|
24
|
+
|
25
25
|
def to_x509_name
|
26
26
|
raise "Invalid Distinguished Name" unless valid?
|
27
|
-
|
27
|
+
|
28
28
|
# NB: the capitalization in the strings counts
|
29
29
|
name = OpenSSL::X509::Name.new
|
30
30
|
name.add_entry("CN", common_name)
|
31
31
|
name.add_entry("O", organization) unless organization.blank?
|
32
|
-
name.add_entry("OU",
|
33
|
-
name.add_entry("
|
34
|
-
name.add_entry("L",
|
35
|
-
|
32
|
+
name.add_entry("OU", organizational_unit) unless organizational_unit.blank?
|
33
|
+
name.add_entry("ST", state) unless state.blank?
|
34
|
+
name.add_entry("L", locality) unless locality.blank?
|
35
|
+
name.add_entry("C", country) unless country.blank?
|
36
|
+
name
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.from_openssl openssl_name
|
40
|
+
unless openssl_name.is_a? OpenSSL::X509::Name
|
41
|
+
raise "Argument must be a OpenSSL::X509::Name"
|
42
|
+
end
|
43
|
+
|
44
|
+
name = DistinguishedName.new
|
45
|
+
openssl_name.to_a.each do |k,v|
|
46
|
+
case k
|
47
|
+
when "CN" then name.common_name = v
|
48
|
+
when "L" then name.locality = v
|
49
|
+
when "ST" then name.state = v
|
50
|
+
when "C" then name.country = v
|
51
|
+
when "O" then name.organization = v
|
52
|
+
when "OU" then name.organizational_unit = v
|
53
|
+
end
|
54
|
+
end
|
36
55
|
name
|
37
56
|
end
|
38
57
|
end
|
39
|
-
end
|
58
|
+
end
|
@@ -4,40 +4,40 @@ module CertificateAuthority
|
|
4
4
|
def to_s
|
5
5
|
raise "Implementation required"
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
def config_extensions
|
9
9
|
{}
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def openssl_identifier
|
13
13
|
raise "Implementation required"
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
class BasicContraints
|
18
18
|
include ExtensionAPI
|
19
19
|
include ActiveModel::Validations
|
20
20
|
attr_accessor :ca
|
21
21
|
attr_accessor :path_len
|
22
22
|
validates :ca, :inclusion => [true,false]
|
23
|
-
|
23
|
+
|
24
24
|
def initialize
|
25
25
|
self.ca = false
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
def is_ca?
|
29
29
|
self.ca
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
def path_len=(value)
|
33
33
|
raise "path_len must be a non-negative integer" if value < 0 or !value.is_a?(Fixnum)
|
34
34
|
@path_len = value
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
def openssl_identifier
|
38
38
|
"basicConstraints"
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
def to_s
|
42
42
|
result = ""
|
43
43
|
result += "CA:#{self.ca}"
|
@@ -45,20 +45,20 @@ module CertificateAuthority
|
|
45
45
|
result
|
46
46
|
end
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
class CrlDistributionPoints
|
50
50
|
include ExtensionAPI
|
51
|
-
|
51
|
+
|
52
52
|
attr_accessor :uri
|
53
|
-
|
53
|
+
|
54
54
|
def initialize
|
55
|
-
self.uri = "http://moo.crlendPoint.example.com/something.crl"
|
55
|
+
# self.uri = "http://moo.crlendPoint.example.com/something.crl"
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
def openssl_identifier
|
59
59
|
"crlDistributionPoints"
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
## NB: At this time it seems OpenSSL's extension handlers don't support
|
63
63
|
## any of the config options the docs claim to support... everything comes back
|
64
64
|
## "missing value" on GENERAL NAME. Even if copied verbatim
|
@@ -68,141 +68,155 @@ module CertificateAuthority
|
|
68
68
|
# "issuer_sect" => {"CN" => "crlissuer.com", "C" => "US", "O" => "shudder"}
|
69
69
|
}
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
def to_s
|
73
|
+
return "" if self.uri.nil?
|
73
74
|
"URI:#{self.uri}"
|
74
75
|
end
|
75
76
|
end
|
76
|
-
|
77
|
+
|
77
78
|
class SubjectKeyIdentifier
|
78
79
|
include ExtensionAPI
|
79
80
|
def openssl_identifier
|
80
81
|
"subjectKeyIdentifier"
|
81
82
|
end
|
82
|
-
|
83
|
+
|
83
84
|
def to_s
|
84
85
|
"hash"
|
85
86
|
end
|
86
87
|
end
|
87
|
-
|
88
|
+
|
88
89
|
class AuthorityKeyIdentifier
|
89
90
|
include ExtensionAPI
|
90
|
-
|
91
|
+
|
91
92
|
def openssl_identifier
|
92
93
|
"authorityKeyIdentifier"
|
93
94
|
end
|
94
|
-
|
95
|
+
|
95
96
|
def to_s
|
96
97
|
"keyid,issuer"
|
97
98
|
end
|
98
99
|
end
|
99
|
-
|
100
|
+
|
100
101
|
class AuthorityInfoAccess
|
101
102
|
include ExtensionAPI
|
102
|
-
|
103
|
+
|
103
104
|
attr_accessor :ocsp
|
104
|
-
|
105
|
+
|
105
106
|
def initialize
|
106
107
|
self.ocsp = []
|
107
108
|
end
|
108
|
-
|
109
|
+
|
109
110
|
def openssl_identifier
|
110
111
|
"authorityInfoAccess"
|
111
112
|
end
|
112
|
-
|
113
|
+
|
113
114
|
def to_s
|
114
115
|
return "" if self.ocsp.empty?
|
115
116
|
"OCSP;URI:#{self.ocsp}"
|
116
117
|
end
|
117
118
|
end
|
118
|
-
|
119
|
+
|
119
120
|
class KeyUsage
|
120
121
|
include ExtensionAPI
|
121
|
-
|
122
|
+
|
122
123
|
attr_accessor :usage
|
123
|
-
|
124
|
+
|
124
125
|
def initialize
|
125
126
|
self.usage = ["digitalSignature", "nonRepudiation"]
|
126
127
|
end
|
127
|
-
|
128
|
+
|
128
129
|
def openssl_identifier
|
129
130
|
"keyUsage"
|
130
131
|
end
|
131
|
-
|
132
|
+
|
132
133
|
def to_s
|
133
134
|
"#{self.usage.join(',')}"
|
134
135
|
end
|
135
136
|
end
|
136
|
-
|
137
|
+
|
137
138
|
class ExtendedKeyUsage
|
138
139
|
include ExtensionAPI
|
139
|
-
|
140
|
+
|
140
141
|
attr_accessor :usage
|
141
|
-
|
142
|
+
|
142
143
|
def initialize
|
143
144
|
self.usage = ["serverAuth","clientAuth"]
|
144
145
|
end
|
145
|
-
|
146
|
+
|
146
147
|
def openssl_identifier
|
147
148
|
"extendedKeyUsage"
|
148
149
|
end
|
149
|
-
|
150
|
+
|
150
151
|
def to_s
|
151
152
|
"#{self.usage.join(',')}"
|
152
153
|
end
|
153
154
|
end
|
154
|
-
|
155
|
+
|
155
156
|
class SubjectAlternativeName
|
156
157
|
include ExtensionAPI
|
157
|
-
|
158
|
-
attr_accessor :uris
|
159
|
-
|
158
|
+
|
159
|
+
attr_accessor :uris, :dns_names, :ips
|
160
|
+
|
160
161
|
def initialize
|
161
162
|
self.uris = []
|
163
|
+
self.dns_names = []
|
164
|
+
self.ips = []
|
162
165
|
end
|
163
|
-
|
166
|
+
|
164
167
|
def uris=(value)
|
165
168
|
raise "URIs must be an array" unless value.is_a?(Array)
|
166
169
|
@uris = value
|
167
170
|
end
|
168
|
-
|
171
|
+
|
172
|
+
def dns_names=(value)
|
173
|
+
raise "DNS names must be an array" unless value.is_a?(Array)
|
174
|
+
@dns_names = value
|
175
|
+
end
|
176
|
+
|
177
|
+
def ips=(value)
|
178
|
+
raise "IPs must be an array" unless value.is_a?(Array)
|
179
|
+
@ips = value
|
180
|
+
end
|
181
|
+
|
169
182
|
def openssl_identifier
|
170
183
|
"subjectAltName"
|
171
184
|
end
|
172
|
-
|
185
|
+
|
173
186
|
def to_s
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
187
|
+
res = self.uris.map {|u| "URI:#{u}" }
|
188
|
+
res += self.dns_names.map {|d| "DNS:#{d}" }
|
189
|
+
res += self.ips.map {|i| "IP:#{i}" }
|
190
|
+
|
191
|
+
return res.join(',')
|
178
192
|
end
|
179
193
|
end
|
180
|
-
|
194
|
+
|
181
195
|
class CertificatePolicies
|
182
196
|
include ExtensionAPI
|
183
|
-
|
197
|
+
|
184
198
|
attr_accessor :policy_identifier
|
185
199
|
attr_accessor :cps_uris
|
186
200
|
##User notice
|
187
201
|
attr_accessor :explicit_text
|
188
202
|
attr_accessor :organization
|
189
203
|
attr_accessor :notice_numbers
|
190
|
-
|
204
|
+
|
191
205
|
def initialize
|
192
206
|
@contains_data = false
|
193
207
|
end
|
194
|
-
|
195
|
-
|
208
|
+
|
209
|
+
|
196
210
|
def openssl_identifier
|
197
211
|
"certificatePolicies"
|
198
212
|
end
|
199
|
-
|
213
|
+
|
200
214
|
def user_notice=(value={})
|
201
215
|
value.keys.each do |key|
|
202
216
|
self.send("#{key}=".to_sym, value[key])
|
203
217
|
end
|
204
218
|
end
|
205
|
-
|
219
|
+
|
206
220
|
def config_extensions
|
207
221
|
config_extension = {}
|
208
222
|
custom_policies = {}
|
@@ -210,43 +224,43 @@ module CertificateAuthority
|
|
210
224
|
unless self.policy_identifier.nil?
|
211
225
|
custom_policies["policyIdentifier"] = self.policy_identifier
|
212
226
|
end
|
213
|
-
|
227
|
+
|
214
228
|
if !self.cps_uris.nil? and self.cps_uris.is_a?(Array)
|
215
229
|
self.cps_uris.each_with_index do |cps_uri,i|
|
216
230
|
custom_policies["CPS.#{i}"] = cps_uri
|
217
231
|
end
|
218
232
|
end
|
219
|
-
|
233
|
+
|
220
234
|
unless self.explicit_text.nil?
|
221
235
|
notice["explicitText"] = self.explicit_text
|
222
236
|
end
|
223
|
-
|
237
|
+
|
224
238
|
unless self.organization.nil?
|
225
239
|
notice["organization"] = self.organization
|
226
240
|
end
|
227
|
-
|
241
|
+
|
228
242
|
unless self.notice_numbers.nil?
|
229
243
|
notice["noticeNumbers"] = self.notice_numbers
|
230
244
|
end
|
231
|
-
|
245
|
+
|
232
246
|
if notice.keys.size > 0
|
233
247
|
custom_policies["userNotice.1"] = "@notice"
|
234
248
|
config_extension["notice"] = notice
|
235
249
|
end
|
236
|
-
|
250
|
+
|
237
251
|
if custom_policies.keys.size > 0
|
238
252
|
config_extension["custom_policies"] = custom_policies
|
239
253
|
@contains_data = true
|
240
254
|
end
|
241
|
-
|
255
|
+
|
242
256
|
config_extension
|
243
257
|
end
|
244
|
-
|
258
|
+
|
245
259
|
def to_s
|
246
260
|
return "" unless @contains_data
|
247
261
|
"ia5org,@custom_policies"
|
248
262
|
end
|
249
263
|
end
|
250
|
-
|
264
|
+
|
251
265
|
end
|
252
|
-
end
|
266
|
+
end
|