Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ECDSA support #196

Merged
merged 11 commits into from
Nov 13, 2019
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