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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/README.rdoc +25 -16
- data/Rakefile +1 -4
- data/doc/general_setup.rdoc +22 -1
- data/doc/macosx_setup.rdoc +86 -0
- data/lib/packetthief.rb +8 -1
- data/lib/packetthief/handlers/abstract_ssl_handler.rb +1 -2
- data/lib/packetthief/handlers/ssl_server.rb +3 -4
- data/lib/packetthief/handlers/ssl_smart_proxy.rb +3 -3
- data/lib/packetthief/handlers/ssl_transparent_proxy.rb +1 -1
- data/lib/packetthief/handlers/transparent_proxy.rb +1 -2
- data/lib/packetthief/logging.rb +32 -24
- data/lib/tlspretense/app.rb +5 -2
- data/lib/tlspretense/cert_maker.rb +5 -0
- data/lib/tlspretense/cert_maker/certificate_factory.rb +11 -1
- data/lib/tlspretense/cert_maker/certificate_suite_generator.rb +21 -3
- data/lib/tlspretense/cert_maker/subject_alt_name_factory.rb +115 -0
- data/lib/tlspretense/ext_compat/openssl_pkey_read.rb +38 -0
- data/lib/tlspretense/skel/config.yml +194 -17
- data/lib/tlspretense/test_harness/input_handler.rb +6 -2
- data/lib/tlspretense/test_harness/runner.rb +9 -3
- data/lib/tlspretense/test_harness/ssl_test_case.rb +2 -9
- data/lib/tlspretense/test_harness/ssl_test_report.rb +3 -0
- data/lib/tlspretense/test_harness/test_listener.rb +2 -2
- data/lib/tlspretense/test_harness/test_manager.rb +1 -2
- data/lib/tlspretense/version.rb +1 -1
- data/packetthief_examples/ssl_client_simple.rb +7 -0
- data/spec/packetthief/impl/ipfw_spec.rb +4 -2
- data/spec/packetthief/impl/netfilter_spec.rb +4 -2
- data/spec/packetthief/impl/pf_divert_spec.rb +4 -2
- data/spec/packetthief/logging_spec.rb +24 -26
- data/spec/spec_helper.rb +7 -8
- data/spec/tlspretense/cert_maker/subject_alt_name_factory_spec.rb +75 -0
- data/spec/tlspretense/ext_compat/openssl_pkey_read_spec.rb +126 -0
- data/spec/tlspretense/test_harness/runner_spec.rb +3 -5
- data/tlspretense.gemspec +2 -2
- metadata +41 -48
- 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
|
-
|
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']
|
84
|
+
certinfo['subject'] = replace_tokens(certinfo['subject'])
|
85
85
|
if certinfo.has_key? 'extensions'
|
86
|
-
certinfo['extensions'] = certinfo['extensions'].map
|
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
|
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
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
200
|
-
#
|
201
|
-
#
|
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
|
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
|
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.
|