tlspretense 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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.