wolfssl-py/examples/client.py

237 lines
6.6 KiB
Python
Executable File

#!/usr/bin/env python
#
# -*- coding: utf-8 -*-
#
# client.py
#
# Copyright (C) 2006-2026 wolfSSL Inc.
#
# This file is part of wolfSSL.
#
# 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 3 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-1335, USA
# pylint: disable=missing-docstring, invalid-name, import-error
import sys
import socket
import argparse
try:
import wolfssl
except ImportError:
print("You must run 'python setup.py install' to use the examples")
sys.exit()
def build_arg_parser():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument(
"-?", "--help", action="help",
help="show this help message and exit"
)
parser.add_argument(
"-h", metavar="host", default="127.0.0.1",
help="Host to connect to, default 127.0.0.1"
)
parser.add_argument(
"-p", metavar="port", type=int, default=11111,
help="Port to connect on, not 0, default 11111"
)
parser.add_argument(
"-v", metavar="version", type=int, choices=[0, 1, 2, 3, 4, 5],
default=5,
help="SSL version [0-5]"
"(SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3, SSLv23)"
)
parser.add_argument(
"-u", action="store_true",
help="Use UDP DTLS, add -v 0 for DTLSv1, -v 1 for DTLSv1.2 (default)"
)
parser.add_argument(
"-l", metavar="ciphers", type=str, default="",
help="Cipher suite list (: delimited)"
)
parser.add_argument(
"-c", metavar="certificate", default="./certs/client-cert.pem",
help="Certificate file, default ./certs/client-cert.pem"
)
parser.add_argument(
"-k", metavar="key", default="./certs/client-key.pem",
help="Key file, default ./certs/client-key.pem"
)
parser.add_argument(
"-A", metavar="ca_file", default="./certs/ca-cert.pem",
help="Certificate Authority file, default ./certs/ca-cert.pem"
)
parser.add_argument(
"-d", action="store_true",
help="Disable client cert check"
)
parser.add_argument(
"-n", action="store_true",
help="Disable server hostname check "
"(IP literal hosts are never hostname-checked)"
)
parser.add_argument(
"-g", action="store_true",
help="Send server HTTP GET"
)
parser.add_argument(
"-C", action="store_true",
help="Disable CRL"
)
parser.add_argument(
"-r", metavar="crl_file", default="./certs/crl.pem",
help="CRL file, default ./certs/crl.pem"
)
return parser
def get_SSLmethod(index):
return (
wolfssl.PROTOCOL_SSLv3,
wolfssl.PROTOCOL_TLSv1,
wolfssl.PROTOCOL_TLSv1_1,
wolfssl.PROTOCOL_TLSv1_2,
wolfssl.PROTOCOL_TLSv1_3,
wolfssl.PROTOCOL_SSLv23
)[index]
def get_DTLSmethod(index):
return (
wolfssl.PROTOCOL_DTLSv1,
wolfssl.PROTOCOL_DTLSv1_2,
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()
# DTLS connection over UDP
if args.u:
if args.v > 2:
args.v = 1
bind_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
context = wolfssl.SSLContext(get_DTLSmethod(args.v))
# SSL/TLS connection over TCP
else:
bind_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
context = wolfssl.SSLContext(get_SSLmethod(args.v))
# enable debug, if native wolfSSL has been compiled with '--enable-debug'
try:
wolfssl.WolfSSL.enable_debug()
except RuntimeError:
pass
context.load_cert_chain(args.c, args.k)
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, server_hostname=server_hostname)
if not args.C:
secure_socket.enable_crl(1)
secure_socket.load_crl_file(args.r, 1);
secure_socket.connect((args.h, args.p))
if args.g:
secure_socket.write(b"GET / HTTP/1.1\n\n")
else:
secure_socket.write(b"hello wolfssl")
print("\n", secure_socket.read(), "\n")
except KeyboardInterrupt:
print()
finally:
if secure_socket:
secure_socket.close()
if __name__ == '__main__':
main()