Enable hostname verification in client example (F-5621)
The client example set CERT_REQUIRED and loaded CA roots but never set check_hostname or passed server_hostname to wrap_socket, so wolfSSL validated the chain to a trusted CA without binding the certificate to the requested host. A peer presenting any CA-trusted certificate for a different hostname would be accepted by anyone reusing this as a secure client template. Make verification configure hostname checking by default (via a new configure_verification helper) and add a -n flag to opt out explicitly for IP literals or test certificates.pull/70/head
parent
99a4416771
commit
9c26572a41
|
|
@ -89,6 +89,12 @@ def build_arg_parser():
|
|||
help="Disable client cert check"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-n", action="store_true",
|
||||
help="Disable server hostname check "
|
||||
"(needed for IP literals or test certificates)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-g", action="store_true",
|
||||
help="Send server HTTP GET"
|
||||
|
|
@ -125,6 +131,33 @@ def get_DTLSmethod(index):
|
|||
wolfssl.PROTOCOL_DTLSv1_3
|
||||
)[index]
|
||||
|
||||
def configure_verification(context, args):
|
||||
"""
|
||||
Configure peer certificate and hostname verification on the context
|
||||
according to the parsed arguments. Returns the server_hostname to pass
|
||||
to wrap_socket() (None when no hostname check should be performed).
|
||||
|
||||
When certificate verification is enabled (the default), hostname
|
||||
verification is enabled too so that a CA-trusted certificate issued for
|
||||
a different host is rejected. Pass -n to opt out explicitly (e.g. when
|
||||
connecting to an IP literal or using test certificates).
|
||||
"""
|
||||
if args.d:
|
||||
context.verify_mode = wolfssl.CERT_NONE
|
||||
context.check_hostname = False
|
||||
return None
|
||||
|
||||
context.verify_mode = wolfssl.CERT_REQUIRED
|
||||
context.load_verify_locations(args.A)
|
||||
|
||||
if args.n:
|
||||
context.check_hostname = False
|
||||
return None
|
||||
|
||||
context.check_hostname = True
|
||||
return args.h
|
||||
|
||||
|
||||
def main():
|
||||
args = build_arg_parser().parse_args()
|
||||
|
||||
|
|
@ -147,18 +180,15 @@ def main():
|
|||
|
||||
context.load_cert_chain(args.c, args.k)
|
||||
|
||||
if args.d:
|
||||
context.verify_mode = wolfssl.CERT_NONE
|
||||
else:
|
||||
context.verify_mode = wolfssl.CERT_REQUIRED
|
||||
context.load_verify_locations(args.A)
|
||||
server_hostname = configure_verification(context, args)
|
||||
|
||||
if args.l:
|
||||
context.set_ciphers(args.l)
|
||||
|
||||
secure_socket = None
|
||||
try:
|
||||
secure_socket = context.wrap_socket(bind_socket)
|
||||
secure_socket = context.wrap_socket(
|
||||
bind_socket, server_hostname=server_hostname)
|
||||
|
||||
if not args.C:
|
||||
secure_socket.enable_crl(1)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# test_client_example.py
|
||||
#
|
||||
# Copyright (C) 2006-2020 wolfSSL Inc.
|
||||
#
|
||||
# This file is part of wolfSSL. (formerly known as CyaSSL)
|
||||
#
|
||||
# wolfSSL is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# wolfSSL is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
# pylint: disable=missing-docstring, invalid-name, import-error
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import wolfssl
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "examples"))
|
||||
import client as client_example # noqa: E402
|
||||
|
||||
|
||||
def _args(argv):
|
||||
return client_example.build_arg_parser().parse_args(argv)
|
||||
|
||||
|
||||
def _ctx():
|
||||
return wolfssl.SSLContext(wolfssl.PROTOCOL_TLSv1_2)
|
||||
|
||||
|
||||
def test_verification_enables_hostname_check_by_default():
|
||||
"""
|
||||
F-5621: with cert verification on (the default), the client must also
|
||||
verify the peer's hostname and pass server_hostname to wrap_socket.
|
||||
"""
|
||||
args = _args(["-h", "example.com"])
|
||||
ctx = _ctx()
|
||||
|
||||
server_hostname = client_example.configure_verification(ctx, args)
|
||||
|
||||
assert ctx.verify_mode == wolfssl.CERT_REQUIRED
|
||||
assert ctx.check_hostname is True
|
||||
assert server_hostname == "example.com"
|
||||
|
||||
|
||||
def test_disable_cert_check_skips_hostname():
|
||||
args = _args(["-d"])
|
||||
ctx = _ctx()
|
||||
# Simulate a reused context that previously had hostname checking on:
|
||||
# -d must clear it, not leave it dangling against CERT_NONE.
|
||||
ctx.verify_mode = wolfssl.CERT_REQUIRED
|
||||
ctx.check_hostname = True
|
||||
|
||||
server_hostname = client_example.configure_verification(ctx, args)
|
||||
|
||||
assert ctx.verify_mode == wolfssl.CERT_NONE
|
||||
assert ctx.check_hostname is False
|
||||
assert server_hostname is None
|
||||
|
||||
|
||||
def test_hostname_check_can_be_opted_out():
|
||||
"""An explicit opt-out is provided for IP literals / test certs."""
|
||||
args = _args(["-n"])
|
||||
ctx = _ctx()
|
||||
# Reused context with hostname checking previously enabled: -n must
|
||||
# actively turn it back off.
|
||||
ctx.verify_mode = wolfssl.CERT_REQUIRED
|
||||
ctx.check_hostname = True
|
||||
|
||||
server_hostname = client_example.configure_verification(ctx, args)
|
||||
|
||||
assert ctx.verify_mode == wolfssl.CERT_REQUIRED
|
||||
assert ctx.check_hostname is False
|
||||
assert server_hostname is None
|
||||
Loading…
Reference in New Issue