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