certificate_authority 0.1.6 → 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 -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