Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 28 additions & 27 deletions certs/crl.pem
Original file line number Diff line number Diff line change
@@ -1,41 +1,42 @@
Certificate Revocation List (CRL):
Version 2 (0x1)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, ST = Montana, L = Bozeman, O = Sawtooth, OU = Consulting, CN = www.wolfssl.com, emailAddress = info@wolfssl.com
Last Update: Feb 15 12:50:27 2022 GMT
Next Update: Nov 11 12:50:27 2024 GMT
Issuer: C=US, ST=Montana, L=Bozeman, O=Sawtooth, OU=Consulting, CN=www.wolfssl.com, emailAddress=info@wolfssl.com
Last Update: Nov 13 20:41:50 2025 GMT
Next Update: Aug 9 20:41:50 2028 GMT
CRL extensions:
X509v3 CRL Number:
2
Revoked Certificates:
Serial Number: 02
Revocation Date: Feb 15 12:50:27 2022 GMT
Revocation Date: Nov 13 20:41:50 2025 GMT
Signature Algorithm: sha256WithRSAEncryption
43:e6:3b:30:0e:32:53:32:a4:08:3c:e5:d5:2e:f1:ce:e9:95:
ff:ba:d6:fe:2e:59:80:f8:0a:2f:cf:1e:e0:37:fe:ca:cc:33:
66:8b:ed:65:50:7d:44:92:d3:5c:52:9a:95:a5:9d:a5:4e:77:
8b:b4:7f:59:c8:7a:e0:eb:34:32:ae:a1:03:99:d2:3c:c0:f4:
7e:1c:87:4c:6c:5a:ba:0a:95:e8:a1:44:01:7b:8f:3e:a4:e3:
e8:1e:07:19:f0:09:7a:85:8f:f3:82:62:f8:1e:08:51:a3:60:
30:5b:06:c8:a2:b3:ff:aa:28:66:ad:fe:4b:81:49:30:ef:5f:
5d:ac:d9:ad:17:9f:2a:b6:22:d6:35:cc:9f:d9:11:26:dd:7a:
06:35:d0:d5:c7:41:6c:52:97:8c:aa:82:5a:e5:a8:58:d4:b7:
2b:31:84:34:15:bd:08:e4:9e:71:9e:c5:40:f8:02:a3:a0:1e:
4f:98:72:2b:eb:9e:8a:4e:01:83:88:e5:cb:6e:3b:52:e3:a9:
34:a1:7c:e4:79:2c:d1:e0:0b:74:22:ba:6d:cb:c3:a1:56:f9:
c9:f4:20:bf:00:49:df:6b:59:49:18:c7:75:27:8e:a1:5a:a6:
ff:f2:be:34:4a:c9:6d:6e:24:a3:1f:15:7e:34:90:b6:81:bf:
15:80:c3:ac
Signature Value:
b7:0d:1c:78:99:1c:e8:0b:d9:33:a2:95:01:ad:cf:35:e9:86:
28:7f:49:6b:93:76:c1:70:08:61:aa:77:57:34:af:45:82:78:
5d:3b:7b:67:ca:b4:fb:d1:68:13:be:34:94:84:2d:65:ad:97:
52:69:1d:67:ea:8e:a7:ff:21:0f:21:6c:8c:75:7f:c7:50:c5:
6b:a5:fd:cd:3f:91:64:7b:5e:0f:4a:9c:c8:cd:39:a0:30:ad:
80:27:50:e0:a7:bf:19:68:cf:6b:26:75:51:14:77:5a:62:6d:
bc:66:1a:90:f7:00:09:34:c7:d0:9d:81:f3:b5:9f:90:40:02:
8d:3f:68:7f:0d:1d:c5:00:32:e5:cf:42:35:1c:b6:eb:02:a8:
d7:2a:a7:f3:f1:10:e2:d5:9e:41:de:2f:78:7d:7f:ad:68:06:
a0:6d:40:96:dd:35:59:4d:a0:d3:bd:2e:ba:b6:75:f8:1c:43:
b9:c0:b7:75:c4:38:59:46:00:71:ab:5a:df:f5:62:e9:ac:2b:
76:11:4f:1b:42:2c:dd:b2:38:6e:57:cf:c5:75:67:4c:3e:27:
bb:4c:d5:09:2c:4a:13:3d:8b:9c:89:76:b7:bd:73:1b:64:50:
ea:d5:13:0e:51:48:d8:43:08:93:00:85:8f:2f:08:ad:0d:aa:
d6:6c:f8:3d
-----BEGIN X509 CRL-----
MIICBDCB7QIBATANBgkqhkiG9w0BAQsFADCBlDELMAkGA1UEBhMCVVMxEDAOBgNV
BAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoMCFNhd3Rvb3Ro
MRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20x
HzAdBgkqhkiG9w0BCQEWEGluZm9Ad29sZnNzbC5jb20XDTIyMDIxNTEyNTAyN1oX
DTI0MTExMTEyNTAyN1owFDASAgECFw0yMjAyMTUxMjUwMjdaoA4wDDAKBgNVHRQE
AwIBAjANBgkqhkiG9w0BAQsFAAOCAQEAQ+Y7MA4yUzKkCDzl1S7xzumV/7rW/i5Z
gPgKL88e4Df+yswzZovtZVB9RJLTXFKalaWdpU53i7R/Wch64Os0Mq6hA5nSPMD0
fhyHTGxaugqV6KFEAXuPPqTj6B4HGfAJeoWP84Ji+B4IUaNgMFsGyKKz/6ooZq3+
S4FJMO9fXazZrRefKrYi1jXMn9kRJt16BjXQ1cdBbFKXjKqCWuWoWNS3KzGENBW9
COSecZ7FQPgCo6AeT5hyK+ueik4Bg4jly247UuOpNKF85Hks0eALdCK6bcvDoVb5
yfQgvwBJ32tZSRjHdSeOoVqm//K+NErJbW4kox8VfjSQtoG/FYDDrA==
HzAdBgkqhkiG9w0BCQEWEGluZm9Ad29sZnNzbC5jb20XDTI1MTExMzIwNDE1MFoX
DTI4MDgwOTIwNDE1MFowFDASAgECFw0yNTExMTMyMDQxNTBaoA4wDDAKBgNVHRQE
AwIBAjANBgkqhkiG9w0BAQsFAAOCAQEAtw0ceJkc6AvZM6KVAa3PNemGKH9Ja5N2
wXAIYap3VzSvRYJ4XTt7Z8q0+9FoE740lIQtZa2XUmkdZ+qOp/8hDyFsjHV/x1DF
a6X9zT+RZHteD0qcyM05oDCtgCdQ4Ke/GWjPayZ1URR3WmJtvGYakPcACTTH0J2B
87WfkEACjT9ofw0dxQAy5c9CNRy26wKo1yqn8/EQ4tWeQd4veH1/rWgGoG1Alt01
WU2g070uurZ1+BxDucC3dcQ4WUYAcata3/Vi6awrdhFPG0Is3bI4blfPxXVnTD4n
u0zVCSxKEz2LnIl2t71zG2RQ6tUTDlFI2EMIkwCFjy8IrQ2q1mz4PQ==
-----END X509 CRL-----
63 changes: 57 additions & 6 deletions examples/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ def build_arg_parser():
help="Disable client cert check"
)

parser.add_argument(
Comment thread
JacobBarthelmeh marked this conversation as resolved.
"-n", action="store_true",
help="Disable server hostname check "
"(IP literal hosts are never hostname-checked)"
)
Comment thread
julek-wolfssl marked this conversation as resolved.

parser.add_argument(
"-g", action="store_true",
help="Send server HTTP GET"
Expand Down Expand Up @@ -125,6 +131,54 @@ def get_DTLSmethod(index):
wolfssl.PROTOCOL_DTLSv1_3
)[index]

def is_ip_literal(host):
# AI_NUMERICHOST never resolves, it only parses. Unlike
# socket.inet_pton() it is available on every supported platform.
try:
socket.getaddrinfo(host, None, 0, 0, 0, socket.AI_NUMERICHOST)
return True
except socket.error:
return False


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
using test certificates).

IP literal hosts are not hostname-checked: wolfSSL_check_domain_name()
only matches DNS names (iPAddress SANs are skipped on this path), and
RFC 6066 forbids IP literals in SNI. Certificate verification against
the CA still applies. Connect by DNS name to also verify the hostname.
"""
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

if is_ip_literal(args.h):
print("Note: skipping hostname check for IP literal '{}'. "
"Connect by DNS name to enable it.".format(args.h))
context.check_hostname = False
return None

context.check_hostname = True
return args.h


def main():
args = build_arg_parser().parse_args()

Expand All @@ -147,18 +201,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)
Expand Down
19 changes: 18 additions & 1 deletion examples/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,21 @@ def get_DTLSmethod(index):
)[index]


# Large enough to peek a DTLS ClientHello source address.
PEEK_BUFSIZE = 1500


def peek_peer_address(sock):
"""
Return the source address of the next pending datagram without removing
it from the socket queue. MSG_PEEK leaves the datagram (the DTLS
ClientHello) intact so wolfSSL_accept() can consume it during the
handshake.
"""
_, from_addr = sock.recvfrom(PEEK_BUFSIZE, socket.MSG_PEEK)
return from_addr


def main():
args = build_arg_parser().parse_args()
# DTLS connection over UDP
Expand All @@ -124,7 +139,6 @@ def main():
args.v = 1
bind_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
bind_socket.bind(("" if args.b else "localhost", args.p))
data, from_addr = bind_socket.recvfrom(1)
context = wolfssl.SSLContext(get_DTLSmethod(args.v), server_side=True)
# SSL/TLS connection over TCP
else:
Expand Down Expand Up @@ -156,6 +170,9 @@ def main():
try:
secure_socket = None
if args.u:
# Peek the client's address for this connection without
# consuming the ClientHello datagram needed by the handshake.
from_addr = peek_peer_address(bind_socket)
secure_socket = context.wrap_socket(bind_socket)
else:
new_socket, from_addr = bind_socket.accept()
Expand Down
171 changes: 171 additions & 0 deletions tests/test_client_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# -*- 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 socket
import subprocess
import sys
import threading

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 test certificates."""
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


def test_ip_literal_host_skips_hostname_check():
"""
The default host (127.0.0.1) is an IP literal. wolfSSL's
check_domain_name() never matches iPAddress SANs, so the example must
not hostname-check IP literals; certificate verification stays on.
"""
args = _args([])
ctx = _ctx()
ctx.verify_mode = wolfssl.CERT_REQUIRED
ctx.check_hostname = True

server_hostname = client_example.configure_verification(ctx, args)

assert args.h == "127.0.0.1"
assert ctx.verify_mode == wolfssl.CERT_REQUIRED
assert ctx.check_hostname is False
assert server_hostname is None


def test_ipv6_literal_host_skips_hostname_check():
args = _args(["-h", "::1"])
ctx = _ctx()

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


def _free_port():
sock = socket.socket()
sock.bind(("localhost", 0))
port = sock.getsockname()[1]
sock.close()
return port


def test_default_invocation_end_to_end():
"""
`python client.py` with the default host must complete a connection to
`python server.py` using the bundled certificates: verification is on
by default and the IP literal host must not trip the hostname check.
"""
root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
port = _free_port()

# The examples must import the same wolfssl as this test process, even
# when the package is not installed (source-tree runs).
env = dict(os.environ)
pkg_root = os.path.dirname(os.path.dirname(os.path.abspath(
wolfssl.__file__)))
env["PYTHONPATH"] = os.pathsep.join(
[pkg_root] + ([env["PYTHONPATH"]] if env.get("PYTHONPATH") else []))

server = subprocess.Popen(
[sys.executable, "-u", os.path.join("examples", "server.py"),
"-p", str(port)],
cwd=root, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
try:
line = server.stdout.readline().decode()
assert "Server listening" in line, line

client = subprocess.Popen(
[sys.executable, "-u", os.path.join("examples", "client.py"),
"-p", str(port)],
cwd=root, env=env, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
# Watchdog instead of communicate(timeout=...), which is 3.3+.
watchdog = threading.Timer(60, client.kill)
watchdog.start()
try:
out, _ = client.communicate()
finally:
watchdog.cancel()

assert client.returncode == 0, out.decode()
assert b"I hear you fa shizzle" in out
finally:
server.kill()
server.wait()
Loading
Loading