certificate_authority 0.1.5 → 1.1.0
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/.github/workflows/ci.yml +26 -0
- data/.gitignore +6 -0
- data/.rspec +3 -0
- data/Gemfile +2 -8
- data/Gemfile.lock +71 -27
- data/README.rdoc +91 -2
- data/Rakefile +6 -41
- data/certificate_authority.gemspec +22 -83
- data/lib/certificate_authority/certificate.rb +139 -49
- data/lib/certificate_authority/certificate_revocation_list.rb +34 -14
- data/lib/certificate_authority/core_extensions.rb +46 -0
- data/lib/certificate_authority/distinguished_name.rb +64 -16
- data/lib/certificate_authority/extensions.rb +417 -45
- data/lib/certificate_authority/key_material.rb +30 -9
- data/lib/certificate_authority/ocsp_handler.rb +75 -5
- data/lib/certificate_authority/pkcs11_key_material.rb +0 -2
- data/lib/certificate_authority/revocable.rb +14 -0
- data/lib/certificate_authority/serial_number.rb +15 -2
- data/lib/certificate_authority/signing_request.rb +91 -0
- data/lib/certificate_authority/validations.rb +31 -0
- data/lib/certificate_authority/version.rb +3 -0
- data/lib/certificate_authority.rb +6 -5
- metadata +76 -71
- data/VERSION.yml +0 -5
- data/spec/spec_helper.rb +0 -4
- data/spec/units/certificate_authority_spec.rb +0 -4
- data/spec/units/certificate_revocation_list_spec.rb +0 -68
- data/spec/units/certificate_spec.rb +0 -428
- data/spec/units/distinguished_name_spec.rb +0 -59
- data/spec/units/extensions_spec.rb +0 -115
- data/spec/units/key_material_spec.rb +0 -100
- data/spec/units/ocsp_handler_spec.rb +0 -104
- data/spec/units/pkcs11_key_material_spec.rb +0 -41
- data/spec/units/serial_number_spec.rb +0 -20
- data/spec/units/signing_entity_spec.rb +0 -4
- data/spec/units/units_helper.rb +0 -1
@@ -1,14 +1,13 @@
|
|
1
1
|
module CertificateAuthority
|
2
2
|
class Certificate
|
3
|
-
|
4
|
-
include
|
3
|
+
include Validations
|
4
|
+
include Revocable
|
5
5
|
|
6
6
|
attr_accessor :distinguished_name
|
7
7
|
attr_accessor :serial_number
|
8
8
|
attr_accessor :key_material
|
9
9
|
attr_accessor :not_before
|
10
10
|
attr_accessor :not_after
|
11
|
-
attr_accessor :revoked_at
|
12
11
|
attr_accessor :extensions
|
13
12
|
attr_accessor :openssl_body
|
14
13
|
|
@@ -16,7 +15,7 @@ module CertificateAuthority
|
|
16
15
|
|
17
16
|
attr_accessor :parent
|
18
17
|
|
19
|
-
validate
|
18
|
+
def validate
|
20
19
|
errors.add :base, "Distinguished name must be valid" unless distinguished_name.valid?
|
21
20
|
errors.add :base, "Key material must be valid" unless key_material.valid?
|
22
21
|
errors.add :base, "Serial number must be valid" unless serial_number.valid?
|
@@ -33,8 +32,8 @@ module CertificateAuthority
|
|
33
32
|
self.distinguished_name = DistinguishedName.new
|
34
33
|
self.serial_number = SerialNumber.new
|
35
34
|
self.key_material = MemoryKeyMaterial.new
|
36
|
-
self.not_before =
|
37
|
-
self.not_after =
|
35
|
+
self.not_before = Date.today.utc
|
36
|
+
self.not_after = Date.today.advance(:years => 1).utc
|
38
37
|
self.parent = self
|
39
38
|
self.extensions = load_extensions()
|
40
39
|
|
@@ -42,12 +41,31 @@ module CertificateAuthority
|
|
42
41
|
|
43
42
|
end
|
44
43
|
|
44
|
+
=begin
|
45
|
+
def self.from_openssl openssl_cert
|
46
|
+
unless openssl_cert.is_a? OpenSSL::X509::Certificate
|
47
|
+
raise "Can only construct from an OpenSSL::X509::Certificate"
|
48
|
+
end
|
49
|
+
|
50
|
+
certificate = Certificate.new
|
51
|
+
# Only subject, key_material, and body are used for signing
|
52
|
+
certificate.distinguished_name = DistinguishedName.from_openssl openssl_cert.subject
|
53
|
+
certificate.key_material.public_key = openssl_cert.public_key
|
54
|
+
certificate.openssl_body = openssl_cert
|
55
|
+
certificate.serial_number.number = openssl_cert.serial.to_i
|
56
|
+
certificate.not_before = openssl_cert.not_before
|
57
|
+
certificate.not_after = openssl_cert.not_after
|
58
|
+
# TODO extensions
|
59
|
+
certificate
|
60
|
+
end
|
61
|
+
=end
|
62
|
+
|
45
63
|
def sign!(signing_profile={})
|
46
64
|
raise "Invalid certificate #{self.errors.full_messages}" unless valid?
|
47
65
|
merge_profile_with_extensions(signing_profile)
|
48
66
|
|
49
67
|
openssl_cert = OpenSSL::X509::Certificate.new
|
50
|
-
openssl_cert.version
|
68
|
+
openssl_cert.version = 2
|
51
69
|
openssl_cert.not_before = self.not_before
|
52
70
|
openssl_cert.not_after = self.not_after
|
53
71
|
openssl_cert.public_key = self.key_material.public_key
|
@@ -57,12 +75,6 @@ module CertificateAuthority
|
|
57
75
|
openssl_cert.subject = self.distinguished_name.to_x509_name
|
58
76
|
openssl_cert.issuer = parent.distinguished_name.to_x509_name
|
59
77
|
|
60
|
-
require 'tempfile'
|
61
|
-
t = Tempfile.new("bullshit_conf")
|
62
|
-
# t = File.new("/tmp/openssl.cnf")
|
63
|
-
## The config requires a file even though we won't use it
|
64
|
-
openssl_config = OpenSSL::Config.new(t.path)
|
65
|
-
|
66
78
|
factory = OpenSSL::X509::ExtensionFactory.new
|
67
79
|
factory.subject_certificate = openssl_cert
|
68
80
|
|
@@ -73,31 +85,23 @@ module CertificateAuthority
|
|
73
85
|
factory.issuer_certificate = parent.openssl_body
|
74
86
|
end
|
75
87
|
|
76
|
-
|
77
|
-
config_extensions = extensions[k].config_extensions
|
78
|
-
openssl_config = merge_options(openssl_config,config_extensions)
|
79
|
-
end
|
80
|
-
|
81
|
-
# p openssl_config.sections
|
82
|
-
|
83
|
-
factory.config = openssl_config
|
88
|
+
factory.config = build_openssl_config
|
84
89
|
|
85
90
|
# Order matters: e.g. for self-signed, subjectKeyIdentifier must come before authorityKeyIdentifier
|
86
91
|
self.extensions.keys.sort{|a,b| b<=>a}.each do |k|
|
87
92
|
e = extensions[k]
|
88
93
|
next if e.to_s.nil? or e.to_s == "" ## If the extension returns an empty string we won't include it
|
89
|
-
ext = factory.create_ext(e.openssl_identifier, e.to_s)
|
94
|
+
ext = factory.create_ext(e.openssl_identifier, e.to_s, e.critical)
|
90
95
|
openssl_cert.add_extension(ext)
|
91
96
|
end
|
92
97
|
|
93
98
|
if signing_profile["digest"].nil?
|
94
|
-
digest = OpenSSL::Digest
|
99
|
+
digest = OpenSSL::Digest.new("SHA512")
|
95
100
|
else
|
96
|
-
digest = OpenSSL::Digest
|
101
|
+
digest = OpenSSL::Digest.new(signing_profile["digest"])
|
97
102
|
end
|
98
|
-
|
99
|
-
|
100
|
-
self.openssl_body
|
103
|
+
|
104
|
+
self.openssl_body = openssl_cert.sign(parent.key_material.private_key, digest)
|
101
105
|
end
|
102
106
|
|
103
107
|
def is_signing_entity?
|
@@ -117,6 +121,34 @@ module CertificateAuthority
|
|
117
121
|
self.openssl_body.to_pem
|
118
122
|
end
|
119
123
|
|
124
|
+
def to_csr
|
125
|
+
csr = SigningRequest.new
|
126
|
+
csr.distinguished_name = self.distinguished_name
|
127
|
+
csr.key_material = self.key_material
|
128
|
+
factory = OpenSSL::X509::ExtensionFactory.new
|
129
|
+
exts = []
|
130
|
+
self.extensions.keys.each do |k|
|
131
|
+
## Don't copy over key identifiers for CSRs
|
132
|
+
next if k == "subjectKeyIdentifier" || k == "authorityKeyIdentifier"
|
133
|
+
e = extensions[k]
|
134
|
+
## If the extension returns an empty string we won't include it
|
135
|
+
next if e.to_s.nil? or e.to_s == ""
|
136
|
+
exts << factory.create_ext(e.openssl_identifier, e.to_s, e.critical)
|
137
|
+
end
|
138
|
+
attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(exts)])
|
139
|
+
attrs = [
|
140
|
+
OpenSSL::X509::Attribute.new("extReq", attrval),
|
141
|
+
OpenSSL::X509::Attribute.new("msExtReq", attrval)
|
142
|
+
]
|
143
|
+
csr.attributes = attrs
|
144
|
+
csr
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.from_x509_cert(raw_cert)
|
148
|
+
openssl_cert = OpenSSL::X509::Certificate.new(raw_cert)
|
149
|
+
Certificate.from_openssl(openssl_cert)
|
150
|
+
end
|
151
|
+
|
120
152
|
def is_root_entity?
|
121
153
|
self.parent == self && is_signing_entity?
|
122
154
|
end
|
@@ -135,6 +167,16 @@ module CertificateAuthority
|
|
135
167
|
items = signing_config[k]
|
136
168
|
items.keys.each do |profile_item_key|
|
137
169
|
if extension.respond_to?("#{profile_item_key}=".to_sym)
|
170
|
+
if k == 'subjectAltName' && profile_item_key == 'emails'
|
171
|
+
items[profile_item_key].map do |email|
|
172
|
+
if email == 'email:copy'
|
173
|
+
fail "no email address provided for subject: #{subject.to_x509_name}" unless subject.email_address
|
174
|
+
"email:#{subject.email_address}"
|
175
|
+
else
|
176
|
+
email
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
138
180
|
extension.send("#{profile_item_key}=".to_sym, items[profile_item_key] )
|
139
181
|
else
|
140
182
|
p "Tried applying '#{profile_item_key}' to #{extension.class} but it doesn't respond!"
|
@@ -143,36 +185,80 @@ module CertificateAuthority
|
|
143
185
|
end
|
144
186
|
end
|
145
187
|
|
188
|
+
# Enumeration of the extensions. Not the worst option since
|
189
|
+
# the likelihood of these needing to be updated is low at best.
|
190
|
+
EXTENSIONS = [
|
191
|
+
CertificateAuthority::Extensions::BasicConstraints,
|
192
|
+
CertificateAuthority::Extensions::CrlDistributionPoints,
|
193
|
+
CertificateAuthority::Extensions::SubjectKeyIdentifier,
|
194
|
+
CertificateAuthority::Extensions::AuthorityKeyIdentifier,
|
195
|
+
CertificateAuthority::Extensions::AuthorityInfoAccess,
|
196
|
+
CertificateAuthority::Extensions::KeyUsage,
|
197
|
+
CertificateAuthority::Extensions::ExtendedKeyUsage,
|
198
|
+
CertificateAuthority::Extensions::SubjectAlternativeName,
|
199
|
+
CertificateAuthority::Extensions::CertificatePolicies
|
200
|
+
]
|
201
|
+
|
146
202
|
def load_extensions
|
147
203
|
extension_hash = {}
|
148
204
|
|
149
|
-
|
150
|
-
|
151
|
-
temp_extensions << basic_constraints
|
152
|
-
crl_distribution_points = CertificateAuthority::Extensions::CrlDistributionPoints.new
|
153
|
-
temp_extensions << crl_distribution_points
|
154
|
-
subject_key_identifier = CertificateAuthority::Extensions::SubjectKeyIdentifier.new
|
155
|
-
temp_extensions << subject_key_identifier
|
156
|
-
authority_key_identifier = CertificateAuthority::Extensions::AuthorityKeyIdentifier.new
|
157
|
-
temp_extensions << authority_key_identifier
|
158
|
-
authority_info_access = CertificateAuthority::Extensions::AuthorityInfoAccess.new
|
159
|
-
temp_extensions << authority_info_access
|
160
|
-
key_usage = CertificateAuthority::Extensions::KeyUsage.new
|
161
|
-
temp_extensions << key_usage
|
162
|
-
extended_key_usage = CertificateAuthority::Extensions::ExtendedKeyUsage.new
|
163
|
-
temp_extensions << extended_key_usage
|
164
|
-
subject_alternative_name = CertificateAuthority::Extensions::SubjectAlternativeName.new
|
165
|
-
temp_extensions << subject_alternative_name
|
166
|
-
certificate_policies = CertificateAuthority::Extensions::CertificatePolicies.new
|
167
|
-
temp_extensions << certificate_policies
|
168
|
-
|
169
|
-
temp_extensions.each do |extension|
|
205
|
+
EXTENSIONS.each do |klass|
|
206
|
+
extension = klass.new
|
170
207
|
extension_hash[extension.openssl_identifier] = extension
|
171
208
|
end
|
172
209
|
|
173
210
|
extension_hash
|
174
211
|
end
|
175
212
|
|
213
|
+
def build_openssl_config
|
214
|
+
OpenSSL::Config.parse(openssl_config_string)
|
215
|
+
end
|
216
|
+
|
217
|
+
def openssl_config_string
|
218
|
+
lines = openssl_config_without_multi_value + openssl_config_with_multi_value
|
219
|
+
return '' if lines.empty?
|
220
|
+
(["[extensions]" ]+ lines).join("\n")
|
221
|
+
end
|
222
|
+
|
223
|
+
def openssl_config_without_multi_value
|
224
|
+
no_multi_value_keys = self.extensions.keys.select { |k| extensions[k].config_extensions.empty? }
|
225
|
+
|
226
|
+
lines = no_multi_value_keys.map do |k|
|
227
|
+
value = extensions[k].to_s
|
228
|
+
value.empty? ? '' : "#{k} = #{value}"
|
229
|
+
end.reject(&:empty?)
|
230
|
+
lines
|
231
|
+
end
|
232
|
+
|
233
|
+
def openssl_config_with_multi_value
|
234
|
+
multi_value_keys = self.extensions.keys.reject { |k| extensions[k].config_extensions.empty? }
|
235
|
+
sections = {}
|
236
|
+
|
237
|
+
entries = multi_value_keys.map do |k|
|
238
|
+
sections.merge!(extensions[k].config_extensions)
|
239
|
+
value = comma_terminate(extensions[k]) + section_ref_str(extensions[k].config_extensions.keys)
|
240
|
+
"#{k} = #{value}"
|
241
|
+
end.reject(&:empty?)
|
242
|
+
|
243
|
+
section_lines = sections.keys.flat_map do |k|
|
244
|
+
section_lines(k, sections[k])
|
245
|
+
end
|
246
|
+
entries + [''] + section_lines
|
247
|
+
end
|
248
|
+
|
249
|
+
def comma_terminate(val)
|
250
|
+
s = val.to_s
|
251
|
+
s.empty? ? s : "#{s},"
|
252
|
+
end
|
253
|
+
|
254
|
+
def section_ref_str(section_names)
|
255
|
+
section_names.map { |n| "@#{n}"}.join(',')
|
256
|
+
end
|
257
|
+
|
258
|
+
def section_lines(section_name, value_hash)
|
259
|
+
["[#{section_name}]"] + value_hash.keys.map { |k| "#{k} = #{value_hash[k]}"} + ['']
|
260
|
+
end
|
261
|
+
|
176
262
|
def merge_options(config,hash)
|
177
263
|
hash.keys.each do |k|
|
178
264
|
config[k] = hash[k]
|
@@ -193,7 +279,11 @@ module CertificateAuthority
|
|
193
279
|
certificate.serial_number.number = openssl_cert.serial.to_i
|
194
280
|
certificate.not_before = openssl_cert.not_before
|
195
281
|
certificate.not_after = openssl_cert.not_after
|
196
|
-
|
282
|
+
EXTENSIONS.each do |klass|
|
283
|
+
_,v,c = (openssl_cert.extensions.detect { |e| e.to_a.first == klass::OPENSSL_IDENTIFIER } || []).to_a
|
284
|
+
certificate.extensions[klass::OPENSSL_IDENTIFIER] = klass.parse(v, c) if v
|
285
|
+
end
|
286
|
+
|
197
287
|
certificate
|
198
288
|
end
|
199
289
|
|
@@ -1,36 +1,52 @@
|
|
1
1
|
module CertificateAuthority
|
2
2
|
class CertificateRevocationList
|
3
|
-
include
|
3
|
+
include 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
|
+
attr_accessor :last_update_skew_seconds
|
9
10
|
|
10
|
-
validate
|
11
|
-
errors.add :next_update, "Next update must be a positive value" if
|
12
|
-
errors.add :parent, "A parent entity must be set" if
|
11
|
+
def validate
|
12
|
+
errors.add :next_update, "Next update must be a positive value" if self.next_update < 0
|
13
|
+
errors.add :parent, "A parent entity must be set" if self.parent.nil?
|
13
14
|
end
|
14
15
|
|
15
16
|
def initialize
|
16
17
|
self.certificates = []
|
17
18
|
self.next_update = 60 * 60 * 4 # 4 hour default
|
19
|
+
self.last_update_skew_seconds = 0
|
18
20
|
end
|
19
21
|
|
20
|
-
def <<(
|
21
|
-
|
22
|
-
|
22
|
+
def <<(revocable)
|
23
|
+
case revocable
|
24
|
+
when Revocable
|
25
|
+
raise "Only revoked entities can be added to a CRL" unless revocable.revoked?
|
26
|
+
self.certificates << revocable
|
27
|
+
when OpenSSL::X509::Certificate
|
28
|
+
raise "Not implemented yet"
|
29
|
+
else
|
30
|
+
raise "#{revocable.class} cannot be included in a CRL"
|
31
|
+
end
|
23
32
|
end
|
24
33
|
|
25
|
-
def sign!
|
34
|
+
def sign!(signing_profile={})
|
26
35
|
raise "No parent entity has been set!" if self.parent.nil?
|
27
36
|
raise "Invalid CRL" unless self.valid?
|
28
37
|
|
29
|
-
revocations = self.certificates.collect do |
|
38
|
+
revocations = self.certificates.collect do |revocable|
|
30
39
|
revocation = OpenSSL::X509::Revoked.new
|
31
|
-
|
32
|
-
|
33
|
-
|
40
|
+
|
41
|
+
## We really just need a serial number, now we have to dig it out
|
42
|
+
case revocable
|
43
|
+
when Certificate
|
44
|
+
x509_cert = OpenSSL::X509::Certificate.new(revocable.to_pem)
|
45
|
+
revocation.serial = x509_cert.serial
|
46
|
+
when SerialNumber
|
47
|
+
revocation.serial = revocable.number
|
48
|
+
end
|
49
|
+
revocation.time = revocable.revoked_at
|
34
50
|
revocation
|
35
51
|
end
|
36
52
|
|
@@ -40,11 +56,15 @@ module CertificateAuthority
|
|
40
56
|
end
|
41
57
|
|
42
58
|
crl.version = 1
|
43
|
-
crl.last_update = Time.now
|
59
|
+
crl.last_update = Time.now - self.last_update_skew_seconds
|
44
60
|
crl.next_update = Time.now + self.next_update
|
45
61
|
|
46
62
|
signing_cert = OpenSSL::X509::Certificate.new(self.parent.to_pem)
|
47
|
-
|
63
|
+
if signing_profile["digest"].nil?
|
64
|
+
digest = OpenSSL::Digest.new("SHA512")
|
65
|
+
else
|
66
|
+
digest = OpenSSL::Digest.new(signing_profile["digest"])
|
67
|
+
end
|
48
68
|
crl.issuer = signing_cert.subject
|
49
69
|
self.crl_body = crl.sign(self.parent.key_material.private_key, digest)
|
50
70
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#
|
2
|
+
# ActiveSupport has these modifications. Now that we don't use ActiveSupport,
|
3
|
+
# these are added here as a kindness.
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'date'
|
7
|
+
|
8
|
+
unless nil.respond_to?(:blank?)
|
9
|
+
class NilClass
|
10
|
+
def blank?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
unless String.respond_to?(:blank?)
|
17
|
+
class String
|
18
|
+
def blank?
|
19
|
+
self.empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Date
|
25
|
+
|
26
|
+
def today
|
27
|
+
t = Time.now.utc
|
28
|
+
Date.new(t.year, t.month, t.day)
|
29
|
+
end
|
30
|
+
|
31
|
+
def utc
|
32
|
+
self.to_datetime.to_time.utc
|
33
|
+
end
|
34
|
+
|
35
|
+
unless Date.respond_to?(:advance)
|
36
|
+
def advance(options)
|
37
|
+
options = options.dup
|
38
|
+
d = self
|
39
|
+
d = d >> options.delete(:years) * 12 if options[:years]
|
40
|
+
d = d >> options.delete(:months) if options[:months]
|
41
|
+
d = d + options.delete(:weeks) * 7 if options[:weeks]
|
42
|
+
d = d + options.delete(:days) if options[:days]
|
43
|
+
d
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,58 +1,106 @@
|
|
1
1
|
module CertificateAuthority
|
2
2
|
class DistinguishedName
|
3
|
-
include
|
3
|
+
include Validations
|
4
4
|
|
5
|
-
|
5
|
+
def validate
|
6
|
+
if self.common_name.nil? || self.common_name.empty?
|
7
|
+
errors.add :common_name, 'cannot be blank'
|
8
|
+
end
|
9
|
+
end
|
6
10
|
|
7
11
|
attr_accessor :common_name
|
8
12
|
alias :cn :common_name
|
13
|
+
alias :cn= :common_name=
|
9
14
|
|
10
15
|
attr_accessor :locality
|
11
16
|
alias :l :locality
|
17
|
+
alias :l= :locality=
|
12
18
|
|
13
19
|
attr_accessor :state
|
14
20
|
alias :s :state
|
21
|
+
alias :st= :state=
|
15
22
|
|
16
23
|
attr_accessor :country
|
17
24
|
alias :c :country
|
25
|
+
alias :c= :country=
|
18
26
|
|
19
27
|
attr_accessor :organization
|
20
28
|
alias :o :organization
|
29
|
+
alias :o= :organization=
|
21
30
|
|
22
31
|
attr_accessor :organizational_unit
|
23
32
|
alias :ou :organizational_unit
|
33
|
+
alias :ou= :organizational_unit=
|
34
|
+
|
35
|
+
attr_accessor :email_address
|
36
|
+
alias :emailAddress :email_address
|
37
|
+
alias :emailAddress= :email_address=
|
38
|
+
|
39
|
+
attr_accessor :serial_number
|
40
|
+
alias :serialNumber :serial_number
|
41
|
+
alias :serialNumber= :serial_number=
|
24
42
|
|
25
43
|
def to_x509_name
|
26
44
|
raise "Invalid Distinguished Name" unless valid?
|
27
45
|
|
28
46
|
# NB: the capitalization in the strings counts
|
29
47
|
name = OpenSSL::X509::Name.new
|
30
|
-
name.add_entry("
|
31
|
-
name.add_entry("
|
32
|
-
name.add_entry("OU", organizational_unit) unless organizational_unit.blank?
|
48
|
+
name.add_entry("serialNumber", serial_number) unless serial_number.blank?
|
49
|
+
name.add_entry("C", country) unless country.blank?
|
33
50
|
name.add_entry("ST", state) unless state.blank?
|
34
51
|
name.add_entry("L", locality) unless locality.blank?
|
35
|
-
name.add_entry("
|
52
|
+
name.add_entry("O", organization) unless organization.blank?
|
53
|
+
name.add_entry("OU", organizational_unit) unless organizational_unit.blank?
|
54
|
+
name.add_entry("CN", common_name)
|
55
|
+
name.add_entry("emailAddress", email_address) unless email_address.blank?
|
36
56
|
name
|
37
57
|
end
|
38
58
|
|
59
|
+
def ==(other)
|
60
|
+
# Use the established OpenSSL comparison
|
61
|
+
self.to_x509_name() == other.to_x509_name()
|
62
|
+
end
|
63
|
+
|
39
64
|
def self.from_openssl openssl_name
|
40
65
|
unless openssl_name.is_a? OpenSSL::X509::Name
|
41
66
|
raise "Argument must be a OpenSSL::X509::Name"
|
42
67
|
end
|
43
68
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
69
|
+
WrappedDistinguishedName.new(openssl_name)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
## This is a significantly more complicated case. It's possible that
|
74
|
+
## generically handled certificates will include custom OIDs in the
|
75
|
+
## subject.
|
76
|
+
class WrappedDistinguishedName < DistinguishedName
|
77
|
+
attr_accessor :x509_name
|
78
|
+
|
79
|
+
def initialize(x509_name)
|
80
|
+
@x509_name = x509_name
|
81
|
+
|
82
|
+
subject = @x509_name.to_a
|
83
|
+
subject.each do |element|
|
84
|
+
field = element[0].downcase
|
85
|
+
value = element[1]
|
86
|
+
#type = element[2] ## -not used
|
87
|
+
method_sym = "#{field}=".to_sym
|
88
|
+
if self.respond_to?(method_sym)
|
89
|
+
self.send("#{field}=",value)
|
90
|
+
else
|
91
|
+
## Custom OID
|
92
|
+
@custom_oids = true
|
53
93
|
end
|
54
94
|
end
|
55
|
-
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_x509_name
|
99
|
+
@x509_name
|
100
|
+
end
|
101
|
+
|
102
|
+
def custom_oids?
|
103
|
+
@custom_oids
|
56
104
|
end
|
57
105
|
end
|
58
106
|
end
|