diff options
author | davidben@chromium.org <davidben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-11 07:20:26 +0000 |
---|---|---|
committer | davidben@chromium.org <davidben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-11 07:20:26 +0000 |
commit | cf7c61b30593123ce40bfdcdb013a8dbbdd3d119 (patch) | |
tree | eccf2aca6c2ac986631d38a17b78efb095c8cfc0 | |
parent | 7325452871ba58155a0896dd8b57bc86937a8fc6 (diff) | |
download | chromium_src-cf7c61b30593123ce40bfdcdb013a8dbbdd3d119.zip chromium_src-cf7c61b30593123ce40bfdcdb013a8dbbdd3d119.tar.gz chromium_src-cf7c61b30593123ce40bfdcdb013a8dbbdd3d119.tar.bz2 |
Add DHE_RSA support to tlslite.
Needed for our test server to be able to False Start.
BUG=354132
Review URL: https://codereview.chromium.org/212883008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@263169 0039d316-1c4b-4281-b951-d872f2087c98
-rwxr-xr-x | net/tools/testserver/testserver.py | 14 | ||||
-rw-r--r-- | third_party/tlslite/README.chromium | 1 | ||||
-rw-r--r-- | third_party/tlslite/patches/dhe_rsa.patch | 417 | ||||
-rw-r--r-- | third_party/tlslite/tlslite/constants.py | 40 | ||||
-rw-r--r-- | third_party/tlslite/tlslite/handshakesettings.py | 12 | ||||
-rw-r--r-- | third_party/tlslite/tlslite/messages.py | 28 | ||||
-rw-r--r-- | third_party/tlslite/tlslite/tlsconnection.py | 150 |
7 files changed, 623 insertions, 39 deletions
diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py index 92aee0d..2b0c36c 100755 --- a/net/tools/testserver/testserver.py +++ b/net/tools/testserver/testserver.py @@ -152,7 +152,8 @@ class HTTPSServer(tlslite.api.TLSSocketServerMixIn, client verification.""" def __init__(self, server_address, request_hander_class, pem_cert_and_key, - ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers, + ssl_client_auth, ssl_client_cas, + ssl_bulk_ciphers, ssl_key_exchanges, record_resume_info, tls_intolerant, signed_cert_timestamps, fallback_scsv_enabled, ocsp_response): self.cert_chain = tlslite.api.X509CertChain() @@ -182,6 +183,8 @@ class HTTPSServer(tlslite.api.TLSSocketServerMixIn, self.ssl_handshake_settings = tlslite.api.HandshakeSettings() if ssl_bulk_ciphers is not None: self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers + if ssl_key_exchanges is not None: + self.ssl_handshake_settings.keyExchangeNames = ssl_key_exchanges if record_resume_info: # If record_resume_info is true then we'll replace the session cache with @@ -1982,6 +1985,7 @@ class ServerRunner(testserver_base.TestServerRunner): self.options.ssl_client_auth, self.options.ssl_client_ca, self.options.ssl_bulk_cipher, + self.options.ssl_key_exchange, self.options.record_resume, self.options.tls_intolerant, self.options.signed_cert_timestamps_tls_ext.decode( @@ -2170,6 +2174,14 @@ class ServerRunner(testserver_base.TestServerRunner): 'algorithms will be used. This option may ' 'appear multiple times, indicating ' 'multiple algorithms should be enabled.'); + self.option_parser.add_option('--ssl-key-exchange', action='append', + help='Specify the key exchange algorithm(s)' + 'that will be accepted by the SSL server. ' + 'Valid values are "rsa", "dhe_rsa". If ' + 'omitted, all algorithms will be used. This ' + 'option may appear multiple times, ' + 'indicating multiple algorithms should be ' + 'enabled.'); self.option_parser.add_option('--file-root-url', default='/files/', help='Specify a root URL for files served.') diff --git a/third_party/tlslite/README.chromium b/third_party/tlslite/README.chromium index c88d58e..28dded0 100644 --- a/third_party/tlslite/README.chromium +++ b/third_party/tlslite/README.chromium @@ -30,3 +30,4 @@ Local Modifications: client_cipher_preferences.patch. - patches/fix_test_file.patch: Fix #! line in random test file to appease our presubmit checks. +- patches/dhe_rsa.patch: Implement DHE_RSA-based cipher suites. diff --git a/third_party/tlslite/patches/dhe_rsa.patch b/third_party/tlslite/patches/dhe_rsa.patch new file mode 100644 index 0000000..1466851 --- /dev/null +++ b/third_party/tlslite/patches/dhe_rsa.patch @@ -0,0 +1,417 @@ +diff --git a/third_party/tlslite/tlslite/constants.py b/third_party/tlslite/tlslite/constants.py +index 52c20ac..feca423 100644 +--- a/third_party/tlslite/tlslite/constants.py ++++ b/third_party/tlslite/tlslite/constants.py +@@ -143,6 +143,10 @@ class CipherSuite: + + TLS_RSA_WITH_RC4_128_MD5 = 0x0004 + ++ TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016 ++ TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 ++ TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 ++ + TLS_DH_ANON_WITH_AES_128_CBC_SHA = 0x0034 + TLS_DH_ANON_WITH_AES_256_CBC_SHA = 0x003A + +@@ -150,17 +154,20 @@ class CipherSuite: + tripleDESSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) + tripleDESSuites.append(TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) + tripleDESSuites.append(TLS_RSA_WITH_3DES_EDE_CBC_SHA) ++ tripleDESSuites.append(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) + + aes128Suites = [] + aes128Suites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) + aes128Suites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) + aes128Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA) ++ aes128Suites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA) + aes128Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) + + aes256Suites = [] + aes256Suites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA) + aes256Suites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) + aes256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA) ++ aes256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA) + aes256Suites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) + + rc4Suites = [] +@@ -178,6 +185,9 @@ class CipherSuite: + shaSuites.append(TLS_RSA_WITH_AES_128_CBC_SHA) + shaSuites.append(TLS_RSA_WITH_AES_256_CBC_SHA) + shaSuites.append(TLS_RSA_WITH_RC4_128_SHA) ++ shaSuites.append(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) ++ shaSuites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA) ++ shaSuites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA) + shaSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) + shaSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) + +@@ -188,6 +198,7 @@ class CipherSuite: + def _filterSuites(suites, settings): + macNames = settings.macNames + cipherNames = settings.cipherNames ++ keyExchangeNames = settings.keyExchangeNames + macSuites = [] + if "sha" in macNames: + macSuites += CipherSuite.shaSuites +@@ -204,7 +215,20 @@ class CipherSuite: + if "rc4" in cipherNames: + cipherSuites += CipherSuite.rc4Suites + +- return [s for s in suites if s in macSuites and s in cipherSuites] ++ keyExchangeSuites = [] ++ if "rsa" in keyExchangeNames: ++ keyExchangeSuites += CipherSuite.certSuites ++ if "dhe_rsa" in keyExchangeNames: ++ keyExchangeSuites += CipherSuite.dheCertSuites ++ if "srp_sha" in keyExchangeNames: ++ keyExchangeSuites += CipherSuite.srpSuites ++ if "srp_sha_rsa" in keyExchangeNames: ++ keyExchangeSuites += CipherSuite.srpCertSuites ++ if "dh_anon" in keyExchangeNames: ++ keyExchangeSuites += CipherSuite.anonSuites ++ ++ return [s for s in suites if s in macSuites and ++ s in cipherSuites and s in keyExchangeSuites] + + srpSuites = [] + srpSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) +@@ -236,12 +260,22 @@ class CipherSuite: + certSuites.append(TLS_RSA_WITH_AES_256_CBC_SHA) + certSuites.append(TLS_RSA_WITH_RC4_128_SHA) + certSuites.append(TLS_RSA_WITH_RC4_128_MD5) +- certAllSuites = srpCertSuites + certSuites + + @staticmethod + def getCertSuites(settings): + return CipherSuite._filterSuites(CipherSuite.certSuites, settings) + ++ dheCertSuites = [] ++ dheCertSuites.append(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) ++ dheCertSuites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA) ++ dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA) ++ ++ @staticmethod ++ def getDheCertSuites(settings): ++ return CipherSuite._filterSuites(CipherSuite.dheCertSuites, settings) ++ ++ certAllSuites = srpCertSuites + certSuites + dheCertSuites ++ + anonSuites = [] + anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) + anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) +@@ -250,6 +284,8 @@ class CipherSuite: + def getAnonSuites(settings): + return CipherSuite._filterSuites(CipherSuite.anonSuites, settings) + ++ dhAllSuites = dheCertSuites + anonSuites ++ + @staticmethod + def canonicalCipherName(ciphersuite): + "Return the canonical name of the cipher whose number is provided." +diff --git a/third_party/tlslite/tlslite/handshakesettings.py b/third_party/tlslite/tlslite/handshakesettings.py +index 7a38ee2..e0bc0e6 100644 +--- a/third_party/tlslite/tlslite/handshakesettings.py ++++ b/third_party/tlslite/tlslite/handshakesettings.py +@@ -13,7 +13,9 @@ from .utils import cipherfactory + # RC4 is preferred as faster in Python, works in SSL3, and immune to CBC + # issues such as timing attacks + CIPHER_NAMES = ["rc4", "aes256", "aes128", "3des"] +-MAC_NAMES = ["sha"] # "md5" is allowed ++MAC_NAMES = ["sha"] # Don't allow "md5" by default. ++ALL_MAC_NAMES = ["sha", "md5"] ++KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "srp_sha", "srp_sha_rsa", "dh_anon"] + CIPHER_IMPLEMENTATIONS = ["openssl", "pycrypto", "python"] + CERTIFICATE_TYPES = ["x509"] + +@@ -102,6 +104,7 @@ class HandshakeSettings(object): + self.maxKeySize = 8193 + self.cipherNames = CIPHER_NAMES + self.macNames = MAC_NAMES ++ self.keyExchangeNames = KEY_EXCHANGE_NAMES + self.cipherImplementations = CIPHER_IMPLEMENTATIONS + self.certificateTypes = CERTIFICATE_TYPES + self.minVersion = (3,0) +@@ -116,6 +119,7 @@ class HandshakeSettings(object): + other.maxKeySize = self.maxKeySize + other.cipherNames = self.cipherNames + other.macNames = self.macNames ++ other.keyExchangeNames = self.keyExchangeNames + other.cipherImplementations = self.cipherImplementations + other.certificateTypes = self.certificateTypes + other.minVersion = self.minVersion +@@ -148,6 +152,12 @@ class HandshakeSettings(object): + for s in other.cipherNames: + if s not in CIPHER_NAMES: + raise ValueError("Unknown cipher name: '%s'" % s) ++ for s in other.macNames: ++ if s not in ALL_MAC_NAMES: ++ raise ValueError("Unknown MAC name: '%s'" % s) ++ for s in other.keyExchangeNames: ++ if s not in KEY_EXCHANGE_NAMES: ++ raise ValueError("Unknown key exchange name: '%s'" % s) + for s in other.cipherImplementations: + if s not in CIPHER_IMPLEMENTATIONS: + raise ValueError("Unknown cipher implementation: '%s'" % s) +diff --git a/third_party/tlslite/tlslite/messages.py b/third_party/tlslite/tlslite/messages.py +index 532d86b..550b387 100644 +--- a/third_party/tlslite/tlslite/messages.py ++++ b/third_party/tlslite/tlslite/messages.py +@@ -533,31 +533,31 @@ class ServerKeyExchange(HandshakeMsg): + p.stopLengthCheck() + return self + +- def write(self): ++ def write_params(self): + w = Writer() + if self.cipherSuite in CipherSuite.srpAllSuites: + w.addVarSeq(numberToByteArray(self.srp_N), 1, 2) + w.addVarSeq(numberToByteArray(self.srp_g), 1, 2) + w.addVarSeq(self.srp_s, 1, 1) + w.addVarSeq(numberToByteArray(self.srp_B), 1, 2) +- if self.cipherSuite in CipherSuite.srpCertSuites: +- w.addVarSeq(self.signature, 1, 2) +- elif self.cipherSuite in CipherSuite.anonSuites: ++ elif self.cipherSuite in CipherSuite.dhAllSuites: + w.addVarSeq(numberToByteArray(self.dh_p), 1, 2) + w.addVarSeq(numberToByteArray(self.dh_g), 1, 2) + w.addVarSeq(numberToByteArray(self.dh_Ys), 1, 2) +- if self.cipherSuite in []: # TODO support for signed_params +- w.addVarSeq(self.signature, 1, 2) ++ else: ++ assert(False) ++ return w.bytes ++ ++ def write(self): ++ w = Writer() ++ w.bytes += self.write_params() ++ if self.cipherSuite in CipherSuite.certAllSuites: ++ w.addVarSeq(self.signature, 1, 2) + return self.postWrite(w) + + def hash(self, clientRandom, serverRandom): +- oldCipherSuite = self.cipherSuite +- self.cipherSuite = None +- try: +- bytes = clientRandom + serverRandom + self.write()[4:] +- return MD5(bytes) + SHA1(bytes) +- finally: +- self.cipherSuite = oldCipherSuite ++ bytes = clientRandom + serverRandom + self.write_params() ++ return MD5(bytes) + SHA1(bytes) + + class ServerHelloDone(HandshakeMsg): + def __init__(self): +@@ -607,7 +607,7 @@ class ClientKeyExchange(HandshakeMsg): + p.getFixBytes(len(p.bytes)-p.index) + else: + raise AssertionError() +- elif self.cipherSuite in CipherSuite.anonSuites: ++ elif self.cipherSuite in CipherSuite.dhAllSuites: + self.dh_Yc = bytesToNumber(p.getVarBytes(2)) + else: + raise AssertionError() +diff --git a/third_party/tlslite/tlslite/tlsconnection.py b/third_party/tlslite/tlslite/tlsconnection.py +index 20cd85b..e6f7820 100644 +--- a/third_party/tlslite/tlslite/tlsconnection.py ++++ b/third_party/tlslite/tlslite/tlsconnection.py +@@ -23,6 +23,103 @@ from .mathtls import * + from .handshakesettings import HandshakeSettings + from .utils.tackwrapper import * + ++class KeyExchange(object): ++ def __init__(self, cipherSuite, clientHello, serverHello, privateKey): ++ """ ++ Initializes the KeyExchange. privateKey is the signing private key. ++ """ ++ self.cipherSuite = cipherSuite ++ self.clientHello = clientHello ++ self.serverHello = serverHello ++ self.privateKey = privateKey ++ ++ def makeServerKeyExchange(): ++ """ ++ Returns a ServerKeyExchange object for the server's initial leg in the ++ handshake. If the key exchange method does not send ServerKeyExchange ++ (e.g. RSA), it returns None. ++ """ ++ raise NotImplementedError() ++ ++ def processClientKeyExchange(clientKeyExchange): ++ """ ++ Processes the client's ClientKeyExchange message and returns the ++ premaster secret. Raises TLSLocalAlert on error. ++ """ ++ raise NotImplementedError() ++ ++class RSAKeyExchange(KeyExchange): ++ def makeServerKeyExchange(self): ++ return None ++ ++ def processClientKeyExchange(self, clientKeyExchange): ++ premasterSecret = self.privateKey.decrypt(\ ++ clientKeyExchange.encryptedPreMasterSecret) ++ ++ # On decryption failure randomize premaster secret to avoid ++ # Bleichenbacher's "million message" attack ++ randomPreMasterSecret = getRandomBytes(48) ++ if not premasterSecret: ++ premasterSecret = randomPreMasterSecret ++ elif len(premasterSecret)!=48: ++ premasterSecret = randomPreMasterSecret ++ else: ++ versionCheck = (premasterSecret[0], premasterSecret[1]) ++ if versionCheck != self.clientHello.client_version: ++ #Tolerate buggy IE clients ++ if versionCheck != self.serverHello.server_version: ++ premasterSecret = randomPreMasterSecret ++ return premasterSecret ++ ++def _hexStringToNumber(s): ++ s = s.replace(" ", "").replace("\n", "") ++ if len(s) % 2 != 0: ++ raise ValueError("Length is not even") ++ return bytesToNumber(bytearray(s.decode("hex"))) ++ ++class DHE_RSAKeyExchange(KeyExchange): ++ # 2048-bit MODP Group (RFC 3526, Section 3) ++ dh_p = _hexStringToNumber(""" ++FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 ++29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD ++EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 ++E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED ++EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D ++C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F ++83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D ++670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B ++E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 ++DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 ++15728E5A 8AACAA68 FFFFFFFF FFFFFFFF""") ++ dh_g = 2 ++ ++ # RFC 3526, Section 8. ++ strength = 160 ++ ++ def makeServerKeyExchange(self): ++ # Per RFC 3526, Section 1, the exponent should have double the entropy ++ # of the strength of the curve. ++ self.dh_Xs = bytesToNumber(getRandomBytes(self.strength * 2 / 8)) ++ dh_Ys = powMod(self.dh_g, self.dh_Xs, self.dh_p) ++ ++ serverKeyExchange = ServerKeyExchange(self.cipherSuite) ++ serverKeyExchange.createDH(self.dh_p, self.dh_g, dh_Ys) ++ serverKeyExchange.signature = self.privateKey.sign( ++ serverKeyExchange.hash(self.clientHello.random, ++ self.serverHello.random)) ++ return serverKeyExchange ++ ++ def processClientKeyExchange(self, clientKeyExchange): ++ dh_Yc = clientKeyExchange.dh_Yc ++ ++ # First half of RFC 2631, Section 2.1.5. Validate the client's public ++ # key. ++ if not 2 <= dh_Yc <= self.dh_p - 1: ++ raise TLSLocalAlert(AlertDescription.illegal_parameter, ++ "Invalid dh_Yc value") ++ ++ S = powMod(dh_Yc, self.dh_Xs, self.dh_p) ++ return numberToByteArray(S) + + class TLSConnection(TLSRecordLayer): + """ +@@ -500,6 +597,8 @@ class TLSConnection(TLSRecordLayer): + cipherSuites += CipherSuite.getSrpAllSuites(settings) + elif certParams: + cipherSuites += CipherSuite.getCertSuites(settings) ++ # TODO: Client DHE_RSA not supported. ++ # cipherSuites += CipherSuite.getDheCertSuites(settings) + elif anonParams: + cipherSuites += CipherSuite.getAnonSuites(settings) + else: +@@ -1204,10 +1303,23 @@ class TLSConnection(TLSRecordLayer): + else: break + premasterSecret = result + +- # Perform the RSA key exchange +- elif cipherSuite in CipherSuite.certSuites: ++ # Perform the RSA or DHE_RSA key exchange ++ elif (cipherSuite in CipherSuite.certSuites or ++ cipherSuite in CipherSuite.dheCertSuites): ++ if cipherSuite in CipherSuite.certSuites: ++ keyExchange = RSAKeyExchange(cipherSuite, ++ clientHello, ++ serverHello, ++ privateKey) ++ elif cipherSuite in CipherSuite.dheCertSuites: ++ keyExchange = DHE_RSAKeyExchange(cipherSuite, ++ clientHello, ++ serverHello, ++ privateKey) ++ else: ++ assert(False) + for result in self._serverCertKeyExchange(clientHello, serverHello, +- certChain, privateKey, ++ certChain, keyExchange, + reqCert, reqCAs, cipherSuite, + settings, ocspResponse): + if result in (0,1): yield result +@@ -1268,6 +1380,7 @@ class TLSConnection(TLSRecordLayer): + cipherSuites += CipherSuite.getSrpSuites(settings) + elif certChain: + cipherSuites += CipherSuite.getCertSuites(settings) ++ cipherSuites += CipherSuite.getDheCertSuites(settings) + elif anon: + cipherSuites += CipherSuite.getAnonSuites(settings) + else: +@@ -1483,11 +1596,11 @@ class TLSConnection(TLSRecordLayer): + + + def _serverCertKeyExchange(self, clientHello, serverHello, +- serverCertChain, privateKey, ++ serverCertChain, keyExchange, + reqCert, reqCAs, cipherSuite, + settings, ocspResponse): +- #Send ServerHello, Certificate[, CertificateRequest], +- #ServerHelloDone ++ #Send ServerHello, Certificate[, ServerKeyExchange] ++ #[, CertificateRequest], ServerHelloDone + msgs = [] + + # If we verify a client cert chain, return it +@@ -1497,6 +1610,9 @@ class TLSConnection(TLSRecordLayer): + msgs.append(Certificate(CertificateType.x509).create(serverCertChain)) + if serverHello.status_request: + msgs.append(CertificateStatus().create(ocspResponse)) ++ serverKeyExchange = keyExchange.makeServerKeyExchange() ++ if serverKeyExchange is not None: ++ msgs.append(serverKeyExchange) + if reqCert and reqCAs: + msgs.append(CertificateRequest().create(\ + [ClientCertificateType.rsa_sign], reqCAs)) +@@ -1555,21 +1671,13 @@ class TLSConnection(TLSRecordLayer): + else: break + clientKeyExchange = result + +- #Decrypt ClientKeyExchange +- premasterSecret = privateKey.decrypt(\ +- clientKeyExchange.encryptedPreMasterSecret) +- +- # On decryption failure randomize premaster secret to avoid +- # Bleichenbacher's "million message" attack +- randomPreMasterSecret = getRandomBytes(48) +- versionCheck = (premasterSecret[0], premasterSecret[1]) +- if not premasterSecret: +- premasterSecret = randomPreMasterSecret +- elif len(premasterSecret)!=48: +- premasterSecret = randomPreMasterSecret +- elif versionCheck != clientHello.client_version: +- if versionCheck != self.version: #Tolerate buggy IE clients +- premasterSecret = randomPreMasterSecret ++ #Process ClientKeyExchange ++ try: ++ premasterSecret = \ ++ keyExchange.processClientKeyExchange(clientKeyExchange) ++ except TLSLocalAlert, alert: ++ for result in self._sendError(alert.description, alert.message): ++ yield result + + #Get and check CertificateVerify, if relevant + if clientCertChain: diff --git a/third_party/tlslite/tlslite/constants.py b/third_party/tlslite/tlslite/constants.py index 52c20ac..feca423 100644 --- a/third_party/tlslite/tlslite/constants.py +++ b/third_party/tlslite/tlslite/constants.py @@ -143,6 +143,10 @@ class CipherSuite: TLS_RSA_WITH_RC4_128_MD5 = 0x0004 + TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 + TLS_DH_ANON_WITH_AES_128_CBC_SHA = 0x0034 TLS_DH_ANON_WITH_AES_256_CBC_SHA = 0x003A @@ -150,17 +154,20 @@ class CipherSuite: tripleDESSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) tripleDESSuites.append(TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) tripleDESSuites.append(TLS_RSA_WITH_3DES_EDE_CBC_SHA) + tripleDESSuites.append(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) aes128Suites = [] aes128Suites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA) + aes128Suites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) aes256Suites = [] aes256Suites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA) + aes256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) rc4Suites = [] @@ -178,6 +185,9 @@ class CipherSuite: shaSuites.append(TLS_RSA_WITH_AES_128_CBC_SHA) shaSuites.append(TLS_RSA_WITH_AES_256_CBC_SHA) shaSuites.append(TLS_RSA_WITH_RC4_128_SHA) + shaSuites.append(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) + shaSuites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA) + shaSuites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA) shaSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) shaSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) @@ -188,6 +198,7 @@ class CipherSuite: def _filterSuites(suites, settings): macNames = settings.macNames cipherNames = settings.cipherNames + keyExchangeNames = settings.keyExchangeNames macSuites = [] if "sha" in macNames: macSuites += CipherSuite.shaSuites @@ -204,7 +215,20 @@ class CipherSuite: if "rc4" in cipherNames: cipherSuites += CipherSuite.rc4Suites - return [s for s in suites if s in macSuites and s in cipherSuites] + keyExchangeSuites = [] + if "rsa" in keyExchangeNames: + keyExchangeSuites += CipherSuite.certSuites + if "dhe_rsa" in keyExchangeNames: + keyExchangeSuites += CipherSuite.dheCertSuites + if "srp_sha" in keyExchangeNames: + keyExchangeSuites += CipherSuite.srpSuites + if "srp_sha_rsa" in keyExchangeNames: + keyExchangeSuites += CipherSuite.srpCertSuites + if "dh_anon" in keyExchangeNames: + keyExchangeSuites += CipherSuite.anonSuites + + return [s for s in suites if s in macSuites and + s in cipherSuites and s in keyExchangeSuites] srpSuites = [] srpSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) @@ -236,12 +260,22 @@ class CipherSuite: certSuites.append(TLS_RSA_WITH_AES_256_CBC_SHA) certSuites.append(TLS_RSA_WITH_RC4_128_SHA) certSuites.append(TLS_RSA_WITH_RC4_128_MD5) - certAllSuites = srpCertSuites + certSuites @staticmethod def getCertSuites(settings): return CipherSuite._filterSuites(CipherSuite.certSuites, settings) + dheCertSuites = [] + dheCertSuites.append(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) + dheCertSuites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA) + dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA) + + @staticmethod + def getDheCertSuites(settings): + return CipherSuite._filterSuites(CipherSuite.dheCertSuites, settings) + + certAllSuites = srpCertSuites + certSuites + dheCertSuites + anonSuites = [] anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) @@ -250,6 +284,8 @@ class CipherSuite: def getAnonSuites(settings): return CipherSuite._filterSuites(CipherSuite.anonSuites, settings) + dhAllSuites = dheCertSuites + anonSuites + @staticmethod def canonicalCipherName(ciphersuite): "Return the canonical name of the cipher whose number is provided." diff --git a/third_party/tlslite/tlslite/handshakesettings.py b/third_party/tlslite/tlslite/handshakesettings.py index 7a38ee2..e0bc0e6 100644 --- a/third_party/tlslite/tlslite/handshakesettings.py +++ b/third_party/tlslite/tlslite/handshakesettings.py @@ -13,7 +13,9 @@ from .utils import cipherfactory # RC4 is preferred as faster in Python, works in SSL3, and immune to CBC # issues such as timing attacks CIPHER_NAMES = ["rc4", "aes256", "aes128", "3des"] -MAC_NAMES = ["sha"] # "md5" is allowed +MAC_NAMES = ["sha"] # Don't allow "md5" by default. +ALL_MAC_NAMES = ["sha", "md5"] +KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "srp_sha", "srp_sha_rsa", "dh_anon"] CIPHER_IMPLEMENTATIONS = ["openssl", "pycrypto", "python"] CERTIFICATE_TYPES = ["x509"] @@ -102,6 +104,7 @@ class HandshakeSettings(object): self.maxKeySize = 8193 self.cipherNames = CIPHER_NAMES self.macNames = MAC_NAMES + self.keyExchangeNames = KEY_EXCHANGE_NAMES self.cipherImplementations = CIPHER_IMPLEMENTATIONS self.certificateTypes = CERTIFICATE_TYPES self.minVersion = (3,0) @@ -116,6 +119,7 @@ class HandshakeSettings(object): other.maxKeySize = self.maxKeySize other.cipherNames = self.cipherNames other.macNames = self.macNames + other.keyExchangeNames = self.keyExchangeNames other.cipherImplementations = self.cipherImplementations other.certificateTypes = self.certificateTypes other.minVersion = self.minVersion @@ -148,6 +152,12 @@ class HandshakeSettings(object): for s in other.cipherNames: if s not in CIPHER_NAMES: raise ValueError("Unknown cipher name: '%s'" % s) + for s in other.macNames: + if s not in ALL_MAC_NAMES: + raise ValueError("Unknown MAC name: '%s'" % s) + for s in other.keyExchangeNames: + if s not in KEY_EXCHANGE_NAMES: + raise ValueError("Unknown key exchange name: '%s'" % s) for s in other.cipherImplementations: if s not in CIPHER_IMPLEMENTATIONS: raise ValueError("Unknown cipher implementation: '%s'" % s) diff --git a/third_party/tlslite/tlslite/messages.py b/third_party/tlslite/tlslite/messages.py index 532d86b..550b387 100644 --- a/third_party/tlslite/tlslite/messages.py +++ b/third_party/tlslite/tlslite/messages.py @@ -533,31 +533,31 @@ class ServerKeyExchange(HandshakeMsg): p.stopLengthCheck() return self - def write(self): + def write_params(self): w = Writer() if self.cipherSuite in CipherSuite.srpAllSuites: w.addVarSeq(numberToByteArray(self.srp_N), 1, 2) w.addVarSeq(numberToByteArray(self.srp_g), 1, 2) w.addVarSeq(self.srp_s, 1, 1) w.addVarSeq(numberToByteArray(self.srp_B), 1, 2) - if self.cipherSuite in CipherSuite.srpCertSuites: - w.addVarSeq(self.signature, 1, 2) - elif self.cipherSuite in CipherSuite.anonSuites: + elif self.cipherSuite in CipherSuite.dhAllSuites: w.addVarSeq(numberToByteArray(self.dh_p), 1, 2) w.addVarSeq(numberToByteArray(self.dh_g), 1, 2) w.addVarSeq(numberToByteArray(self.dh_Ys), 1, 2) - if self.cipherSuite in []: # TODO support for signed_params - w.addVarSeq(self.signature, 1, 2) + else: + assert(False) + return w.bytes + + def write(self): + w = Writer() + w.bytes += self.write_params() + if self.cipherSuite in CipherSuite.certAllSuites: + w.addVarSeq(self.signature, 1, 2) return self.postWrite(w) def hash(self, clientRandom, serverRandom): - oldCipherSuite = self.cipherSuite - self.cipherSuite = None - try: - bytes = clientRandom + serverRandom + self.write()[4:] - return MD5(bytes) + SHA1(bytes) - finally: - self.cipherSuite = oldCipherSuite + bytes = clientRandom + serverRandom + self.write_params() + return MD5(bytes) + SHA1(bytes) class ServerHelloDone(HandshakeMsg): def __init__(self): @@ -607,7 +607,7 @@ class ClientKeyExchange(HandshakeMsg): p.getFixBytes(len(p.bytes)-p.index) else: raise AssertionError() - elif self.cipherSuite in CipherSuite.anonSuites: + elif self.cipherSuite in CipherSuite.dhAllSuites: self.dh_Yc = bytesToNumber(p.getVarBytes(2)) else: raise AssertionError() diff --git a/third_party/tlslite/tlslite/tlsconnection.py b/third_party/tlslite/tlslite/tlsconnection.py index 20cd85b..e6f7820 100644 --- a/third_party/tlslite/tlslite/tlsconnection.py +++ b/third_party/tlslite/tlslite/tlsconnection.py @@ -23,6 +23,103 @@ from .mathtls import * from .handshakesettings import HandshakeSettings from .utils.tackwrapper import * +class KeyExchange(object): + def __init__(self, cipherSuite, clientHello, serverHello, privateKey): + """ + Initializes the KeyExchange. privateKey is the signing private key. + """ + self.cipherSuite = cipherSuite + self.clientHello = clientHello + self.serverHello = serverHello + self.privateKey = privateKey + + def makeServerKeyExchange(): + """ + Returns a ServerKeyExchange object for the server's initial leg in the + handshake. If the key exchange method does not send ServerKeyExchange + (e.g. RSA), it returns None. + """ + raise NotImplementedError() + + def processClientKeyExchange(clientKeyExchange): + """ + Processes the client's ClientKeyExchange message and returns the + premaster secret. Raises TLSLocalAlert on error. + """ + raise NotImplementedError() + +class RSAKeyExchange(KeyExchange): + def makeServerKeyExchange(self): + return None + + def processClientKeyExchange(self, clientKeyExchange): + premasterSecret = self.privateKey.decrypt(\ + clientKeyExchange.encryptedPreMasterSecret) + + # On decryption failure randomize premaster secret to avoid + # Bleichenbacher's "million message" attack + randomPreMasterSecret = getRandomBytes(48) + if not premasterSecret: + premasterSecret = randomPreMasterSecret + elif len(premasterSecret)!=48: + premasterSecret = randomPreMasterSecret + else: + versionCheck = (premasterSecret[0], premasterSecret[1]) + if versionCheck != self.clientHello.client_version: + #Tolerate buggy IE clients + if versionCheck != self.serverHello.server_version: + premasterSecret = randomPreMasterSecret + return premasterSecret + +def _hexStringToNumber(s): + s = s.replace(" ", "").replace("\n", "") + if len(s) % 2 != 0: + raise ValueError("Length is not even") + return bytesToNumber(bytearray(s.decode("hex"))) + +class DHE_RSAKeyExchange(KeyExchange): + # 2048-bit MODP Group (RFC 3526, Section 3) + dh_p = _hexStringToNumber(""" +FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 +29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD +EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 +E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED +EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D +C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F +83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D +670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B +E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 +DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510 +15728E5A 8AACAA68 FFFFFFFF FFFFFFFF""") + dh_g = 2 + + # RFC 3526, Section 8. + strength = 160 + + def makeServerKeyExchange(self): + # Per RFC 3526, Section 1, the exponent should have double the entropy + # of the strength of the curve. + self.dh_Xs = bytesToNumber(getRandomBytes(self.strength * 2 / 8)) + dh_Ys = powMod(self.dh_g, self.dh_Xs, self.dh_p) + + serverKeyExchange = ServerKeyExchange(self.cipherSuite) + serverKeyExchange.createDH(self.dh_p, self.dh_g, dh_Ys) + serverKeyExchange.signature = self.privateKey.sign( + serverKeyExchange.hash(self.clientHello.random, + self.serverHello.random)) + return serverKeyExchange + + def processClientKeyExchange(self, clientKeyExchange): + dh_Yc = clientKeyExchange.dh_Yc + + # First half of RFC 2631, Section 2.1.5. Validate the client's public + # key. + if not 2 <= dh_Yc <= self.dh_p - 1: + raise TLSLocalAlert(AlertDescription.illegal_parameter, + "Invalid dh_Yc value") + + S = powMod(dh_Yc, self.dh_Xs, self.dh_p) + return numberToByteArray(S) class TLSConnection(TLSRecordLayer): """ @@ -500,6 +597,8 @@ class TLSConnection(TLSRecordLayer): cipherSuites += CipherSuite.getSrpAllSuites(settings) elif certParams: cipherSuites += CipherSuite.getCertSuites(settings) + # TODO: Client DHE_RSA not supported. + # cipherSuites += CipherSuite.getDheCertSuites(settings) elif anonParams: cipherSuites += CipherSuite.getAnonSuites(settings) else: @@ -1204,10 +1303,23 @@ class TLSConnection(TLSRecordLayer): else: break premasterSecret = result - # Perform the RSA key exchange - elif cipherSuite in CipherSuite.certSuites: + # Perform the RSA or DHE_RSA key exchange + elif (cipherSuite in CipherSuite.certSuites or + cipherSuite in CipherSuite.dheCertSuites): + if cipherSuite in CipherSuite.certSuites: + keyExchange = RSAKeyExchange(cipherSuite, + clientHello, + serverHello, + privateKey) + elif cipherSuite in CipherSuite.dheCertSuites: + keyExchange = DHE_RSAKeyExchange(cipherSuite, + clientHello, + serverHello, + privateKey) + else: + assert(False) for result in self._serverCertKeyExchange(clientHello, serverHello, - certChain, privateKey, + certChain, keyExchange, reqCert, reqCAs, cipherSuite, settings, ocspResponse): if result in (0,1): yield result @@ -1268,6 +1380,7 @@ class TLSConnection(TLSRecordLayer): cipherSuites += CipherSuite.getSrpSuites(settings) elif certChain: cipherSuites += CipherSuite.getCertSuites(settings) + cipherSuites += CipherSuite.getDheCertSuites(settings) elif anon: cipherSuites += CipherSuite.getAnonSuites(settings) else: @@ -1483,11 +1596,11 @@ class TLSConnection(TLSRecordLayer): def _serverCertKeyExchange(self, clientHello, serverHello, - serverCertChain, privateKey, + serverCertChain, keyExchange, reqCert, reqCAs, cipherSuite, settings, ocspResponse): - #Send ServerHello, Certificate[, CertificateRequest], - #ServerHelloDone + #Send ServerHello, Certificate[, ServerKeyExchange] + #[, CertificateRequest], ServerHelloDone msgs = [] # If we verify a client cert chain, return it @@ -1497,6 +1610,9 @@ class TLSConnection(TLSRecordLayer): msgs.append(Certificate(CertificateType.x509).create(serverCertChain)) if serverHello.status_request: msgs.append(CertificateStatus().create(ocspResponse)) + serverKeyExchange = keyExchange.makeServerKeyExchange() + if serverKeyExchange is not None: + msgs.append(serverKeyExchange) if reqCert and reqCAs: msgs.append(CertificateRequest().create(\ [ClientCertificateType.rsa_sign], reqCAs)) @@ -1555,21 +1671,13 @@ class TLSConnection(TLSRecordLayer): else: break clientKeyExchange = result - #Decrypt ClientKeyExchange - premasterSecret = privateKey.decrypt(\ - clientKeyExchange.encryptedPreMasterSecret) - - # On decryption failure randomize premaster secret to avoid - # Bleichenbacher's "million message" attack - randomPreMasterSecret = getRandomBytes(48) - versionCheck = (premasterSecret[0], premasterSecret[1]) - if not premasterSecret: - premasterSecret = randomPreMasterSecret - elif len(premasterSecret)!=48: - premasterSecret = randomPreMasterSecret - elif versionCheck != clientHello.client_version: - if versionCheck != self.version: #Tolerate buggy IE clients - premasterSecret = randomPreMasterSecret + #Process ClientKeyExchange + try: + premasterSecret = \ + keyExchange.processClientKeyExchange(clientKeyExchange) + except TLSLocalAlert, alert: + for result in self._sendError(alert.description, alert.message): + yield result #Get and check CertificateVerify, if relevant if clientCertChain: |