462 lines
16 KiB
Python
Executable File
462 lines
16 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
import warnings
|
|
warnings.simplefilter( "always", DeprecationWarning)
|
|
|
|
import os
|
|
import sys
|
|
import getopt
|
|
import getpass
|
|
|
|
from nss.error import NSPRError
|
|
import nss.io as io
|
|
import nss.nss as nss
|
|
import nss.ssl as ssl
|
|
|
|
# -----------------------------------------------------------------------------
|
|
NO_CLIENT_CERT = 0
|
|
REQUEST_CLIENT_CERT_ONCE = 1
|
|
REQUIRE_CLIENT_CERT_ONCE = 2
|
|
REQUEST_CLIENT_CERT_ALWAYS = 3
|
|
REQUIRE_CLIENT_CERT_ALWAYS = 4
|
|
|
|
# command line parameters, default them to something reasonable
|
|
client = False
|
|
server = False
|
|
password = 'db_passwd'
|
|
use_ssl = True
|
|
client_cert_action = NO_CLIENT_CERT
|
|
certdir = 'pki'
|
|
hostname = os.uname()[1]
|
|
server_nickname = 'test_server'
|
|
client_nickname = 'test_user'
|
|
port = 1234
|
|
timeout_secs = 3
|
|
family = io.PR_AF_UNSPEC
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Utility Functions
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Callback Functions
|
|
# -----------------------------------------------------------------------------
|
|
|
|
def password_callback(slot, retry, password):
|
|
if password: return password
|
|
return getpass.getpass("Enter password: ");
|
|
|
|
def handshake_callback(sock):
|
|
print "handshake complete, peer = %s" % (sock.get_peer_name())
|
|
|
|
def auth_certificate_callback(sock, check_sig, is_server, certdb):
|
|
print "auth_certificate_callback: check_sig=%s is_server=%s" % (check_sig, is_server)
|
|
cert_is_valid = False
|
|
|
|
cert = sock.get_peer_certificate()
|
|
pin_args = sock.get_pkcs11_pin_arg()
|
|
if pin_args is None:
|
|
pin_args = ()
|
|
|
|
print "cert:\n%s" % cert
|
|
|
|
# Define how the cert is being used based upon the is_server flag. This may
|
|
# seem backwards, but isn't. If we're a server we're trying to validate a
|
|
# client cert. If we're a client we're trying to validate a server cert.
|
|
if is_server:
|
|
intended_usage = nss.certificateUsageSSLClient
|
|
else:
|
|
intended_usage = nss.certificateUsageSSLServer
|
|
|
|
try:
|
|
# If the cert fails validation it will raise an exception, the errno attribute
|
|
# will be set to the error code matching the reason why the validation failed
|
|
# and the strerror attribute will contain a string describing the reason.
|
|
approved_usage = cert.verify_now(certdb, check_sig, intended_usage, *pin_args)
|
|
except Exception, e:
|
|
print e.strerror
|
|
cert_is_valid = False
|
|
print "Returning cert_is_valid = %s" % cert_is_valid
|
|
return cert_is_valid
|
|
|
|
print "approved_usage = %s" % ', '.join(nss.cert_usage_flags(approved_usage))
|
|
|
|
# Is the intended usage a proper subset of the approved usage
|
|
if approved_usage & intended_usage:
|
|
cert_is_valid = True
|
|
else:
|
|
cert_is_valid = False
|
|
|
|
# If this is a server, we're finished
|
|
if is_server or not cert_is_valid:
|
|
print "Returning cert_is_valid = %s" % cert_is_valid
|
|
return cert_is_valid
|
|
|
|
# Certificate is OK. Since this is the client side of an SSL
|
|
# connection, we need to verify that the name field in the cert
|
|
# matches the desired hostname. This is our defense against
|
|
# man-in-the-middle attacks.
|
|
|
|
hostname = sock.get_hostname()
|
|
print "verifying socket hostname (%s) matches cert subject (%s)" % (hostname, cert.subject)
|
|
try:
|
|
# If the cert fails validation it will raise an exception
|
|
cert_is_valid = cert.verify_hostname(hostname)
|
|
except Exception, e:
|
|
print e.strerror
|
|
cert_is_valid = False
|
|
print "Returning cert_is_valid = %s" % cert_is_valid
|
|
return cert_is_valid
|
|
|
|
print "Returning cert_is_valid = %s" % cert_is_valid
|
|
return cert_is_valid
|
|
|
|
def client_auth_data_callback(ca_names, chosen_nickname, password, certdb):
|
|
cert = None
|
|
if chosen_nickname:
|
|
try:
|
|
cert = nss.find_cert_from_nickname(chosen_nickname, password)
|
|
priv_key = nss.find_key_by_any_cert(cert, password)
|
|
print "client cert:\n%s" % cert
|
|
return cert, priv_key
|
|
except NSPRError, e:
|
|
print e
|
|
return False
|
|
else:
|
|
nicknames = nss.get_cert_nicknames(certdb, cert.SEC_CERT_NICKNAMES_USER)
|
|
for nickname in nicknames:
|
|
try:
|
|
cert = nss.find_cert_from_nickname(nickname, password)
|
|
print "client cert:\n%s" % cert
|
|
if cert.check_valid_times():
|
|
if cert.has_signer_in_ca_names(ca_names):
|
|
priv_key = nss.find_key_by_any_cert(cert, password)
|
|
return cert, priv_key
|
|
except NSPRError, e:
|
|
print e
|
|
return False
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Client Implementation
|
|
# -----------------------------------------------------------------------------
|
|
|
|
def Client():
|
|
valid_addr = False
|
|
# Get the IP Address of our server
|
|
try:
|
|
addr_info = io.AddrInfo(hostname)
|
|
except Exception, e:
|
|
print "could not resolve host address \"%s\"" % hostname
|
|
return
|
|
|
|
for net_addr in addr_info:
|
|
if family != io.PR_AF_UNSPEC:
|
|
if net_addr.family != family: continue
|
|
net_addr.port = port
|
|
|
|
if use_ssl:
|
|
sock = ssl.SSLSocket(net_addr.family)
|
|
|
|
# Set client SSL socket options
|
|
sock.set_ssl_option(ssl.SSL_SECURITY, True)
|
|
sock.set_ssl_option(ssl.SSL_HANDSHAKE_AS_CLIENT, True)
|
|
sock.set_hostname(hostname)
|
|
|
|
# Provide a callback which notifies us when the SSL handshake is complete
|
|
sock.set_handshake_callback(handshake_callback)
|
|
|
|
# Provide a callback to supply our client certificate info
|
|
sock.set_client_auth_data_callback(client_auth_data_callback, client_nickname,
|
|
password, nss.get_default_certdb())
|
|
|
|
# Provide a callback to verify the servers certificate
|
|
sock.set_auth_certificate_callback(auth_certificate_callback,
|
|
nss.get_default_certdb())
|
|
else:
|
|
sock = io.Socket(net_addr.family)
|
|
|
|
try:
|
|
print "client trying connection to: %s" % (net_addr)
|
|
sock.connect(net_addr, timeout=io.seconds_to_interval(timeout_secs))
|
|
print "client connected to: %s" % (net_addr)
|
|
valid_addr = True
|
|
break
|
|
except Exception, e:
|
|
sock.close()
|
|
print "client connection to: %s failed (%s)" % (net_addr, e)
|
|
|
|
if not valid_addr:
|
|
print "Could not establish valid address for \"%s\" in family %s" % \
|
|
(hostname, io.addr_family_name(family))
|
|
return
|
|
|
|
# Talk to the server
|
|
try:
|
|
sock.send("Hello")
|
|
buf = sock.recv(1024)
|
|
if not buf:
|
|
print "client lost connection"
|
|
sock.close()
|
|
return
|
|
print "client received: %s" % (buf)
|
|
except Exception, e:
|
|
print e.strerror
|
|
try:
|
|
sock.close()
|
|
except:
|
|
pass
|
|
return
|
|
|
|
# End of (simple) protocol session?
|
|
if buf == 'Goodbye':
|
|
try:
|
|
sock.shutdown()
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
sock.close()
|
|
if use_ssl:
|
|
ssl.clear_session_cache()
|
|
except Exception, e:
|
|
print e
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Server Implementation
|
|
# -----------------------------------------------------------------------------
|
|
|
|
def Server():
|
|
global family
|
|
|
|
# Perform basic SSL server configuration
|
|
ssl.set_default_cipher_pref(ssl.SSL_RSA_WITH_NULL_MD5, True)
|
|
ssl.config_server_session_id_cache()
|
|
|
|
# Get our certificate and private key
|
|
server_cert = nss.find_cert_from_nickname(server_nickname, password)
|
|
priv_key = nss.find_key_by_any_cert(server_cert, password)
|
|
server_cert_kea = server_cert.find_kea_type();
|
|
|
|
print "server cert:\n%s" % server_cert
|
|
|
|
# Setup an IP Address to listen on any of our interfaces
|
|
if family == io.PR_AF_UNSPEC:
|
|
family = io.PR_AF_INET
|
|
net_addr = io.NetworkAddress(io.PR_IpAddrAny, port, family)
|
|
|
|
if use_ssl:
|
|
sock = ssl.SSLSocket(net_addr.family)
|
|
|
|
# Set server SSL socket options
|
|
sock.set_pkcs11_pin_arg(password)
|
|
sock.set_ssl_option(ssl.SSL_SECURITY, True)
|
|
sock.set_ssl_option(ssl.SSL_HANDSHAKE_AS_SERVER, True)
|
|
|
|
# If we're doing client authentication then set it up
|
|
if client_cert_action >= REQUEST_CLIENT_CERT_ONCE:
|
|
sock.set_ssl_option(ssl.SSL_REQUEST_CERTIFICATE, True)
|
|
if client_cert_action == REQUIRE_CLIENT_CERT_ONCE:
|
|
sock.set_ssl_option(ssl.SSL_REQUIRE_CERTIFICATE, True)
|
|
sock.set_auth_certificate_callback(auth_certificate_callback, nss.get_default_certdb())
|
|
|
|
# Configure the server SSL socket
|
|
sock.config_secure_server(server_cert, priv_key, server_cert_kea)
|
|
|
|
else:
|
|
sock = io.Socket(net_addr.family)
|
|
|
|
# Bind to our network address and listen for clients
|
|
sock.bind(net_addr)
|
|
print "listening on: %s" % (net_addr)
|
|
sock.listen()
|
|
|
|
while True:
|
|
# Accept a connection from a client
|
|
client_sock, client_addr = sock.accept()
|
|
if use_ssl:
|
|
client_sock.set_handshake_callback(handshake_callback)
|
|
|
|
print "client connect from: %s" % (client_addr)
|
|
|
|
while True:
|
|
try:
|
|
# Handle the client connection
|
|
buf = client_sock.recv(1024)
|
|
if not buf:
|
|
print "server lost lost connection to %s" % (client_addr)
|
|
break
|
|
|
|
print "server received: %s" % (buf)
|
|
|
|
client_sock.send("Goodbye")
|
|
try:
|
|
client_sock.shutdown(io.PR_SHUTDOWN_RCV)
|
|
client_sock.close()
|
|
except:
|
|
pass
|
|
break
|
|
except Exception, e:
|
|
print e.strerror
|
|
break
|
|
break
|
|
|
|
try:
|
|
sock.shutdown()
|
|
sock.close()
|
|
if use_ssl:
|
|
ssl.shutdown_server_session_id_cache()
|
|
except Exception, e:
|
|
print e
|
|
pass
|
|
|
|
# -----------------------------------------------------------------------------
|
|
|
|
usage_str = '''
|
|
-C --client run as the client (default: %(client)s)
|
|
-S --server run as the server (default: %(server)s)
|
|
-d --certdir certificate directory (default: %(certdir)s)
|
|
-h --hostname host to connect to (default: %(hostname)s)
|
|
-f --family may be inet|inet6|unspec (default: %(family)s)
|
|
if unspec client tries all addresses returned by AddrInfo
|
|
server binds to IPv4 "any" wildcard address
|
|
if inet client tries IPv4 addresses returned by AddrInfo
|
|
server binds to IPv4 "any" wildcard address
|
|
if inet6 client tries IPv6 addresses returned by AddrInfo
|
|
server binds to IPv6 "any" wildcard address
|
|
-4 --inet set family to inet (see family)
|
|
-6 --inet6 set family to inet6 (see family)
|
|
-n --server_nickname server certificate nickname (default: %(server_nickname)s)
|
|
-N --client_nickname client certificate nickname (default: %(client_nickname)s)
|
|
-w --password certificate database password (default: %(password)s)
|
|
-p --port host port (default: %(port)s)
|
|
-e --encrypt use SSL (default) (default: %(encrypt)s)
|
|
-E --noencrypt don't use SSL (default: %(noencrypt)s)
|
|
-f --require_cert_once (default: %(require_cert_once)s)
|
|
-F --require_cert_always (default: %(require_cert_always)s)
|
|
-r --request_cert_once (default: %(request_cert_once)s)
|
|
-R --request_cert_always (default: %(request_cert_always)s)
|
|
-H --help
|
|
''' % {
|
|
'client' : client,
|
|
'server' : server,
|
|
'certdir' : certdir,
|
|
'hostname' : hostname,
|
|
'family' : io.addr_family_name(family),
|
|
'server_nickname' : server_nickname,
|
|
'client_nickname' : client_nickname,
|
|
'password' : password,
|
|
'port' : port,
|
|
'encrypt' : use_ssl is True,
|
|
'noencrypt' : use_ssl is False,
|
|
'require_cert_once' : client_cert_action == REQUIRE_CLIENT_CERT_ONCE,
|
|
'require_cert_always' : client_cert_action == REQUIRE_CLIENT_CERT_ALWAYS,
|
|
'request_cert_once' : client_cert_action == REQUEST_CLIENT_CERT_ONCE,
|
|
'request_cert_always' : client_cert_action == REQUEST_CLIENT_CERT_ALWAYS,
|
|
}
|
|
|
|
def usage():
|
|
print usage_str
|
|
|
|
try:
|
|
opts, args = getopt.getopt(sys.argv[1:], "Hd:h:f:46n:N:w:p:CSeE",
|
|
["help", "certdir=", "hostname=",
|
|
"family", "inet", "inet6",
|
|
"server_nickname=", "client_nickname=",
|
|
"password=", "port=",
|
|
"client", "server", "encrypt", "noencrypt",
|
|
"require_cert_once", "require_cert_always",
|
|
"request_cert_once", "request_cert_always",
|
|
])
|
|
except getopt.GetoptError:
|
|
# print help information and exit:
|
|
usage()
|
|
sys.exit(2)
|
|
|
|
|
|
for o, a in opts:
|
|
if o in ("-d", "--certdir"):
|
|
certdir = a
|
|
elif o in ("-h", "--hostname"):
|
|
hostname = a
|
|
elif o in ("-f", "--family"):
|
|
if a == "inet":
|
|
family = io.PR_AF_INET
|
|
elif a == "inet6":
|
|
family = io.PR_AF_INET6
|
|
elif a == "unspec":
|
|
family = io.PR_AF_UNSPEC
|
|
else:
|
|
print "unknown address family (%s)" % (a)
|
|
usage()
|
|
sys.exit()
|
|
elif o in ("-4", "--inet"):
|
|
family = io.PR_AF_INET
|
|
elif o in ("-6", "--inet6"):
|
|
family = io.PR_AF_INET6
|
|
elif o in ("-n", "--server_nickname"):
|
|
server_nickname = a
|
|
elif o in ("-N", "--client_nickname"):
|
|
client_nickname = a
|
|
elif o in ("-w", "--password"):
|
|
password = a
|
|
elif o in ("-p", "--port"):
|
|
port = int(a)
|
|
elif o in ("-C", "--client"):
|
|
client = True
|
|
elif o in ("-S", "--server"):
|
|
server = True
|
|
elif o in ("-e", "--encrypt"):
|
|
use_ssl = True
|
|
elif o in ("-E", "--noencrypt"):
|
|
use_ssl = False
|
|
elif o in ("--require_cert_once"):
|
|
client_cert_action = REQUIRE_CLIENT_CERT_ONCE
|
|
elif o in ("--require_cert_always"):
|
|
client_cert_action = REQUIRE_CLIENT_CERT_ALWAYS
|
|
elif o in ("--request_cert_once"):
|
|
client_cert_action = REQUEST_CLIENT_CERT_ONCE
|
|
elif o in ("--request_cert_always"):
|
|
client_cert_action = REQUEST_CLIENT_CERT_ALWAYS
|
|
elif o in ("-H", "--help"):
|
|
usage()
|
|
sys.exit()
|
|
else:
|
|
usage()
|
|
sys.exit()
|
|
|
|
if client and server:
|
|
print "can't be both client and server"
|
|
sys.exit(1)
|
|
if not (client or server):
|
|
print "must be one of client or server"
|
|
sys.exit(1)
|
|
|
|
# Perform basic configuration and setup
|
|
if certdir is None:
|
|
nss.nss_init_nodb()
|
|
else:
|
|
nss.nss_init(certdir)
|
|
|
|
ssl.set_domestic_policy()
|
|
nss.set_password_callback(password_callback)
|
|
|
|
# Run as a client or as a server
|
|
if client:
|
|
print "starting as client"
|
|
Client()
|
|
|
|
if server:
|
|
print "starting as server"
|
|
Server()
|
|
|
|
try:
|
|
nss.nss_shutdown()
|
|
except Exception, e:
|
|
print e
|
|
|