tlspretense 0.6.2 → 0.7.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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -0
  4. data/README.rdoc +25 -16
  5. data/Rakefile +1 -4
  6. data/doc/general_setup.rdoc +22 -1
  7. data/doc/macosx_setup.rdoc +86 -0
  8. data/lib/packetthief.rb +8 -1
  9. data/lib/packetthief/handlers/abstract_ssl_handler.rb +1 -2
  10. data/lib/packetthief/handlers/ssl_server.rb +3 -4
  11. data/lib/packetthief/handlers/ssl_smart_proxy.rb +3 -3
  12. data/lib/packetthief/handlers/ssl_transparent_proxy.rb +1 -1
  13. data/lib/packetthief/handlers/transparent_proxy.rb +1 -2
  14. data/lib/packetthief/logging.rb +32 -24
  15. data/lib/tlspretense/app.rb +5 -2
  16. data/lib/tlspretense/cert_maker.rb +5 -0
  17. data/lib/tlspretense/cert_maker/certificate_factory.rb +11 -1
  18. data/lib/tlspretense/cert_maker/certificate_suite_generator.rb +21 -3
  19. data/lib/tlspretense/cert_maker/subject_alt_name_factory.rb +115 -0
  20. data/lib/tlspretense/ext_compat/openssl_pkey_read.rb +38 -0
  21. data/lib/tlspretense/skel/config.yml +194 -17
  22. data/lib/tlspretense/test_harness/input_handler.rb +6 -2
  23. data/lib/tlspretense/test_harness/runner.rb +9 -3
  24. data/lib/tlspretense/test_harness/ssl_test_case.rb +2 -9
  25. data/lib/tlspretense/test_harness/ssl_test_report.rb +3 -0
  26. data/lib/tlspretense/test_harness/test_listener.rb +2 -2
  27. data/lib/tlspretense/test_harness/test_manager.rb +1 -2
  28. data/lib/tlspretense/version.rb +1 -1
  29. data/packetthief_examples/ssl_client_simple.rb +7 -0
  30. data/spec/packetthief/impl/ipfw_spec.rb +4 -2
  31. data/spec/packetthief/impl/netfilter_spec.rb +4 -2
  32. data/spec/packetthief/impl/pf_divert_spec.rb +4 -2
  33. data/spec/packetthief/logging_spec.rb +24 -26
  34. data/spec/spec_helper.rb +7 -8
  35. data/spec/tlspretense/cert_maker/subject_alt_name_factory_spec.rb +75 -0
  36. data/spec/tlspretense/ext_compat/openssl_pkey_read_spec.rb +126 -0
  37. data/spec/tlspretense/test_harness/runner_spec.rb +3 -5
  38. data/tlspretense.gemspec +2 -2
  39. metadata +41 -48
  40. data/Gemfile.lock +0 -41
@@ -59,6 +59,7 @@ module CertMaker
59
59
  # Prep for extensions
60
60
  ef = OpenSSL::X509::ExtensionFactory.new
61
61
  ef.subject_certificate = nc
62
+ san_ef = SubjectAltNameFactory.new
62
63
 
63
64
  self.ca = args.indifferent_fetch(:ca,self.ca)
64
65
  # Issuer handling
@@ -85,7 +86,16 @@ module CertMaker
85
86
 
86
87
  # Add the extensions
87
88
  exts.each do |ext|
88
- nc.add_extension(ef.create_ext_from_string(ext))
89
+ # hack to allow null bytes in subjectAltName DNS entries.
90
+ if ext.kind_of? String
91
+ if ext.strip.index('subjectAltName') == 0
92
+ nc.add_extension(san_ef.create_san_ext(ext))
93
+ else
94
+ nc.add_extension(ef.create_ext_from_string(ext))
95
+ end
96
+ else # hash
97
+ nc.add_extension(OpenSSL::X509::Extension.new(ext['oid'],ext['value'],ext['critical']))
98
+ end
89
99
  end
90
100
 
91
101
  # Look up the signing algorithm. If it is set to a symbol or string,
@@ -81,12 +81,26 @@ module CertMaker
81
81
  cf.ca_key = @certificates[signeralias][:key]
82
82
  end
83
83
  # doctor the certinfo's subject line and any extensions.
84
- certinfo['subject'] = certinfo['subject'].gsub(/%HOSTNAME%/, @defaulthostname).gsub(/%PARENTHOSTNAME%/, @parenthostname)
84
+ certinfo['subject'] = replace_tokens(certinfo['subject'])
85
85
  if certinfo.has_key? 'extensions'
86
- certinfo['extensions'] = certinfo['extensions'].map { |ext| ext.gsub(/%HOSTNAME%/, @defaulthostname).gsub(/%PARENTHOSTNAME%/, @parenthostname) }
86
+ certinfo['extensions'] = certinfo['extensions'].map do |ext|
87
+ if ext.kind_of? String
88
+ replace_tokens ext
89
+ else
90
+ ext['value'] = replace_tokens(ext['value'])
91
+ ext
92
+ end
93
+ end
87
94
  end
88
95
  if certinfo.has_key? 'addextensions'
89
- certinfo['addextensions'] = certinfo['addextensions'].map { |ext| ext.gsub(/%HOSTNAME%/, @defaulthostname).gsub(/%PARENTHOSTNAME%/, @parenthostname) }
96
+ certinfo['addextensions'] = certinfo['addextensions'].map do |ext|
97
+ if ext.kind_of? String
98
+ replace_tokens(ext)
99
+ else
100
+ ext['value'] = replace_tokens(ext['value'])
101
+ ext
102
+ end
103
+ end
90
104
  end
91
105
  # doctor the serial number.
92
106
  if @config.has_key? 'missing_serial_generation'
@@ -116,6 +130,10 @@ module CertMaker
116
130
  end
117
131
  end
118
132
 
133
+ def replace_tokens(str)
134
+ str.gsub(/%HOSTNAME%/, @defaulthostname).gsub(/%PARENTHOSTNAME%/, @parenthostname)
135
+ end
136
+
119
137
  end
120
138
  end
121
139
  end
@@ -0,0 +1,115 @@
1
+ module TLSPretense
2
+ module CertMaker
3
+ # SubjectAltName, in ASN1, is a sequence comprised of a tag identifying the
4
+ # extension type (subjectAltName, which has objectid tag of 6), and then an
5
+ # octect string that contains an ASN1 encoded sequence of alternative
6
+ # names. Each alternative name is then an ASN1Data with a context-specific
7
+ # tag id. DNS has a tag id of 2, and the string (an IA5String) is just an
8
+ # ascii string (meaning we can have null bytes!)
9
+ #
10
+ # pp asn1
11
+ # #<OpenSSL::ASN1::Sequence:0x00000100ac2ca8
12
+ # @infinite_length=false,
13
+ # @tag=16,
14
+ # @tag_class=:UNIVERSAL,
15
+ # @tagging=nil,
16
+ # @value=
17
+ # [#<OpenSSL::ASN1::ObjectId:0x00000100ac2d20
18
+ # @infinite_length=false,
19
+ # @tag=6,
20
+ # @tag_class=:UNIVERSAL,
21
+ # @tagging=nil,
22
+ # @value="subjectAltName">,
23
+ # #<OpenSSL::ASN1::OctetString:0x00000100ac2cd0
24
+ # @infinite_length=false,
25
+ # @tag=4,
26
+ # @tag_class=:UNIVERSAL,
27
+ # @tagging=nil,
28
+ # @value="0\x1E\x82\x1Cwww.isecpartners.com\x00foo.com">]>
29
+ #
30
+ # pp OpenSSL::ASN1.decode(asn1.value[1].value)
31
+ # #<OpenSSL::ASN1::Sequence:0x00000100ba16d8
32
+ # @infinite_length=false,
33
+ # @tag=16,
34
+ # @tag_class=:UNIVERSAL,
35
+ # @tagging=nil,
36
+ # @value=
37
+ # [#<OpenSSL::ASN1::ASN1Data:0x00000100ba1700
38
+ # @infinite_length=false,
39
+ # @tag=2,
40
+ # @tag_class=:CONTEXT_SPECIFIC,
41
+ # @value="www.isecpartners.com\x00foo.com">]>
42
+ class SubjectAltNameFactory
43
+ # Creates a subjectAltName extension using a specified dnsname.
44
+ #
45
+ # The purpose here is to bypass the normal OpenSSL subjectAltName
46
+ # constructors because they treat input as a C string at some point,
47
+ # losing everything after a null byte.
48
+ def initialize
49
+ # Lazy-make an existing subjectAltName extension to start from.
50
+ @ef = OpenSSL::X509::ExtensionFactory.new
51
+ end
52
+
53
+ def create_san_with_dns(dnsname)
54
+ ext = @ef.create_ext_from_string('subjectAltName=DNS:placeholder')
55
+ ext_asn1 = OpenSSL::ASN1.decode(ext.to_der)
56
+ san_list_der = ext_asn1.value[1].value
57
+ san_list_asn1 = OpenSSL::ASN1.decode(san_list_der)
58
+
59
+ san_list_asn1.value[0].value = dnsname
60
+
61
+ ext_asn1.value[1].value = san_list_asn1.to_der
62
+
63
+ OpenSSL::X509::Extension.new ext_asn1
64
+ end
65
+
66
+ # Generates a subjectAltName extension, but with improved null byte
67
+ # support.
68
+ #
69
+ # Desc should look like normal OpenSSL extension description. Eg:
70
+ #
71
+ # subjectAltName=DNS:foo.com, DNS:bar.com
72
+ #
73
+ # However, if a DNS entry contains a null byte, it doctors the extension
74
+ # to properly include the null byte (Either Ruby's openssl library or
75
+ # something in OpenSSL itself uses a C string at some point, dropping the
76
+ # null byte).
77
+ def create_san_ext(desc)
78
+ # Remove any DNSName entries with null bytes.
79
+ nulldomains = {}
80
+ # $1 is the domain
81
+ # $2 is the comma after
82
+ desc = desc.gsub(/DNS:\s*([^\s,]*\0[^\s,]*)\s*(,?)/) do |match|
83
+ nulldomains[$1.hash] = $1
84
+
85
+ "DNS:placeholder#{$1.hash.to_s}#{$2}"
86
+ end
87
+ ext = @ef.create_ext_from_string(desc)
88
+
89
+ # Find the placeholder entries for the removed entries and doctor them.
90
+ nulldomains.each_pair do |domainhash,domainwithnull|
91
+ ext = replace_in_san(ext,"placeholder#{domainhash.to_s}",domainwithnull)
92
+ end
93
+ ext
94
+ end
95
+
96
+ def replace_in_san(ext, olddns, newdns)
97
+ ext_asn1 = OpenSSL::ASN1.decode(ext.to_der)
98
+ san_list_der = ext_asn1.value[1].value
99
+ san_list_asn1 = OpenSSL::ASN1.decode(san_list_der)
100
+
101
+ san_list_asn1.value.map do |entry|
102
+ if entry.tag == 2 and entry.value == olddns
103
+ entry.value = newdns
104
+ end
105
+ entry
106
+ end
107
+
108
+ ext_asn1.value[1].value = san_list_asn1.to_der
109
+
110
+ OpenSSL::X509::Extension.new ext_asn1
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,38 @@
1
+
2
+ module TLSPretense
3
+ module ExtCompat
4
+ # In Ruby versions before 1.9.3, OpenSSL::PKey.read does not exist. It is
5
+ # not something that can be easily back-ported (it is written in C with
6
+ # OpenSSL functions that are not directly exposed through the Ruby
7
+ # library), so this module provides a partial reimplementation suitable for
8
+ # TLSPretense's purposes.
9
+ #
10
+ # It blindly tries each class until one works. If none work, it finally
11
+ # gives up. The error message won't ever be particularly helpful.
12
+ module OpenSSLPKeyRead
13
+ def read(data, passwd=nil)
14
+ begin
15
+ OpenSSL::PKey::RSA.new(data, passwd)
16
+ rescue OpenSSL::PKey::RSAError => e
17
+ begin
18
+ OpenSSL::PKey::DSA.new(data, passwd)
19
+ rescue OpenSSL::PKey::DSAError
20
+ begin
21
+ OpenSSL::PKey::EC.new(data, passwd)
22
+ rescue OpenSSL::PKey::ECError
23
+ raise "Failed to read a private key. Is the password correct?"
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.check_and_apply_patch
30
+ unless ::OpenSSL::PKey.respond_to? :read
31
+ $stderr.puts "Warning: OpenSSL::PKey does not respond to :read (added in Ruby 1.9.3). Monkeypatching it to provide enough functionality for TLSPretense."
32
+ ::OpenSSL::PKey.extend(self)
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -192,13 +192,27 @@ certs:
192
192
  addextensions:
193
193
  - "subjectAltName=DNS:www.foo.com"
194
194
 
195
- # This fails to generate as desired. the null byte truncates the
196
- # subjectAltName somewhere within OpenSSL. We need to manually construct the
197
- # ASN1 encoding ourselves.
198
- # nullinsubjectaltname: &nullinsubjectaltname
199
- # <<: *subjectaltnameonly
200
- # addextensions:
201
- # - "subjectAltName=DNS:%HOSTNAME%\x00.foo.com, DNS:another.com"
195
+ # The nullinsubjectaltname certificate will generate as intended with
196
+ # TLSPretense 0.7.0 and later. TLSPretense partially reconstructs
197
+ # subjectAltName extensions that contain a null byte in a DNSName. Note that
198
+ # the OpenSSL command line display of the certificate will truncate a
199
+ # subjectAltName DNSName entry at the null byte, failing to display the full
200
+ # entry. Eg:
201
+ #
202
+ # $ openssl x509 -in certs/nullinsubjectaltnamecert.pem -noout -text
203
+ # ...
204
+ # X509v3 Subject Alternative Name:
205
+ # DNS:www.isecpartners.com, DNS:another.com
206
+ # ...
207
+ #
208
+ # when the first entry is actually "www.isecpartners.com\0.foo.com". However,
209
+ # the full DNSName can be verified by examining the bytes of the DER encoding
210
+ # of the certificate, or by parsing the extension and carefully examining the
211
+ # ASN1 tree.
212
+ nullinsubjectaltname: &nullinsubjectaltname
213
+ <<: *subjectaltnameonly
214
+ addextensions:
215
+ - "subjectAltName=DNS:%HOSTNAME%\x00.foo.com, DNS:another.com"
202
216
 
203
217
  parentinsubjectaltname: &parentinsubjectaltname
204
218
  <<: *subjectaltnameonly
@@ -315,6 +329,19 @@ certs:
315
329
  <<: *baseline
316
330
  issuer: expiredca
317
331
 
332
+ unknownnoncriticalextension:
333
+ <<: *baseline
334
+ addextensions:
335
+ - oid: 1.2.3.4.5.6.7.8.9.10
336
+ value: ' ' # must have something
337
+ critical: false
338
+
339
+ unknowncriticalextension:
340
+ <<: *baseline
341
+ addextensions:
342
+ - oid: 1.2.3.4.5.6.7.8.9.10
343
+ value: ' ' # must have something
344
+ critical: true
318
345
 
319
346
  tests:
320
347
  # baseline
@@ -324,6 +351,9 @@ tests:
324
351
  - baseline
325
352
  - goodca
326
353
  expected_result: connected
354
+ description: |
355
+ A "good" certificate and chain that should always pass. If this test fails,
356
+ then your client probably does not trust the goodca CA certificate.
327
357
 
328
358
  # cname tests
329
359
  - alias: wrongcname
@@ -332,6 +362,9 @@ tests:
332
362
  - wrongcname
333
363
  - goodca
334
364
  expected_result: rejected
365
+ description: |
366
+ If the supplied CNAME does not match the expected hostname (and there is no
367
+ subjectAltName), then the certificate is for the wrong host.
335
368
 
336
369
  # cname tests
337
370
  - alias: parentcname
@@ -340,6 +373,9 @@ tests:
340
373
  - parentcname
341
374
  - goodca
342
375
  expected_result: rejected
376
+ description: |
377
+ Sanity check for extremely lenient hostname verification. A common name on
378
+ a certificate should match all of a hostname, not just the parent domain.
343
379
 
344
380
  - alias: nullincname
345
381
  name: Null character in CNAME
@@ -347,21 +383,33 @@ tests:
347
383
  - nullincname
348
384
  - goodca
349
385
  expected_result: rejected
386
+ description: |
387
+ If the common name contains a null character (often represented
388
+ in printable form as "\0" or "\x00" for single-byte text encodings), then
389
+ an attacker might be able to bypass hostname validation that uses C
390
+ strings. For example, a common name of "example.com\x00.evil.com", when
391
+ interpreted as a C string, would match "example.com".
350
392
 
351
393
  # subjectAltName tests
352
394
  - alias: happysubjectaltname
353
- name: Hostname is a dnsName in subjectAltName and in subject
395
+ name: Hostname is a dNSName in subjectAltName and in subject
354
396
  certchain:
355
397
  - baselinesubjectaltname
356
398
  - goodca
357
399
  expected_result: connected
400
+ description: |
401
+ Test to ensure that the client does not behave badly if the hostname is in
402
+ both the common name and in a dNSName subjectAltNAme entry.
358
403
 
359
404
  - alias: happysubjectaltnameonly
360
- name: hostname only a dnsName subjectAltName
405
+ name: hostname only a dNSName subjectAltName
361
406
  certchain:
362
407
  - subjectaltnameonly
363
408
  - goodca
364
409
  expected_result: connected
410
+ description: |
411
+ Test to ensure that the client supports a matching dNSName entry in the
412
+ subjectAltName when the hostname is not represented in the common name.
365
413
 
366
414
  - alias: wrongsubjectaltnamewrongsubject
367
415
  name: hostname in neither subjectAltName nor subject
@@ -369,6 +417,11 @@ tests:
369
417
  - wrongsubjectaltnamewrongsubject
370
418
  - goodca
371
419
  expected_result: rejected
420
+ description: |
421
+ If the hostname is not in either a subjectAltName or the common name, then
422
+ the certificate does not match the hostname. Failure to reject means an
423
+ attacker may be able to use a certificate for a different hostname to spoof
424
+ users.
372
425
 
373
426
  - alias: wrongsubjectaltnamerightsubject
374
427
  name: hostname in subject but not in subjectAltName
@@ -376,20 +429,35 @@ tests:
376
429
  - wrongsubjectaltnamerightsubject
377
430
  - goodca
378
431
  expected_result: rejected
432
+ description: |
433
+ If a certificate has a subjectAltName extension, then the client should not
434
+ check the common name. The common name does not have to be a hostname, and
435
+ there might be circumstance where a CA allows an arbitrary common name that
436
+ happens to match someone elses hostname.
437
+
438
+ - alias: nullinsubjectaltname
439
+ name: "null byte in subjectAltName"
440
+ certchain:
441
+ - nullinsubjectaltname
442
+ - goodca
443
+ expected_result: rejected
444
+ description: |
445
+ dNSName entries in the subjectAltName extension are susceptible to null
446
+ characters, just like the common name. If a dNSName entry contains a null
447
+ character (often represented in printable form as "\0" or "\x00" for
448
+ single-byte text encodings), then an attacker might be able to bypass
449
+ hostname validation that uses C strings. For example, a dNSName of
450
+ "example.com\x00.evil.com", when interpreted as a C string, would match
451
+ "example.com".
379
452
 
380
- #- alias: nullinsubjectaltname
381
- # name: "null byte in subjectAltName"
382
- # certchain:
383
- # - nullinsubjectaltname
384
- # - goodca
385
- # expected_result: rejected
386
- #
387
453
  - alias: parentinsubjectaltname
388
454
  name: "parent domain in subjectAltName"
389
455
  certchain:
390
456
  - parentinsubjectaltname
391
457
  - goodca
392
458
  expected_result: rejected
459
+ description: |
460
+ A dNSName entry should match all of a hostname, not just the parent domain.
393
461
 
394
462
  # key usage
395
463
  - alias: wrongextendedkeyusage
@@ -398,6 +466,17 @@ tests:
398
466
  - wrongextendedkeyusage
399
467
  - goodca
400
468
  expected_result: rejected
469
+ description: |
470
+ If a certificate has an extendedKeyUsage extension, then a client should
471
+ ensure that the certificate has the right usage flags set. TLSPretense
472
+ assumes the client is connecting to a server (as opposed to validating the
473
+ signature on an SMIME-signed email message, code signature, or some other
474
+ operation), so a certificate that has an extendedKeyUsage that lacks the
475
+ serverAuth bit should be rejected.
476
+
477
+ To exploit, an attacker would need to have a valid certificate for the
478
+ targeted domain, except that its extendedKeyUsage states it is for some
479
+ other purpose, such as code signing, non-repudiation, or OCSP signing.
401
480
 
402
481
  - alias: rightextendedkeyusagecrit
403
482
  name: extendedKeyUsage lacks serverAuth
@@ -405,6 +484,9 @@ tests:
405
484
  - rightextendedkeyusagecrit
406
485
  - goodca
407
486
  expected_result: connected
487
+ description: |
488
+ Happy test that essentially duplicates the baseline test, but for
489
+ comparison to the other extendedKeyUsage tests.
408
490
 
409
491
  #####################
410
492
  # This one fails against Java/Android's standard SSL client code.
@@ -414,6 +496,12 @@ tests:
414
496
  - wrongextendedkeyusagecrit
415
497
  - goodca
416
498
  expected_result: rejected
499
+ description: |
500
+ If the extendedKeyUsage extension is marked critical, and it has the wrong
501
+ flags set, then the client should reject this certificate. This particular
502
+ test is here to help determine whether a client that failed the
503
+ wrongextendedkeyusage test also properly honors the critical flag.
504
+
417
505
  #####################
418
506
 
419
507
  # cert chain issues
@@ -422,6 +510,10 @@ tests:
422
510
  certchain:
423
511
  - selfsigned
424
512
  expected_result: rejected
513
+ description: |
514
+ Anyone can create an arbitrary self-signed certificate. Without some
515
+ non-PKI mechanism for verifying whether the certificate is valid or not,
516
+ self-signed certificates cannot be trusted at all.
425
517
 
426
518
  - alias: unknownca
427
519
  name: Signed by an untrusted CA
@@ -429,6 +521,9 @@ tests:
429
521
  - unknowncacert
430
522
  - unknownca
431
523
  expected_result: rejected
524
+ description: |
525
+ A certificate signed by a CA that the client is unaware of is as dangerous
526
+ as a self-signed certificate -- anyone can craft such a certificate chain.
432
527
 
433
528
  - alias: differentkeyca
434
529
  name: Signed by an untrusted CA (provided in the chain) with the same name but a different key
@@ -436,6 +531,12 @@ tests:
436
531
  - signedbydifferentkey
437
532
  - cawithdifferentkey
438
533
  expected_result: rejected
534
+ description: |
535
+ When validating the certificate chain, it is critical for a client to
536
+ ensure that the CA is valid. An attacker could craft a CA that is identical
537
+ in every way to a CA that the client trusts but that uses a different
538
+ key-pair. This would easily allow an attacker to spoof a legitimate
539
+ certificate.
439
540
 
440
541
  - alias: badsignature
441
542
  name: Bad signature
@@ -443,6 +544,9 @@ tests:
443
544
  - badsignature
444
545
  - goodca
445
546
  expected_result: rejected
547
+ description: |
548
+ If the signature on the leaf certificate does not match the public key of
549
+ the CA that signed it, then an attacker could easily spoof the site.
446
550
 
447
551
  - alias: cafalseintermediate
448
552
  name: "Intermediate certificate where BasicConstraints sets CA:FALSE"
@@ -451,6 +555,13 @@ tests:
451
555
  - cafalseintermediate
452
556
  - goodca
453
557
  expected_result: rejected
558
+ description: |
559
+ While verifying the certificate chain, the client should ensure that all
560
+ certificates that attest to the identity of the leaf certificate are
561
+ actually CAs themselves (including all intermediate certificates).
562
+ Otherwise, an attacker could take a legitimate leaf node and use it to sign
563
+ a fake certificate. The verificate of the certificate chain passes because
564
+ the client fails to check the BasicConstraints.
454
565
 
455
566
  - alias: nobcintermediate
456
567
  name: Intermediate certificate lacks BasicConstraints
@@ -459,6 +570,10 @@ tests:
459
570
  - nobcintermediate
460
571
  - goodca
461
572
  expected_result: rejected
573
+ description: |
574
+ An intermediate certificate that lacks the BasicConstraints extension
575
+ should be treated as if its constriants stated that it was not a CA
576
+ (CA:FALSE).
462
577
 
463
578
  - alias: badsigonintermediate
464
579
  name: Intermediate certificate has bad signature from CA
@@ -467,6 +582,10 @@ tests:
467
582
  - badsigintermediate
468
583
  - goodca
469
584
  expected_result: rejected
585
+ description: |
586
+ If the signature on an intermediate certificate does not match the public
587
+ key of the CA that signed it, then an attacker could easily spoof the leaf
588
+ certificate.
470
589
 
471
590
  - alias: signedwithmd5
472
591
  name: Certificate signed with MD5
@@ -474,6 +593,12 @@ tests:
474
593
  - signedwithmd5
475
594
  - goodca
476
595
  expected_result: rejected
596
+ description: |
597
+ MD5 is a hash signing algorithm with known weaknesses. Researchers have
598
+ been able to create signature collisions, which means an attacker might be
599
+ able to create a certificate whose MD5 hash matches that of a certificate
600
+ with a valid signature. However, many existing certificates are signed with
601
+ MD5, although new leaf certificates should not be signed this way.
477
602
 
478
603
  - alias: signedwithmd4
479
604
  name: Certificate signed with MD4
@@ -481,6 +606,8 @@ tests:
481
606
  - signedwithmd4
482
607
  - goodca
483
608
  expected_result: rejected
609
+ description: |
610
+ MD5 is a deprecated hash signing algorithm. It should never be used.
484
611
 
485
612
  ## Need OpenSSL built with MD2 support
486
613
  #- alias: signedwithmd2
@@ -489,6 +616,8 @@ tests:
489
616
  # - signedwithmd2
490
617
  # - goodca
491
618
  # expected_result: rejected
619
+ # description: |
620
+ # MD2 is an even older hash signing algorithm.
492
621
 
493
622
  - alias: expiredcert
494
623
  name: Certificate that has expired
@@ -496,6 +625,12 @@ tests:
496
625
  - expiredcert
497
626
  - goodca
498
627
  expected_result: rejected
628
+ description: |
629
+ An expired certificate should not be trusted by a client. CAs mandate
630
+ expiration dates in order to limit how long a certificate is valid for.
631
+ This allows them to charge money to re-issue a certificate, but it also
632
+ allows them change certificate requirements over time and eventually phase
633
+ out old certificates.
499
634
 
500
635
  - alias: notyetvalidcert
501
636
  name: Certificate that is valid in the future
@@ -503,6 +638,9 @@ tests:
503
638
  - notyetvalidcert
504
639
  - goodca
505
640
  expected_result: rejected
641
+ description: |
642
+ A certificate that is not yet valid is suspicious. Either the client's
643
+ clock is running slow, or something else odd is going on.'
506
644
 
507
645
  - alias: expiredintermediate
508
646
  name: Certificate signed by an intermediate that has expired
@@ -511,6 +649,12 @@ tests:
511
649
  - expiredintermediate
512
650
  - goodca
513
651
  expected_result: rejected
652
+ description: |
653
+ An expired certificate, including expired intermediate certificates, should
654
+ not be trusted by a client. CAs mandate expiration dates in order to limit
655
+ how long a certificate is valid for. This allows them to charge money to
656
+ re-issue a certificate, but it also allows them change certificate
657
+ requirements over time and eventually phase out old certificates.
514
658
 
515
659
  # This requires installing the expired CA that is also installed into the
516
660
  # client's trusted root store.
@@ -520,4 +664,37 @@ tests:
520
664
  # - signedbyexpiredca
521
665
  # - expiredca
522
666
  # expected_result: rejected
523
-
667
+ # description: |
668
+ # An expired CA should not be trusted. If the CA has been compromised since
669
+ # its expiration (eg, the company who created it went out of business and its
670
+ # assets were sold, including drives that contained the CA's private key),
671
+ # then an attacker could use it to create arbitrary certificates that would
672
+ # work against clients that still trust the CA.'
673
+
674
+ - alias: unknownnoncriticalextension
675
+ name: Certificate contains a non-critical extension that the client does not understand
676
+ certchain:
677
+ - unknownnoncriticalextension
678
+ - goodca
679
+ expected_result: connected
680
+ description: |
681
+ In general, a well behaved client should ignore non-critical extensions
682
+ that it does not know how to validate. This test exepcts clients to ignore
683
+ such an extension, although rejecting an arbitrary non-critical extension
684
+ does not create a security issue (although the client may not be very
685
+ usable in practice).
686
+
687
+ - alias: unknowncriticalextension
688
+ name: Certificate contains a critical extension that the client does not understand
689
+ certchain:
690
+ - unknowncriticalextension
691
+ - goodca
692
+ expected_result: rejected
693
+ description: |
694
+ A well behaved client must reject the certificate if the certificate has an
695
+ extension marked critical that it does not know how to validate. Many
696
+ extensions on legitimately signed certificates are marked as critical to
697
+ force clients to verify them. Being unable to verify a particular critical
698
+ extension could mean that someone could use some other nearly identical
699
+ certificate (aside from the details of the unhandled critical extension) in
700
+ place of the correct one.