certificate_authority 0.1.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +2 -8
  6. data/Gemfile.lock +71 -27
  7. data/README.rdoc +184 -89
  8. data/Rakefile +6 -41
  9. data/certificate_authority.gemspec +22 -81
  10. data/lib/certificate_authority.rb +7 -6
  11. data/lib/certificate_authority/certificate.rb +151 -71
  12. data/lib/certificate_authority/certificate_revocation_list.rb +46 -26
  13. data/lib/certificate_authority/core_extensions.rb +46 -0
  14. data/lib/certificate_authority/distinguished_name.rb +84 -17
  15. data/lib/certificate_authority/extensions.rb +483 -96
  16. data/lib/certificate_authority/key_material.rb +75 -21
  17. data/lib/certificate_authority/ocsp_handler.rb +99 -29
  18. data/lib/certificate_authority/pkcs11_key_material.rb +13 -15
  19. data/lib/certificate_authority/revocable.rb +14 -0
  20. data/lib/certificate_authority/serial_number.rb +18 -5
  21. data/lib/certificate_authority/signing_entity.rb +5 -7
  22. data/lib/certificate_authority/signing_request.rb +91 -0
  23. data/lib/certificate_authority/validations.rb +31 -0
  24. data/lib/certificate_authority/version.rb +3 -0
  25. metadata +96 -94
  26. data/VERSION.yml +0 -5
  27. data/spec/spec_helper.rb +0 -4
  28. data/spec/units/certificate_authority_spec.rb +0 -4
  29. data/spec/units/certificate_revocation_list_spec.rb +0 -68
  30. data/spec/units/certificate_spec.rb +0 -351
  31. data/spec/units/distinguished_name_spec.rb +0 -38
  32. data/spec/units/extensions_spec.rb +0 -53
  33. data/spec/units/key_material_spec.rb +0 -96
  34. data/spec/units/ocsp_handler_spec.rb +0 -104
  35. data/spec/units/serial_number_spec.rb +0 -20
  36. data/spec/units/signing_entity_spec.rb +0 -4
  37. data/spec/units/units_helper.rb +0 -1
@@ -1,59 +1,79 @@
1
1
  module CertificateAuthority
2
2
  class CertificateRevocationList
3
- include ActiveModel::Validations
4
-
3
+ include Validations
4
+
5
5
  attr_accessor :certificates
6
6
  attr_accessor :parent
7
7
  attr_accessor :crl_body
8
8
  attr_accessor :next_update
9
-
10
- validate do |crl|
11
- errors.add :next_update, "Next update must be a positive value" if crl.next_update < 0
12
- errors.add :parent, "A parent entity must be set" if crl.parent.nil?
9
+ attr_accessor :last_update_skew_seconds
10
+
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
-
20
- def <<(cert)
21
- raise "Only revoked certificates can be added to a CRL" unless cert.revoked?
22
- self.certificates << cert
21
+
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
-
25
- def sign!
33
+
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
-
29
- revocations = self.certificates.collect do |certificate|
37
+
38
+ revocations = self.certificates.collect do |revocable|
30
39
  revocation = OpenSSL::X509::Revoked.new
31
- x509_cert = OpenSSL::X509::Certificate.new(certificate.to_pem)
32
- revocation.serial = x509_cert.serial
33
- revocation.time = certificate.revoked_at
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
+
37
53
  crl = OpenSSL::X509::CRL.new
38
54
  revocations.each do |revocation|
39
55
  crl.add_revoked(revocation)
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
- digest = OpenSSL::Digest::Digest.new("SHA512")
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
+
51
71
  self.crl_body
52
72
  end
53
-
73
+
54
74
  def to_pem
55
75
  raise "No signed CRL body" if self.crl_body.nil?
56
76
  self.crl_body.to_pem
57
77
  end
58
78
  end#CertificateRevocationList
59
- end
79
+ end
@@ -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,39 +1,106 @@
1
1
  module CertificateAuthority
2
2
  class DistinguishedName
3
- include ActiveModel::Validations
4
-
5
- validates_presence_of :common_name
6
-
3
+ include Validations
4
+
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
10
+
7
11
  attr_accessor :common_name
8
12
  alias :cn :common_name
9
-
13
+ alias :cn= :common_name=
14
+
10
15
  attr_accessor :locality
11
16
  alias :l :locality
12
-
17
+ alias :l= :locality=
18
+
13
19
  attr_accessor :state
14
20
  alias :s :state
15
-
21
+ alias :st= :state=
22
+
16
23
  attr_accessor :country
17
24
  alias :c :country
18
-
25
+ alias :c= :country=
26
+
19
27
  attr_accessor :organization
20
28
  alias :o :organization
21
-
29
+ alias :o= :organization=
30
+
22
31
  attr_accessor :organizational_unit
23
32
  alias :ou :organizational_unit
24
-
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=
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("CN", common_name)
48
+ name.add_entry("serialNumber", serial_number) unless serial_number.blank?
49
+ name.add_entry("C", country) unless country.blank?
50
+ name.add_entry("ST", state) unless state.blank?
51
+ name.add_entry("L", locality) unless locality.blank?
31
52
  name.add_entry("O", organization) unless organization.blank?
32
- name.add_entry("OU", common_name) unless organizational_unit.blank?
33
- name.add_entry("S", common_name) unless state.blank?
34
- name.add_entry("L", common_name) unless locality.blank?
35
-
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
58
+
59
+ def ==(other)
60
+ # Use the established OpenSSL comparison
61
+ self.to_x509_name() == other.to_x509_name()
62
+ end
63
+
64
+ def self.from_openssl openssl_name
65
+ unless openssl_name.is_a? OpenSSL::X509::Name
66
+ raise "Argument must be a OpenSSL::X509::Name"
67
+ end
68
+
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
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ def to_x509_name
99
+ @x509_name
100
+ end
101
+
102
+ def custom_oids?
103
+ @custom_oids
104
+ end
38
105
  end
39
- end
106
+ end
@@ -4,61 +4,116 @@ module CertificateAuthority
4
4
  def to_s
5
5
  raise "Implementation required"
6
6
  end
7
-
7
+
8
+ def self.parse(value, critical)
9
+ raise "Implementation required"
10
+ end
11
+
8
12
  def config_extensions
9
13
  {}
10
14
  end
11
-
15
+
12
16
  def openssl_identifier
13
17
  raise "Implementation required"
14
18
  end
19
+
20
+ def ==(value)
21
+ raise "Implementation required"
22
+ end
15
23
  end
16
-
17
- class BasicContraints
24
+
25
+ # Specifies whether an X.509v3 certificate can act as a CA, signing other
26
+ # certificates to be verified. If set, a path length constraint can also be
27
+ # specified.
28
+ # Reference: Section 4.2.1.10 of RFC3280
29
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.10
30
+ class BasicConstraints
31
+ OPENSSL_IDENTIFIER = "basicConstraints"
32
+
18
33
  include ExtensionAPI
19
- include ActiveModel::Validations
34
+ include Validations
35
+
36
+ attr_accessor :critical
20
37
  attr_accessor :ca
21
38
  attr_accessor :path_len
22
- validates :ca, :inclusion => [true,false]
23
-
39
+
40
+ def validate
41
+ unless [true, false].include? self.critical
42
+ errors.add :critical, 'must be true or false'
43
+ end
44
+ unless [true, false].include? self.ca
45
+ errors.add :ca, 'must be true or false'
46
+ end
47
+ end
48
+
24
49
  def initialize
25
- self.ca = false
50
+ @critical = false
51
+ @ca = false
26
52
  end
27
-
53
+
54
+ def openssl_identifier
55
+ OPENSSL_IDENTIFIER
56
+ end
57
+
28
58
  def is_ca?
29
- self.ca
59
+ @ca
30
60
  end
31
-
61
+
32
62
  def path_len=(value)
33
- raise "path_len must be a non-negative integer" if value < 0 or !value.is_a?(Fixnum)
63
+ fail(ArgumentError, "path_len must be a non-negative integer") if !value.is_a?(Integer) || value < 0
34
64
  @path_len = value
35
65
  end
36
-
37
- def openssl_identifier
38
- "basicConstraints"
39
- end
40
-
66
+
41
67
  def to_s
42
- result = ""
43
- result += "CA:#{self.ca}"
44
- result += ",pathlen:#{self.path_len}" unless self.path_len.nil?
45
- result
68
+ res = []
69
+ res << "CA:#{@ca}"
70
+ res << "pathlen:#{@path_len}" unless @path_len.nil?
71
+ res.join(',')
72
+ end
73
+
74
+ def ==(o)
75
+ o.class == self.class && o.state == state
76
+ end
77
+
78
+ def self.parse(value, critical)
79
+ obj = self.new
80
+ return obj if value.nil?
81
+ obj.critical = critical
82
+ value.split(/,\s*/).each do |v|
83
+ c = v.split(':', 2)
84
+ obj.ca = (c.last.upcase == "TRUE") if c.first == "CA"
85
+ obj.path_len = c.last.to_i if c.first == "pathlen"
86
+ end
87
+ obj
88
+ end
89
+
90
+ protected
91
+ def state
92
+ [@critical,@ca,@path_len]
46
93
  end
47
94
  end
48
-
95
+
96
+ # Specifies where CRL information be be retrieved. This extension isn't
97
+ # critical, but is recommended for proper CAs.
98
+ # Reference: Section 4.2.1.14 of RFC3280
99
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.14
49
100
  class CrlDistributionPoints
101
+ OPENSSL_IDENTIFIER = "crlDistributionPoints"
102
+
50
103
  include ExtensionAPI
51
-
52
- attr_accessor :uri
53
-
104
+
105
+ attr_accessor :critical
106
+ attr_accessor :uris
107
+
54
108
  def initialize
55
- self.uri = "http://moo.crlendPoint.example.com/something.crl"
109
+ @critical = false
110
+ @uris = []
56
111
  end
57
-
112
+
58
113
  def openssl_identifier
59
- "crlDistributionPoints"
114
+ OPENSSL_IDENTIFIER
60
115
  end
61
-
116
+
62
117
  ## NB: At this time it seems OpenSSL's extension handlers don't support
63
118
  ## any of the config options the docs claim to support... everything comes back
64
119
  ## "missing value" on GENERAL NAME. Even if copied verbatim
@@ -68,140 +123,386 @@ module CertificateAuthority
68
123
  # "issuer_sect" => {"CN" => "crlissuer.com", "C" => "US", "O" => "shudder"}
69
124
  }
70
125
  end
71
-
126
+
127
+ # This is for legacy support. Technically it can (and probably should)
128
+ # be an array. But if someone is calling the old accessor we shouldn't
129
+ # necessarily break it.
130
+ def uri=(value)
131
+ @uris << value
132
+ end
133
+
72
134
  def to_s
73
- "URI:#{self.uri}"
135
+ res = []
136
+ @uris.each do |uri|
137
+ res << "URI:#{uri}"
138
+ end
139
+ res.join(',')
140
+ end
141
+
142
+ def ==(o)
143
+ o.class == self.class && o.state == state
144
+ end
145
+
146
+ def self.parse(value, critical)
147
+ obj = self.new
148
+ return obj if value.nil?
149
+ obj.critical = critical
150
+ value.split(/,\s*/).each do |v|
151
+ c = v.split(':', 2)
152
+ obj.uris << c.last if c.first == "URI"
153
+ end
154
+ obj
155
+ end
156
+
157
+ protected
158
+ def state
159
+ [@critical,@uri]
74
160
  end
75
161
  end
76
-
162
+
163
+ # Identifies the public key associated with a given certificate.
164
+ # Should be required for "CA" certificates.
165
+ # Reference: Section 4.2.1.2 of RFC3280
166
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.2
77
167
  class SubjectKeyIdentifier
168
+ OPENSSL_IDENTIFIER = "subjectKeyIdentifier"
169
+
78
170
  include ExtensionAPI
171
+
172
+ attr_accessor :critical
173
+ attr_accessor :identifier
174
+
175
+ def initialize
176
+ @critical = false
177
+ @identifier = "hash"
178
+ end
179
+
79
180
  def openssl_identifier
80
- "subjectKeyIdentifier"
181
+ OPENSSL_IDENTIFIER
81
182
  end
82
-
183
+
83
184
  def to_s
84
- "hash"
185
+ res = []
186
+ res << @identifier
187
+ res.join(',')
188
+ end
189
+
190
+ def ==(o)
191
+ o.class == self.class && o.state == state
192
+ end
193
+
194
+ def self.parse(value, critical)
195
+ obj = self.new
196
+ return obj if value.nil?
197
+ obj.critical = critical
198
+ obj.identifier = value
199
+ obj
200
+ end
201
+
202
+ protected
203
+ def state
204
+ [@critical,@identifier]
85
205
  end
86
206
  end
87
-
207
+
208
+ # Identifies the public key associated with a given private key.
209
+ # Reference: Section 4.2.1.1 of RFC3280
210
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.1
88
211
  class AuthorityKeyIdentifier
212
+ OPENSSL_IDENTIFIER = "authorityKeyIdentifier"
213
+
89
214
  include ExtensionAPI
90
-
215
+
216
+ attr_accessor :critical
217
+ attr_accessor :identifier
218
+
219
+ def initialize
220
+ @critical = false
221
+ @identifier = ["keyid", "issuer"]
222
+ end
223
+
91
224
  def openssl_identifier
92
- "authorityKeyIdentifier"
225
+ OPENSSL_IDENTIFIER
93
226
  end
94
-
227
+
95
228
  def to_s
96
- "keyid,issuer"
229
+ res = []
230
+ res += @identifier
231
+ res.join(',')
232
+ end
233
+
234
+ def ==(o)
235
+ o.class == self.class && o.state == state
236
+ end
237
+
238
+ def self.parse(value, critical)
239
+ obj = self.new
240
+ return obj if value.nil?
241
+ obj.critical = critical
242
+ obj.identifier = value.split(/,\s*/).last.chomp
243
+ obj
244
+ end
245
+
246
+ protected
247
+ def state
248
+ [@critical,@identifier]
97
249
  end
98
250
  end
99
-
251
+
252
+ # Specifies how to access CA information and services for the CA that
253
+ # issued this certificate.
254
+ # Generally used to specify OCSP servers.
255
+ # Reference: Section 4.2.2.1 of RFC3280
256
+ # http://tools.ietf.org/html/rfc3280#section-4.2.2.1
100
257
  class AuthorityInfoAccess
258
+ OPENSSL_IDENTIFIER = "authorityInfoAccess"
259
+
101
260
  include ExtensionAPI
102
-
261
+
262
+ attr_accessor :critical
103
263
  attr_accessor :ocsp
104
-
264
+ attr_accessor :ca_issuers
265
+
105
266
  def initialize
106
- self.ocsp = []
267
+ @critical = false
268
+ @ocsp = []
269
+ @ca_issuers = []
107
270
  end
108
-
271
+
109
272
  def openssl_identifier
110
- "authorityInfoAccess"
273
+ OPENSSL_IDENTIFIER
111
274
  end
112
-
275
+
113
276
  def to_s
114
- "OCSP;URI:#{self.ocsp}"
277
+ res = []
278
+ res += @ocsp.map {|o| "OCSP;URI:#{o}" }
279
+ res += @ca_issuers.map {|c| "caIssuers;URI:#{c}" }
280
+ res.join(',')
281
+ end
282
+
283
+ def ==(o)
284
+ o.class == self.class && o.state == state
285
+ end
286
+
287
+ def self.parse(value, critical)
288
+ obj = self.new
289
+ return obj if value.nil?
290
+ obj.critical = critical
291
+ value.split("\n").each do |v|
292
+ if v =~ /^OCSP/
293
+ obj.ocsp << v.split.last
294
+ end
295
+
296
+ if v =~ /^CA Issuers/
297
+ obj.ca_issuers << v.split.last
298
+ end
299
+ end
300
+ obj
301
+ end
302
+
303
+ protected
304
+ def state
305
+ [@critical,@ocsp,@ca_issuers]
115
306
  end
116
307
  end
117
-
308
+
309
+ # Specifies the allowed usage purposes of the keypair specified in this certificate.
310
+ # Reference: Section 4.2.1.3 of RFC3280
311
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.3
312
+ #
313
+ # Note: OpenSSL when parsing an extension will return results in the form
314
+ # 'Digital Signature', but on signing you have to set it to 'digitalSignature'.
315
+ # So copying an extension from an imported cert isn't going to work yet.
118
316
  class KeyUsage
317
+ OPENSSL_IDENTIFIER = "keyUsage"
318
+
119
319
  include ExtensionAPI
120
-
320
+
321
+ attr_accessor :critical
121
322
  attr_accessor :usage
122
-
323
+
123
324
  def initialize
124
- self.usage = ["digitalSignature", "nonRepudiation"]
325
+ @critical = false
326
+ @usage = ["digitalSignature", "nonRepudiation"]
125
327
  end
126
-
328
+
127
329
  def openssl_identifier
128
- "keyUsage"
330
+ OPENSSL_IDENTIFIER
129
331
  end
130
-
332
+
131
333
  def to_s
132
- "#{self.usage.join(',')}"
334
+ res = []
335
+ res += @usage
336
+ res.join(',')
337
+ end
338
+
339
+ def ==(o)
340
+ o.class == self.class && o.state == state
341
+ end
342
+
343
+ def self.parse(value, critical)
344
+ obj = self.new
345
+ return obj if value.nil?
346
+ obj.critical = critical
347
+ obj.usage = value.split(/,\s*/)
348
+ obj
349
+ end
350
+
351
+ protected
352
+ def state
353
+ [@critical,@usage]
133
354
  end
134
355
  end
135
-
356
+
357
+ # Specifies even more allowed usages in addition to what is specified in
358
+ # the Key Usage extension.
359
+ # Reference: Section 4.2.1.13 of RFC3280
360
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.13
136
361
  class ExtendedKeyUsage
362
+ OPENSSL_IDENTIFIER = "extendedKeyUsage"
363
+
137
364
  include ExtensionAPI
138
-
365
+
366
+ attr_accessor :critical
139
367
  attr_accessor :usage
140
-
368
+
141
369
  def initialize
142
- self.usage = ["serverAuth","clientAuth"]
370
+ @critical = false
371
+ @usage = ["serverAuth"]
143
372
  end
144
-
373
+
145
374
  def openssl_identifier
146
- "extendedKeyUsage"
375
+ OPENSSL_IDENTIFIER
147
376
  end
148
-
377
+
149
378
  def to_s
150
- "#{self.usage.join(',')}"
379
+ res = []
380
+ res += @usage
381
+ res.join(',')
382
+ end
383
+
384
+ def ==(o)
385
+ o.class == self.class && o.state == state
386
+ end
387
+
388
+ def self.parse(value, critical)
389
+ obj = self.new
390
+ return obj if value.nil?
391
+ obj.critical = critical
392
+ obj.usage = value.split(/,\s*/)
393
+ obj
394
+ end
395
+
396
+ protected
397
+ def state
398
+ [@critical,@usage]
151
399
  end
152
400
  end
153
-
401
+
402
+ # Specifies additional "names" for which this certificate is valid.
403
+ # Reference: Section 4.2.1.7 of RFC3280
404
+ # http://tools.ietf.org/html/rfc3280#section-4.2.1.7
154
405
  class SubjectAlternativeName
406
+ OPENSSL_IDENTIFIER = "subjectAltName"
407
+
155
408
  include ExtensionAPI
156
-
157
- attr_accessor :uris
158
-
409
+
410
+ attr_accessor :critical
411
+ attr_accessor :uris, :dns_names, :ips, :emails
412
+
159
413
  def initialize
160
- self.uris = []
414
+ @critical = false
415
+ @uris = []
416
+ @dns_names = []
417
+ @ips = []
418
+ @emails = []
419
+ end
420
+
421
+ def openssl_identifier
422
+ OPENSSL_IDENTIFIER
161
423
  end
162
-
424
+
163
425
  def uris=(value)
164
426
  raise "URIs must be an array" unless value.is_a?(Array)
165
427
  @uris = value
166
428
  end
167
-
168
- def openssl_identifier
169
- "subjectAltName"
429
+
430
+ def dns_names=(value)
431
+ raise "DNS names must be an array" unless value.is_a?(Array)
432
+ @dns_names = value
433
+ end
434
+
435
+ def ips=(value)
436
+ raise "IPs must be an array" unless value.is_a?(Array)
437
+ @ips = value
438
+ end
439
+
440
+ def emails=(value)
441
+ raise "Emails must be an array" unless value.is_a?(Array)
442
+ @emails = value
170
443
  end
171
-
444
+
172
445
  def to_s
173
- if self.uris.empty?
174
- return ""
446
+ res = []
447
+ res += @uris.map {|u| "URI:#{u}" }
448
+ res += @dns_names.map {|d| "DNS:#{d}" }
449
+ res += @ips.map {|i| "IP:#{i}" }
450
+ res += @emails.map {|i| "email:#{i}" }
451
+ res.join(',')
452
+ end
453
+
454
+ def ==(o)
455
+ o.class == self.class && o.state == state
456
+ end
457
+
458
+ def self.parse(value, critical)
459
+ obj = self.new
460
+ return obj if value.nil?
461
+ obj.critical = critical
462
+ value.split(/,\s*/).each do |v|
463
+ c = v.split(':', 2)
464
+ obj.uris << c.last if c.first == "URI"
465
+ obj.dns_names << c.last if c.first == "DNS"
466
+ obj.ips << c.last if c.first == "IP"
467
+ obj.emails << c.last if c.first == "EMAIL"
175
468
  end
176
- "URI:#{self.uris.join(',URI:')}"
469
+ obj
470
+ end
471
+
472
+ protected
473
+ def state
474
+ [@critical,@uris,@dns_names,@ips,@emails]
177
475
  end
178
476
  end
179
-
477
+
180
478
  class CertificatePolicies
479
+ OPENSSL_IDENTIFIER = "certificatePolicies"
480
+
181
481
  include ExtensionAPI
182
-
482
+
483
+ attr_accessor :critical
183
484
  attr_accessor :policy_identifier
184
485
  attr_accessor :cps_uris
185
486
  ##User notice
186
487
  attr_accessor :explicit_text
187
488
  attr_accessor :organization
188
489
  attr_accessor :notice_numbers
189
-
490
+
190
491
  def initialize
492
+ self.critical = false
191
493
  @contains_data = false
192
494
  end
193
-
194
-
495
+
195
496
  def openssl_identifier
196
- "certificatePolicies"
497
+ OPENSSL_IDENTIFIER
197
498
  end
198
-
499
+
199
500
  def user_notice=(value={})
200
501
  value.keys.each do |key|
201
502
  self.send("#{key}=".to_sym, value[key])
202
503
  end
203
504
  end
204
-
505
+
205
506
  def config_extensions
206
507
  config_extension = {}
207
508
  custom_policies = {}
@@ -209,43 +510,129 @@ module CertificateAuthority
209
510
  unless self.policy_identifier.nil?
210
511
  custom_policies["policyIdentifier"] = self.policy_identifier
211
512
  end
212
-
513
+
213
514
  if !self.cps_uris.nil? and self.cps_uris.is_a?(Array)
214
515
  self.cps_uris.each_with_index do |cps_uri,i|
215
516
  custom_policies["CPS.#{i}"] = cps_uri
216
517
  end
217
518
  end
218
-
519
+
219
520
  unless self.explicit_text.nil?
220
521
  notice["explicitText"] = self.explicit_text
221
522
  end
222
-
523
+
223
524
  unless self.organization.nil?
224
525
  notice["organization"] = self.organization
225
526
  end
226
-
527
+
227
528
  unless self.notice_numbers.nil?
228
529
  notice["noticeNumbers"] = self.notice_numbers
229
530
  end
230
-
531
+
231
532
  if notice.keys.size > 0
232
533
  custom_policies["userNotice.1"] = "@notice"
233
534
  config_extension["notice"] = notice
234
535
  end
235
-
536
+
236
537
  if custom_policies.keys.size > 0
237
538
  config_extension["custom_policies"] = custom_policies
238
539
  @contains_data = true
239
540
  end
240
-
541
+
241
542
  config_extension
242
543
  end
243
-
544
+
244
545
  def to_s
245
546
  return "" unless @contains_data
246
- "ia5org,@custom_policies"
547
+ res = []
548
+ res << "ia5org"
549
+ res += @config_extensions["custom_policies"] unless @config_extensions.nil?
550
+ res.join(',')
551
+ end
552
+
553
+ def self.parse(value, critical)
554
+ obj = self.new
555
+ return obj if value.nil?
556
+ obj.critical = critical
557
+ value.split(/,\s*/).each do |v|
558
+ c = v.split(':', 2)
559
+ obj.policy_identifier = c.last if c.first == "policyIdentifier"
560
+ obj.cps_uris << c.last if c.first =~ %r{CPS.\d+}
561
+ # TODO: explicit_text, organization, notice_numbers
562
+ end
563
+ obj
564
+ end
565
+ end
566
+
567
+ # DEPRECATED
568
+ # Specifics the purposes for which a certificate can be used.
569
+ # The basicConstraints, keyUsage, and extendedKeyUsage extensions are now used instead.
570
+ # https://www.openssl.org/docs/apps/x509v3_config.html#Netscape_Certificate_Type
571
+ class NetscapeCertificateType
572
+ OPENSSL_IDENTIFIER = "nsCertType"
573
+
574
+ include ExtensionAPI
575
+
576
+ attr_accessor :critical
577
+ attr_accessor :flags
578
+
579
+ def initialize
580
+ self.critical = false
581
+ self.flags = []
582
+ end
583
+
584
+ def openssl_identifier
585
+ OPENSSL_IDENTIFIER
586
+ end
587
+
588
+ def to_s
589
+ res = []
590
+ res += self.flags
591
+ res.join(',')
592
+ end
593
+
594
+ def self.parse(value, critical)
595
+ obj = self.new
596
+ return obj if value.nil?
597
+ obj.critical = critical
598
+ obj.flags = value.split(/,\s*/)
599
+ obj
600
+ end
601
+ end
602
+
603
+ # DEPRECATED
604
+ # Contains a comment which will be displayed when the certificate is viewed in some browsers.
605
+ # https://www.openssl.org/docs/apps/x509v3_config.html#Netscape_String_extensions_
606
+ class NetscapeComment
607
+ OPENSSL_IDENTIFIER = "nsComment"
608
+
609
+ include ExtensionAPI
610
+
611
+ attr_accessor :critical
612
+ attr_accessor :comment
613
+
614
+ def initialize
615
+ self.critical = false
616
+ end
617
+
618
+ def openssl_identifier
619
+ OPENSSL_IDENTIFIER
620
+ end
621
+
622
+ def to_s
623
+ res = []
624
+ res << self.comment if self.comment
625
+ res.join(',')
626
+ end
627
+
628
+ def self.parse(value, critical)
629
+ obj = self.new
630
+ return obj if value.nil?
631
+ obj.critical = critical
632
+ obj.comment = value
633
+ obj
247
634
  end
248
635
  end
249
-
636
+
250
637
  end
251
- end
638
+ end