Skip to content

Commit

Permalink
Merge pull request #196 from tomato42/ecdsa-support
Browse files Browse the repository at this point in the history
ECDSA support
  • Loading branch information
tomato42 authored Nov 13, 2019
2 parents 3d63ee4 + 2493373 commit a15122a
Show file tree
Hide file tree
Showing 8 changed files with 455 additions and 80 deletions.
43 changes: 35 additions & 8 deletions scripts/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from tlslite.api import *
from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \
GroupName, SignatureScheme
from tlslite.handshakesettings import Keypair, VirtualHost
from tlslite import __version__
from tlslite.utils.compat import b2a_hex, a2b_hex, time_stamp
from tlslite.utils.dns_utils import is_valid_hostname
Expand Down Expand Up @@ -74,13 +75,13 @@ def printUsage(s=None):
print("""Commands:
server
[-k KEY] [-c CERT] [-t TACK] [-v VERIFIERDB] [-d DIR] [-l LABEL] [-L LENGTH]
[-c CERT] [-k KEY] [-t TACK] [-v VERIFIERDB] [-d DIR] [-l LABEL] [-L LENGTH]
[--reqcert] [--param DHFILE] [--psk PSK] [--psk-ident IDENTITY]
[--psk-sha384] [--ssl3] [--max-ver VER] [--tickets COUNT]
HOST:PORT
client
[-k KEY] [-c CERT] [-u USER] [-p PASS] [-l LABEL] [-L LENGTH] [-a ALPN]
[-c CERT] [-k KEY] [-u USER] [-p PASS] [-l LABEL] [-L LENGTH] [-a ALPN]
[--psk PSK] [--psk-ident IDENTITY] [--psk-sha384] [--resumption] [--ssl3]
[--max-ver VER]
HOST:PORT
Expand All @@ -98,6 +99,10 @@ def printUsage(s=None):
"tls1.3"
--tickets COUNT - how many tickets should server send after handshake is
finished
CERT, KEY - the file with key and certificates that will be used by client or
server. The server can accept multiple pairs of `-c` and `-k` options
to configure different certificates (like RSA and ECDSA)
""")
sys.exit(-1)

Expand Down Expand Up @@ -131,6 +136,8 @@ def handleArgs(argv, argString, flagsList=[]):
# Default values if arg not present
privateKey = None
cert_chain = None
virtual_hosts = []
v_host_cert = None
username = None
password = None
tacks = None
Expand All @@ -155,14 +162,30 @@ def handleArgs(argv, argString, flagsList=[]):
if sys.version_info[0] >= 3:
s = str(s, 'utf-8')
# OpenSSL/m2crypto does not support RSASSA-PSS certificates
privateKey = parsePEMKey(s, private=True,
implementations=["python"])
if not privateKey:
privateKey = parsePEMKey(s, private=True,
implementations=["python"])
else:
if not v_host_cert:
raise ValueError("Virtual host certificate missing "
"(must be listed before key)")
p_key = parsePEMKey(s, private=True,
implementations=["python"])
if not virtual_hosts:
virtual_hosts.append(VirtualHost())
virtual_hosts[0].keys.append(
Keypair(p_key, v_host_cert.x509List))
v_host_cert = None
elif opt == "-c":
s = open(arg, "rb").read()
if sys.version_info[0] >= 3:
s = str(s, 'utf-8')
cert_chain = X509CertChain()
cert_chain.parsePemList(s)
if not cert_chain:
cert_chain = X509CertChain()
cert_chain.parsePemList(s)
else:
v_host_cert = X509CertChain()
v_host_cert.parsePemList(s)
elif opt == "-u":
username = arg
elif opt == "-p":
Expand Down Expand Up @@ -228,6 +251,7 @@ def handleArgs(argv, argString, flagsList=[]):
retList.append(privateKey)
if "c" in argString:
retList.append(cert_chain)
retList.append(virtual_hosts)
if "u" in argString:
retList.append(username)
if "p" in argString:
Expand Down Expand Up @@ -323,7 +347,8 @@ def printExporter(connection, expLabel, expLength):


def clientCmd(argv):
(address, privateKey, cert_chain, username, password, expLabel,
(address, privateKey, cert_chain, virtual_hosts, username, password,
expLabel,
expLength, alpn, psk, psk_ident, psk_hash, resumption, ssl3,
max_ver) = \
handleArgs(argv, "kcuplLa", ["psk=", "psk-ident=", "psk-sha384",
Expand Down Expand Up @@ -455,7 +480,8 @@ def clientCmd(argv):


def serverCmd(argv):
(address, privateKey, cert_chain, tacks, verifierDB, directory, reqCert,
(address, privateKey, cert_chain, virtual_hosts, tacks, verifierDB,
directory, reqCert,
expLabel, expLength, dhparam, psk, psk_ident, psk_hash, ssl3,
max_ver, tickets) = \
handleArgs(argv, "kctbvdlL",
Expand Down Expand Up @@ -502,6 +528,7 @@ def serverCmd(argv):
settings.minVersion = (3, 0)
if max_ver:
settings.maxVersion = max_ver
settings.virtual_hosts = virtual_hosts

class MySimpleHTTPHandler(SimpleHTTPRequestHandler):
"""Buffer the header and body of HTTP message."""
Expand Down
101 changes: 101 additions & 0 deletions tests/tlstest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
AlertDescription, HTTPTLSConnection, TLSSocketServerMixIn, \
POP3_TLS, m2cryptoLoaded, pycryptoLoaded, gmpyLoaded, tackpyLoaded, \
Checker, __version__
from tlslite.handshakesettings import VirtualHost, Keypair

from tlslite.errors import *
from tlslite.utils.cryptomath import prngName, getRandomBytes
Expand Down Expand Up @@ -360,6 +361,84 @@ def connect():

test_no += 1

print("Test {0} - good RSA and ECDSA, TLSv1.3, rsa"
.format(test_no))
synchro.recv(1)
connection = connect()
settings = HandshakeSettings()
settings.minVersion = (3, 4)
settings.maxVersion = (3, 4)
connection.handshakeClientCert(settings=settings)
testConnClient(connection)
assert connection.session.cipherSuite in\
constants.CipherSuite.tls13Suites
assert isinstance(connection.session.serverCertChain, X509CertChain)
assert connection.session.serverCertChain.getEndEntityPublicKey().key_type\
== "rsa"
assert connection.version == (3, 4)
connection.close()

test_no += 1

print("Test {0} - good RSA and ECDSA, TLSv1.3, ecdsa"
.format(test_no))
synchro.recv(1)
connection = connect()
settings = HandshakeSettings()
settings.minVersion = (3, 4)
settings.maxVersion = (3, 4)
settings.rsaSigHashes = []
connection.handshakeClientCert(settings=settings)
testConnClient(connection)
assert connection.session.cipherSuite in\
constants.CipherSuite.tls13Suites
assert isinstance(connection.session.serverCertChain, X509CertChain)
assert connection.session.serverCertChain.getEndEntityPublicKey().key_type\
== "ecdsa"
assert connection.version == (3, 4)
connection.close()

test_no += 1

print("Test {0} - good RSA and ECDSA, TLSv1.2, rsa"
.format(test_no))
synchro.recv(1)
connection = connect()
settings = HandshakeSettings()
settings.minVersion = (3, 3)
settings.maxVersion = (3, 3)
connection.handshakeClientCert(settings=settings)
testConnClient(connection)
assert connection.session.cipherSuite in\
constants.CipherSuite.ecdheCertSuites, connection.session.cipherSuite
assert isinstance(connection.session.serverCertChain, X509CertChain)
assert connection.session.serverCertChain.getEndEntityPublicKey().key_type\
== "rsa"
assert connection.version == (3, 3)
connection.close()

test_no += 1

print("Test {0} - good RSA and ECDSA, TLSv1.2, ecdsa"
.format(test_no))
synchro.recv(1)
connection = connect()
settings = HandshakeSettings()
settings.minVersion = (3, 3)
settings.maxVersion = (3, 3)
settings.rsaSigHashes = []
connection.handshakeClientCert(settings=settings)
testConnClient(connection)
assert connection.session.cipherSuite in\
constants.CipherSuite.ecdheEcdsaSuites, connection.session.cipherSuite
assert isinstance(connection.session.serverCertChain, X509CertChain)
assert connection.session.serverCertChain.getEndEntityPublicKey().key_type\
== "ecdsa"
assert connection.version == (3, 3)
connection.close()

test_no += 1

print("Test {0} - good X.509, mismatched key_share".format(test_no))
synchro.recv(1)
connection = connect()
Expand Down Expand Up @@ -1502,6 +1581,28 @@ def connect():

test_no += 1

for prot in ["TLSv1.3", "TLSv1.2"]:
for c_type, exp_chain in (("rsa", x509Chain),
("ecdsa", x509ecdsaChain)):
print("Test {0} - good RSA and ECDSA, {2}, {1}"
.format(test_no, c_type, prot))
synchro.send(b'R')
connection = connect()
settings = HandshakeSettings()
settings.minVersion = (3, 3)
settings.maxVersion = (3, 4)
v_host = VirtualHost()
v_host.keys = [Keypair(x509ecdsaKey, x509ecdsaChain.x509List)]
settings.virtual_hosts = [v_host]
connection.handshakeServer(certChain=x509Chain,
privateKey=x509Key, settings=settings)
assert connection.extendedMasterSecret
assert connection.session.serverCertChain == exp_chain
testConnServer(connection)
connection.close()

test_no += 1

print("Test {0} - good X.509, mismatched key_share".format(test_no))
synchro.send(b'R')
connection = connect()
Expand Down
80 changes: 80 additions & 0 deletions tlslite/handshakesettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,80 @@
PSK_MODES = ["psk_dhe_ke", "psk_ke"]


class Keypair(object):
"""
Key, certificate and related data.
Stores also certificate associate data like OCSPs and transparency info.
TODO: add the above
First certificate in certificates needs to match key, remaining should
build a trust path to a root CA.
:vartype key: RSAKey or ECDSAKey
:ivar key: private key
:vartype certificates: list of X509
:ivar certificates: the certificates to send to peer if the key is selected
for use. The first one MUST include the public key of the ``key``
"""
def __init__(self, key=None, certificates=tuple()):
self.key = key
self.certificates = certificates

def validate(self):
"""Sanity check the keypair."""
if not self.key or not self.certificates:
raise ValueError("Key or certificate missing in Keypair")


class VirtualHost(object):
"""
Configuration of keys and certs for a single virual server.
This class encapsulates keys and certificates for hosts specified by
server_name (SNI) and ALPN extensions.
TODO: support SRP as alternative to certificates
TODO: support PSK as alternative to certificates
:vartype keys: list of :ref:`~Keypair`
:ivar keys: List of certificates and keys to be used in this
virtual host. First keypair able to server ClientHello will be used.
:vartype hostnames: set of bytes
:ivar hostnames: all the hostnames that server supports
please use :ref:`matches_hostname` to verify if the VirtualHost
can serve a request to a given hostname as that allows wildcard hosts
that always reply True.
:vartype trust_anchors: list of X509
:ivar trust_anchors: list of CA certificates supported for client
certificate authentication, sent in CertificateRequest
:ivar app_protocols: all the application protocols that the server supports
(for ALPN)
"""

def __init__(self):
"""Set up default configuration."""
self.keys = []
self.hostnames = set()
self.trust_anchors = []
self.app_protocols = []

def matches_hostname(self, hostname):
"""Checks if the virtual host can serve hostname"""
return hostname in self.hostnames

def validate(self):
"""Sanity check the settings"""
if not self.keys:
raise ValueError("Virtual host missing keys")
for i in self.keys:
i.validate()


class HandshakeSettings(object):
"""
This class encapsulates various parameters that can be used with
Expand Down Expand Up @@ -254,6 +328,7 @@ def _init_key_settings(self):
self.maxKeySize = 8193
self.rsaSigHashes = list(RSA_SIGNATURE_HASHES)
self.rsaSchemes = list(RSA_SCHEMES)
self.virtual_hosts = []
# DH key settings
self.eccCurves = list(CURVE_NAMES)
self.dhParams = None
Expand Down Expand Up @@ -312,6 +387,9 @@ def _sanityCheckKeySizes(other):
raise ValueError("maxKeySize too large")
if other.maxKeySize < other.minKeySize:
raise ValueError("maxKeySize smaller than minKeySize")
# check also keys of virtual hosts
for i in other.virtual_hosts:
i.validate()

@staticmethod
def _not_matching(values, sieve):
Expand Down Expand Up @@ -530,6 +608,7 @@ def _copy_extension_settings(self, other):
"""Copy values of settings related to extensions."""
other.useExtendedMasterSecret = self.useExtendedMasterSecret
other.requireExtendedMasterSecret = self.requireExtendedMasterSecret
other.useExperimentalTackExtension = self.useExperimentalTackExtension
other.sendFallbackSCSV = self.sendFallbackSCSV
other.useEncryptThenMAC = self.useEncryptThenMAC
other.usePaddingExtension = self.usePaddingExtension
Expand Down Expand Up @@ -574,6 +653,7 @@ def _copy_key_settings(self, other):
other.rsaSigHashes = self.rsaSigHashes
other.rsaSchemes = self.rsaSchemes
other.ecdsaSigHashes = self.ecdsaSigHashes
other.virtual_hosts = self.virtual_hosts
# DH key params
other.eccCurves = self.eccCurves
other.dhParams = self.dhParams
Expand Down
Loading

0 comments on commit a15122a

Please sign in to comment.