certificate_authority 0.1.2 → 1.0.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.
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