certificate_authority 0.1.6 → 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 -31
  7. data/README.rdoc +91 -2
  8. data/Rakefile +6 -41
  9. data/certificate_authority.gemspec +22 -66
  10. data/lib/certificate_authority.rb +6 -5
  11. data/lib/certificate_authority/certificate.rb +91 -36
  12. data/lib/certificate_authority/certificate_revocation_list.rb +34 -14
  13. data/lib/certificate_authority/core_extensions.rb +46 -0
  14. data/lib/certificate_authority/distinguished_name.rb +64 -16
  15. data/lib/certificate_authority/extensions.rb +417 -45
  16. data/lib/certificate_authority/key_material.rb +30 -9
  17. data/lib/certificate_authority/ocsp_handler.rb +75 -5
  18. data/lib/certificate_authority/pkcs11_key_material.rb +0 -2
  19. data/lib/certificate_authority/revocable.rb +14 -0
  20. data/lib/certificate_authority/serial_number.rb +15 -2
  21. data/lib/certificate_authority/signing_request.rb +91 -0
  22. data/lib/certificate_authority/validations.rb +31 -0
  23. data/lib/certificate_authority/version.rb +3 -0
  24. metadata +76 -48
  25. data/VERSION.yml +0 -5
  26. data/spec/spec_helper.rb +0 -4
  27. data/spec/units/certificate_authority_spec.rb +0 -4
  28. data/spec/units/certificate_revocation_list_spec.rb +0 -68
  29. data/spec/units/certificate_spec.rb +0 -428
  30. data/spec/units/distinguished_name_spec.rb +0 -59
  31. data/spec/units/extensions_spec.rb +0 -115
  32. data/spec/units/key_material_spec.rb +0 -100
  33. data/spec/units/ocsp_handler_spec.rb +0 -104
  34. data/spec/units/pkcs11_key_material_spec.rb +0 -41
  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
@@ -5,6 +5,10 @@ module CertificateAuthority
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
@@ -12,51 +16,102 @@ module CertificateAuthority
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
24
 
17
- class BasicContraints
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]
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
23
48
 
24
49
  def initialize
25
- self.ca = false
50
+ @critical = false
51
+ @ca = false
52
+ end
53
+
54
+ def openssl_identifier
55
+ OPENSSL_IDENTIFIER
26
56
  end
27
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
66
 
37
- def openssl_identifier
38
- "basicConstraints"
67
+ def to_s
68
+ res = []
69
+ res << "CA:#{@ca}"
70
+ res << "pathlen:#{@path_len}" unless @path_len.nil?
71
+ res.join(',')
39
72
  end
40
73
 
41
- def to_s
42
- result = ""
43
- result += "CA:#{self.ca}"
44
- result += ",pathlen:#{self.path_len}" unless self.path_len.nil?
45
- result
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
104
 
52
- attr_accessor :uri
105
+ attr_accessor :critical
106
+ attr_accessor :uris
53
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
@@ -69,99 +124,302 @@ module CertificateAuthority
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
- return "" if self.uri.nil?
74
- "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]
75
160
  end
76
161
  end
77
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
78
167
  class SubjectKeyIdentifier
168
+ OPENSSL_IDENTIFIER = "subjectKeyIdentifier"
169
+
79
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
+
80
180
  def openssl_identifier
81
- "subjectKeyIdentifier"
181
+ OPENSSL_IDENTIFIER
82
182
  end
83
183
 
84
184
  def to_s
85
- "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]
86
205
  end
87
206
  end
88
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
89
211
  class AuthorityKeyIdentifier
212
+ OPENSSL_IDENTIFIER = "authorityKeyIdentifier"
213
+
90
214
  include ExtensionAPI
91
215
 
216
+ attr_accessor :critical
217
+ attr_accessor :identifier
218
+
219
+ def initialize
220
+ @critical = false
221
+ @identifier = ["keyid", "issuer"]
222
+ end
223
+
92
224
  def openssl_identifier
93
- "authorityKeyIdentifier"
225
+ OPENSSL_IDENTIFIER
94
226
  end
95
227
 
96
228
  def to_s
97
- "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]
98
249
  end
99
250
  end
100
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
101
257
  class AuthorityInfoAccess
258
+ OPENSSL_IDENTIFIER = "authorityInfoAccess"
259
+
102
260
  include ExtensionAPI
103
261
 
262
+ attr_accessor :critical
104
263
  attr_accessor :ocsp
264
+ attr_accessor :ca_issuers
105
265
 
106
266
  def initialize
107
- self.ocsp = []
267
+ @critical = false
268
+ @ocsp = []
269
+ @ca_issuers = []
108
270
  end
109
271
 
110
272
  def openssl_identifier
111
- "authorityInfoAccess"
273
+ OPENSSL_IDENTIFIER
112
274
  end
113
275
 
114
276
  def to_s
115
- return "" if self.ocsp.empty?
116
- "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]
117
306
  end
118
307
  end
119
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.
120
316
  class KeyUsage
317
+ OPENSSL_IDENTIFIER = "keyUsage"
318
+
121
319
  include ExtensionAPI
122
320
 
321
+ attr_accessor :critical
123
322
  attr_accessor :usage
124
323
 
125
324
  def initialize
126
- self.usage = ["digitalSignature", "nonRepudiation"]
325
+ @critical = false
326
+ @usage = ["digitalSignature", "nonRepudiation"]
127
327
  end
128
328
 
129
329
  def openssl_identifier
130
- "keyUsage"
330
+ OPENSSL_IDENTIFIER
131
331
  end
132
332
 
133
333
  def to_s
134
- "#{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]
135
354
  end
136
355
  end
137
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
138
361
  class ExtendedKeyUsage
362
+ OPENSSL_IDENTIFIER = "extendedKeyUsage"
363
+
139
364
  include ExtensionAPI
140
365
 
366
+ attr_accessor :critical
141
367
  attr_accessor :usage
142
368
 
143
369
  def initialize
144
- self.usage = ["serverAuth","clientAuth"]
370
+ @critical = false
371
+ @usage = ["serverAuth"]
145
372
  end
146
373
 
147
374
  def openssl_identifier
148
- "extendedKeyUsage"
375
+ OPENSSL_IDENTIFIER
149
376
  end
150
377
 
151
378
  def to_s
152
- "#{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]
153
399
  end
154
400
  end
155
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
156
405
  class SubjectAlternativeName
406
+ OPENSSL_IDENTIFIER = "subjectAltName"
407
+
157
408
  include ExtensionAPI
158
409
 
159
- attr_accessor :uris, :dns_names, :ips
410
+ attr_accessor :critical
411
+ attr_accessor :uris, :dns_names, :ips, :emails
160
412
 
161
413
  def initialize
162
- self.uris = []
163
- self.dns_names = []
164
- self.ips = []
414
+ @critical = false
415
+ @uris = []
416
+ @dns_names = []
417
+ @ips = []
418
+ @emails = []
419
+ end
420
+
421
+ def openssl_identifier
422
+ OPENSSL_IDENTIFIER
165
423
  end
166
424
 
167
425
  def uris=(value)
@@ -179,22 +437,50 @@ module CertificateAuthority
179
437
  @ips = value
180
438
  end
181
439
 
182
- def openssl_identifier
183
- "subjectAltName"
440
+ def emails=(value)
441
+ raise "Emails must be an array" unless value.is_a?(Array)
442
+ @emails = value
184
443
  end
185
444
 
186
445
  def to_s
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}" }
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"
468
+ end
469
+ obj
470
+ end
190
471
 
191
- return res.join(',')
472
+ protected
473
+ def state
474
+ [@critical,@uris,@dns_names,@ips,@emails]
192
475
  end
193
476
  end
194
477
 
195
478
  class CertificatePolicies
479
+ OPENSSL_IDENTIFIER = "certificatePolicies"
480
+
196
481
  include ExtensionAPI
197
482
 
483
+ attr_accessor :critical
198
484
  attr_accessor :policy_identifier
199
485
  attr_accessor :cps_uris
200
486
  ##User notice
@@ -203,12 +489,12 @@ module CertificateAuthority
203
489
  attr_accessor :notice_numbers
204
490
 
205
491
  def initialize
492
+ self.critical = false
206
493
  @contains_data = false
207
494
  end
208
495
 
209
-
210
496
  def openssl_identifier
211
- "certificatePolicies"
497
+ OPENSSL_IDENTIFIER
212
498
  end
213
499
 
214
500
  def user_notice=(value={})
@@ -258,7 +544,93 @@ module CertificateAuthority
258
544
 
259
545
  def to_s
260
546
  return "" unless @contains_data
261
- "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
262
634
  end
263
635
  end
264
636