From a0b5b19921b28d98789f96a9383e3aea16bcf0f4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 25 May 2015 18:09:24 +0200 Subject: [PATCH 001/574] travis.yml: follow redirects, debugging info logging add debug information printing to .travis.yml make curl follow redirects make curl print error messages even in silent mode make curl options more explicit --- .travis.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 08270522..89d586e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,12 +43,21 @@ matrix: env: PYCRYPTO=true GMPY=true before_install: + - | + echo -e "TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST\n" \ + "TRAVIS_REPO_SLUG=$TRAVIS_REPO_SLUG\n" \ + "TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST\n" \ + "TRAVIS_COMMIT=$TRAVIS_COMMIT\n" \ + "TRAVIS_PYTHON_VERSION=$TRAVIS_PYTHON_VERSION" - | # workaround https://github.com/travis-ci/travis-ci/issues/2666 if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - PR_FIRST=$(curl -s https://github.com/${TRAVIS_REPO_SLUG}/pull/${TRAVIS_PULL_REQUEST}.patch | head -1 | grep -o -E '\b[0-9a-f]{40}\b' | tr -d '\n') + URL="https://github.com/${TRAVIS_REPO_SLUG}/pull/${TRAVIS_PULL_REQUEST}.patch" + # `--location` makes curl follow redirects + PR_FIRST=$(curl --silent --show-error --location $URL | head -1 | grep -o -E '\b[0-9a-f]{40}\b' | tr -d '\n') TRAVIS_COMMIT_RANGE=$PR_FIRST^..$TRAVIS_COMMIT fi + - echo "TRAVIS_COMMIT_RANGE=$TRAVIS_COMMIT_RANGE" - git fetch origin master:refs/remotes/origin/master install: From ba15221248ca5305c692c768cfeae3c8a73a71c3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 17:37:26 +0200 Subject: [PATCH 002/574] mark the repository as a fork of TLS Lite --- README | 95 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/README b/README index fb7c6d17..a8d04f20 100644 --- a/README +++ b/README @@ -1,7 +1,7 @@ -tlslite version 0.4.8 Nov 12 2014 -Trevor Perrin -http://trevp.net/tlslite/ +tlslite-ng version 0.5.0-alpha1 2015-05-28 +Hubert Kario +https://github.com/tomato42/tlslite-ng/ ============================================================================ @@ -12,36 +12,37 @@ Table of Contents 3 Installation 4 Getting Started with the Command-Line Tools 5 Getting Started with the Library -6 Using TLS Lite with httplib -7 Using TLS Lite with poplib or imaplib -8 Using TLS Lite with smtplib -9 Using TLS Lite with SocketServer -10 Using TLS Lite with asyncore +6 Using tlslite-ng with httplib +7 Using tlslite-ng with poplib or imaplib +8 Using tlslite-ng with smtplib +9 Using tlslite-ng with SocketServer +10 Using tlslite-ng with asyncore 11 SECURITY CONSIDERATIONS 12 History -1 Introduction -=============== -TLS Lite is an open source python library that implements SSL and TLS. TLS -Lite supports RSA and SRP ciphersuites. TLS Lite is pure python, however it -can use other libraries for faster crypto operations. TLS Lite integrates with -several stdlib neworking libraries. +1 Introduction +=============== +tlslite-ng is an open source python library that implements SSL and TLS. +tlslite-ng supports RSA and SRP ciphersuites. tlslite-ng is pure python, however +it can use other libraries for faster crypto operations. tlslite-ng integrates +with several stdlib neworking libraries. API documentation is available in the 'docs' directory. -If you have questions or feedback, feel free to contact me. For discussing -improvements to tlslite, also see 'tlslite-dev@googlegroups.com'. +If you have questions or feedback, feel free to contact me. Issues and pull +requests can also be submitted through github issue tracking system. +tlslite-ng aims to be a drop in replacement for the original TLS Lite. 2 Licenses/Acknowledgements ============================ -TLS Lite is written (mostly) by Trevor Perrin. It includes code from Bram -Cohen, Google, Kees Bos, Sam Rushing, Dimitris Moraitis, Marcelo Fernandez, -Martin von Loewis, Dave Baggett, Yngve N. Pettersen (ported by Paul -Sokolovsky), and Mirko Dziadzka. +tlslite-ng is a fork of TLS Lite. TLS Lite was written (mostly) by Trevor +Perrin. It includes code from Bram Cohen, Google, Kees Bos, Sam Rushing, +Dimitris Moraitis, Marcelo Fernandez, Martin von Loewis, Dave Baggett, Yngve +N. Pettersen (ported by Paul Sokolovsky), and Mirko Dziadzka. -All code in TLS Lite has either been dedicated to the public domain by its +Original code in TLS Lite has either been dedicated to the public domain by its authors, or placed under a BSD-style license. See the LICENSE file for details. @@ -68,17 +69,15 @@ Options: Run 'python setup.py install' Test the Installation: - - From the distribution's ./tests subdirectory, run: - ./tlstest.py server localhost:4443 . - - While the test server is waiting, run: - ./tlstest.py client localhost:4443 . + - From the distribution's directory, run: + make test - If both say "Test succeeded" at the end, you're ready to go. + If it says "Test succeeded" at the end, you're ready to go. 4 Getting Started with the Command-Line Tools ============================================== -tlslite installs two command-line scripts: 'tlsdb.py' and 'tls.py'. +tlslite-ng installs two command-line scripts: 'tlsdb.py' and 'tls.py'. 'tls.py' lets you run test clients and servers. It can be used for testing other TLS implementations, or as example code. Note that 'tls.py server' runs @@ -137,7 +136,7 @@ Whether you're writing a client or server, there are six steps: 5) Use the TLSConnection to exchange data. 6) Call close() on the TLSConnection when you're done. -TLS Lite also integrates with several stdlib python libraries. See the +tlslite-ng also integrates with several stdlib python libraries. See the sections following this one for details. 5 Step 1 - create a socket @@ -368,9 +367,9 @@ close_notify alert that close() generates, so the connection will hang if closeSocket is set to True.) -6 Using TLS Lite with httplib -============================== -TLS Lite comes with an HTTPTLSConnection class that extends httplib to work +6 Using tlslite-ng with httplib +=============================== +tlslite-ng comes with an HTTPTLSConnection class that extends httplib to work over SSL/TLS connections. Depending on how you construct it, it will do different types of authentication. @@ -391,9 +390,9 @@ different types of authentication. [...] -7 Using TLS Lite with poplib or imaplib -======================================== -TLS Lite comes with POP3_TLS and IMAP4_TLS classes that extend poplib and +7 Using tlslite-ng with poplib or imaplib +========================================= +tlslite-ng comes with POP3_TLS and IMAP4_TLS classes that extend poplib and imaplib to work over SSL/TLS connections. These classes can be constructed with the same parameters as HTTPTLSConnection (see previous section), and behave similarly. @@ -411,9 +410,9 @@ behave similarly. [...] -8 Using TLS Lite with smtplib -============================== -TLS Lite comes with an SMTP_TLS class that extends smtplib to work +8 Using tlslite-ng with smtplib +=============================== +tlslite comes with an SMTP_TLS class that extends smtplib to work over SSL/TLS connections. This class accepts the same parameters as HTTPTLSConnection (see previous section), and behaves similarly. Depending on how you call starttls(), it will do different types of authentication. @@ -426,10 +425,10 @@ on how you call starttls(), it will do different types of authentication. [...] -9 Using TLS Lite with SocketServer +9 Using tlslite-ng with SocketServer ==================================== -You can use TLS Lite to implement servers using Python's SocketServer -framework. TLS Lite comes with a TLSSocketServerMixIn class. You can combine +You can use tlslite-ng to implement servers using Python's SocketServer +framework. tlslite-ng comes with a TLSSocketServerMixIn class. You can combine this with a TCPServer such as HTTPServer. To combine them, define a new class that inherits from both of them (with the mix-in first). Then implement the handshake() method, doing some sort of server handshake on the connection @@ -437,25 +436,25 @@ argument. If the handshake method returns True, the RequestHandler will be triggered. See the tests/httpsserver.py example. -10 Using TLS Lite with asyncore -================================ -TLS Lite can be used with subclasses of asyncore.dispatcher. See the comments +10 Using tlslite-ng with asyncore +================================= +tlslite-ng can be used with subclasses of asyncore.dispatcher. See the comments in TLSAsyncDispatcherMixIn.py for details. This is still experimental, and may not work with all asyncore.dispatcher subclasses. 11 Security Considerations =========================== -TLS Lite is beta-quality code. It hasn't received much security analysis. Use +tlslite-ng is beta-quality code. It hasn't received much security analysis. Use at your own risk. -TLS Lite does NOT verify certificates by default. +tlslite-ng does NOT verify certificates by default. -TLS Lite's pure-python ciphers are probably vulnerable to timing attacks. +tlslite-ng's pure-python ciphers are probably vulnerable to timing attacks. -TLS Lite is probably vulnerable to the "Lucky 13" timing attack if AES or 3DES +tlslite-ng is probably vulnerable to the "Lucky 13" timing attack if AES or 3DES are used, or the weak cipher RC4 otherwise. This unhappy situation will remain -until TLS Lite implements authenticated-encryption ciphersuites (like GCM), or +until tlslite-ng implements authenticated-encryption ciphersuites (like GCM), or RFC 7366. From a21ce5ac86f7168ae10b868f362e468fc6fb1750 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 18:21:41 +0200 Subject: [PATCH 003/574] don't run travis jobs on development branches --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 89d586e2..70dacdc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,10 @@ language: python +# whitelist branches on which tests will be run +branches: + only: + - master + addons: apt_packages: # needed for M2Crypto From f519197fd7a5ef7bafeda75e4c28945c378cfca5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 18:09:08 +0200 Subject: [PATCH 004/574] "markdownify" README --- README => README.md | 255 +++++++++++++++++++++++++++++--------------- 1 file changed, 169 insertions(+), 86 deletions(-) rename README => README.md (85%) diff --git a/README b/README.md similarity index 85% rename from README rename to README.md index a8d04f20..9e5438b0 100644 --- a/README +++ b/README.md @@ -1,28 +1,30 @@ - +``` tlslite-ng version 0.5.0-alpha1 2015-05-28 Hubert Kario https://github.com/tomato42/tlslite-ng/ -============================================================================ +``` Table of Contents ================== -1 Introduction -2 License/Acknowledgements -3 Installation -4 Getting Started with the Command-Line Tools -5 Getting Started with the Library -6 Using tlslite-ng with httplib -7 Using tlslite-ng with poplib or imaplib -8 Using tlslite-ng with smtplib -9 Using tlslite-ng with SocketServer -10 Using tlslite-ng with asyncore -11 SECURITY CONSIDERATIONS -12 History + +1. Introduction +2. License/Acknowledgements +3. Installation +4. Getting Started with the Command-Line Tools +5. Getting Started with the Library +6. Using tlslite-ng with httplib +7. Using tlslite-ng with poplib or imaplib +8. Using tlslite-ng with smtplib +9. Using tlslite-ng with SocketServer +10. Using tlslite-ng with asyncore +11. SECURITY CONSIDERATIONS +12. History 1 Introduction =============== + tlslite-ng is an open source python library that implements SSL and TLS. tlslite-ng supports RSA and SRP ciphersuites. tlslite-ng is pure python, however it can use other libraries for faster crypto operations. tlslite-ng integrates @@ -37,6 +39,7 @@ tlslite-ng aims to be a drop in replacement for the original TLS Lite. 2 Licenses/Acknowledgements ============================ + tlslite-ng is a fork of TLS Lite. TLS Lite was written (mostly) by Trevor Perrin. It includes code from Bram Cohen, Google, Kees Bos, Sam Rushing, Dimitris Moraitis, Marcelo Fernandez, Martin von Loewis, Dave Baggett, Yngve @@ -50,91 +53,107 @@ Thanks to Edward Loper for Epydoc, which generated the API docs. 3 Installation =============== + Requirements: - Python 2.6 or higher is required. Python 3 is supported. + + * Python 2.6 or higher is required. + * Python 3 is supported. Options: - - If you have the M2Crypto interface to OpenSSL, this will be used for fast + + * If you have the M2Crypto interface to OpenSSL, this will be used for fast RSA operations and fast ciphers. - - If you have pycrypto this will be used for fast RSA operations and fast + * If you have pycrypto this will be used for fast RSA operations and fast ciphers. - - If you have the GMPY interface to GMP, this will be used for fast RSA and + * If you have the GMPY interface to GMP, this will be used for fast RSA and SRP operations. - - These modules don't need to be present at installation - you can install + * These modules don't need to be present at installation - you can install them any time. Run 'python setup.py install' -Test the Installation: - - From the distribution's directory, run: - make test - - If it says "Test succeeded" at the end, you're ready to go. +Test the Installation + * From the distribution's directory, run: + ``` + make test + ``` + * If it says "Test succeeded" at the end, you're ready to go. 4 Getting Started with the Command-Line Tools ============================================== -tlslite-ng installs two command-line scripts: 'tlsdb.py' and 'tls.py'. +tlslite-ng installs two command-line scripts: `tlsdb.py` and `tls.py`. -'tls.py' lets you run test clients and servers. It can be used for testing -other TLS implementations, or as example code. Note that 'tls.py server' runs +`tls.py` lets you run test clients and servers. It can be used for testing +other TLS implementations, or as example code. Note that `tls.py server` runs an HTTPS server which will serve files rooted at the current directory by default, so be careful. -'tlsdb.py' lets you manage SRP verifier databases. These databases are used by +`tlsdb.py` lets you manage SRP verifier databases. These databases are used by a TLS server when authenticating clients with SRP. X.509 ------ To run an X.509 server, go to the ./tests directory and do: - tls.py server -k serverX509Key.pem -c serverX509Cert.pem localhost:4443 +``` +tls.py server -k serverX509Key.pem -c serverX509Cert.pem localhost:4443 +``` Try connecting to the server with a web browser, or with: - tls.py client localhost:4443 +``` +tls.py client localhost:4443 +``` X.509 with TACK ---------------- To run an X.509 server using a TACK, install TACKpy, then run the same server command as above with added arguments: - ... -t TACK1.pem localhost:4443 +``` +... -t TACK1.pem localhost:4443 +``` SRP ---- To run an SRP server, try something like: - tlsdb.py createsrp verifierDB - tlsdb.py add verifierDB alice abra123cadabra 1024 - tlsdb.py add verifierDB bob swordfish 2048 +``` +tlsdb.py createsrp verifierDB +tlsdb.py add verifierDB alice abra123cadabra 1024 +tlsdb.py add verifierDB bob swordfish 2048 - tls.py server -v verifierDB localhost:4443 +tls.py server -v verifierDB localhost:4443 +``` Then try connecting to the server with: - tls.py client localhost:4443 alice abra123cadabra +``` +tls.py client localhost:4443 alice abra123cadabra +``` HTTPS ------ -To run an HTTPS server with less typing, run ./tests/httpsserver.sh. +To run an HTTPS server with less typing, run `./tests/httpsserver.sh`. -To run an HTTPS client, run ./tests/httpsclient.py. +To run an HTTPS client, run `./tests/httpsclient.py`. 5 Getting Started with the Library =================================== + Whether you're writing a client or server, there are six steps: -1) Create a socket and connect it to the other party. -2) Construct a TLSConnection instance with the socket. -3) Call a handshake function on TLSConnection to perform the TLS handshake. -4) Check the results to make sure you're talking to the right party. -5) Use the TLSConnection to exchange data. -6) Call close() on the TLSConnection when you're done. +1. Create a socket and connect it to the other party. +2. Construct a TLSConnection instance with the socket. +3. Call a handshake function on TLSConnection to perform the TLS handshake. +4. Check the results to make sure you're talking to the right party. +5. Use the TLSConnection to exchange data. +6. Call close() on the TLSConnection when you're done. tlslite-ng also integrates with several stdlib python libraries. See the sections following this one for details. @@ -143,30 +162,40 @@ sections following this one for details. --------------------------- Below demonstrates a socket connection to Amazon's secure site. +``` from socket import * sock = socket(AF_INET, SOCK_STREAM) sock.connect( ("www.amazon.com", 443) ) +``` 5 Step 2 - construct a TLSConnection ------------------------------------- You can import tlslite objects individually, such as: +``` from tlslite import TLSConnection +``` Or import the most useful objects through: +``` from tlslite.api import * +``` Then do: +``` connection = TLSConnection(sock) +``` 5 Step 3 - call a handshake function (client) ---------------------------------------------- If you're a client, there's two different handshake functions you can call, depending on how you want to authenticate: +``` connection.handshakeClientCert() connection.handshakeClientCert(certChain, privateKey) connection.handshakeClientSRP("alice", "abra123cadabra") +``` The ClientCert function without arguments is used when connecting to a site like Amazon, which doesn't require client authentication, but which will @@ -178,7 +207,7 @@ you'll need some way of creating these, such as OpenSSL (see http://www.openssl.org/docs/HOWTO/ for details). Below is an example of loading an X.509 chain and private key: - +``` from tlslite import X509, X509CertChain, parsePEMKey s = open("./test/clientX509Cert.pem").read() x509 = X509() @@ -186,6 +215,7 @@ Below is an example of loading an X.509 chain and private key: certChain = X509CertChain([x509]) s = open("./test/clientX509Key.pem").read() privateKey = parsePEMKey(s, private=True) +``` The SRP function does mutual authentication with a username and password - see RFC 5054 for details. @@ -196,6 +226,7 @@ only want to use SRP parameters of at least 2048 bits, and you only want to use the AES-256 cipher, and you only want to allow TLS (version 3.1), not SSL (version 3.0), you can do: +``` settings = HandshakeSettings() settings.minKeySize = 2048 settings.cipherNames = ["aes256"] @@ -203,6 +234,7 @@ use the AES-256 cipher, and you only want to allow TLS (version 3.1), not SSL settings.useExperimentalTACKExtension = True # Needed for TACK support connection.handshakeClientSRP("alice", "abra123cadabra", settings=settings) +``` If you want to check the server's certificate using TACK, you should set the "useExperiementalTACKExtension" value in HandshakeSettings. (Eventually, TACK @@ -216,12 +248,14 @@ previous session by passing in the session object from the old session. If the server remembers this old session and supports resumption, the handshake will finish more quickly. Otherwise, the full handshake will be done. For example: +``` connection.handshakeClientSRP("alice", "abra123cadabra") . . oldSession = connection.session connection2.handshakeClientSRP("alice", "abra123cadabra", session= oldSession) +``` 5 Step 3 - call a handshake function (server) ---------------------------------------------- @@ -233,17 +267,21 @@ To perform SRP authentication, you have to pass in a database of password verifiers. The VerifierDB class manages an in-memory or on-disk verifier database. +``` verifierDB = VerifierDB("./test/verifierDB") verifierDB.open() connection.handshakeServer(verifierDB=verifierDB) +``` To perform authentication with a certificate and private key, the server must load these as described in the previous section, then pass them in. If the server sets the reqCert boolean to True, a certificate chain will be requested from the client. +``` connection.handshakeServer(certChain=certChain, privateKey=privateKey, reqCert=True) +``` You can pass in a verifier database and/or a certificate chain+private key. The client will use one or both to authenticate the server. @@ -255,17 +293,21 @@ If you are passing in a certificate chain+private key, you may additionally provide a TACK to assist the client in authenticating your certificate chain. This requires the TACKpy library. Load a TACKpy.TACK object, then do: +``` settings = HandshakeSettings() settings.useExperimentalTACKExtension = True # Needed for TACK support connection.handshakeServer(certChain=certChain, privateKey=privateKey, tack=tack, settings=settings) +``` Finally, the server can maintain a SessionCache, which will allow clients to use session resumption: +``` sessionCache = SessionCache() connection.handshakeServer(verifierDB=verifierDB, sessionCache=sessionCache) +``` It should be noted that the session cache, and the verifier databases, are all thread-safe. @@ -276,10 +318,12 @@ If the handshake completes without raising an exception, authentication results will be stored in the connection's session object. The following variables will be populated if applicable, or else set to None: +``` connection.session.srpUsername # string connection.session.clientCertChain # X509CertChain connection.session.serverCertChain # X509CertChain connection.session.tackExt # TACKpy.TACK_Extension +``` X.509 chain objects return the end-entity fingerprint via getFingerprint(), and ignore the other certificates. @@ -303,37 +347,39 @@ have more details. Example of handling a remote alert: +``` try: [...] except TLSRemoteAlert as alert: if alert.description == AlertDescription.unknown_psk_identity: print "Unknown user." [...] +``` Below are some common alerts and their probable causes, and whether they are signalled by the client or server. -Client handshake_failure: - - SRP parameters are not recognized by client - - Server's TACK was unrelated to its certificate chain +Client `handshake_failure`: + * SRP parameters are not recognized by client + * Server's TACK was unrelated to its certificate chain -Client insufficient_security: - - SRP parameters are too small +Client `insufficient_security`: + * SRP parameters are too small -Client protocol_version: - - Client doesn't support the server's protocol version +Client `protocol_version`: + * Client doesn't support the server's protocol version -Server protocol_version: - - Server doesn't support the client's protocol version +Server `protocol_version`: + * Server doesn't support the client's protocol version -Server bad_record_mac: - - bad SRP username or password +Server `bad_record_mac`: + * bad SRP username or password -Server unknown_psk_identity - - bad SRP username (bad_record_mac could be used for the same thing) +Server `unknown_psk_identity`: + * bad SRP username (`bad_record_mac` could be used for the same thing) -Server handshake_failure: - - no matching cipher suites +Server `handshake_failure`: + * no matching cipher suites 5 Step 5 - exchange data ------------------------- @@ -363,7 +409,7 @@ shouldn't even try. By default, calling close() will close the underlying socket. If you set the connection's closeSocket flag to False, the socket will remain open after close. (NOTE: some TLS implementations will not respond properly to the -close_notify alert that close() generates, so the connection will hang if +`close_notify` alert that close() generates, so the connection will hang if closeSocket is set to True.) @@ -373,6 +419,7 @@ tlslite-ng comes with an HTTPTLSConnection class that extends httplib to work over SSL/TLS connections. Depending on how you construct it, it will do different types of authentication. +``` #No authentication whatsoever h = HTTPTLSConnection("www.amazon.com", 443) h.request("GET", "") @@ -388,15 +435,17 @@ different types of authentication. h = HTTPTLSConnection("localhost", 443, username="alice", password="abra123cadabra") [...] +``` 7 Using tlslite-ng with poplib or imaplib ========================================= -tlslite-ng comes with POP3_TLS and IMAP4_TLS classes that extend poplib and +tlslite-ng comes with `POP3_TLS` and `IMAP4_TLS` classes that extend poplib and imaplib to work over SSL/TLS connections. These classes can be constructed -with the same parameters as HTTPTLSConnection (see previous section), and +with the same parameters as HTTPTLSConnection (see previous section), and behave similarly. +``` #To connect to a POP3 server over SSL and display its fingerprint: from tlslite.api import * p = POP3_TLS("---------.net", port=995) @@ -407,23 +456,25 @@ behave similarly. from tlslite.api import * i = IMAP4_TLS("cyrus.andrew.cmu.edu", x509Fingerprint="00c14371227b3b677ddb9c4901e6f2aee18d3e45") - [...] - + [...] +``` + 8 Using tlslite-ng with smtplib =============================== -tlslite comes with an SMTP_TLS class that extends smtplib to work +tlslite-ng comes with an `SMTP_TLS` class that extends smtplib to work over SSL/TLS connections. This class accepts the same parameters as -HTTPTLSConnection (see previous section), and behaves similarly. Depending +HTTPTLSConnection (see previous section), and behaves similarly. Depending on how you call starttls(), it will do different types of authentication. +``` #To connect to an SMTP server once you know its fingerprint: from tlslite.api import * s = SMTP_TLS("----------.net", port=587) s.ehlo() s.starttls(x509Fingerprint="7e39be84a2e3a7ad071752e3001d931bf82c32dc") [...] - +``` 9 Using tlslite-ng with SocketServer ==================================== @@ -462,22 +513,25 @@ RFC 7366. =========== 0.4.8 - 11/12/2014 - Added more acknowledgements and security considerations + 0.4.7 - 11/12/2014 - Added TLS 1.2 support (Yngve Pettersen and Paul Sokolovsky) - Don't offer SSLv3 by default (e.g. POODLE) - - Fixed bug with PyCrypto_RSA integration + - Fixed bug with `PyCrypto_RSA` integration - Fixed harmless bug that added non-prime into sieves list - Added "make test" and "make test-dev" targets (Hubert Kario) + 0.4.5 - 3/20/2013 - **API CHANGE**: TLSClosedConnectionError instead of ValueError when writing - to a closed connection. This inherits from socket.error, so should + to a closed connection. This inherits from socket.error, so should interact better with SocketServer (see http://bugs.python.org/issue14574) and other things expecting a socket.error in this situation. - Added support for RC4-MD5 ciphersuite (if enabled in settings) - This is allegedly necessary to connect to some Internet servers. - - Added TLSConnection.unread() function + - Added TLSConnection.unread() function - Switched to New-style classes (inherit from 'object') - Minor cleanups + 0.4.4 - 2/25/2013 - Added Python 3 support (Martin von Loewis) - Added NPN client support (Marcelo Fernandez) @@ -487,10 +541,13 @@ RFC 7366. - Made RSA hashAndVerify() tolerant of sigs w/o encoded NULL AlgorithmParam - (this function is not used for TLS currently, and this tolerance may not even be necessary) + 0.4.3 - 9/27/2012 - Minor bugfix (0.4.2 doesn't load tackpy) + 0.4.2 - 9/25/2012 - Updated TACK (compatible with tackpy 0.9.9) + 0.4.1 - 5/22/2012 - Fixed RSA padding bugs (w/help from John Randolph) - Updated TACK (compatible with tackpy 0.9.7) @@ -503,7 +560,7 @@ RFC 7366. 0.4.0 - 2/11/2012 - Fixed pycrypto support - Fixed python 2.6 problems - + 0.3.9.x - 2/7/2012 Much code cleanup, in particular decomposing the handshake functions so they @@ -516,25 +573,25 @@ Also: - Security Fixes - Sends SCSV ciphersuite as per RFC 5746, to signal non-renegotiated Client Hello. Does not support renegotiation (never has). - - Change from e=3 to e=65537 for generated RSA keys, not strictly + - Change from e=3 to e=65537 for generated RSA keys, not strictly necessary but mitigates risk of sloppy verifier. - 1/(n-1) countermeasure for BEAST. - Behavior changes: - Split cmdline into tls.py and tlstest.py, improved options. - Formalized LICENSE. - - Defaults to closing socket after sending close_notify, fixes hanging. - problem that would occur sometime when waiting for other party's + - Defaults to closing socket after sending `close_notify`, fixes hanging. + problem that would occur sometime when waiting for other party's close_notify. - Update SRP to RFC 5054 compliance. - - Removed client handshake "callbacks", no longer support the SRP + - Removed client handshake "callbacks", no longer support the SRP re-handshake idiom within a single handshake function. - Bugfixes - Added hashlib support, removes Deprecation Warning due to sha and md5. - Handled GeneratorExit exceptions that are a new Python feature, and interfere with the async code if not handled. - + - Removed: - Shared keys (it was based on an ancient I-D, not TLS-PSK). - cryptlib support, it wasn't used much, we have enough other options. @@ -546,7 +603,7 @@ Also: - Additions - Support for TACK via TACKpy. - - Support for CertificateRequest.certificate_authorities ("reqCAs") + - Support for `CertificateRequest.certificate_authorities` ("reqCAs") - Added TLSConnection.shutdown() to better mimic socket. - Enabled Session resumption for XMLRPCTransport. @@ -554,28 +611,35 @@ Also: - Added support for poplib, imaplib, and smtplib - Added python 2.4 windows installer - Fixed occassional timing problems with test suite + 0.3.7 - 10/05/2004 - Added support for Python 2.2 - Cleaned up compatibility code, and docs, a bit + 0.3.6 - 9/28/2004 - Fixed script installation on UNIX - Give better error message on old Python versions + 0.3.5 - 9/16/2004 - TLS 1.1 support - os.urandom() support - Fixed win32prng on some systems + 0.3.4 - 9/12/2004 - Updated for TLS/SRP draft 8 - - Bugfix: was setting _versioncheck on SRP 1st hello, causing problems + - Bugfix: was setting `_versioncheck` on SRP 1st hello, causing problems with GnuTLS (which was offering TLS 1.1) - - Removed _versioncheck checking, since it could cause interop problems - - Minor bugfix: when cryptlib_py and and cryptoIDlib present, cryptlib + - Removed `_versioncheck` checking, since it could cause interop problems + - Minor bugfix: when `cryptlib_py` and and cryptoIDlib present, cryptlib was complaining about being initialized twice + 0.3.3 - 6/10/2004 - Updated for TLS/SRP draft 7 - Updated test cryptoID cert chains for cryptoIDlib 0.3.1 + 0.3.2 - 5/21/2004 - fixed bug when handling multiple handshake messages per record (e.g. IIS) + 0.3.1 - 4/21/2004 - added xmlrpclib integration - fixed hanging bug in Twisted integration @@ -583,80 +647,99 @@ Also: - fixed import problem with cryptoIDlib - fixed port allocation problem when test scripts are run on some UNIXes - made tolerant of buggy IE sending wrong version in premaster secret + 0.3.0 - 3/20/2004 - added API docs thanks to epydoc - added X.509 path validation via cryptlib - much cleaning/tweaking/re-factoring/minor fixes + 0.2.7 - 3/12/2004 - changed Twisted error handling to use connectionLost() - added ignoreAbruptClose + 0.2.6 - 3/11/2004 - added Twisted errorHandler - added TLSAbruptCloseError - added 'integration' subdirectory + 0.2.5 - 3/10/2004 - improved asynchronous support a bit - added first-draft of Twisted support + 0.2.4 - 3/5/2004 - cleaned up asyncore support - added proof-of-concept for Twisted + 0.2.3 - 3/4/2004 - added pycrypto RSA support - added asyncore support + 0.2.2 - 3/1/2004 - added GMPY support - added pycrypto support - added support for PEM-encoded private keys, in pure python + 0.2.1 - 2/23/2004 - improved PRNG use (cryptlib, or /dev/random, or CryptoAPI) - added RSA blinding, to avoid timing attacks - don't install local copy of M2Crypto, too problematic + 0.2.0 - 2/19/2004 - changed VerifierDB to take per-user parameters - - renamed tls_lite -> tlslite + - renamed `tls_lite` -> tlslite + 0.1.9 - 2/16/2004 - added post-handshake 'Checker' - made compatible with Python 2.2 - made more forgiving of abrupt closure, since everyone does it: - if the socket is closed while sending/recv'ing close_notify, + if the socket is closed while sending/recv'ing `close_notify`, just ignore it. + 0.1.8 - 2/12/2004 - TLSConnections now emulate sockets, including makefile() - HTTPTLSConnection and TLSMixIn simplified as a result + 0.1.7 - 2/11/2004 - fixed httplib.HTTPTLSConnection with multiple requests - - fixed SocketServer to handle close_notify + - fixed SocketServer to handle `close_notify` - changed handshakeClientNoAuth() to ignore CertificateRequests - changed handshakeClient() to ignore non-resumable session arguments + 0.1.6 - 2/10/2004 - fixed httplib support + 0.1.5 - 2/09/2004 - added support for httplib and SocketServer - added support for SSLv3 - added support for 3DES - cleaned up read()/write() behavior - improved HMAC speed + 0.1.4 - 2/06/2004 - fixed dumb bug in tls.py + 0.1.3 - 2/05/2004 - change read() to only return requested number of bytes - added support for shared-key and in-memory databases - added support for PEM-encoded X.509 certificates - added support for SSLv2 ClientHello - fixed shutdown/re-handshaking behavior - - cleaned up handling of missing_srp_username + - cleaned up handling of `missing_srp_username` - renamed readString()/writeString() -> read()/write() - added documentation + 0.1.2 - 2/04/2004 - added clienttest/servertest functions - improved OpenSSL cipher wrappers speed - fixed server when it has a key, but client selects plain SRP - fixed server to postpone errors until it has read client's messages - fixed ServerHello to only include extension data if necessary + 0.1.1 - 2/02/2004 - - fixed close_notify behavior + - fixed `close_notify` behavior - fixed handling of empty application data packets - fixed socket reads to not consume extra bytes - added testing functions to tls.py + 0.1.0 - 2/01/2004 - first release From 610dce2a04a91530cf6e852e60840625f2833c3f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 18:44:34 +0200 Subject: [PATCH 005/574] run detailed checks only for pull requests --- .travis.yml | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 70dacdc7..cfe9a8c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -89,20 +89,23 @@ script: pylint --msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" tlslite > pylint_report.txt || : diff-quality --violations=pylint --fail-under=90 pylint_report.txt fi - - echo "Will test commits between $TRAVIS_COMMIT_RANGE:" - - git log --oneline --reverse $TRAVIS_COMMIT_RANGE - - | - for i in $(git log --pretty=format:%H --reverse $TRAVIS_COMMIT_RANGE); do - git checkout $i - make clean - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then - unit2 discover || exit 1 - else - python -m unittest discover || exit 1 - fi - make test-local || exit 1 - cd $TRAVIS_BUILD_DIR - done + - | + if [[ $TRAVIS_PULL_REQUEST != "false" ]]; then + echo "Will test commits between $TRAVIS_COMMIT_RANGE:" + git log --oneline --reverse $TRAVIS_COMMIT_RANGE + + for i in $(git log --pretty=format:%H --reverse $TRAVIS_COMMIT_RANGE); do + git checkout $i + make clean + if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then + unit2 discover || exit 1 + else + python -m unittest discover || exit 1 + fi + make test-local || exit 1 + cd $TRAVIS_BUILD_DIR + done + fi sudo: false From 742519144658ba69229347e40633d99d8baaa82d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 18:45:05 +0200 Subject: [PATCH 006/574] .travis.yml: print current HEAD position for duugging --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index cfe9a8c2..2e12fc76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,6 +62,8 @@ before_install: PR_FIRST=$(curl --silent --show-error --location $URL | head -1 | grep -o -E '\b[0-9a-f]{40}\b' | tr -d '\n') TRAVIS_COMMIT_RANGE=$PR_FIRST^..$TRAVIS_COMMIT fi + # sanity check current commit + - git rev-parse HEAD - echo "TRAVIS_COMMIT_RANGE=$TRAVIS_COMMIT_RANGE" - git fetch origin master:refs/remotes/origin/master From 8b6b96c929a2dc0ea5f7dffebc92eb0fd20e31bb Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:08:05 +0200 Subject: [PATCH 007/574] add badges --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9e5438b0..83190f29 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` +[![Build Status](https://travis-ci.org/tomato42/tlslite-ng.svg?branch=master)](https://travis-ci.org/tomato42/tlslite-ng) +[![Coverage Status](https://coveralls.io/repos/tomato42/tlslite-ng/badge.svg?branch=master)](https://coveralls.io/r/tomato42/tlslite-ng?branch=master) + Table of Contents ================== From 5d2e92e8350b3629ae5f4d6992d7d2453eabf7b7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:34:10 +0200 Subject: [PATCH 008/574] generic TLS message add generic TLS message class --- tlslite/messages.py | 19 +++++++++++++++++++ unit_tests/test_tlslite_messages.py | 14 +++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 4bfb6b41..d5e89afc 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -82,6 +82,25 @@ def parse(self, p): self.length = p.get(1) return self +class Message(object): + + """Generic TLS message""" + + def __init__(self, contentType, data): + """ + Initialize object with specified contentType and data + + @type contentType: int + @param contentType: TLS record layer content type of associated data + @type data: bytearray + @param data: data + """ + self.contentType = contentType + self.data = data + + def write(self): + """Return serialised object data""" + return self.data class Alert(object): def __init__(self): diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index d7587601..ee4f6754 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -8,7 +8,7 @@ except ImportError: import unittest from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ - RecordHeader2 + RecordHeader2, Message from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType @@ -16,6 +16,18 @@ SRPExtension, TLSExtension from tlslite.errors import TLSInternalError +class TestMessage(unittest.TestCase): + def test___init__(self): + msg = Message(ContentType.application_data, bytearray(0)) + + self.assertEqual(ContentType.application_data, msg.contentType) + self.assertEqual(bytearray(0), msg.data) + + def test_write(self): + msg = Message(0, bytearray(10)) + + self.assertEqual(bytearray(10), msg.write()) + class TestClientHello(unittest.TestCase): def test___init__(self): client_hello = ClientHello() From a3dace54038baff955aea7990aeda85749e6300c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:35:26 +0200 Subject: [PATCH 009/574] add mock version of socket.sock --- unit_tests/mocksock.py | 68 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 unit_tests/mocksock.py diff --git a/unit_tests/mocksock.py b/unit_tests/mocksock.py new file mode 100644 index 00000000..bd05235a --- /dev/null +++ b/unit_tests/mocksock.py @@ -0,0 +1,68 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +import socket +import errno +class MockSocket(socket.socket): + def __init__(self, buf, maxRet=None, maxWrite=None, blockEveryOther=False): + self.index = 0 + self.buf = buf + self.sent = [] + self.closed = False + self.maxRet = maxRet + self.maxWrite = maxWrite + self.blockEveryOther = blockEveryOther + self.blockRead = False + self.blockWrite = False + + def __repr__(self): + return "MockSocket(index={0}, buf={1!r}, sent={2!r})".format( + self.index, self.buf, self.sent) + + def recv(self, size): + if self.closed: + raise ValueError("Read from closed socket") + + if self.blockEveryOther: + if self.blockRead: + self.blockRead = False + raise socket.error(errno.EWOULDBLOCK) + else: + self.blockRead = True + + if size == 0: + return bytearray(0) + if self.maxRet is not None and self.maxRet < size: + size = self.maxRet + if len(self.buf[self.index:]) == 0: + raise socket.error(errno.EWOULDBLOCK) + elif len(self.buf[self.index:]) < size: + ret = self.buf[self.index:] + self.index = len(self.buf) + return ret + else: + ret = self.buf[self.index:self.index+size] + self.index+=size + return ret + + def send(self, data): + if self.closed: + raise ValueError("Write to closed socket") + + if self.blockEveryOther: + if self.blockWrite: + self.blockWrite = False + raise socket.error(errno.EWOULDBLOCK) + else: + self.blockWrite = True + + if self.maxWrite is None or len(data) < self.maxWrite: + self.sent.append(data) + return len(data) + + self.sent.append(data[:self.maxWrite]) + return self.maxWrite + + def close(self): + self.closed = True From 8476d991c6096ec5cb80e8c2105e0aaede1dafc3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:37:06 +0200 Subject: [PATCH 010/574] exceptions to internally handle errors extend exceptions hierarchy to allow for internal handling of errors --- tlslite/errors.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/tlslite/errors.py b/tlslite/errors.py index 2c523176..35878987 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -14,7 +14,15 @@ from .constants import AlertDescription, AlertLevel -class TLSError(Exception): +class BaseTLSException(Exception): + """Metaclass for TLS Lite exceptions. + + Look to L{TLSError} for exceptions that should be caught by tlslite + consumers + """ + pass + +class TLSError(BaseTLSException): """Base class for all TLS Lite exceptions.""" def __str__(self): @@ -173,5 +181,20 @@ class TLSUnsupportedError(TLSError): pass class TLSInternalError(TLSError): - """The internal state of object is unexpected or invalid""" + """The internal state of object is unexpected or invalid. + + Caused by incorrect use of API. + """ + pass + +class TLSProtocolException(BaseTLSException): + """Exceptions used internally for handling errors in received messages""" + pass + +class TLSIllegalParameterException(TLSProtocolException): + """Parameters specified in message were incorrect or invalid""" + pass + +class TLSRecordOverflow(TLSProtocolException): + """The received record size was too big""" pass From 2b043ac81075ecb39c7e9edc98a434cc55aa9e40 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:37:37 +0200 Subject: [PATCH 011/574] add implementation of record layer socket --- tlslite/recordlayer.py | 183 +++++++++++++++ unit_tests/test_tlslite_recordlayer.py | 299 +++++++++++++++++++++++++ 2 files changed, 482 insertions(+) create mode 100644 tlslite/recordlayer.py create mode 100644 unit_tests/test_tlslite_recordlayer.py diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py new file mode 100644 index 00000000..fc12b3ce --- /dev/null +++ b/tlslite/recordlayer.py @@ -0,0 +1,183 @@ +# Copyright (c) 2014, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +"""Implementation of the TLS Record Layer protocol""" + +import socket +import errno +from tlslite.constants import ContentType +from .messages import RecordHeader3, RecordHeader2 +from .utils.codec import Parser +from .errors import TLSRecordOverflow, TLSIllegalParameterException,\ + TLSAbruptCloseError + +class RecordSocket(object): + + """Socket wrapper for reading and writing TLS Records""" + + def __init__(self, sock): + """ + Assign socket to wrapper + + @type sock: socket.socket + """ + self.sock = sock + self.version = (0, 0) + + def _sockSendAll(self, data): + """ + Send all data through socket + + @type data: bytearray + @param data: data to send + @raise socket.error: when write to socket failed + """ + while 1: + try: + bytesSent = self.sock.send(data) + except socket.error as why: + if why.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): + yield 1 + continue + raise + + if bytesSent == len(data): + return + data = data[bytesSent:] + yield 1 + + def send(self, msg): + """ + Send the message through socket. + + @type msg: bytearray + @param msg: TLS message to send + @raise socket.error: when write to socket failed + """ + + data = msg.write() + + header = RecordHeader3().create(self.version, + msg.contentType, + len(data)) + + data = header.write() + data + + for result in self._sockSendAll(data): + yield result + + def _sockRecvAll(self, length): + """ + Read exactly the amount of bytes specified in L{length} from raw socket. + + @rtype: generator + @return: generator that will return 0 or 1 in case the socket is non + blocking and would block and bytearray in case the read finished + @raise TLSAbruptCloseError: when the socket closed + """ + + buf = bytearray(0) + + if length == 0: + yield buf + + while True: + try: + socketBytes = self.sock.recv(length - len(buf)) + except socket.error as why: + if why.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): + yield 0 + continue + else: + raise + + #if the connection closed, raise socket error + if len(socketBytes) == 0: + raise TLSAbruptCloseError() + + buf += bytearray(socketBytes) + if len(buf) == length: + yield buf + + def _recvHeader(self): + """Read a single record header from socket""" + #Read the next record header + buf = bytearray(0) + ssl2 = False + for result in self._sockRecvAll(1): + if result in (0, 1): + yield result + else: break + buf += result + + if buf[0] in ContentType.all: + ssl2 = False + # SSLv3 record layer header is 5 bytes long, we already read 1 + for result in self._sockRecvAll(4): + if result in (0, 1): + yield result + else: break + buf += result + # XXX this should be 'buf[0] & 128', otherwise hello messages longer + # than 127 bytes won't be properly parsed + elif buf[0] == 128: + ssl2 = True + # in SSLv2 we need to read 2 bytes in total to know the size of + # header, we already read 1 + for result in self._sockRecvAll(1): + if result in (0, 1): + yield result + else: break + buf += result + else: + raise TLSIllegalParameterException( + "Record header type doesn't specify known type") + + #Parse the record header + if ssl2: + record = RecordHeader2().parse(Parser(buf)) + else: + record = RecordHeader3().parse(Parser(buf)) + + yield record + + def recv(self): + """ + Read a single record from socket, handles both SSLv2 and SSLv3 record + layer + + @rtype: generator + @return: generator that returns 0 or 1 in case the read would be + blocking or a tuple containing record header (object) and record + data (bytearray) read from socket + @raise socket.error: In case of network error + @raise TLSAbruptCloseError: When the socket was closed on the other + side in middle of record receiving + @raise TLSRecordOverflow: When the received record was longer than + allowed by TLS + @raise TLSIllegalParameterException: When the record header was + malformed + """ + + for record in self._recvHeader(): + if record in (0, 1): + yield record + else: break + + #Check the record header fields + # 18432 = 2**14 (basic record size limit) + 1024 (maximum compression + # overhead) + 1024 (maximum encryption overhead) + if record.length > 18432: + raise TLSRecordOverflow() + + #Read the record contents + buf = bytearray(0) + for result in self._sockRecvAll(record.length): + if result in (0, 1): + yield result + else: break + buf += result + + yield (record, buf) + diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py new file mode 100644 index 00000000..34e9c9be --- /dev/null +++ b/unit_tests/test_tlslite_recordlayer.py @@ -0,0 +1,299 @@ +# Copyright (c) 2014, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call + +import socket +import errno + +from tlslite.messages import Message +from tlslite.recordlayer import RecordSocket +from tlslite.constants import ContentType +from unit_tests.mocksock import MockSocket +from tlslite.errors import TLSRecordOverflow, TLSIllegalParameterException,\ + TLSAbruptCloseError + +class TestRecordSocket(unittest.TestCase): + def test___init__(self): + sock = RecordSocket(-42) + + self.assertIsNotNone(sock) + self.assertEqual(sock.sock, -42) + self.assertEqual(sock.version, (0, 0)) + + def test_send(self): + mockSock = MockSocket(bytearray(0)) + sock = RecordSocket(mockSock) + sock.version = (3, 3) + + msg = Message(ContentType.handshake, bytearray(10)) + + for result in sock.send(msg): + if result in (0, 1): + self.assertTrue(False, "Blocking socket") + else: break + + self.assertEqual(len(mockSock.sent), 1) + self.assertEqual(bytearray( + b'\x16' + # handshake message + b'\x03\x03' + # version + b'\x00\x0a' + # payload length + b'\x00'*10 # payload + ), mockSock.sent[0]) + + def test_send_with_very_slow_socket(self): + mockSock = MockSocket(bytearray(0), maxWrite=1, blockEveryOther=True) + sock = RecordSocket(mockSock) + + msg = Message(ContentType.handshake, bytearray(b'\x32'*2)) + + gotRetry = False + for result in sock.send(msg): + if result in (0, 1): + gotRetry = True + else: break + + self.assertTrue(gotRetry) + self.assertEqual([ + bytearray(b'\x16'), # handshake message + bytearray(b'\x00'), bytearray(b'\x00'), # version (unset) + bytearray(b'\x00'), bytearray(b'\x02'), # payload length + bytearray(b'\x32'), bytearray(b'\x32')], + mockSock.sent) + + def test_send_with_errored_out_socket(self): + mockSock = mock.MagicMock() + mockSock.send.side_effect = socket.error(errno.ETIMEDOUT) + + sock = RecordSocket(mockSock) + + msg = Message(ContentType.handshake, bytearray(10)) + + gen = sock.send(msg) + + with self.assertRaises(socket.error): + next(gen) + + def test_recv(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + )) + sock = RecordSocket(mockSock) + + for result in sock.recv(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + + self.assertEqual(data, bytearray(4)) + self.assertEqual(header.type, ContentType.handshake) + self.assertEqual(header.version, (3, 3)) + self.assertEqual(header.length, 4) + + def test_recv_stops_itelf(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + )) + sock = RecordSocket(mockSock) + + for result in sock.recv(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + + header, data = result + + self.assertEqual(data, bytearray(4)) + self.assertEqual(header.type, ContentType.handshake) + self.assertEqual(header.version, (3, 3)) + self.assertEqual(header.length, 4) + + def test_recv_with_trickling_socket(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + ), maxRet=1) + + sock = RecordSocket(mockSock) + + for result in sock.recv(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + + self.assertEqual(bytearray(4), data) + + def test_recv_with_blocking_socket(self): + mockSock = mock.MagicMock() + mockSock.recv.side_effect = socket.error(errno.EWOULDBLOCK) + + sock = RecordSocket(mockSock) + + gen = sock.recv() + + self.assertEqual(0, next(gen)) + + def test_recv_with_errored_out_socket(self): + mockSock = mock.MagicMock() + mockSock.recv.side_effect = socket.error(errno.ETIMEDOUT) + + sock = RecordSocket(mockSock) + + gen = sock.recv() + + with self.assertRaises(socket.error): + next(gen) + + def test_recv_with_empty_socket(self): + mockSock = mock.MagicMock() + mockSock.recv.side_effect = [bytearray(0)] + + sock = RecordSocket(mockSock) + + gen = sock.recv() + + with self.assertRaises(TLSAbruptCloseError): + next(gen) + + def test_recv_with_slow_socket(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + ), maxRet=1, blockEveryOther=True) + + sock = RecordSocket(mockSock) + + gotRetry = False + for result in sock.recv(): + if result in (0, 1): + gotRetry = True + else: break + + header, data = result + + self.assertTrue(gotRetry) + self.assertEqual(bytearray(4), data) + + def test_recv_with_malformed_record(self): + mockSock = MockSocket(bytearray( + b'\x01' + # wrong type + b'\x03\x03' + # TLSv1.2 + b'\x00\x01' + # length + b'\x00')) + + sock = RecordSocket(mockSock) + + gen = sock.recv() + + with self.assertRaises(TLSIllegalParameterException): + next(gen) + + def test_recv_with_too_big_record(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\xff\xff' + # length + b'\x00'*65536)) + + sock = RecordSocket(mockSock) + + gen = sock.recv() + + with self.assertRaises(TLSRecordOverflow): + next(gen) + + + def test_recv_with_empty_data(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x00')) # length + + sock = RecordSocket(mockSock) + + gen = sock.recv() + + for result in sock.recv(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + + self.assertEqual(ContentType.handshake, header.type) + self.assertEqual((3, 3), header.version) + self.assertEqual(0, header.length) + + self.assertEqual(bytearray(0), data) + + def test_recv_with_SSL2_record(self): + mockSock = MockSocket(bytearray( + b'\x80' + # tag + b'\x04' + # length + b'\x00'*4)) + + sock = RecordSocket(mockSock) + + for result in sock.recv(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + + self.assertTrue(header.ssl2) + self.assertEqual(ContentType.handshake, header.type) + self.assertEqual(4, header.length) + self.assertEqual((2, 0), header.version) + + self.assertEqual(bytearray(4), data) + + def test_recv_with_not_complete_SSL2_record(self): + mockSock = MockSocket(bytearray( + b'\x80' + # tag + b'\x04' + # length + b'\x00'*3)) + + sock = RecordSocket(mockSock) + + for result in sock.recv(): + break + + self.assertEqual(0, result) + + def test_recv_with_SSL2_record_with_incomplete_header(self): + mockSock = MockSocket(bytearray( + b'\x80' # tag + )) + + sock = RecordSocket(mockSock) + + for result in sock.recv(): + break + + self.assertEqual(0, result) From 436940925bab54ee4ebfa956d2e2a53de4f29d63 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:39:07 +0200 Subject: [PATCH 012/574] add retention tests to TLSRecordLayer --- tlslite/tlsrecordlayer.py | 12 +- unit_tests/test_tlslite_tlsrecordlayer.py | 297 ++++++++++++++++++++++ 2 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 unit_tests/test_tlslite_tlsrecordlayer.py diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index ecabd561..e6ec4fcb 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -120,7 +120,7 @@ def __init__(self, sock): self._handshake_sha256 = hashlib.sha256() #TLS Protocol Version - self.version = (0,0) #read-only + self._version = (0, 0) #read-only self._versionCheck = False #Once we choose a version, this is True #Current and Pending connection states @@ -149,6 +149,16 @@ def __init__(self, sock): #Fault we will induce, for testing purposes self.fault = None + @property + def version(self): + """Get the SSL protocol version of connection""" + return self._version + + @version.setter + def version(self, value): + """Set the SSL protocol version of connection""" + self._version = value + def clearReadBuffer(self): self._readBuffer = b'' diff --git a/unit_tests/test_tlslite_tlsrecordlayer.py b/unit_tests/test_tlslite_tlsrecordlayer.py new file mode 100644 index 00000000..e37b4775 --- /dev/null +++ b/unit_tests/test_tlslite_tlsrecordlayer.py @@ -0,0 +1,297 @@ +# Copyright (c) 2014, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call + +import socket +import errno +from tlslite.tlsrecordlayer import TLSRecordLayer +from tlslite.constants import ContentType +from tlslite.errors import TLSAbruptCloseError, TLSLocalAlert +from tlslite.messages import Message +from unit_tests.mocksock import MockSocket + +class TestTLSRecordLayer(unittest.TestCase): + def test___init__(self): + record_layer = TLSRecordLayer(None) + + self.assertIsNotNone(record_layer) + self.assertIsInstance(record_layer, TLSRecordLayer) + + def test__getNextRecord(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + )) + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + for result in sock._getNextRecord(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + data = data.bytes + + self.assertEqual(data, bytearray(4)) + self.assertEqual(header.type, ContentType.handshake) + self.assertEqual(header.version, (3, 3)) + self.assertEqual(header.length, 4) + + def test__getNextRecord_stops_itelf(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + )) + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + for result in sock._getNextRecord(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + + header, data = result + data = data.bytes + + self.assertEqual(data, bytearray(4)) + self.assertEqual(header.type, ContentType.handshake) + self.assertEqual(header.version, (3, 3)) + self.assertEqual(header.length, 4) + + def test__getNextRecord_with_trickling_socket(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + ), maxRet=1) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + for result in sock._getNextRecord(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + data = data.bytes + + self.assertEqual(bytearray(4), data) + + def test__getNextRecord_with_blocking_socket(self): + mockSock = mock.MagicMock() + mockSock.recv.side_effect = socket.error(errno.EWOULDBLOCK) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + gen = sock._getNextRecord() + + self.assertEqual(0, next(gen)) + + def test__getNextRecord_with_errored_out_socket(self): + mockSock = mock.MagicMock() + mockSock.recv.side_effect = socket.error(errno.ETIMEDOUT) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + gen = sock._getNextRecord() + + with self.assertRaises(socket.error): + next(gen) + + def test__getNextRecord_with_empty_socket(self): + mockSock = mock.MagicMock() + mockSock.recv.side_effect = [bytearray(0)] + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + gen = sock._getNextRecord() + + with self.assertRaises(TLSAbruptCloseError): + next(gen) + + def test__getNextRecord_with_slow_socket(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + ), maxRet=1, blockEveryOther=True) + + sock = TLSRecordLayer(mockSock) + + gotRetry = False + # XXX using private method! + for result in sock._getNextRecord(): + if result in (0, 1): + gotRetry = True + else: break + + header, data = result + data = data.bytes + + self.assertTrue(gotRetry) + self.assertEqual(bytearray(4), data) + + def test__getNextRecord_with_malformed_record(self): + mockSock = MockSocket(bytearray( + b'\x01' + # wrong type + b'\x03\x03' + # TLSv1.2 + b'\x00\x01' + # length + b'\x00')) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + gen = sock._getNextRecord() + + # XXX this should be TLSLocalAlert as we're expected to tell the other + # side they have sent us garbage + with self.assertRaises(SyntaxError) as context: + next(gen) + + #self.assertEqual(str(context.exception), "illegal_parameter") + + def test__getNextRecord_with_too_big_record(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\xff\xff' + # length + b'\x00'*65536)) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + gen = sock._getNextRecord() + + with self.assertRaises(TLSLocalAlert) as context: + next(gen) + + self.assertEqual(str(context.exception), "record_overflow") + + def test__getNextRecord_with_SSL2_record(self): + mockSock = MockSocket(bytearray( + b'\x80' + # tag + b'\x04' + # length + b'\x00'*4)) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + for result in sock._getNextRecord(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + data = data.bytes + + self.assertTrue(header.ssl2) + self.assertEqual(ContentType.handshake, header.type) + self.assertEqual(4, header.length) + self.assertEqual((2, 0), header.version) + + self.assertEqual(bytearray(4), data) + + def test__getNextRecord_with_not_complete_SSL2_record(self): + mockSock = MockSocket(bytearray( + b'\x80' + # tag + b'\x04' + # length + b'\x00'*3)) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + for result in sock._getNextRecord(): + break + + self.assertEqual(0, result) + + def test__getNextRecord_with_SSL2_record_with_incomplete_header(self): + mockSock = MockSocket(bytearray( + b'\x80' # tag + )) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method + for result in sock._getNextRecord(): + break + + self.assertEqual(0, result) + + def test__sendMsg(self): + mockSock = MockSocket(bytearray(0)) + sock = TLSRecordLayer(mockSock) + sock.version = (3, 3) + + msg = Message(ContentType.handshake, bytearray(10)) + + # XXX using private method + for result in sock._sendMsg(msg, False): + if result in (0, 1): + self.assertTrue(False, "Blocking socket") + else: break + + self.assertEqual(len(mockSock.sent), 1) + self.assertEqual(bytearray( + b'\x16' + # handshake message + b'\x03\x03' + # version + b'\x00\x0a' + # payload length + b'\x00'*10 # payload + ), mockSock.sent[0]) + + def test__sendMsg_with_very_slow_socket(self): + mockSock = MockSocket(bytearray(0), maxWrite=1, blockEveryOther=True) + sock = TLSRecordLayer(mockSock) + + msg = Message(ContentType.handshake, bytearray(b'\x32'*2)) + + gotRetry = False + # XXX using private method! + for result in sock._sendMsg(msg, False): + if result in (0, 1): + gotRetry = True + else: break + + self.assertTrue(gotRetry) + self.assertEqual([ + bytearray(b'\x16'), # handshake message + bytearray(b'\x00'), bytearray(b'\x00'), # version (unset) + bytearray(b'\x00'), bytearray(b'\x02'), # payload length + bytearray(b'\x32'), bytearray(b'\x32')], + mockSock.sent) + + def test__sendMsg_with_errored_out_socket(self): + mockSock = mock.MagicMock() + mockSock.send.side_effect = socket.error(errno.ETIMEDOUT) + + sock = TLSRecordLayer(mockSock) + + msg = Message(ContentType.handshake, bytearray(10)) + + gen = sock._sendMsg(msg, False) + + with self.assertRaises(TLSAbruptCloseError): + next(gen) From 694c62471dfb075f6465c221045ff48128708768 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:39:40 +0200 Subject: [PATCH 013/574] add RecordSocket initialization to TLSRecordLayer --- tlslite/tlsrecordlayer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index e6ec4fcb..ee58a77a 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -19,6 +19,7 @@ from .mathtls import * from .constants import * from .utils.cryptomath import getRandomBytes +from .recordlayer import RecordSocket import socket import errno @@ -102,6 +103,7 @@ class TLSRecordLayer(object): def __init__(self, sock): self.sock = sock + self._recordSocket = RecordSocket(sock) #My session object (Session instance; read-only) self.session = None @@ -158,6 +160,7 @@ def version(self): def version(self, value): """Set the SSL protocol version of connection""" self._version = value + self._recordSocket.version = value def clearReadBuffer(self): self._readBuffer = b'' From 36d76f6eee7f3b89e765849e4961de4236bbfb17 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 25 Mar 2015 15:01:14 +0100 Subject: [PATCH 014/574] use RecordSocket.recv() --- tlslite/tlsrecordlayer.py | 70 ++++------------------- unit_tests/test_tlslite_tlsrecordlayer.py | 6 +- 2 files changed, 12 insertions(+), 64 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index ee58a77a..ffb51def 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -840,68 +840,18 @@ def _getNextRecord(self): yield (recordHeader, Parser(b)) return - #Otherwise... - #Read the next record header - b = bytearray(0) - recordHeaderLength = 1 - ssl2 = False - while 1: - try: - s = self.sock.recv(recordHeaderLength-len(b)) - except socket.error as why: - if why.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): - yield 0 - continue - else: - raise - - #If the connection was abruptly closed, raise an error - if len(s)==0: - raise TLSAbruptCloseError() - - b += bytearray(s) - if len(b)==1: - if b[0] in ContentType.all: - ssl2 = False - recordHeaderLength = 5 - elif b[0] == 128: - ssl2 = True - recordHeaderLength = 2 - else: - raise SyntaxError() - if len(b) == recordHeaderLength: - break - - #Parse the record header - if ssl2: - r = RecordHeader2().parse(Parser(b)) - else: - r = RecordHeader3().parse(Parser(b)) - - #Check the record header fields - if r.length > 18432: + try: + for result in self._recordSocket.recv(): + if result in (0, 1): + yield result + else: break + (r, b) = result + except TLSRecordOverflow: for result in self._sendError(AlertDescription.record_overflow): yield result - - #Read the record contents - b = bytearray(0) - while 1: - try: - s = self.sock.recv(r.length - len(b)) - except socket.error as why: - if why.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): - yield 0 - continue - else: - raise - - #If the connection is closed, raise a socket error - if len(s)==0: - raise TLSAbruptCloseError() - - b += bytearray(s) - if len(b) == r.length: - break + except TLSIllegalParameterException: + for result in self._sendError(AlertDescription.illegal_parameter): + yield result #Check the record header fields (2) #We do this after reading the contents from the socket, so that diff --git a/unit_tests/test_tlslite_tlsrecordlayer.py b/unit_tests/test_tlslite_tlsrecordlayer.py index e37b4775..e311808c 100644 --- a/unit_tests/test_tlslite_tlsrecordlayer.py +++ b/unit_tests/test_tlslite_tlsrecordlayer.py @@ -166,12 +166,10 @@ def test__getNextRecord_with_malformed_record(self): # XXX using private method! gen = sock._getNextRecord() - # XXX this should be TLSLocalAlert as we're expected to tell the other - # side they have sent us garbage - with self.assertRaises(SyntaxError) as context: + with self.assertRaises(TLSLocalAlert) as context: next(gen) - #self.assertEqual(str(context.exception), "illegal_parameter") + self.assertEqual(str(context.exception), "illegal_parameter") def test__getNextRecord_with_too_big_record(self): mockSock = MockSocket(bytearray( From 17cdbf8959ffc39877cd778072bb1e1e4a8e6f72 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 25 Mar 2015 16:18:23 +0100 Subject: [PATCH 015/574] use RecordSocket.send() --- tlslite/tlsrecordlayer.py | 82 +++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index ffb51def..09e0bd04 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -623,53 +623,43 @@ def _sendMsg(self, msg, randomizeFirstBlock = True): b += macBytes b = self._writeState.encContext.encrypt(b) - #Add record header and send - r = RecordHeader3().create(self.version, contentType, len(b)) - s = r.write() + b - while 1: - try: - bytesSent = self.sock.send(s) #Might raise socket.error - except socket.error as why: - if why.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): - yield 1 - continue - else: - # The socket was unexpectedly closed. The tricky part - # is that there may be an alert sent by the other party - # sitting in the read buffer. So, if we get here after - # handshaking, we will just raise the error and let the - # caller read more data if it would like, thus stumbling - # upon the error. - # - # However, if we get here DURING handshaking, we take - # it upon ourselves to see if the next message is an - # Alert. - if contentType == ContentType.handshake: - - # See if there's an alert record - # Could raise socket.error or TLSAbruptCloseError - for result in self._getNextRecord(): - if result in (0,1): - yield result - - # Closes the socket - self._shutdown(False) - - # If we got an alert, raise it - recordHeader, p = result - if recordHeader.type == ContentType.alert: - alert = Alert().parse(p) - raise TLSRemoteAlert(alert) - else: - # If we got some other message who know what - # the remote side is doing, just go ahead and - # raise the socket.error - raise - if bytesSent == len(s): - return - s = s[bytesSent:] - yield 1 + msg = Message(contentType, b) + try: + for result in self._recordSocket.send(msg): + if result in (0, 1): + yield result + except socket.error: + # The socket was unexpectedly closed. The tricky part + # is that there may be an alert sent by the other party + # sitting in the read buffer. So, if we get here after + # handshaking, we will just raise the error and let the + # caller read more data if it would like, thus stumbling + # upon the error. + # + # However, if we get here DURING handshaking, we take + # it upon ourselves to see if the next message is an + # Alert. + if contentType == ContentType.handshake: + + # See if there's an alert record + # Could raise socket.error or TLSAbruptCloseError + for result in self._getNextRecord(): + if result in (0, 1): + yield result + + # Closes the socket + self._shutdown(False) + # If we got an alert, raise it + recordHeader, p = result + if recordHeader.type == ContentType.alert: + alert = Alert().parse(p) + raise TLSRemoteAlert(alert) + else: + # If we got some other message who know what + # the remote side is doing, just go ahead and + # raise the socket.error + raise def _getMsg(self, expectedType, secondaryType=None, constructorType=None): try: From d3a2493d5b3c3ba95b1b48f87f2815ed51648f86 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 May 2015 12:28:57 +0200 Subject: [PATCH 016/574] add landscape.io and codeclimate --- .codeclimate.yml | 9 +++++++++ .landscape.yaml | 12 ++++++++++++ README.md | 2 ++ 3 files changed, 23 insertions(+) create mode 100644 .codeclimate.yml create mode 100644 .landscape.yaml diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 00000000..0c7446b9 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,9 @@ +languages: + Ruby: true + JavaScript: true + PHP: true + Python: true +exclude_paths: + - "tests/*" + - "scripts/*" + - "unit_tests/*" diff --git a/.landscape.yaml b/.landscape.yaml new file mode 100644 index 00000000..7bddad22 --- /dev/null +++ b/.landscape.yaml @@ -0,0 +1,12 @@ +doc-warnings: true + +test-warnings: false + +strictness: medium + +max-line-length: 80 + +ignore-paths: + - tests + - unit_tests + - scripts diff --git a/README.md b/README.md index 83190f29..a6f063ce 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ https://github.com/tomato42/tlslite-ng/ [![Build Status](https://travis-ci.org/tomato42/tlslite-ng.svg?branch=master)](https://travis-ci.org/tomato42/tlslite-ng) [![Coverage Status](https://coveralls.io/repos/tomato42/tlslite-ng/badge.svg?branch=master)](https://coveralls.io/r/tomato42/tlslite-ng?branch=master) +[![Code Health](https://landscape.io/github/tomato42/tlslite-ng/master/landscape.svg?style=flat)](https://landscape.io/github/tomato42/tlslite-ng/master) +[![Code Climate](https://codeclimate.com/github/tomato42/tlslite-ng/badges/gpa.svg)](https://codeclimate.com/github/tomato42/tlslite-ng) Table of Contents From dc10604b80b07a135a5b20f1ae933831975511d3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 May 2015 12:19:15 +0200 Subject: [PATCH 017/574] rm duplication between RecordHeader2 and 3 initializers can be shared between RecordHeader2 and RecordHeader3 also add some documentation to the classes --- tlslite/messages.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index d5e89afc..cbcbcfb3 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -20,12 +20,24 @@ from .utils.tackwrapper import * from .extensions import * -class RecordHeader3(object): - def __init__(self): +class RecordHeader(object): + + """Generic interface to SSLv2 and SSLv3 (and later) record headers""" + + def __init__(self, ssl2): + """define instance variables""" self.type = 0 - self.version = (0,0) + self.version = (0, 0) self.length = 0 - self.ssl2 = False + self.ssl2 = ssl2 + +class RecordHeader3(RecordHeader): + + """SSLv3 (and later) TLS record header""" + + def __init__(self): + """Define a SSLv3 style class""" + super(RecordHeader3, self).__init__(ssl2=False) def create(self, version, type, length): self.type = type @@ -66,18 +78,17 @@ def __repr__(self): return "RecordHeader3(type={0}, version=({1[0]}.{1[1]}), length={2})".\ format(self.type, self.version, self.length) -class RecordHeader2(object): +class RecordHeader2(RecordHeader): + """SSLv2 record header (just reading)""" def __init__(self): - self.type = 0 - self.version = (0,0) - self.length = 0 - self.ssl2 = True + """Define a SSLv2 style class""" + super(RecordHeader2, self).__init__(ssl2=True) def parse(self, p): if p.get(1)!=128: raise SyntaxError() self.type = ContentType.handshake - self.version = (2,0) + self.version = (2, 0) #We don't support 2-byte-length-headers; could be a problem self.length = p.get(1) return self From 6aebb42de64e64f22bb5d2761fb342fb402ed6c9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 May 2015 12:24:23 +0200 Subject: [PATCH 018/574] trivial formatting fixes in RecordHeader2 and 3 --- tlslite/messages.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index cbcbcfb3..b9040d24 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -40,30 +40,33 @@ def __init__(self): super(RecordHeader3, self).__init__(ssl2=False) def create(self, version, type, length): + """Set object values for writing (serialisation)""" self.type = type self.version = version self.length = length return self def write(self): - w = Writer() - w.add(self.type, 1) - w.add(self.version[0], 1) - w.add(self.version[1], 1) - w.add(self.length, 2) - return w.bytes - - def parse(self, p): - self.type = p.get(1) - self.version = (p.get(1), p.get(1)) - self.length = p.get(2) + """Serialise object to bytearray""" + writer = Writer() + writer.add(self.type, 1) + writer.add(self.version[0], 1) + writer.add(self.version[1], 1) + writer.add(self.length, 2) + return writer.bytes + + def parse(self, parser): + """Deserialise object from Parser""" + self.type = parser.get(1) + self.version = (parser.get(1), parser.get(1)) + self.length = parser.get(2) self.ssl2 = False return self @property def type_name(self): matching = [x[0] for x in ContentType.__dict__.items() - if x[1] == self.type] + if x[1] == self.type] if len(matching) == 0: return "unknown(" + str(self.type) + ")" else: @@ -84,13 +87,14 @@ def __init__(self): """Define a SSLv2 style class""" super(RecordHeader2, self).__init__(ssl2=True) - def parse(self, p): - if p.get(1)!=128: + def parse(self, parser): + """Deserialise object from Parser""" + if parser.get(1) != 128: raise SyntaxError() self.type = ContentType.handshake self.version = (2, 0) - #We don't support 2-byte-length-headers; could be a problem - self.length = p.get(1) + #XXX We don't support 2-byte-length-headers; could be a problem + self.length = parser.get(1) return self class Message(object): From 91fc348297aea155836049837c224e474d92adb8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 May 2015 12:52:36 +0200 Subject: [PATCH 019/574] make sure loop variables are initialised fixes pylint W0631 undefined-loop-variable. this is not entirely necessary, as _recvHeader() and _sockRecvAll() will never return without yielding at least once --- tlslite/recordlayer.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index fc12b3ce..38ff0859 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -105,19 +105,25 @@ def _recvHeader(self): #Read the next record header buf = bytearray(0) ssl2 = False + + result = None for result in self._sockRecvAll(1): if result in (0, 1): yield result else: break + assert result is not None + buf += result if buf[0] in ContentType.all: ssl2 = False # SSLv3 record layer header is 5 bytes long, we already read 1 + result = None for result in self._sockRecvAll(4): if result in (0, 1): yield result else: break + assert result is not None buf += result # XXX this should be 'buf[0] & 128', otherwise hello messages longer # than 127 bytes won't be properly parsed @@ -125,10 +131,12 @@ def _recvHeader(self): ssl2 = True # in SSLv2 we need to read 2 bytes in total to know the size of # header, we already read 1 + result = None for result in self._sockRecvAll(1): if result in (0, 1): yield result else: break + assert result is not None buf += result else: raise TLSIllegalParameterException( @@ -160,10 +168,12 @@ def recv(self): malformed """ + record = None for record in self._recvHeader(): if record in (0, 1): yield record else: break + assert record is not None #Check the record header fields # 18432 = 2**14 (basic record size limit) + 1024 (maximum compression @@ -173,11 +183,14 @@ def recv(self): #Read the record contents buf = bytearray(0) + + result = None for result in self._sockRecvAll(record.length): if result in (0, 1): yield result else: break + assert result is not None + buf += result yield (record, buf) - From 2442bc9e65e752f72c8127f7e010a845b50a25bf Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 May 2015 12:54:00 +0200 Subject: [PATCH 020/574] remove unused import --- tlslite/tlsrecordlayer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 09e0bd04..75c148c7 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -22,7 +22,6 @@ from .recordlayer import RecordSocket import socket -import errno import traceback class _ConnectionState(object): From 2b5e914d483eba68fd6b3a7066739adfb4cf88fd Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:34:10 +0200 Subject: [PATCH 021/574] generic TLS message add generic TLS message class --- tlslite/messages.py | 19 +++++++++++++++++++ unit_tests/test_tlslite_messages.py | 14 +++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 4bfb6b41..d5e89afc 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -82,6 +82,25 @@ def parse(self, p): self.length = p.get(1) return self +class Message(object): + + """Generic TLS message""" + + def __init__(self, contentType, data): + """ + Initialize object with specified contentType and data + + @type contentType: int + @param contentType: TLS record layer content type of associated data + @type data: bytearray + @param data: data + """ + self.contentType = contentType + self.data = data + + def write(self): + """Return serialised object data""" + return self.data class Alert(object): def __init__(self): diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index d7587601..ee4f6754 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -8,7 +8,7 @@ except ImportError: import unittest from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ - RecordHeader2 + RecordHeader2, Message from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType @@ -16,6 +16,18 @@ SRPExtension, TLSExtension from tlslite.errors import TLSInternalError +class TestMessage(unittest.TestCase): + def test___init__(self): + msg = Message(ContentType.application_data, bytearray(0)) + + self.assertEqual(ContentType.application_data, msg.contentType) + self.assertEqual(bytearray(0), msg.data) + + def test_write(self): + msg = Message(0, bytearray(10)) + + self.assertEqual(bytearray(10), msg.write()) + class TestClientHello(unittest.TestCase): def test___init__(self): client_hello = ClientHello() From 4d40620bef86574a4a8d00e2ffd8627fbd6fdab6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:35:26 +0200 Subject: [PATCH 022/574] add mock version of socket.sock --- unit_tests/mocksock.py | 68 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 unit_tests/mocksock.py diff --git a/unit_tests/mocksock.py b/unit_tests/mocksock.py new file mode 100644 index 00000000..bd05235a --- /dev/null +++ b/unit_tests/mocksock.py @@ -0,0 +1,68 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +import socket +import errno +class MockSocket(socket.socket): + def __init__(self, buf, maxRet=None, maxWrite=None, blockEveryOther=False): + self.index = 0 + self.buf = buf + self.sent = [] + self.closed = False + self.maxRet = maxRet + self.maxWrite = maxWrite + self.blockEveryOther = blockEveryOther + self.blockRead = False + self.blockWrite = False + + def __repr__(self): + return "MockSocket(index={0}, buf={1!r}, sent={2!r})".format( + self.index, self.buf, self.sent) + + def recv(self, size): + if self.closed: + raise ValueError("Read from closed socket") + + if self.blockEveryOther: + if self.blockRead: + self.blockRead = False + raise socket.error(errno.EWOULDBLOCK) + else: + self.blockRead = True + + if size == 0: + return bytearray(0) + if self.maxRet is not None and self.maxRet < size: + size = self.maxRet + if len(self.buf[self.index:]) == 0: + raise socket.error(errno.EWOULDBLOCK) + elif len(self.buf[self.index:]) < size: + ret = self.buf[self.index:] + self.index = len(self.buf) + return ret + else: + ret = self.buf[self.index:self.index+size] + self.index+=size + return ret + + def send(self, data): + if self.closed: + raise ValueError("Write to closed socket") + + if self.blockEveryOther: + if self.blockWrite: + self.blockWrite = False + raise socket.error(errno.EWOULDBLOCK) + else: + self.blockWrite = True + + if self.maxWrite is None or len(data) < self.maxWrite: + self.sent.append(data) + return len(data) + + self.sent.append(data[:self.maxWrite]) + return self.maxWrite + + def close(self): + self.closed = True From da47987a75e994549df18f5ff776ca8ce3b26a2c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:37:06 +0200 Subject: [PATCH 023/574] exceptions to internally handle errors extend exceptions hierarchy to allow for internal handling of errors --- tlslite/errors.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/tlslite/errors.py b/tlslite/errors.py index 2c523176..35878987 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -14,7 +14,15 @@ from .constants import AlertDescription, AlertLevel -class TLSError(Exception): +class BaseTLSException(Exception): + """Metaclass for TLS Lite exceptions. + + Look to L{TLSError} for exceptions that should be caught by tlslite + consumers + """ + pass + +class TLSError(BaseTLSException): """Base class for all TLS Lite exceptions.""" def __str__(self): @@ -173,5 +181,20 @@ class TLSUnsupportedError(TLSError): pass class TLSInternalError(TLSError): - """The internal state of object is unexpected or invalid""" + """The internal state of object is unexpected or invalid. + + Caused by incorrect use of API. + """ + pass + +class TLSProtocolException(BaseTLSException): + """Exceptions used internally for handling errors in received messages""" + pass + +class TLSIllegalParameterException(TLSProtocolException): + """Parameters specified in message were incorrect or invalid""" + pass + +class TLSRecordOverflow(TLSProtocolException): + """The received record size was too big""" pass From 67e70c58236548091d8773ee8f7da3dad1ad4d42 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:37:37 +0200 Subject: [PATCH 024/574] add implementation of record layer socket --- tlslite/recordlayer.py | 183 +++++++++++++++ unit_tests/test_tlslite_recordlayer.py | 299 +++++++++++++++++++++++++ 2 files changed, 482 insertions(+) create mode 100644 tlslite/recordlayer.py create mode 100644 unit_tests/test_tlslite_recordlayer.py diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py new file mode 100644 index 00000000..fc12b3ce --- /dev/null +++ b/tlslite/recordlayer.py @@ -0,0 +1,183 @@ +# Copyright (c) 2014, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +"""Implementation of the TLS Record Layer protocol""" + +import socket +import errno +from tlslite.constants import ContentType +from .messages import RecordHeader3, RecordHeader2 +from .utils.codec import Parser +from .errors import TLSRecordOverflow, TLSIllegalParameterException,\ + TLSAbruptCloseError + +class RecordSocket(object): + + """Socket wrapper for reading and writing TLS Records""" + + def __init__(self, sock): + """ + Assign socket to wrapper + + @type sock: socket.socket + """ + self.sock = sock + self.version = (0, 0) + + def _sockSendAll(self, data): + """ + Send all data through socket + + @type data: bytearray + @param data: data to send + @raise socket.error: when write to socket failed + """ + while 1: + try: + bytesSent = self.sock.send(data) + except socket.error as why: + if why.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): + yield 1 + continue + raise + + if bytesSent == len(data): + return + data = data[bytesSent:] + yield 1 + + def send(self, msg): + """ + Send the message through socket. + + @type msg: bytearray + @param msg: TLS message to send + @raise socket.error: when write to socket failed + """ + + data = msg.write() + + header = RecordHeader3().create(self.version, + msg.contentType, + len(data)) + + data = header.write() + data + + for result in self._sockSendAll(data): + yield result + + def _sockRecvAll(self, length): + """ + Read exactly the amount of bytes specified in L{length} from raw socket. + + @rtype: generator + @return: generator that will return 0 or 1 in case the socket is non + blocking and would block and bytearray in case the read finished + @raise TLSAbruptCloseError: when the socket closed + """ + + buf = bytearray(0) + + if length == 0: + yield buf + + while True: + try: + socketBytes = self.sock.recv(length - len(buf)) + except socket.error as why: + if why.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): + yield 0 + continue + else: + raise + + #if the connection closed, raise socket error + if len(socketBytes) == 0: + raise TLSAbruptCloseError() + + buf += bytearray(socketBytes) + if len(buf) == length: + yield buf + + def _recvHeader(self): + """Read a single record header from socket""" + #Read the next record header + buf = bytearray(0) + ssl2 = False + for result in self._sockRecvAll(1): + if result in (0, 1): + yield result + else: break + buf += result + + if buf[0] in ContentType.all: + ssl2 = False + # SSLv3 record layer header is 5 bytes long, we already read 1 + for result in self._sockRecvAll(4): + if result in (0, 1): + yield result + else: break + buf += result + # XXX this should be 'buf[0] & 128', otherwise hello messages longer + # than 127 bytes won't be properly parsed + elif buf[0] == 128: + ssl2 = True + # in SSLv2 we need to read 2 bytes in total to know the size of + # header, we already read 1 + for result in self._sockRecvAll(1): + if result in (0, 1): + yield result + else: break + buf += result + else: + raise TLSIllegalParameterException( + "Record header type doesn't specify known type") + + #Parse the record header + if ssl2: + record = RecordHeader2().parse(Parser(buf)) + else: + record = RecordHeader3().parse(Parser(buf)) + + yield record + + def recv(self): + """ + Read a single record from socket, handles both SSLv2 and SSLv3 record + layer + + @rtype: generator + @return: generator that returns 0 or 1 in case the read would be + blocking or a tuple containing record header (object) and record + data (bytearray) read from socket + @raise socket.error: In case of network error + @raise TLSAbruptCloseError: When the socket was closed on the other + side in middle of record receiving + @raise TLSRecordOverflow: When the received record was longer than + allowed by TLS + @raise TLSIllegalParameterException: When the record header was + malformed + """ + + for record in self._recvHeader(): + if record in (0, 1): + yield record + else: break + + #Check the record header fields + # 18432 = 2**14 (basic record size limit) + 1024 (maximum compression + # overhead) + 1024 (maximum encryption overhead) + if record.length > 18432: + raise TLSRecordOverflow() + + #Read the record contents + buf = bytearray(0) + for result in self._sockRecvAll(record.length): + if result in (0, 1): + yield result + else: break + buf += result + + yield (record, buf) + diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py new file mode 100644 index 00000000..34e9c9be --- /dev/null +++ b/unit_tests/test_tlslite_recordlayer.py @@ -0,0 +1,299 @@ +# Copyright (c) 2014, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call + +import socket +import errno + +from tlslite.messages import Message +from tlslite.recordlayer import RecordSocket +from tlslite.constants import ContentType +from unit_tests.mocksock import MockSocket +from tlslite.errors import TLSRecordOverflow, TLSIllegalParameterException,\ + TLSAbruptCloseError + +class TestRecordSocket(unittest.TestCase): + def test___init__(self): + sock = RecordSocket(-42) + + self.assertIsNotNone(sock) + self.assertEqual(sock.sock, -42) + self.assertEqual(sock.version, (0, 0)) + + def test_send(self): + mockSock = MockSocket(bytearray(0)) + sock = RecordSocket(mockSock) + sock.version = (3, 3) + + msg = Message(ContentType.handshake, bytearray(10)) + + for result in sock.send(msg): + if result in (0, 1): + self.assertTrue(False, "Blocking socket") + else: break + + self.assertEqual(len(mockSock.sent), 1) + self.assertEqual(bytearray( + b'\x16' + # handshake message + b'\x03\x03' + # version + b'\x00\x0a' + # payload length + b'\x00'*10 # payload + ), mockSock.sent[0]) + + def test_send_with_very_slow_socket(self): + mockSock = MockSocket(bytearray(0), maxWrite=1, blockEveryOther=True) + sock = RecordSocket(mockSock) + + msg = Message(ContentType.handshake, bytearray(b'\x32'*2)) + + gotRetry = False + for result in sock.send(msg): + if result in (0, 1): + gotRetry = True + else: break + + self.assertTrue(gotRetry) + self.assertEqual([ + bytearray(b'\x16'), # handshake message + bytearray(b'\x00'), bytearray(b'\x00'), # version (unset) + bytearray(b'\x00'), bytearray(b'\x02'), # payload length + bytearray(b'\x32'), bytearray(b'\x32')], + mockSock.sent) + + def test_send_with_errored_out_socket(self): + mockSock = mock.MagicMock() + mockSock.send.side_effect = socket.error(errno.ETIMEDOUT) + + sock = RecordSocket(mockSock) + + msg = Message(ContentType.handshake, bytearray(10)) + + gen = sock.send(msg) + + with self.assertRaises(socket.error): + next(gen) + + def test_recv(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + )) + sock = RecordSocket(mockSock) + + for result in sock.recv(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + + self.assertEqual(data, bytearray(4)) + self.assertEqual(header.type, ContentType.handshake) + self.assertEqual(header.version, (3, 3)) + self.assertEqual(header.length, 4) + + def test_recv_stops_itelf(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + )) + sock = RecordSocket(mockSock) + + for result in sock.recv(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + + header, data = result + + self.assertEqual(data, bytearray(4)) + self.assertEqual(header.type, ContentType.handshake) + self.assertEqual(header.version, (3, 3)) + self.assertEqual(header.length, 4) + + def test_recv_with_trickling_socket(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + ), maxRet=1) + + sock = RecordSocket(mockSock) + + for result in sock.recv(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + + self.assertEqual(bytearray(4), data) + + def test_recv_with_blocking_socket(self): + mockSock = mock.MagicMock() + mockSock.recv.side_effect = socket.error(errno.EWOULDBLOCK) + + sock = RecordSocket(mockSock) + + gen = sock.recv() + + self.assertEqual(0, next(gen)) + + def test_recv_with_errored_out_socket(self): + mockSock = mock.MagicMock() + mockSock.recv.side_effect = socket.error(errno.ETIMEDOUT) + + sock = RecordSocket(mockSock) + + gen = sock.recv() + + with self.assertRaises(socket.error): + next(gen) + + def test_recv_with_empty_socket(self): + mockSock = mock.MagicMock() + mockSock.recv.side_effect = [bytearray(0)] + + sock = RecordSocket(mockSock) + + gen = sock.recv() + + with self.assertRaises(TLSAbruptCloseError): + next(gen) + + def test_recv_with_slow_socket(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + ), maxRet=1, blockEveryOther=True) + + sock = RecordSocket(mockSock) + + gotRetry = False + for result in sock.recv(): + if result in (0, 1): + gotRetry = True + else: break + + header, data = result + + self.assertTrue(gotRetry) + self.assertEqual(bytearray(4), data) + + def test_recv_with_malformed_record(self): + mockSock = MockSocket(bytearray( + b'\x01' + # wrong type + b'\x03\x03' + # TLSv1.2 + b'\x00\x01' + # length + b'\x00')) + + sock = RecordSocket(mockSock) + + gen = sock.recv() + + with self.assertRaises(TLSIllegalParameterException): + next(gen) + + def test_recv_with_too_big_record(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\xff\xff' + # length + b'\x00'*65536)) + + sock = RecordSocket(mockSock) + + gen = sock.recv() + + with self.assertRaises(TLSRecordOverflow): + next(gen) + + + def test_recv_with_empty_data(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x00')) # length + + sock = RecordSocket(mockSock) + + gen = sock.recv() + + for result in sock.recv(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + + self.assertEqual(ContentType.handshake, header.type) + self.assertEqual((3, 3), header.version) + self.assertEqual(0, header.length) + + self.assertEqual(bytearray(0), data) + + def test_recv_with_SSL2_record(self): + mockSock = MockSocket(bytearray( + b'\x80' + # tag + b'\x04' + # length + b'\x00'*4)) + + sock = RecordSocket(mockSock) + + for result in sock.recv(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + + self.assertTrue(header.ssl2) + self.assertEqual(ContentType.handshake, header.type) + self.assertEqual(4, header.length) + self.assertEqual((2, 0), header.version) + + self.assertEqual(bytearray(4), data) + + def test_recv_with_not_complete_SSL2_record(self): + mockSock = MockSocket(bytearray( + b'\x80' + # tag + b'\x04' + # length + b'\x00'*3)) + + sock = RecordSocket(mockSock) + + for result in sock.recv(): + break + + self.assertEqual(0, result) + + def test_recv_with_SSL2_record_with_incomplete_header(self): + mockSock = MockSocket(bytearray( + b'\x80' # tag + )) + + sock = RecordSocket(mockSock) + + for result in sock.recv(): + break + + self.assertEqual(0, result) From 1f6c9cbd494471a7082807cb9ffbe9da222bd251 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:39:07 +0200 Subject: [PATCH 025/574] add retention tests to TLSRecordLayer --- tlslite/tlsrecordlayer.py | 12 +- unit_tests/test_tlslite_tlsrecordlayer.py | 297 ++++++++++++++++++++++ 2 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 unit_tests/test_tlslite_tlsrecordlayer.py diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index ecabd561..e6ec4fcb 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -120,7 +120,7 @@ def __init__(self, sock): self._handshake_sha256 = hashlib.sha256() #TLS Protocol Version - self.version = (0,0) #read-only + self._version = (0, 0) #read-only self._versionCheck = False #Once we choose a version, this is True #Current and Pending connection states @@ -149,6 +149,16 @@ def __init__(self, sock): #Fault we will induce, for testing purposes self.fault = None + @property + def version(self): + """Get the SSL protocol version of connection""" + return self._version + + @version.setter + def version(self, value): + """Set the SSL protocol version of connection""" + self._version = value + def clearReadBuffer(self): self._readBuffer = b'' diff --git a/unit_tests/test_tlslite_tlsrecordlayer.py b/unit_tests/test_tlslite_tlsrecordlayer.py new file mode 100644 index 00000000..e37b4775 --- /dev/null +++ b/unit_tests/test_tlslite_tlsrecordlayer.py @@ -0,0 +1,297 @@ +# Copyright (c) 2014, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call + +import socket +import errno +from tlslite.tlsrecordlayer import TLSRecordLayer +from tlslite.constants import ContentType +from tlslite.errors import TLSAbruptCloseError, TLSLocalAlert +from tlslite.messages import Message +from unit_tests.mocksock import MockSocket + +class TestTLSRecordLayer(unittest.TestCase): + def test___init__(self): + record_layer = TLSRecordLayer(None) + + self.assertIsNotNone(record_layer) + self.assertIsInstance(record_layer, TLSRecordLayer) + + def test__getNextRecord(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + )) + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + for result in sock._getNextRecord(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + data = data.bytes + + self.assertEqual(data, bytearray(4)) + self.assertEqual(header.type, ContentType.handshake) + self.assertEqual(header.version, (3, 3)) + self.assertEqual(header.length, 4) + + def test__getNextRecord_stops_itelf(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + )) + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + for result in sock._getNextRecord(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + + header, data = result + data = data.bytes + + self.assertEqual(data, bytearray(4)) + self.assertEqual(header.type, ContentType.handshake) + self.assertEqual(header.version, (3, 3)) + self.assertEqual(header.length, 4) + + def test__getNextRecord_with_trickling_socket(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + ), maxRet=1) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + for result in sock._getNextRecord(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + data = data.bytes + + self.assertEqual(bytearray(4), data) + + def test__getNextRecord_with_blocking_socket(self): + mockSock = mock.MagicMock() + mockSock.recv.side_effect = socket.error(errno.EWOULDBLOCK) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + gen = sock._getNextRecord() + + self.assertEqual(0, next(gen)) + + def test__getNextRecord_with_errored_out_socket(self): + mockSock = mock.MagicMock() + mockSock.recv.side_effect = socket.error(errno.ETIMEDOUT) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + gen = sock._getNextRecord() + + with self.assertRaises(socket.error): + next(gen) + + def test__getNextRecord_with_empty_socket(self): + mockSock = mock.MagicMock() + mockSock.recv.side_effect = [bytearray(0)] + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + gen = sock._getNextRecord() + + with self.assertRaises(TLSAbruptCloseError): + next(gen) + + def test__getNextRecord_with_slow_socket(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x00'*4 + ), maxRet=1, blockEveryOther=True) + + sock = TLSRecordLayer(mockSock) + + gotRetry = False + # XXX using private method! + for result in sock._getNextRecord(): + if result in (0, 1): + gotRetry = True + else: break + + header, data = result + data = data.bytes + + self.assertTrue(gotRetry) + self.assertEqual(bytearray(4), data) + + def test__getNextRecord_with_malformed_record(self): + mockSock = MockSocket(bytearray( + b'\x01' + # wrong type + b'\x03\x03' + # TLSv1.2 + b'\x00\x01' + # length + b'\x00')) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + gen = sock._getNextRecord() + + # XXX this should be TLSLocalAlert as we're expected to tell the other + # side they have sent us garbage + with self.assertRaises(SyntaxError) as context: + next(gen) + + #self.assertEqual(str(context.exception), "illegal_parameter") + + def test__getNextRecord_with_too_big_record(self): + mockSock = MockSocket(bytearray( + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\xff\xff' + # length + b'\x00'*65536)) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + gen = sock._getNextRecord() + + with self.assertRaises(TLSLocalAlert) as context: + next(gen) + + self.assertEqual(str(context.exception), "record_overflow") + + def test__getNextRecord_with_SSL2_record(self): + mockSock = MockSocket(bytearray( + b'\x80' + # tag + b'\x04' + # length + b'\x00'*4)) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + for result in sock._getNextRecord(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, data = result + data = data.bytes + + self.assertTrue(header.ssl2) + self.assertEqual(ContentType.handshake, header.type) + self.assertEqual(4, header.length) + self.assertEqual((2, 0), header.version) + + self.assertEqual(bytearray(4), data) + + def test__getNextRecord_with_not_complete_SSL2_record(self): + mockSock = MockSocket(bytearray( + b'\x80' + # tag + b'\x04' + # length + b'\x00'*3)) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method! + for result in sock._getNextRecord(): + break + + self.assertEqual(0, result) + + def test__getNextRecord_with_SSL2_record_with_incomplete_header(self): + mockSock = MockSocket(bytearray( + b'\x80' # tag + )) + + sock = TLSRecordLayer(mockSock) + + # XXX using private method + for result in sock._getNextRecord(): + break + + self.assertEqual(0, result) + + def test__sendMsg(self): + mockSock = MockSocket(bytearray(0)) + sock = TLSRecordLayer(mockSock) + sock.version = (3, 3) + + msg = Message(ContentType.handshake, bytearray(10)) + + # XXX using private method + for result in sock._sendMsg(msg, False): + if result in (0, 1): + self.assertTrue(False, "Blocking socket") + else: break + + self.assertEqual(len(mockSock.sent), 1) + self.assertEqual(bytearray( + b'\x16' + # handshake message + b'\x03\x03' + # version + b'\x00\x0a' + # payload length + b'\x00'*10 # payload + ), mockSock.sent[0]) + + def test__sendMsg_with_very_slow_socket(self): + mockSock = MockSocket(bytearray(0), maxWrite=1, blockEveryOther=True) + sock = TLSRecordLayer(mockSock) + + msg = Message(ContentType.handshake, bytearray(b'\x32'*2)) + + gotRetry = False + # XXX using private method! + for result in sock._sendMsg(msg, False): + if result in (0, 1): + gotRetry = True + else: break + + self.assertTrue(gotRetry) + self.assertEqual([ + bytearray(b'\x16'), # handshake message + bytearray(b'\x00'), bytearray(b'\x00'), # version (unset) + bytearray(b'\x00'), bytearray(b'\x02'), # payload length + bytearray(b'\x32'), bytearray(b'\x32')], + mockSock.sent) + + def test__sendMsg_with_errored_out_socket(self): + mockSock = mock.MagicMock() + mockSock.send.side_effect = socket.error(errno.ETIMEDOUT) + + sock = TLSRecordLayer(mockSock) + + msg = Message(ContentType.handshake, bytearray(10)) + + gen = sock._sendMsg(msg, False) + + with self.assertRaises(TLSAbruptCloseError): + next(gen) From 5a43f1d3499ae90c7782fb5bddc0368b49ad35ec Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 May 2015 19:39:40 +0200 Subject: [PATCH 026/574] add RecordSocket initialization to TLSRecordLayer --- tlslite/tlsrecordlayer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index e6ec4fcb..ee58a77a 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -19,6 +19,7 @@ from .mathtls import * from .constants import * from .utils.cryptomath import getRandomBytes +from .recordlayer import RecordSocket import socket import errno @@ -102,6 +103,7 @@ class TLSRecordLayer(object): def __init__(self, sock): self.sock = sock + self._recordSocket = RecordSocket(sock) #My session object (Session instance; read-only) self.session = None @@ -158,6 +160,7 @@ def version(self): def version(self, value): """Set the SSL protocol version of connection""" self._version = value + self._recordSocket.version = value def clearReadBuffer(self): self._readBuffer = b'' From 0153cd0f4746c031cdc6f5246d21a22db0adbab2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 25 Mar 2015 15:01:14 +0100 Subject: [PATCH 027/574] use RecordSocket.recv() --- tlslite/tlsrecordlayer.py | 70 ++++------------------- unit_tests/test_tlslite_tlsrecordlayer.py | 6 +- 2 files changed, 12 insertions(+), 64 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index ee58a77a..ffb51def 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -840,68 +840,18 @@ def _getNextRecord(self): yield (recordHeader, Parser(b)) return - #Otherwise... - #Read the next record header - b = bytearray(0) - recordHeaderLength = 1 - ssl2 = False - while 1: - try: - s = self.sock.recv(recordHeaderLength-len(b)) - except socket.error as why: - if why.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): - yield 0 - continue - else: - raise - - #If the connection was abruptly closed, raise an error - if len(s)==0: - raise TLSAbruptCloseError() - - b += bytearray(s) - if len(b)==1: - if b[0] in ContentType.all: - ssl2 = False - recordHeaderLength = 5 - elif b[0] == 128: - ssl2 = True - recordHeaderLength = 2 - else: - raise SyntaxError() - if len(b) == recordHeaderLength: - break - - #Parse the record header - if ssl2: - r = RecordHeader2().parse(Parser(b)) - else: - r = RecordHeader3().parse(Parser(b)) - - #Check the record header fields - if r.length > 18432: + try: + for result in self._recordSocket.recv(): + if result in (0, 1): + yield result + else: break + (r, b) = result + except TLSRecordOverflow: for result in self._sendError(AlertDescription.record_overflow): yield result - - #Read the record contents - b = bytearray(0) - while 1: - try: - s = self.sock.recv(r.length - len(b)) - except socket.error as why: - if why.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): - yield 0 - continue - else: - raise - - #If the connection is closed, raise a socket error - if len(s)==0: - raise TLSAbruptCloseError() - - b += bytearray(s) - if len(b) == r.length: - break + except TLSIllegalParameterException: + for result in self._sendError(AlertDescription.illegal_parameter): + yield result #Check the record header fields (2) #We do this after reading the contents from the socket, so that diff --git a/unit_tests/test_tlslite_tlsrecordlayer.py b/unit_tests/test_tlslite_tlsrecordlayer.py index e37b4775..e311808c 100644 --- a/unit_tests/test_tlslite_tlsrecordlayer.py +++ b/unit_tests/test_tlslite_tlsrecordlayer.py @@ -166,12 +166,10 @@ def test__getNextRecord_with_malformed_record(self): # XXX using private method! gen = sock._getNextRecord() - # XXX this should be TLSLocalAlert as we're expected to tell the other - # side they have sent us garbage - with self.assertRaises(SyntaxError) as context: + with self.assertRaises(TLSLocalAlert) as context: next(gen) - #self.assertEqual(str(context.exception), "illegal_parameter") + self.assertEqual(str(context.exception), "illegal_parameter") def test__getNextRecord_with_too_big_record(self): mockSock = MockSocket(bytearray( From 629ca91d510d5828df68efbfdc170538ef950d0c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 25 Mar 2015 16:18:23 +0100 Subject: [PATCH 028/574] use RecordSocket.send() --- tlslite/tlsrecordlayer.py | 82 +++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index ffb51def..09e0bd04 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -623,53 +623,43 @@ def _sendMsg(self, msg, randomizeFirstBlock = True): b += macBytes b = self._writeState.encContext.encrypt(b) - #Add record header and send - r = RecordHeader3().create(self.version, contentType, len(b)) - s = r.write() + b - while 1: - try: - bytesSent = self.sock.send(s) #Might raise socket.error - except socket.error as why: - if why.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): - yield 1 - continue - else: - # The socket was unexpectedly closed. The tricky part - # is that there may be an alert sent by the other party - # sitting in the read buffer. So, if we get here after - # handshaking, we will just raise the error and let the - # caller read more data if it would like, thus stumbling - # upon the error. - # - # However, if we get here DURING handshaking, we take - # it upon ourselves to see if the next message is an - # Alert. - if contentType == ContentType.handshake: - - # See if there's an alert record - # Could raise socket.error or TLSAbruptCloseError - for result in self._getNextRecord(): - if result in (0,1): - yield result - - # Closes the socket - self._shutdown(False) - - # If we got an alert, raise it - recordHeader, p = result - if recordHeader.type == ContentType.alert: - alert = Alert().parse(p) - raise TLSRemoteAlert(alert) - else: - # If we got some other message who know what - # the remote side is doing, just go ahead and - # raise the socket.error - raise - if bytesSent == len(s): - return - s = s[bytesSent:] - yield 1 + msg = Message(contentType, b) + try: + for result in self._recordSocket.send(msg): + if result in (0, 1): + yield result + except socket.error: + # The socket was unexpectedly closed. The tricky part + # is that there may be an alert sent by the other party + # sitting in the read buffer. So, if we get here after + # handshaking, we will just raise the error and let the + # caller read more data if it would like, thus stumbling + # upon the error. + # + # However, if we get here DURING handshaking, we take + # it upon ourselves to see if the next message is an + # Alert. + if contentType == ContentType.handshake: + + # See if there's an alert record + # Could raise socket.error or TLSAbruptCloseError + for result in self._getNextRecord(): + if result in (0, 1): + yield result + + # Closes the socket + self._shutdown(False) + # If we got an alert, raise it + recordHeader, p = result + if recordHeader.type == ContentType.alert: + alert = Alert().parse(p) + raise TLSRemoteAlert(alert) + else: + # If we got some other message who know what + # the remote side is doing, just go ahead and + # raise the socket.error + raise def _getMsg(self, expectedType, secondaryType=None, constructorType=None): try: From 5f598554fbd1da3d311bc8b047e0ea5eb553990d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 May 2015 12:19:15 +0200 Subject: [PATCH 029/574] rm duplication between RecordHeader2 and 3 initializers can be shared between RecordHeader2 and RecordHeader3 also add some documentation to the classes --- tlslite/messages.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index d5e89afc..cbcbcfb3 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -20,12 +20,24 @@ from .utils.tackwrapper import * from .extensions import * -class RecordHeader3(object): - def __init__(self): +class RecordHeader(object): + + """Generic interface to SSLv2 and SSLv3 (and later) record headers""" + + def __init__(self, ssl2): + """define instance variables""" self.type = 0 - self.version = (0,0) + self.version = (0, 0) self.length = 0 - self.ssl2 = False + self.ssl2 = ssl2 + +class RecordHeader3(RecordHeader): + + """SSLv3 (and later) TLS record header""" + + def __init__(self): + """Define a SSLv3 style class""" + super(RecordHeader3, self).__init__(ssl2=False) def create(self, version, type, length): self.type = type @@ -66,18 +78,17 @@ def __repr__(self): return "RecordHeader3(type={0}, version=({1[0]}.{1[1]}), length={2})".\ format(self.type, self.version, self.length) -class RecordHeader2(object): +class RecordHeader2(RecordHeader): + """SSLv2 record header (just reading)""" def __init__(self): - self.type = 0 - self.version = (0,0) - self.length = 0 - self.ssl2 = True + """Define a SSLv2 style class""" + super(RecordHeader2, self).__init__(ssl2=True) def parse(self, p): if p.get(1)!=128: raise SyntaxError() self.type = ContentType.handshake - self.version = (2,0) + self.version = (2, 0) #We don't support 2-byte-length-headers; could be a problem self.length = p.get(1) return self From 4cc625dc23c07762a84328f08a5ddb670177c516 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 May 2015 12:24:23 +0200 Subject: [PATCH 030/574] trivial formatting fixes in RecordHeader2 and 3 --- tlslite/messages.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index cbcbcfb3..b9040d24 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -40,30 +40,33 @@ def __init__(self): super(RecordHeader3, self).__init__(ssl2=False) def create(self, version, type, length): + """Set object values for writing (serialisation)""" self.type = type self.version = version self.length = length return self def write(self): - w = Writer() - w.add(self.type, 1) - w.add(self.version[0], 1) - w.add(self.version[1], 1) - w.add(self.length, 2) - return w.bytes - - def parse(self, p): - self.type = p.get(1) - self.version = (p.get(1), p.get(1)) - self.length = p.get(2) + """Serialise object to bytearray""" + writer = Writer() + writer.add(self.type, 1) + writer.add(self.version[0], 1) + writer.add(self.version[1], 1) + writer.add(self.length, 2) + return writer.bytes + + def parse(self, parser): + """Deserialise object from Parser""" + self.type = parser.get(1) + self.version = (parser.get(1), parser.get(1)) + self.length = parser.get(2) self.ssl2 = False return self @property def type_name(self): matching = [x[0] for x in ContentType.__dict__.items() - if x[1] == self.type] + if x[1] == self.type] if len(matching) == 0: return "unknown(" + str(self.type) + ")" else: @@ -84,13 +87,14 @@ def __init__(self): """Define a SSLv2 style class""" super(RecordHeader2, self).__init__(ssl2=True) - def parse(self, p): - if p.get(1)!=128: + def parse(self, parser): + """Deserialise object from Parser""" + if parser.get(1) != 128: raise SyntaxError() self.type = ContentType.handshake self.version = (2, 0) - #We don't support 2-byte-length-headers; could be a problem - self.length = p.get(1) + #XXX We don't support 2-byte-length-headers; could be a problem + self.length = parser.get(1) return self class Message(object): From a4ca3a87abed34d71ab9399e8be31bfc50f14cd0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 May 2015 12:52:36 +0200 Subject: [PATCH 031/574] make sure loop variables are initialised fixes pylint W0631 undefined-loop-variable. this is not entirely necessary, as _recvHeader() and _sockRecvAll() will never return without yielding at least once --- tlslite/recordlayer.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index fc12b3ce..38ff0859 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -105,19 +105,25 @@ def _recvHeader(self): #Read the next record header buf = bytearray(0) ssl2 = False + + result = None for result in self._sockRecvAll(1): if result in (0, 1): yield result else: break + assert result is not None + buf += result if buf[0] in ContentType.all: ssl2 = False # SSLv3 record layer header is 5 bytes long, we already read 1 + result = None for result in self._sockRecvAll(4): if result in (0, 1): yield result else: break + assert result is not None buf += result # XXX this should be 'buf[0] & 128', otherwise hello messages longer # than 127 bytes won't be properly parsed @@ -125,10 +131,12 @@ def _recvHeader(self): ssl2 = True # in SSLv2 we need to read 2 bytes in total to know the size of # header, we already read 1 + result = None for result in self._sockRecvAll(1): if result in (0, 1): yield result else: break + assert result is not None buf += result else: raise TLSIllegalParameterException( @@ -160,10 +168,12 @@ def recv(self): malformed """ + record = None for record in self._recvHeader(): if record in (0, 1): yield record else: break + assert record is not None #Check the record header fields # 18432 = 2**14 (basic record size limit) + 1024 (maximum compression @@ -173,11 +183,14 @@ def recv(self): #Read the record contents buf = bytearray(0) + + result = None for result in self._sockRecvAll(record.length): if result in (0, 1): yield result else: break + assert result is not None + buf += result yield (record, buf) - From 9fc246ce264500322afb92d980cc316201f974ba Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 May 2015 12:54:00 +0200 Subject: [PATCH 032/574] remove unused import --- tlslite/tlsrecordlayer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 09e0bd04..75c148c7 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -22,7 +22,6 @@ from .recordlayer import RecordSocket import socket -import errno import traceback class _ConnectionState(object): From c5bb31941b80db8adb6518d6290c8a4ef471b926 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Mar 2015 12:07:58 +0100 Subject: [PATCH 033/574] implement RecordLayer.sendMessage() add RecordLayer class and assorted classes to implement sending encrypted messages using TLS record layer protocol --- tlslite/recordlayer.py | 247 +++++++++++++- unit_tests/test_tlslite_recordlayer.py | 429 ++++++++++++++++++++++++- 2 files changed, 670 insertions(+), 6 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 38ff0859..f9dac75c 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -6,11 +6,16 @@ import socket import errno -from tlslite.constants import ContentType -from .messages import RecordHeader3, RecordHeader2 -from .utils.codec import Parser +import hashlib +from .constants import ContentType, CipherSuite +from .messages import RecordHeader3, RecordHeader2, Message +from .utils.cipherfactory import createAES, createRC4, createTripleDES +from .utils.codec import Parser, Writer +from .utils.compat import compatHMAC +from .utils.cryptomath import getRandomBytes from .errors import TLSRecordOverflow, TLSIllegalParameterException,\ TLSAbruptCloseError +from .mathtls import createMAC_SSL, createHMAC, PRF_SSL, PRF, PRF_1_2 class RecordSocket(object): @@ -194,3 +199,239 @@ def recv(self): buf += result yield (record, buf) + +class ConnectionState(object): + + """Preserve the connection state for reading and writing data to records""" + + def __init__(self): + """Create an instance with empty encryption and MACing contexts""" + self.macContext = None + self.encContext = None + self.seqnum = 0 + + def getSeqNumBytes(self): + """Return encoded sequence number and increment it.""" + writer = Writer() + writer.add(self.seqnum, 8) + self.seqnum += 1 + return writer.bytes + +class RecordLayer(object): + + """ + Implementation of TLS record layer protocol + + @ivar version: the TLS version to use (tuple encoded as on the wire) + @ivar sock: underlying socket + @ivar client: whatever the connection should use encryption + """ + + def __init__(self, sock): + self.sock = sock + self._recordSocket = RecordSocket(sock) + self._version = (0, 0) + + self.client = True + + self._writeState = ConnectionState() + self._readState = ConnectionState() + self._pendingWriteState = ConnectionState() + self._pendingReadState = ConnectionState() + self.fixedIVBlock = None + + @property + def version(self): + """Return the TLS version used by record layer""" + return self._version + + @version.setter + def version(self, val): + """Set the TLS version used by record layer""" + self._version = val + self._recordSocket.version = val + + # + # sending messages + # + + def _macThenEncrypt(self, b, contentType): + """MAC then encrypt data""" + if self._writeState.macContext: + seqnumBytes = self._writeState.getSeqNumBytes() + mac = self._writeState.macContext.copy() + mac.update(compatHMAC(seqnumBytes)) + mac.update(compatHMAC(bytearray([contentType]))) + assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) + if self.version == (3, 0): + mac.update(compatHMAC(bytearray([len(b)//256]))) + mac.update(compatHMAC(bytearray([len(b)%256]))) + else: + mac.update(compatHMAC(bytearray([self.version[0]]))) + mac.update(compatHMAC(bytearray([self.version[1]]))) + mac.update(compatHMAC(bytearray([len(b)//256]))) + mac.update(compatHMAC(bytearray([len(b)%256]))) + mac.update(compatHMAC(b)) + macBytes = bytearray(mac.digest()) + + #Encrypt for Block or Stream Cipher + if self._writeState.encContext: + #Add padding and encrypt (for Block Cipher): + if self._writeState.encContext.isBlockCipher: + + #Add TLS 1.1 fixed block + if self.version >= (3, 2): + b = self.fixedIVBlock + b + + #Add padding: b = b+ (macBytes + paddingBytes) + currentLength = len(b) + len(macBytes) + blockLength = self._writeState.encContext.block_size + paddingLength = blockLength - 1 - (currentLength % blockLength) + + paddingBytes = bytearray([paddingLength] * (paddingLength+1)) + endBytes = macBytes + paddingBytes + b += endBytes + #Encrypt + b = self._writeState.encContext.encrypt(b) + + #Encrypt (for Stream Cipher) + else: + b += macBytes + b = self._writeState.encContext.encrypt(b) + + return b + + def sendMessage(self, msg, randomizeFirstBlock=True): + """ + Encrypt, MAC and send message through socket. + + @param msg: TLS message to send + @type msg: ApplicationData, HandshakeMessage, etc. + @param randomizeFirstBlock: set to perform 1/n-1 record splitting in + SSLv3 and TLSv1.0 in application data + """ + + data = msg.write() + contentType = msg.contentType + + data = self._macThenEncrypt(data, contentType) + + encryptedMessage = Message(contentType, data) + + for result in self._recordSocket.send(encryptedMessage): + yield result + + # + # cryptography state methods + # + + def changeWriteState(self): + """ + Change the cipher state to the pending one for write operations. + + This should be done only once after a call to L{calcPendingStates} was + performed and directly after sending a L{ChangeCipherSpec} message. + """ + self._writeState = self._pendingWriteState + self._pendingWriteState = ConnectionState() + + def changeReadState(self): + """ + Change the cipher state to the pending one for read operations. + + This should be done only once after a call to L{calcPendingStates} was + performed and directly after receiving a L{ChangeCipherSpec} message. + """ + self._readState = self._pendingReadState + self._pendingReadState = ConnectionState() + + def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, + serverRandom, implementations): + """Create pending states for encryption and decryption.""" + if cipherSuite in CipherSuite.aes128Suites: + keyLength = 16 + ivLength = 16 + createCipherFunc = createAES + elif cipherSuite in CipherSuite.aes256Suites: + keyLength = 32 + ivLength = 16 + createCipherFunc = createAES + elif cipherSuite in CipherSuite.rc4Suites: + keyLength = 16 + ivLength = 0 + createCipherFunc = createRC4 + elif cipherSuite in CipherSuite.tripleDESSuites: + keyLength = 24 + ivLength = 8 + createCipherFunc = createTripleDES + else: + raise AssertionError() + + if cipherSuite in CipherSuite.shaSuites: + macLength = 20 + digestmod = hashlib.sha1 + elif cipherSuite in CipherSuite.sha256Suites: + macLength = 32 + digestmod = hashlib.sha256 + elif cipherSuite in CipherSuite.md5Suites: + macLength = 16 + digestmod = hashlib.md5 + + if self.version == (3, 0): + createMACFunc = createMAC_SSL + elif self.version in ((3, 1), (3, 2), (3, 3)): + createMACFunc = createHMAC + + outputLength = (macLength*2) + (keyLength*2) + (ivLength*2) + + #Calculate Keying Material from Master Secret + if self.version == (3, 0): + keyBlock = PRF_SSL(masterSecret, + serverRandom + clientRandom, + outputLength) + elif self.version in ((3, 1), (3, 2)): + keyBlock = PRF(masterSecret, + b"key expansion", + serverRandom + clientRandom, + outputLength) + elif self.version == (3, 3): + keyBlock = PRF_1_2(masterSecret, + b"key expansion", + serverRandom + clientRandom, + outputLength) + else: + raise AssertionError() + + #Slice up Keying Material + clientPendingState = ConnectionState() + serverPendingState = ConnectionState() + p = Parser(keyBlock) + clientMACBlock = p.getFixBytes(macLength) + serverMACBlock = p.getFixBytes(macLength) + clientKeyBlock = p.getFixBytes(keyLength) + serverKeyBlock = p.getFixBytes(keyLength) + clientIVBlock = p.getFixBytes(ivLength) + serverIVBlock = p.getFixBytes(ivLength) + clientPendingState.macContext = createMACFunc( + compatHMAC(clientMACBlock), digestmod=digestmod) + serverPendingState.macContext = createMACFunc( + compatHMAC(serverMACBlock), digestmod=digestmod) + clientPendingState.encContext = createCipherFunc(clientKeyBlock, + clientIVBlock, + implementations) + serverPendingState.encContext = createCipherFunc(serverKeyBlock, + serverIVBlock, + implementations) + + #Assign new connection states to pending states + if self.client: + self._pendingWriteState = clientPendingState + self._pendingReadState = serverPendingState + else: + self._pendingWriteState = serverPendingState + self._pendingReadState = clientPendingState + + if self.version >= (3, 2) and ivLength: + #Choose fixedIVBlock for TLS 1.1 (this is encrypted with the CBC + #residue to create the IV for each sent block) + self.fixedIVBlock = getRandomBytes(ivLength) \ No newline at end of file diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 34e9c9be..f7614c0e 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -15,12 +15,14 @@ import unittest.mock as mock from unittest.mock import call +import os import socket import errno -from tlslite.messages import Message -from tlslite.recordlayer import RecordSocket -from tlslite.constants import ContentType +import tlslite.utils.cryptomath as cryptomath +from tlslite.messages import Message, ApplicationData +from tlslite.recordlayer import RecordSocket, ConnectionState, RecordLayer +from tlslite.constants import ContentType, CipherSuite from unit_tests.mocksock import MockSocket from tlslite.errors import TLSRecordOverflow, TLSIllegalParameterException,\ TLSAbruptCloseError @@ -297,3 +299,424 @@ def test_recv_with_SSL2_record_with_incomplete_header(self): break self.assertEqual(0, result) + +class TestConnectionState(unittest.TestCase): + def test___init__(self): + connState = ConnectionState() + + self.assertIsNotNone(connState) + self.assertIsNone(connState.macContext) + self.assertIsNone(connState.encContext) + self.assertEqual(0, connState.seqnum) + + def test_getSeqNumBytes(self): + connState = ConnectionState() + + self.assertEqual(bytearray(b'\x00'*8), connState.getSeqNumBytes()) + self.assertEqual(bytearray(b'\x00'*7 + b'\x01'), + connState.getSeqNumBytes()) + self.assertEqual(bytearray(b'\x00'*7 + b'\x02'), + connState.getSeqNumBytes()) + self.assertEqual(bytearray(b'\x00'*7 + b'\x03'), + connState.getSeqNumBytes()) + self.assertEqual(4, connState.seqnum) + +class TestRecordLayer(unittest.TestCase): + def test___init__(self): + recordLayer = RecordLayer(None) + + self.assertIsNotNone(recordLayer) + + def test_sendMessage(self): + sock = MockSocket(bytearray(0)) + recordLayer = RecordLayer(sock) + + hello = Message(ContentType.handshake, bytearray(10)) + + for result in recordLayer.sendMessage(hello): + if result in (0, 1): + self.assertTrue(False, "Blocking write") + else: + break + + self.assertEqual(len(sock.sent), 1) + + + def test_sendMessage_with_encrypting_set_up_tls1_2(self): + patcher = mock.patch.object(os, + 'urandom', + lambda x: bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 3) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsNotNone(app_data) + self.assertTrue(len(app_data.write()) > 3) + + for result in recordLayer.sendMessage(app_data, False): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x03' + # TLS1.2 + b'\x00\x30' # length - 48 bytes (3 blocks) + )) # (4 bytes of data + 20 bytes of MAC + IV) + self.assertEqual(bytearray( + b'\x48\x26\x1f\xc1\x9c\xde\x22\x92\xdd\xe4\x7c\xfc\x6f\x29\x52\xd6'+ + b'\xc5\xec\x44\x21\xca\xe3\xd1\x34\x64\xad\xff\xb1\xea\xfa\xd5\xe3'+ + b'\x9f\x73\xec\xa9\xa6\x82\x55\x8e\x3a\x8c\x94\x96\xda\x06\x09\x8d' + ), sock.sent[0][5:]) + + def test_sendMessage_with_SHA256_tls1_2(self): + patcher = mock.patch.object(os, + 'urandom', + lambda x: bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 3) + + recordLayer.calcPendingStates( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsNotNone(app_data) + self.assertTrue(len(app_data.write()) > 3) + + for result in recordLayer.sendMessage(app_data, False): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x03' + # TLS1.2 + b'\x00\x40' # length - 64 bytes (4 blocks) + )) # (4 bytes of data + 32 bytes of MAC + IV) + self.assertEqual(bytearray( + b'pd\x87\xde\xab\x9aU^\x7f\x7f\xa9\x00\xd14\'\x0c' + + b'\xde\xa73r\x9f\xb0O\x0eo_\x93\xec-\xb1c^' + + b'\x9a{\xde7g=\xef\x94\xd9K\xcc\x92\xe8\xa6\x10R' + + b'\xe0"c:7\xa9\xd7}X\x00[\x88\xce\xfe|\t' + ), sock.sent[0][5:]) + + def test_sendMessage_with_encrypting_set_up_tls1_1(self): + patcher = mock.patch.object(os, + 'urandom', + lambda x: bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 2) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsNotNone(app_data) + self.assertTrue(len(app_data.write()) > 3) + + for result in recordLayer.sendMessage(app_data, False): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x02' + # TLS1.2 + b'\x00\x30' # length - 48 bytes (3 blocks) + )) # (4 bytes of data + 20 bytes of MAC + IV) + self.assertEqual(bytearray( + b'b\x8e\xee\xddV\\W=\x810\xd5\x0c\xae \x84\xa8' + + b'^\x91\xa4d[\xe4\xde\x90\xee{f\xbb\xcd_\x1ao' + + b'\xa8\x8c!k\xab\x03\x03\x19.\x1dFMt\x08h^' + ), sock.sent[0][5:]) + + def test_sendMessage_with_encrypting_set_up_tls1_0(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 1) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsNotNone(app_data) + self.assertTrue(len(app_data.write()) > 3) + + for result in recordLayer.sendMessage(app_data, False): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLS1.0 + b'\x00\x20' # length - 48 bytes (3 blocks) + )) # (4 bytes of data + 20 bytes of MAC) + self.assertEqual(sock.sent[0][5:], bytearray( + b'\xebK\x0ff\x9cI\n\x011\xd0w\x9d\x11Z\xb4\xe5' + + b'D\xe9\xec\x8d\xdfd\xed\x94\x9f\xe6K\x08(\x08\xf6\xb7' + )) + + def test_sendMessage_with_stream_cipher_and_tls1_0(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 1) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_RC4_128_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsNotNone(app_data) + self.assertTrue(len(app_data.write()) > 3) + + for result in recordLayer.sendMessage(app_data, False): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x01' + # SSL3 + b'\x00\x18' # length - 24 bytes + )) # (4 bytes of data + 20 bytes of MAC) + self.assertEqual(sock.sent[0][5:], bytearray( + b'B\xb8H\xc6\xd7\\\x01\xe27\xa9\x86\xf2\xfdm!\x1d' + + b'\xa1\xaf]Q%y5\x1e' + )) + + def test_sendMessage_with_MD5_MAC_and_tls1_0(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 1) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_RC4_128_MD5, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsNotNone(app_data) + self.assertTrue(len(app_data.write()) > 3) + + for result in recordLayer.sendMessage(app_data, False): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x01' + # SSL3 + b'\x00\x14' # length - 20 bytes + )) # (4 bytes of data + 16 bytes of MAC) + self.assertEqual(sock.sent[0][5:], bytearray( + b'0}R\xe3T\xce`\xf9\x8f\x9d\xe6r\xc4\xdf\xd9\xd5' + + b'\xbf/sL' + )) + + + def test_sendMessage_with_AES256_cipher_and_tls1_0(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 1) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsNotNone(app_data) + self.assertTrue(len(app_data.write()) > 3) + + for result in recordLayer.sendMessage(app_data, False): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x01' + # SSL3 + b'\x00\x20' # length - 32 bytes (2 blocks) + )) # (4 bytes of data + 20 bytes of MAC) + self.assertEqual(sock.sent[0][5:], bytearray( + b'\xb8\xe5\xc5\x9c\xe6\xad\xf0uY\x19L\x17\xf8\xe7F3' + + b'}\xcct\x84 3) + + for result in recordLayer.sendMessage(app_data, False): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x01' + # SSL3 + b'\x00\x20' # length - 32 bytes (2 blocks) + )) # (4 bytes of data + 20 bytes of MAC) + self.assertEqual(sock.sent[0][5:], bytearray( + b'\xac\x12\xa55\x1a\x1f\xe2\xe5<\xb3[;\xc4\xa6\x9bF' + + b'\x8d\x16\x8b\xa3N\xe6\xfa\x14\xa9\xb9\xc7\x08w\xf2V\xe2' + )) + + def test_sendMessage_with_encrypting_set_up_ssl3(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 0) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsNotNone(app_data) + self.assertTrue(len(app_data.write()) > 3) + + for result in recordLayer.sendMessage(app_data, False): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x00' + # SSL3 + b'\x00\x20' # length - 48 bytes (3 blocks) + )) # (4 bytes of data + 20 bytes of MAC) + self.assertEqual(sock.sent[0][5:], bytearray( + b'\xc5\x16y\xf9\ra\xd9=\xec\x8b\x93\'\xb7\x05\xe6\xad' + + b'\xff\x842\xc7\xa2\x0byd\xab\x1a\xfd\xaf\x05\xd6\xba\x89' + )) + + def test_sendMessage_with_wrong_SSL_version(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + + with self.assertRaises(AssertionError): + recordLayer.calcPendingStates( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + + def test_sendMessage_with_invalid_ciphersuite(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + + with self.assertRaises(AssertionError): + recordLayer.calcPendingStates( + CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + + def test_sendMessage_with_slow_socket(self): + mockSock = MockSocket(bytearray(0), maxWrite=1, blockEveryOther=True) + sock = RecordLayer(mockSock) + + msg = Message(ContentType.handshake, bytearray(b'\x32'*2)) + + gotRetry = False + for result in sock.sendMessage(msg): + if result in (0, 1): + gotRetry = True + else: break + + self.assertTrue(gotRetry) + self.assertEqual([ + bytearray(b'\x16'), # handshake message + bytearray(b'\x00'), bytearray(b'\x00'), # version (unset) + bytearray(b'\x00'), bytearray(b'\x02'), # payload length + bytearray(b'\x32'), bytearray(b'\x32')], + mockSock.sent) From 7a10aca1a06bcce8b730f86d307e723ddf15d76d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Mar 2015 13:11:25 +0100 Subject: [PATCH 034/574] rm code duplication in RecordLayer._macThenEncrypt() --- tlslite/recordlayer.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index f9dac75c..816cec1c 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -255,6 +255,16 @@ def version(self, val): # sending messages # + def _addPadding(self, data): + """Add padding to data so that it is multiple of block size""" + currentLength = len(data) + blockLength = self._writeState.encContext.block_size + paddingLength = blockLength - 1 - (currentLength % blockLength) + + paddingBytes = bytearray([paddingLength] * (paddingLength+1)) + data += paddingBytes + return data + def _macThenEncrypt(self, b, contentType): """MAC then encrypt data""" if self._writeState.macContext: @@ -276,28 +286,18 @@ def _macThenEncrypt(self, b, contentType): #Encrypt for Block or Stream Cipher if self._writeState.encContext: - #Add padding and encrypt (for Block Cipher): + b += macBytes + #Add padding (for Block Cipher): if self._writeState.encContext.isBlockCipher: #Add TLS 1.1 fixed block if self.version >= (3, 2): b = self.fixedIVBlock + b - #Add padding: b = b+ (macBytes + paddingBytes) - currentLength = len(b) + len(macBytes) - blockLength = self._writeState.encContext.block_size - paddingLength = blockLength - 1 - (currentLength % blockLength) + b = self._addPadding(b) - paddingBytes = bytearray([paddingLength] * (paddingLength+1)) - endBytes = macBytes + paddingBytes - b += endBytes - #Encrypt - b = self._writeState.encContext.encrypt(b) - - #Encrypt (for Stream Cipher) - else: - b += macBytes - b = self._writeState.encContext.encrypt(b) + #Encrypt + b = self._writeState.encContext.encrypt(b) return b From 3ab0488899878da773b01be177247cea4ecfbe33 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Mar 2015 14:50:03 +0100 Subject: [PATCH 035/574] implement RecordLayer.recvMessage() --- tlslite/errors.py | 8 + tlslite/recordlayer.py | 104 ++++- unit_tests/test_tlslite_recordlayer.py | 539 ++++++++++++++++++++++++- 3 files changed, 648 insertions(+), 3 deletions(-) diff --git a/tlslite/errors.py b/tlslite/errors.py index 35878987..c35eb072 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -198,3 +198,11 @@ class TLSIllegalParameterException(TLSProtocolException): class TLSRecordOverflow(TLSProtocolException): """The received record size was too big""" pass + +class TLSDecryptionFailed(TLSProtocolException): + """Decryption of data was unsuccessful""" + pass + +class TLSBadRecordMAC(TLSProtocolException): + """Bad MAC (or padding in case of mac-then-encrypt)""" + pass diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 816cec1c..02c76619 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -14,7 +14,7 @@ from .utils.compat import compatHMAC from .utils.cryptomath import getRandomBytes from .errors import TLSRecordOverflow, TLSIllegalParameterException,\ - TLSAbruptCloseError + TLSAbruptCloseError, TLSDecryptionFailed, TLSBadRecordMAC from .mathtls import createMAC_SSL, createHMAC, PRF_SSL, PRF, PRF_1_2 class RecordSocket(object): @@ -321,6 +321,108 @@ def sendMessage(self, msg, randomizeFirstBlock=True): for result in self._recordSocket.send(encryptedMessage): yield result + # + # receiving messages + # + + def _decryptThenMAC(self, recordType, b): + """Decrypt data, check padding and MAC""" + if self._readState.encContext: + + #Decrypt if it's a block cipher + if self._readState.encContext.isBlockCipher: + blockLength = self._readState.encContext.block_size + if len(b) % blockLength != 0: + raise TLSDecryptionFailed() + b = self._readState.encContext.decrypt(b) + if self.version >= (3, 2): #For TLS 1.1, remove explicit IV + b = b[self._readState.encContext.block_size : ] + + #Check padding + paddingGood = True + paddingLength = b[-1] + if (paddingLength+1) > len(b): + paddingGood = False + totalPaddingLength = 0 + else: + assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) + if self.version == (3,0): + totalPaddingLength = paddingLength+1 + else: + totalPaddingLength = paddingLength+1 + paddingBytes = b[-totalPaddingLength:-1] + for byte in paddingBytes: + if byte != paddingLength: + paddingGood = False + totalPaddingLength = 0 + + #Decrypt if it's a stream cipher + else: + paddingGood = True + b = self._readState.encContext.decrypt(b) + totalPaddingLength = 0 + + #Check MAC + macGood = True + macLength = self._readState.macContext.digest_size + endLength = macLength + totalPaddingLength + if endLength > len(b): + macGood = False + else: + #Read MAC + startIndex = len(b) - endLength + endIndex = startIndex + macLength + checkBytes = b[startIndex : endIndex] + + #Calculate MAC + seqnumBytes = self._readState.getSeqNumBytes() + b = b[:-endLength] + mac = self._readState.macContext.copy() + mac.update(compatHMAC(seqnumBytes)) + mac.update(compatHMAC(bytearray([recordType]))) + assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) + if self.version == (3,0): + mac.update(compatHMAC(bytearray([len(b)//256]))) + mac.update(compatHMAC(bytearray([len(b)%256]))) + else: + mac.update(compatHMAC(bytearray([self.version[0]]))) + mac.update(compatHMAC(bytearray([self.version[1]]))) + mac.update(compatHMAC(bytearray([len(b)//256]))) + mac.update(compatHMAC(bytearray([len(b)%256]))) + mac.update(compatHMAC(b)) + macBytes = bytearray(mac.digest()) + + #Compare MACs + if macBytes != checkBytes: + macGood = False + + if not (paddingGood and macGood): + raise TLSBadRecordMAC() + + return b + + def recvMessage(self): + """ + Read, decrypt and check integrity of message + + @rtype: tuple + @return: message header and decrypted message payload + @raise TLSDecryptionFailed: when decryption of data failed + @raise TLSBadRecordMAC: when record has bad MAC or padding + @raise socket.error: when reading from socket was unsuccessfull + """ + + for result in self._recordSocket.recv(): + if result in (0, 1): + yield result + else: break + + (header, data) = result + + data = self._decryptThenMAC(header.type, data) + + yield (header, Parser(data)) + # # cryptography state methods # diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index f7614c0e..6fe48732 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -20,12 +20,12 @@ import errno import tlslite.utils.cryptomath as cryptomath -from tlslite.messages import Message, ApplicationData +from tlslite.messages import Message, ApplicationData, RecordHeader3 from tlslite.recordlayer import RecordSocket, ConnectionState, RecordLayer from tlslite.constants import ContentType, CipherSuite from unit_tests.mocksock import MockSocket from tlslite.errors import TLSRecordOverflow, TLSIllegalParameterException,\ - TLSAbruptCloseError + TLSAbruptCloseError, TLSDecryptionFailed, TLSBadRecordMAC class TestRecordSocket(unittest.TestCase): def test___init__(self): @@ -720,3 +720,538 @@ def test_sendMessage_with_slow_socket(self): bytearray(b'\x00'), bytearray(b'\x02'), # payload length bytearray(b'\x32'), bytearray(b'\x32')], mockSock.sent) + + def test_recvMessage(self): + sock = MockSocket(bytearray( + b'\x16' + # handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x0e' + # server hello done + b'\x00\x00\x00' # length + )) + recordLayer = RecordLayer(sock) + + for result in recordLayer.recvMessage(): + if result in (0, 1): + self.assertTrue(False, "Blocking read") + else: + break + + header, parser = result + + self.assertIsInstance(header, RecordHeader3) + self.assertEqual(ContentType.handshake, header.type) + self.assertEqual((3, 3), header.version) + self.assertEqual(bytearray(b'\x0e' + b'\x00'*3), parser.bytes) + + def test_recvMessage_with_slow_socket(self): + sock = MockSocket(bytearray( + b'\x16' + # handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x04' + # length + b'\x0e' + # server hello done + b'\x00\x00\x00' # length + ), maxRet=3, blockEveryOther=True) + recordLayer = RecordLayer(sock) + + wasBlocked = False + for result in recordLayer.recvMessage(): + if result in (0, 1): + wasBlocked = True + else: + break + self.assertTrue(wasBlocked) + + header, parser = result + + self.assertIsInstance(header, RecordHeader3) + self.assertEqual(ContentType.handshake, header.type) + self.assertEqual((3, 3), header.version) + self.assertEqual(bytearray(b'\x0e' + b'\x00'*3), parser.bytes) + + + def test_recvMessage_with_encrypted_content_TLS1_1(self): + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x02' + # TLSv1.1 + b'\x00\x30' + # length + # data from test_sendMessage_with_encrypting_set_up_tls1_1 + b'b\x8e\xee\xddV\\W=\x810\xd5\x0c\xae \x84\xa8' + + b'^\x91\xa4d[\xe4\xde\x90\xee{f\xbb\xcd_\x1ao' + + b'\xa8\x8c!k\xab\x03\x03\x19.\x1dFMt\x08h^' + )) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 2) + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + for result in recordLayer.recvMessage(): + if result in (0, 1): + self.assertTrue(False, "Blocking read") + else: + break + + header, parser = result + + self.assertIsInstance(header, RecordHeader3) + self.assertEqual(ContentType.application_data, header.type) + self.assertEqual((3, 2), header.version) + self.assertEqual(bytearray(b'test'), parser.bytes) + + def test_recvMessage_with_encrypted_content_SSLv3(self): + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x00' + # SSLv3 + b'\x00\x20' + # length + # data from test_sendMessage_with_encrypting_set_up_ssl3 + b'\xc5\x16y\xf9\ra\xd9=\xec\x8b\x93\'\xb7\x05\xe6\xad' + + b'\xff\x842\xc7\xa2\x0byd\xab\x1a\xfd\xaf\x05\xd6\xba\x89' + )) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 0) + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + for result in recordLayer.recvMessage(): + if result in (0, 1): + self.assertTrue(False, "Blocking read") + else: + break + + header, parser = result + + self.assertIsInstance(header, RecordHeader3) + self.assertEqual(ContentType.application_data, header.type) + self.assertEqual((3, 0), header.version) + self.assertEqual(bytearray(b'test'), parser.bytes) + + def test_recvMessage_with_stream_cipher_and_tls1_0(self): + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLSv1.0 + b'\x00\x18' + # length (24 bytes) + # data from test_sendMessage_with_stream_cipher_and_tls1_0 + b'B\xb8H\xc6\xd7\\\x01\xe27\xa9\x86\xf2\xfdm!\x1d' + + b'\xa1\xaf]Q%y5\x1e' + )) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 1) + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_RC4_128_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + for result in recordLayer.recvMessage(): + if result in (0, 1): + self.assertTrue(False, "Blocking read") + else: + break + + header, parser = result + + self.assertIsInstance(header, RecordHeader3) + self.assertEqual(ContentType.application_data, header.type) + self.assertEqual((3, 1), header.version) + self.assertEqual(bytearray(b'test'), parser.bytes) + + def test_recvMessage_with_invalid_length_payload(self): + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x02' + # TLSv1.1 + b'\x00\x2f' + # length + b'b\x8e\xee\xddV\\W=\x810\xd5\x0c\xae \x84\xa8' + + b'^\x91\xa4d[\xe4\xde\x90\xee{f\xbb\xcd_\x1ao' + + b'\xa8\x8c!k\xab\x03\x03\x19.\x1dFMt\x08h' + )) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 2) + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + gen = recordLayer.recvMessage() + + with self.assertRaises(TLSDecryptionFailed): + next(gen) + + def test_recvMessage_with_zero_filled_padding_in_SSLv3(self): + # make sure the IV is predictible (all zero) + patcher = mock.patch.object(os, + 'urandom', + lambda x: bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + # constructor for the data + sendingSocket = MockSocket(bytearray()) + + sendingRecordLayer = RecordLayer(sendingSocket) + sendingRecordLayer.version = (3, 0) + sendingRecordLayer.calcPendingStates( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + sendingRecordLayer.changeWriteState() + + # change the padding method to return simple version of padding (SSLv3) + def broken_padding(data): + currentLength = len(data) + blockLength = sendingRecordLayer._writeState.encContext.block_size + paddingLength = blockLength - 1 - (currentLength % blockLength) + + paddingBytes = bytearray([0] * (paddingLength)) + \ + bytearray([paddingLength]) + data += paddingBytes + return data + sendingRecordLayer._addPadding = broken_padding + + msg = ApplicationData().create(bytearray(b'test')) + + # create the data + for result in sendingRecordLayer.sendMessage(msg): + if result in (0, 1): + self.assertTrue(False, "Blocking socket") + else: + break + + # sanity check the data + self.assertEqual(1, len(sendingSocket.sent)) + self.assertEqual(bytearray( + b'\x17' + # app data + b'\x03\x00' + # SSLv3 + b'\x00\x20' # length - 32 bytes + ), sendingSocket.sent[0][:5]) + self.assertEqual(len(sendingSocket.sent[0][5:]), 32) + + # test proper + sock = MockSocket(sendingSocket.sent[0]) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 0) + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + for result in recordLayer.recvMessage(): + if result in (0, 1): + self.assertTrue(False, "Blocking socket") + else: break + + header, parser = result + + self.assertIsInstance(header, RecordHeader3) + self.assertEqual(ContentType.application_data, header.type) + self.assertEqual((3, 0), header.version) + self.assertEqual(bytearray(b'test'), parser.bytes) + + def test_recvMessage_with_invalid_last_byte_in_padding(self): + # make sure the IV is predictible (all zero) + patcher = mock.patch.object(os, + 'urandom', + lambda x: bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + + # constructor for the bad data + sendingSocket = MockSocket(bytearray()) + + sendingRecordLayer = RecordLayer(sendingSocket) + sendingRecordLayer.version = (3, 2) + sendingRecordLayer.calcPendingStates( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + sendingRecordLayer.changeWriteState() + + # change the padding method to return invalid padding + def broken_padding(data): + currentLength = len(data) + blockLength = sendingRecordLayer._writeState.encContext.block_size + paddingLength = blockLength - 1 - (currentLength % blockLength) + + # make the value of last byte longer than all data + paddingBytes = bytearray([paddingLength] * (paddingLength)) + \ + bytearray([255]) + data += paddingBytes + return data + sendingRecordLayer._addPadding = broken_padding + + msg = ApplicationData().create(bytearray(b'test')) + + # create the bad data + for result in sendingRecordLayer.sendMessage(msg): + if result in (0, 1): + self.assertTrue(False, "Blocking socket") + else: + break + + # sanity check the data + self.assertEqual(1, len(sendingSocket.sent)) + self.assertEqual(bytearray( + b'\x17' + # app data + b'\x03\x02' + # tls 1.1 + b'\x00\x30' # length - 48 bytes + ), sendingSocket.sent[0][:5]) + self.assertEqual(len(sendingSocket.sent[0][5:]), 48) + + # test proper + sock = MockSocket(sendingSocket.sent[0]) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 2) + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + gen = recordLayer.recvMessage() + + with self.assertRaises(TLSBadRecordMAC): + next(gen) + + def test_recvMessage_with_invalid_middle_byte_in_padding(self): + # make sure the IV is predictible (all zero) + patcher = mock.patch.object(os, + 'urandom', + lambda x: bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + + # constructor for the bad data + sendingSocket = MockSocket(bytearray()) + + sendingRecordLayer = RecordLayer(sendingSocket) + sendingRecordLayer.version = (3, 2) + sendingRecordLayer.calcPendingStates( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + sendingRecordLayer.changeWriteState() + + # change the padding method to return invalid padding + def broken_padding(data): + currentLength = len(data) + blockLength = sendingRecordLayer._writeState.encContext.block_size + paddingLength = blockLength - 1 - (currentLength % blockLength) + + # make the value of last byte longer than all data + paddingBytes = bytearray([paddingLength, 0] + + [paddingLength] * (paddingLength-2)) + \ + bytearray([paddingLength]) + data += paddingBytes + return data + sendingRecordLayer._addPadding = broken_padding + + msg = ApplicationData().create(bytearray(b'test')) + + # create the bad data + for result in sendingRecordLayer.sendMessage(msg): + if result in (0, 1): + self.assertTrue(False, "Blocking socket") + else: + break + + # sanity check the data + self.assertEqual(1, len(sendingSocket.sent)) + self.assertEqual(bytearray( + b'\x17' + # app data + b'\x03\x02' + # tls 1.1 + b'\x00\x30' # length - 48 bytes + ), sendingSocket.sent[0][:5]) + self.assertEqual(len(sendingSocket.sent[0][5:]), 48) + + # test proper + sock = MockSocket(sendingSocket.sent[0]) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 2) + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + gen = recordLayer.recvMessage() + + with self.assertRaises(TLSBadRecordMAC): + next(gen) + + def test_recvMessage_with_truncated_MAC(self): + # make sure the IV is predictible (all zero) + patcher = mock.patch.object(os, + 'urandom', + lambda x: bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + + # constructor for the bad data + sendingSocket = MockSocket(bytearray()) + + sendingRecordLayer = RecordLayer(sendingSocket) + sendingRecordLayer.version = (3, 2) + sendingRecordLayer.calcPendingStates( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + sendingRecordLayer.changeWriteState() + + # change the padding method to truncate padded data + def broken_padding(data): + data = data[:18] + currentLength = len(data) + blockLength = sendingRecordLayer._writeState.encContext.block_size + paddingLength = blockLength - 1 - (currentLength % blockLength) + + paddingBytes = bytearray([paddingLength] * (paddingLength)) + \ + bytearray([paddingLength]) + data += paddingBytes + return data + sendingRecordLayer._addPadding = broken_padding + + msg = ApplicationData().create(bytearray(b'test')) + + # create the bad data + for result in sendingRecordLayer.sendMessage(msg): + if result in (0, 1): + self.assertTrue(False, "Blocking socket") + else: + break + + # sanity check the data + self.assertEqual(1, len(sendingSocket.sent)) + self.assertEqual(bytearray( + b'\x17' + # app data + b'\x03\x02' + # tls 1.1 + b'\x00\x20' # length - 32 bytes + ), sendingSocket.sent[0][:5]) + self.assertEqual(len(sendingSocket.sent[0][5:]), 32) + + # test proper + sock = MockSocket(sendingSocket.sent[0]) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 2) + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + gen = recordLayer.recvMessage() + + with self.assertRaises(TLSBadRecordMAC): + next(gen) + + def test_recvMessage_with_invalid_MAC(self): + # make sure the IV is predictible (all zero) + patcher = mock.patch.object(os, + 'urandom', + lambda x: bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + + # constructor for the bad data + sendingSocket = MockSocket(bytearray()) + + sendingRecordLayer = RecordLayer(sendingSocket) + sendingRecordLayer.version = (3, 2) + sendingRecordLayer.calcPendingStates( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + sendingRecordLayer.changeWriteState() + + # change the padding method to make MAC bad + def broken_padding(data): + data[-1] ^= 255 + currentLength = len(data) + blockLength = sendingRecordLayer._writeState.encContext.block_size + paddingLength = blockLength - 1 - (currentLength % blockLength) + + paddingBytes = bytearray([paddingLength] * (paddingLength)) + \ + bytearray([paddingLength]) + data += paddingBytes + return data + sendingRecordLayer._addPadding = broken_padding + + msg = ApplicationData().create(bytearray(b'test')) + + # create the bad data + for result in sendingRecordLayer.sendMessage(msg): + if result in (0, 1): + self.assertTrue(False, "Blocking socket") + else: + break + + # sanity check the data + self.assertEqual(1, len(sendingSocket.sent)) + self.assertEqual(bytearray( + b'\x17' + # app data + b'\x03\x02' + # tls 1.1 + b'\x00\x30' # length - 48 bytes + ), sendingSocket.sent[0][:5]) + self.assertEqual(len(sendingSocket.sent[0][5:]), 48) + + # test proper + sock = MockSocket(sendingSocket.sent[0]) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 2) + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + gen = recordLayer.recvMessage() + + with self.assertRaises(TLSBadRecordMAC): + next(gen) From 05e8b29edeb3d90370d947c45b0bab1daef0e02d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Mar 2015 14:54:22 +0100 Subject: [PATCH 036/574] remove code duplication in RecordLayer._decryptThenMAC() --- tlslite/recordlayer.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 02c76619..5c8974ab 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -328,6 +328,7 @@ def sendMessage(self, msg, randomizeFirstBlock=True): def _decryptThenMAC(self, recordType, b): """Decrypt data, check padding and MAC""" if self._readState.encContext: + assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) #Decrypt if it's a block cipher if self._readState.encContext.isBlockCipher: @@ -345,11 +346,9 @@ def _decryptThenMAC(self, recordType, b): paddingGood = False totalPaddingLength = 0 else: - assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) - if self.version == (3,0): - totalPaddingLength = paddingLength+1 - else: - totalPaddingLength = paddingLength+1 + totalPaddingLength = paddingLength+1 + if self.version != (3, 0): + # check if all padding bytes have correct value paddingBytes = b[-totalPaddingLength:-1] for byte in paddingBytes: if byte != paddingLength: @@ -380,15 +379,11 @@ def _decryptThenMAC(self, recordType, b): mac = self._readState.macContext.copy() mac.update(compatHMAC(seqnumBytes)) mac.update(compatHMAC(bytearray([recordType]))) - assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) - if self.version == (3,0): - mac.update(compatHMAC(bytearray([len(b)//256]))) - mac.update(compatHMAC(bytearray([len(b)%256]))) - else: + if self.version != (3, 0): mac.update(compatHMAC(bytearray([self.version[0]]))) mac.update(compatHMAC(bytearray([self.version[1]]))) - mac.update(compatHMAC(bytearray([len(b)//256]))) - mac.update(compatHMAC(bytearray([len(b)%256]))) + mac.update(compatHMAC(bytearray([len(b)//256]))) + mac.update(compatHMAC(bytearray([len(b)%256]))) mac.update(compatHMAC(b)) macBytes = bytearray(mac.digest()) From f35875d6f5eba2e8a035e3670be55aa6436ca5de Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Mar 2015 16:36:05 +0100 Subject: [PATCH 037/574] make TLSRecordLayer use RecordLayer --- tlslite/tlsrecordlayer.py | 299 ++++++-------------------------------- 1 file changed, 46 insertions(+), 253 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 75c148c7..af609367 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -19,7 +19,7 @@ from .mathtls import * from .constants import * from .utils.cryptomath import getRandomBytes -from .recordlayer import RecordSocket +from .recordlayer import RecordLayer import socket import traceback @@ -102,14 +102,11 @@ class TLSRecordLayer(object): def __init__(self, sock): self.sock = sock - self._recordSocket = RecordSocket(sock) + self._recordLayer = RecordLayer(sock) #My session object (Session instance; read-only) self.session = None - #Am I a client or server? - self._client = None - #Buffers for processing messages self._handshakeBuffer = [] self.clearReadBuffer() @@ -121,15 +118,8 @@ def __init__(self, sock): self._handshake_sha256 = hashlib.sha256() #TLS Protocol Version - self._version = (0, 0) #read-only self._versionCheck = False #Once we choose a version, this is True - #Current and Pending connection states - self._writeState = _ConnectionState() - self._readState = _ConnectionState() - self._pendingWriteState = _ConnectionState() - self._pendingReadState = _ConnectionState() - #Is the connection open? self.closed = True #read-only self._refCount = 0 #Used to trigger closure @@ -150,16 +140,25 @@ def __init__(self, sock): #Fault we will induce, for testing purposes self.fault = None + @property + def _client(self): + """Boolean stating if the endpoint acts as a client""" + return self._recordLayer.client + + @_client.setter + def _client(self, value): + """Set the endpoint to act as a client or not""" + self._recordLayer.client = value + @property def version(self): """Get the SSL protocol version of connection""" - return self._version + return self._recordLayer.version @version.setter def version(self, value): """Set the SSL protocol version of connection""" - self._version = value - self._recordSocket.version = value + self._recordLayer.version = value def clearReadBuffer(self): self._readBuffer = b'' @@ -408,9 +407,10 @@ def getCipherName(self): @return: The name of the cipher used with this connection. Either 'aes128', 'aes256', 'rc4', or '3des'. """ - if not self._writeState.encContext: + # TODO don't use private variable of class + if not self._recordLayer._writeState.encContext: return None - return self._writeState.encContext.name + return self._recordLayer._writeState.encContext.name def getCipherImplementation(self): """Get the name of the cipher implementation used with @@ -420,9 +420,9 @@ def getCipherImplementation(self): @return: The name of the cipher implementation used with this connection. Either 'python', 'openssl', or 'pycrypto'. """ - if not self._writeState.encContext: + if not self._recordLayer._writeState.encContext: return None - return self._writeState.encContext.implementation + return self._recordLayer._writeState.encContext.implementation @@ -518,8 +518,9 @@ def fileno(self): #********************************************************* def _shutdown(self, resumable): - self._writeState = _ConnectionState() - self._readState = _ConnectionState() + # TODO don't use private field + self._recordLayer._writeState = _ConnectionState() + self._recordLayer._readState = _ConnectionState() self.version = (0,0) self._versionCheck = False self.closed = True @@ -550,9 +551,10 @@ def _sendMsg(self, msg, randomizeFirstBlock = True): #we first send the first byte of the message. This prevents #an attacker from launching a chosen-plaintext attack based on #knowing the next IV (a la BEAST). + # TODO don't reference private fields in _recordLayer if not self.closed and randomizeFirstBlock and self.version <= (3,1) \ - and self._writeState.encContext \ - and self._writeState.encContext.isBlockCipher \ + and self._recordLayer._writeState.encContext \ + and self._recordLayer._writeState.encContext.isBlockCipher \ and isinstance(msg, ApplicationData): msgFirstByte = msg.splitFirstByte() for result in self._sendMsg(msgFirstByte, @@ -574,57 +576,10 @@ def _sendMsg(self, msg, randomizeFirstBlock = True): self._handshake_sha.update(compat26Str(b)) self._handshake_sha256.update(compat26Str(b)) - #Calculate MAC - if self._writeState.macContext: - seqnumBytes = self._writeState.getSeqNumBytes() - mac = self._writeState.macContext.copy() - mac.update(compatHMAC(seqnumBytes)) - mac.update(compatHMAC(bytearray([contentType]))) - if self.version == (3,0): - mac.update( compatHMAC( bytearray([len(b)//256] ))) - mac.update( compatHMAC( bytearray([len(b)%256] ))) - elif self.version in ((3,1), (3,2), (3,3)): - mac.update(compatHMAC( bytearray([self.version[0]] ))) - mac.update(compatHMAC( bytearray([self.version[1]] ))) - mac.update( compatHMAC( bytearray([len(b)//256] ))) - mac.update( compatHMAC( bytearray([len(b)%256] ))) - else: - raise AssertionError() - mac.update(compatHMAC(b)) - macBytes = bytearray(mac.digest()) - if self.fault == Fault.badMAC: - macBytes[0] = (macBytes[0]+1) % 256 - - #Encrypt for Block or Stream Cipher - if self._writeState.encContext: - #Add padding and encrypt (for Block Cipher): - if self._writeState.encContext.isBlockCipher: - - #Add TLS 1.1 fixed block - if self.version >= (3,2): - b = self.fixedIVBlock + b - - #Add padding: b = b+ (macBytes + paddingBytes) - currentLength = len(b) + len(macBytes) - blockLength = self._writeState.encContext.block_size - paddingLength = blockLength - 1 - (currentLength % blockLength) - - paddingBytes = bytearray([paddingLength] * (paddingLength+1)) - if self.fault == Fault.badPadding: - paddingBytes[0] = (paddingBytes[0]+1) % 256 - endBytes = macBytes + paddingBytes - b += endBytes - #Encrypt - b = self._writeState.encContext.encrypt(b) - - #Encrypt (for Stream Cipher) - else: - b += macBytes - b = self._writeState.encContext.encrypt(b) - msg = Message(contentType, b) + try: - for result in self._recordSocket.send(msg): + for result in self._recordLayer.sendMessage(msg): if result in (0, 1): yield result except socket.error: @@ -830,17 +785,28 @@ def _getNextRecord(self): return try: - for result in self._recordSocket.recv(): + for result in self._recordLayer.recvMessage(): if result in (0, 1): yield result else: break - (r, b) = result except TLSRecordOverflow: for result in self._sendError(AlertDescription.record_overflow): yield result except TLSIllegalParameterException: for result in self._sendError(AlertDescription.illegal_parameter): yield result + except TLSDecryptionFailed: + for result in self._sendError( + AlertDescription.decryption_failed, + "Encrypted data not a multiple of blocksize"): + yield result + except TLSBadRecordMAC: + for result in self._sendError( + AlertDescription.bad_record_mac, + "MAC failure (or padding failure)"): + yield result + (r, p) = result + b = p.bytes #Check the record header fields (2) #We do this after reading the contents from the socket, so that @@ -857,11 +823,6 @@ def _getNextRecord(self): # yield result #Decrypt the record - for result in self._decryptRecord(r.type, b): - if result in (0,1): yield result - else: break - b = result - p = Parser(b) #If it doesn't contain handshake messages, we can just return it if r.type != ContentType.handshake: @@ -905,88 +866,6 @@ def _getNextRecord(self): yield (recordHeader, Parser(b)) - def _decryptRecord(self, recordType, b): - if self._readState.encContext: - - #Decrypt if it's a block cipher - if self._readState.encContext.isBlockCipher: - blockLength = self._readState.encContext.block_size - if len(b) % blockLength != 0: - for result in self._sendError(\ - AlertDescription.decryption_failed, - "Encrypted data not a multiple of blocksize"): - yield result - b = self._readState.encContext.decrypt(b) - if self.version >= (3,2): #For TLS 1.1, remove explicit IV - b = b[self._readState.encContext.block_size : ] - - #Check padding - paddingGood = True - paddingLength = b[-1] - if (paddingLength+1) > len(b): - paddingGood=False - totalPaddingLength = 0 - else: - if self.version == (3,0): - totalPaddingLength = paddingLength+1 - elif self.version in ((3,1), (3,2), (3,3)): - totalPaddingLength = paddingLength+1 - paddingBytes = b[-totalPaddingLength:-1] - for byte in paddingBytes: - if byte != paddingLength: - paddingGood = False - totalPaddingLength = 0 - else: - raise AssertionError() - - #Decrypt if it's a stream cipher - else: - paddingGood = True - b = self._readState.encContext.decrypt(b) - totalPaddingLength = 0 - - #Check MAC - macGood = True - macLength = self._readState.macContext.digest_size - endLength = macLength + totalPaddingLength - if endLength > len(b): - macGood = False - else: - #Read MAC - startIndex = len(b) - endLength - endIndex = startIndex + macLength - checkBytes = b[startIndex : endIndex] - - #Calculate MAC - seqnumBytes = self._readState.getSeqNumBytes() - b = b[:-endLength] - mac = self._readState.macContext.copy() - mac.update(compatHMAC(seqnumBytes)) - mac.update(compatHMAC(bytearray([recordType]))) - if self.version == (3,0): - mac.update( compatHMAC(bytearray( [len(b)//256] ) )) - mac.update( compatHMAC(bytearray( [len(b)%256] ) )) - elif self.version in ((3,1), (3,2), (3,3)): - mac.update(compatHMAC(bytearray( [self.version[0]] ) )) - mac.update(compatHMAC(bytearray( [self.version[1]] ) )) - mac.update(compatHMAC(bytearray( [len(b)//256] ) )) - mac.update(compatHMAC(bytearray( [len(b)%256] ) )) - else: - raise AssertionError() - mac.update(compatHMAC(b)) - macBytes = bytearray(mac.digest()) - - #Compare MACs - if macBytes != checkBytes: - macGood = False - - if not (paddingGood and macGood): - for result in self._sendError(AlertDescription.bad_record_mac, - "MAC failure (or padding failure)"): - yield result - - yield b - def _handshakeStart(self, client): if not self.closed: raise ValueError("Renegotiation disallowed for security reasons") @@ -1003,102 +882,16 @@ def _handshakeDone(self, resumed): self.closed = False def _calcPendingStates(self, cipherSuite, masterSecret, - clientRandom, serverRandom, implementations): - if cipherSuite in CipherSuite.aes128Suites: - keyLength = 16 - ivLength = 16 - createCipherFunc = createAES - elif cipherSuite in CipherSuite.aes256Suites: - keyLength = 32 - ivLength = 16 - createCipherFunc = createAES - elif cipherSuite in CipherSuite.rc4Suites: - keyLength = 16 - ivLength = 0 - createCipherFunc = createRC4 - elif cipherSuite in CipherSuite.tripleDESSuites: - keyLength = 24 - ivLength = 8 - createCipherFunc = createTripleDES - else: - raise AssertionError() - - if cipherSuite in CipherSuite.shaSuites: - macLength = 20 - digestmod = hashlib.sha1 - elif cipherSuite in CipherSuite.sha256Suites: - macLength = 32 - digestmod = hashlib.sha256 - elif cipherSuite in CipherSuite.md5Suites: - macLength = 16 - digestmod = hashlib.md5 - - if self.version == (3,0): - createMACFunc = createMAC_SSL - elif self.version in ((3,1), (3,2), (3,3)): - createMACFunc = createHMAC - - outputLength = (macLength*2) + (keyLength*2) + (ivLength*2) - - #Calculate Keying Material from Master Secret - if self.version == (3,0): - keyBlock = PRF_SSL(masterSecret, - serverRandom + clientRandom, - outputLength) - elif self.version in ((3,1), (3,2)): - keyBlock = PRF(masterSecret, - b"key expansion", - serverRandom + clientRandom, - outputLength) - elif self.version == (3,3): - keyBlock = PRF_1_2(masterSecret, - b"key expansion", - serverRandom + clientRandom, - outputLength) - else: - raise AssertionError() - - #Slice up Keying Material - clientPendingState = _ConnectionState() - serverPendingState = _ConnectionState() - p = Parser(keyBlock) - clientMACBlock = p.getFixBytes(macLength) - serverMACBlock = p.getFixBytes(macLength) - clientKeyBlock = p.getFixBytes(keyLength) - serverKeyBlock = p.getFixBytes(keyLength) - clientIVBlock = p.getFixBytes(ivLength) - serverIVBlock = p.getFixBytes(ivLength) - clientPendingState.macContext = createMACFunc( - compatHMAC(clientMACBlock), digestmod=digestmod) - serverPendingState.macContext = createMACFunc( - compatHMAC(serverMACBlock), digestmod=digestmod) - clientPendingState.encContext = createCipherFunc(clientKeyBlock, - clientIVBlock, - implementations) - serverPendingState.encContext = createCipherFunc(serverKeyBlock, - serverIVBlock, - implementations) - - #Assign new connection states to pending states - if self._client: - self._pendingWriteState = clientPendingState - self._pendingReadState = serverPendingState - else: - self._pendingWriteState = serverPendingState - self._pendingReadState = clientPendingState - - if self.version >= (3,2) and ivLength: - #Choose fixedIVBlock for TLS 1.1 (this is encrypted with the CBC - #residue to create the IV for each sent block) - self.fixedIVBlock = getRandomBytes(ivLength) + clientRandom, serverRandom, implementations): + self._recordLayer.calcPendingStates(cipherSuite, masterSecret, + clientRandom, serverRandom, + implementations) def _changeWriteState(self): - self._writeState = self._pendingWriteState - self._pendingWriteState = _ConnectionState() + self._recordLayer.changeWriteState() def _changeReadState(self): - self._readState = self._pendingReadState - self._pendingReadState = _ConnectionState() + self._recordLayer.changeReadState() #Used for Finished messages and CertificateVerify messages in SSL v3 def _calcSSLHandshakeHash(self, masterSecret, label): From eefe4a832d68db0407f0cf8fb8ca7e6cb3ff5698 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Mar 2015 16:39:19 +0100 Subject: [PATCH 038/574] remove dead code This was of relevance only to SSLv3 - TLSv1.0 and later include on-the-wire protocol version in the MAC input, so if the content is MACed the version check is performed implicitly --- tlslite/tlsconnection.py | 15 --------------- tlslite/tlsrecordlayer.py | 18 ------------------ 2 files changed, 33 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 0fde0314..10168a9f 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -557,9 +557,6 @@ def _clientGetServerHello(self, settings, clientHello): #error alerts will use the server's version self.version = serverHello.server_version - #Future responses from server must use this version - self._versionCheck = True - #Check ServerHello if serverHello.server_version < settings.minVersion: for result in self._sendError(\ @@ -1315,9 +1312,6 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, for result in self._sendMsg(serverHello): yield result - #From here on, the client's messages must have right version - self._versionCheck = True - #Calculate pending connection states self._calcPendingStates(session.cipherSuite, session.masterSecret, @@ -1412,9 +1406,6 @@ def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, for result in self._sendMsgs(msgs): yield result - #From here on, the client's messages must have the right version - self._versionCheck = True - #Get and check ClientKeyExchange for result in self._getMsg(ContentType.handshake, HandshakeType.client_key_exchange, @@ -1461,9 +1452,6 @@ def _serverCertKeyExchange(self, clientHello, serverHello, for result in self._sendMsgs(msgs): yield result - #From here on, the client's messages must have the right version - self._versionCheck = True - #Get [Certificate,] (if was requested) if reqCert: if self.version == (3,0): @@ -1584,9 +1572,6 @@ def _serverAnonKeyExchange(self, clientHello, serverHello, cipherSuite, for result in self._sendMsgs(msgs): yield result - #From here on, the client's messages must have the right version - self._versionCheck = True - #Get and check ClientKeyExchange for result in self._getMsg(ContentType.handshake, HandshakeType.client_key_exchange, diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index af609367..708d2d2b 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -117,9 +117,6 @@ def __init__(self, sock): self._handshake_sha = hashlib.sha1() self._handshake_sha256 = hashlib.sha256() - #TLS Protocol Version - self._versionCheck = False #Once we choose a version, this is True - #Is the connection open? self.closed = True #read-only self._refCount = 0 #Used to trigger closure @@ -522,7 +519,6 @@ def _shutdown(self, resumable): self._recordLayer._writeState = _ConnectionState() self._recordLayer._readState = _ConnectionState() self.version = (0,0) - self._versionCheck = False self.closed = True if self.closeSocket: self.sock.close() @@ -808,20 +804,6 @@ def _getNextRecord(self): (r, p) = result b = p.bytes - #Check the record header fields (2) - #We do this after reading the contents from the socket, so that - #if there's an error, we at least don't leave extra bytes in the - #socket.. - # - # THIS CHECK HAS NO SECURITY RELEVANCE (?), BUT COULD HURT INTEROP. - # SO WE LEAVE IT OUT FOR NOW. - # - #if self._versionCheck and r.version != self.version: - # for result in self._sendError(AlertDescription.protocol_version, - # "Version in header field: %s, should be %s" % (str(r.version), - # str(self.version))): - # yield result - #Decrypt the record #If it doesn't contain handshake messages, we can just return it From cb4918af7b31b162ca5f132ebbf722560781abd3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Mar 2015 17:00:22 +0100 Subject: [PATCH 039/574] utility functions for RecordLayer stop TLSRecordLayer using private fields of RecordLayer --- tlslite/recordlayer.py | 31 ++++++++++++++++++ tlslite/tlsrecordlayer.py | 15 ++------- unit_tests/test_tlslite_recordlayer.py | 44 ++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 5c8974ab..ca2aab98 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -251,6 +251,37 @@ def version(self, val): self._version = val self._recordSocket.version = val + def getCipherName(self): + """ + Return the name of the bulk cipher used by this connection + + @rtype: str + @return: The name of the cipher, like 'aes128', 'rc4', etc. + """ + if self._writeState.encContext is None: + return None + return self._writeState.encContext.name + + def getCipherImplementation(self): + """ + Return the name of the implementation used for the connection + + 'python' for tlslite internal implementation, 'openssl' for M2crypto + and 'pycrypto' for pycrypto + @rtype: str + @return: Name of cipher implementation used, None if not initialised + """ + if self._writeState.encContext is None: + return None + return self._writeState.encContext.implementation + + def shutdown(self): + """Clear read and write states""" + self._writeState = ConnectionState() + self._readState = ConnectionState() + self._pendingWriteState = ConnectionState() + self._pendingReadState = ConnectionState() + # # sending messages # diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 708d2d2b..17fff6de 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -404,10 +404,7 @@ def getCipherName(self): @return: The name of the cipher used with this connection. Either 'aes128', 'aes256', 'rc4', or '3des'. """ - # TODO don't use private variable of class - if not self._recordLayer._writeState.encContext: - return None - return self._recordLayer._writeState.encContext.name + return self._recordLayer.getCipherName() def getCipherImplementation(self): """Get the name of the cipher implementation used with @@ -417,11 +414,7 @@ def getCipherImplementation(self): @return: The name of the cipher implementation used with this connection. Either 'python', 'openssl', or 'pycrypto'. """ - if not self._recordLayer._writeState.encContext: - return None - return self._recordLayer._writeState.encContext.implementation - - + return self._recordLayer.getCipherImplementation() #Emulate a socket, somewhat - def send(self, s): @@ -515,9 +508,7 @@ def fileno(self): #********************************************************* def _shutdown(self, resumable): - # TODO don't use private field - self._recordLayer._writeState = _ConnectionState() - self._recordLayer._readState = _ConnectionState() + self._recordLayer.shutdown() self.version = (0,0) self.closed = True if self.closeSocket: diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 6fe48732..502eb656 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -327,6 +327,9 @@ def test___init__(self): self.assertIsNotNone(recordLayer) + self.assertIsNone(recordLayer.getCipherName()) + self.assertIsNone(recordLayer.getCipherImplementation()) + def test_sendMessage(self): sock = MockSocket(bytearray(0)) recordLayer = RecordLayer(sock) @@ -341,6 +344,47 @@ def test_sendMessage(self): self.assertEqual(len(sock.sent), 1) + def test_shutdown(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + # make sure it doesn't throw exceptions + recordLayer.shutdown() + + def test_getCipherName(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 3) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + self.assertEqual('aes128', recordLayer.getCipherName()) + + def test_getCipherImplementation(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 3) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + if cryptomath.m2cryptoLoaded: + self.assertEqual('openssl', recordLayer.getCipherImplementation()) + elif cryptomath.pycryptoLoaded: + self.assertEqual('pycrypto', recordLayer.getCipherImplementation()) + else: + self.assertEqual('python', recordLayer.getCipherImplementation()) def test_sendMessage_with_encrypting_set_up_tls1_2(self): patcher = mock.patch.object(os, From 506b199c47926a760ba27f84aeea72078d856c5b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Mar 2015 17:14:04 +0100 Subject: [PATCH 040/574] remove more duplicated code in _macThenEncrypt() --- tlslite/recordlayer.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index ca2aab98..477190e8 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -304,14 +304,11 @@ def _macThenEncrypt(self, b, contentType): mac.update(compatHMAC(seqnumBytes)) mac.update(compatHMAC(bytearray([contentType]))) assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) - if self.version == (3, 0): - mac.update(compatHMAC(bytearray([len(b)//256]))) - mac.update(compatHMAC(bytearray([len(b)%256]))) - else: + if self.version != (3, 0): mac.update(compatHMAC(bytearray([self.version[0]]))) mac.update(compatHMAC(bytearray([self.version[1]]))) - mac.update(compatHMAC(bytearray([len(b)//256]))) - mac.update(compatHMAC(bytearray([len(b)%256]))) + mac.update(compatHMAC(bytearray([len(b)//256]))) + mac.update(compatHMAC(bytearray([len(b)%256]))) mac.update(compatHMAC(b)) macBytes = bytearray(mac.digest()) From ff35f4fd5feb90c07e21d383c4a3de595ed83133 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Mar 2015 17:17:18 +0100 Subject: [PATCH 041/574] remove dead code class was moved to recordlayer module --- tlslite/tlsrecordlayer.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 17fff6de..f4b0fd3c 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -24,19 +24,6 @@ import socket import traceback -class _ConnectionState(object): - def __init__(self): - self.macContext = None - self.encContext = None - self.seqnum = 0 - - def getSeqNumBytes(self): - w = Writer() - w.add(self.seqnum, 8) - self.seqnum += 1 - return w.bytes - - class TLSRecordLayer(object): """ This class handles data transmission for a TLS connection. From 44cce6767e417a2fcea7de6f7d0010a06adca8ad Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 27 Mar 2015 21:23:54 +0100 Subject: [PATCH 042/574] fix mistakes in comments --- tlslite/recordlayer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 477190e8..6e6af7e9 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -224,7 +224,7 @@ class RecordLayer(object): @ivar version: the TLS version to use (tuple encoded as on the wire) @ivar sock: underlying socket - @ivar client: whatever the connection should use encryption + @ivar client: whether the connection should use encryption """ def __init__(self, sock): @@ -432,7 +432,7 @@ def recvMessage(self): @return: message header and decrypted message payload @raise TLSDecryptionFailed: when decryption of data failed @raise TLSBadRecordMAC: when record has bad MAC or padding - @raise socket.error: when reading from socket was unsuccessfull + @raise socket.error: when reading from socket was unsuccessful """ for result in self._recordSocket.recv(): From cd5c4f81ecdb8967b996155b7439c37235992e3d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 May 2015 15:40:25 +0200 Subject: [PATCH 043/574] make variable name more descriptive fixes pylint C0103 (invalid-name) --- tlslite/recordlayer.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 6e6af7e9..172f4849 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -296,7 +296,7 @@ def _addPadding(self, data): data += paddingBytes return data - def _macThenEncrypt(self, b, contentType): + def _macThenEncrypt(self, data, contentType): """MAC then encrypt data""" if self._writeState.macContext: seqnumBytes = self._writeState.getSeqNumBytes() @@ -307,27 +307,27 @@ def _macThenEncrypt(self, b, contentType): if self.version != (3, 0): mac.update(compatHMAC(bytearray([self.version[0]]))) mac.update(compatHMAC(bytearray([self.version[1]]))) - mac.update(compatHMAC(bytearray([len(b)//256]))) - mac.update(compatHMAC(bytearray([len(b)%256]))) - mac.update(compatHMAC(b)) + mac.update(compatHMAC(bytearray([len(data)//256]))) + mac.update(compatHMAC(bytearray([len(data)%256]))) + mac.update(compatHMAC(data)) macBytes = bytearray(mac.digest()) #Encrypt for Block or Stream Cipher if self._writeState.encContext: - b += macBytes + data += macBytes #Add padding (for Block Cipher): if self._writeState.encContext.isBlockCipher: #Add TLS 1.1 fixed block if self.version >= (3, 2): - b = self.fixedIVBlock + b + data = self.fixedIVBlock + data - b = self._addPadding(b) + data = self._addPadding(data) #Encrypt - b = self._writeState.encContext.encrypt(b) + data = self._writeState.encContext.encrypt(data) - return b + return data def sendMessage(self, msg, randomizeFirstBlock=True): """ From 28952cd518e6f804bb7adc1a8f67d53e2de4ed80 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 May 2015 15:45:34 +0200 Subject: [PATCH 044/574] change variable name to a more descriptive one fixes pylint C0103 (invalid-name) --- tlslite/recordlayer.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 172f4849..51bff02d 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -353,7 +353,7 @@ def sendMessage(self, msg, randomizeFirstBlock=True): # receiving messages # - def _decryptThenMAC(self, recordType, b): + def _decryptThenMAC(self, recordType, data): """Decrypt data, check padding and MAC""" if self._readState.encContext: assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) @@ -361,23 +361,23 @@ def _decryptThenMAC(self, recordType, b): #Decrypt if it's a block cipher if self._readState.encContext.isBlockCipher: blockLength = self._readState.encContext.block_size - if len(b) % blockLength != 0: + if len(data) % blockLength != 0: raise TLSDecryptionFailed() - b = self._readState.encContext.decrypt(b) + data = self._readState.encContext.decrypt(data) if self.version >= (3, 2): #For TLS 1.1, remove explicit IV - b = b[self._readState.encContext.block_size : ] + data = data[self._readState.encContext.block_size : ] #Check padding paddingGood = True - paddingLength = b[-1] - if (paddingLength+1) > len(b): + paddingLength = data[-1] + if (paddingLength+1) > len(data): paddingGood = False totalPaddingLength = 0 else: totalPaddingLength = paddingLength+1 if self.version != (3, 0): # check if all padding bytes have correct value - paddingBytes = b[-totalPaddingLength:-1] + paddingBytes = data[-totalPaddingLength:-1] for byte in paddingBytes: if byte != paddingLength: paddingGood = False @@ -386,33 +386,33 @@ def _decryptThenMAC(self, recordType, b): #Decrypt if it's a stream cipher else: paddingGood = True - b = self._readState.encContext.decrypt(b) + data = self._readState.encContext.decrypt(data) totalPaddingLength = 0 #Check MAC macGood = True macLength = self._readState.macContext.digest_size endLength = macLength + totalPaddingLength - if endLength > len(b): + if endLength > len(data): macGood = False else: #Read MAC - startIndex = len(b) - endLength + startIndex = len(data) - endLength endIndex = startIndex + macLength - checkBytes = b[startIndex : endIndex] + checkBytes = data[startIndex : endIndex] #Calculate MAC seqnumBytes = self._readState.getSeqNumBytes() - b = b[:-endLength] + data = data[:-endLength] mac = self._readState.macContext.copy() mac.update(compatHMAC(seqnumBytes)) mac.update(compatHMAC(bytearray([recordType]))) if self.version != (3, 0): mac.update(compatHMAC(bytearray([self.version[0]]))) mac.update(compatHMAC(bytearray([self.version[1]]))) - mac.update(compatHMAC(bytearray([len(b)//256]))) - mac.update(compatHMAC(bytearray([len(b)%256]))) - mac.update(compatHMAC(b)) + mac.update(compatHMAC(bytearray([len(data)//256]))) + mac.update(compatHMAC(bytearray([len(data)%256]))) + mac.update(compatHMAC(data)) macBytes = bytearray(mac.digest()) #Compare MACs @@ -422,7 +422,7 @@ def _decryptThenMAC(self, recordType, b): if not (paddingGood and macGood): raise TLSBadRecordMAC() - return b + return data def recvMessage(self): """ From 4e128eba5a72ec604f136fd4ece1f41343de49db Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 May 2015 15:51:47 +0200 Subject: [PATCH 045/574] fix variable name resolves pylint C0103 (invalid-name) --- tlslite/recordlayer.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 51bff02d..941043e3 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -530,13 +530,13 @@ def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, #Slice up Keying Material clientPendingState = ConnectionState() serverPendingState = ConnectionState() - p = Parser(keyBlock) - clientMACBlock = p.getFixBytes(macLength) - serverMACBlock = p.getFixBytes(macLength) - clientKeyBlock = p.getFixBytes(keyLength) - serverKeyBlock = p.getFixBytes(keyLength) - clientIVBlock = p.getFixBytes(ivLength) - serverIVBlock = p.getFixBytes(ivLength) + parser = Parser(keyBlock) + clientMACBlock = parser.getFixBytes(macLength) + serverMACBlock = parser.getFixBytes(macLength) + clientKeyBlock = parser.getFixBytes(keyLength) + serverKeyBlock = parser.getFixBytes(keyLength) + clientIVBlock = parser.getFixBytes(ivLength) + serverIVBlock = parser.getFixBytes(ivLength) clientPendingState.macContext = createMACFunc( compatHMAC(clientMACBlock), digestmod=digestmod) serverPendingState.macContext = createMACFunc( From ca56436d9d64cc38291321cc1cb72264a48ba966 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 May 2015 11:41:29 +0200 Subject: [PATCH 046/574] remove code duplication in MtE encrypt and decrypt since the code is essentially the same, we can move it to a separate method --- tlslite/recordlayer.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 941043e3..5f18c38d 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -296,21 +296,25 @@ def _addPadding(self, data): data += paddingBytes return data + def _calculateMAC(self, mac, seqnumBytes, contentType, data): + """Calculate the SSL/TLS version of a MAC""" + mac.update(compatHMAC(seqnumBytes)) + mac.update(compatHMAC(bytearray([contentType]))) + assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) + if self.version != (3, 0): + mac.update(compatHMAC(bytearray([self.version[0]]))) + mac.update(compatHMAC(bytearray([self.version[1]]))) + mac.update(compatHMAC(bytearray([len(data)//256]))) + mac.update(compatHMAC(bytearray([len(data)%256]))) + mac.update(compatHMAC(data)) + return bytearray(mac.digest()) + def _macThenEncrypt(self, data, contentType): """MAC then encrypt data""" if self._writeState.macContext: seqnumBytes = self._writeState.getSeqNumBytes() mac = self._writeState.macContext.copy() - mac.update(compatHMAC(seqnumBytes)) - mac.update(compatHMAC(bytearray([contentType]))) - assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) - if self.version != (3, 0): - mac.update(compatHMAC(bytearray([self.version[0]]))) - mac.update(compatHMAC(bytearray([self.version[1]]))) - mac.update(compatHMAC(bytearray([len(data)//256]))) - mac.update(compatHMAC(bytearray([len(data)%256]))) - mac.update(compatHMAC(data)) - macBytes = bytearray(mac.digest()) + macBytes = self._calculateMAC(mac, seqnumBytes, contentType, data) #Encrypt for Block or Stream Cipher if self._writeState.encContext: @@ -405,15 +409,8 @@ def _decryptThenMAC(self, recordType, data): seqnumBytes = self._readState.getSeqNumBytes() data = data[:-endLength] mac = self._readState.macContext.copy() - mac.update(compatHMAC(seqnumBytes)) - mac.update(compatHMAC(bytearray([recordType]))) - if self.version != (3, 0): - mac.update(compatHMAC(bytearray([self.version[0]]))) - mac.update(compatHMAC(bytearray([self.version[1]]))) - mac.update(compatHMAC(bytearray([len(data)//256]))) - mac.update(compatHMAC(bytearray([len(data)%256]))) - mac.update(compatHMAC(data)) - macBytes = bytearray(mac.digest()) + macBytes = self._calculateMAC(mac, seqnumBytes, recordType, + data) #Compare MACs if macBytes != checkBytes: From f521f5113b851ed13db10832c53070b6f89149b6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 May 2015 13:22:10 +0200 Subject: [PATCH 047/574] fix pylint warnings W0631 undefined-loop-variable - not necessary as recv() will always yield at least once before returning --- tlslite/recordlayer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 5f18c38d..566fd3f0 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -432,10 +432,12 @@ def recvMessage(self): @raise socket.error: when reading from socket was unsuccessful """ + result = None for result in self._recordSocket.recv(): if result in (0, 1): yield result else: break + assert result is not None (header, data) = result @@ -556,4 +558,5 @@ def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, if self.version >= (3, 2) and ivLength: #Choose fixedIVBlock for TLS 1.1 (this is encrypted with the CBC #residue to create the IV for each sent block) - self.fixedIVBlock = getRandomBytes(ivLength) \ No newline at end of file + self.fixedIVBlock = getRandomBytes(ivLength) + From f3c2927b358353cda188c05eb838fad67bdff535 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 31 May 2015 13:59:00 +0200 Subject: [PATCH 048/574] fix some pylint warninings remove unused imports, fix documentation warnings, add excuses for not fixing other issues --- tlslite/errors.py | 13 +++++++++++++ tlslite/messages.py | 2 ++ tlslite/recordlayer.py | 10 +++------- tlslite/tlsrecordlayer.py | 5 ++--- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/tlslite/errors.py b/tlslite/errors.py index c35eb072..78bba126 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -15,14 +15,17 @@ from .constants import AlertDescription, AlertLevel class BaseTLSException(Exception): + """Metaclass for TLS Lite exceptions. Look to L{TLSError} for exceptions that should be caught by tlslite consumers """ + pass class TLSError(BaseTLSException): + """Base class for all TLS Lite exceptions.""" def __str__(self): @@ -188,21 +191,31 @@ class TLSInternalError(TLSError): pass class TLSProtocolException(BaseTLSException): + """Exceptions used internally for handling errors in received messages""" + pass class TLSIllegalParameterException(TLSProtocolException): + """Parameters specified in message were incorrect or invalid""" + pass class TLSRecordOverflow(TLSProtocolException): + """The received record size was too big""" + pass class TLSDecryptionFailed(TLSProtocolException): + """Decryption of data was unsuccessful""" + pass class TLSBadRecordMAC(TLSProtocolException): + """Bad MAC (or padding in case of mac-then-encrypt)""" + pass diff --git a/tlslite/messages.py b/tlslite/messages.py index b9040d24..24fbb8d3 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -82,7 +82,9 @@ def __repr__(self): format(self.type, self.version, self.length) class RecordHeader2(RecordHeader): + """SSLv2 record header (just reading)""" + def __init__(self): """Define a SSLv2 style class""" super(RecordHeader2, self).__init__(ssl2=True) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 566fd3f0..4827e89b 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -60,7 +60,6 @@ def send(self, msg): @param msg: TLS message to send @raise socket.error: when write to socket failed """ - data = msg.write() header = RecordHeader3().create(self.version, @@ -81,7 +80,6 @@ def _sockRecvAll(self, length): blocking and would block and bytearray in case the read finished @raise TLSAbruptCloseError: when the socket closed """ - buf = bytearray(0) if length == 0: @@ -157,8 +155,7 @@ def _recvHeader(self): def recv(self): """ - Read a single record from socket, handles both SSLv2 and SSLv3 record - layer + Read a single record from socket, handle SSLv2 and SSLv3 record layer @rtype: generator @return: generator that returns 0 or 1 in case the read would be @@ -172,7 +169,6 @@ def recv(self): @raise TLSIllegalParameterException: When the record header was malformed """ - record = None for record in self._recvHeader(): if record in (0, 1): @@ -333,6 +329,8 @@ def _macThenEncrypt(self, data, contentType): return data + # randomizeFirstBlock will get used once handling of fragmented + # messages is implemented def sendMessage(self, msg, randomizeFirstBlock=True): """ Encrypt, MAC and send message through socket. @@ -342,7 +340,6 @@ def sendMessage(self, msg, randomizeFirstBlock=True): @param randomizeFirstBlock: set to perform 1/n-1 record splitting in SSLv3 and TLSv1.0 in application data """ - data = msg.write() contentType = msg.contentType @@ -431,7 +428,6 @@ def recvMessage(self): @raise TLSBadRecordMAC: when record has bad MAC or padding @raise socket.error: when reading from socket was unsuccessful """ - result = None for result in self._recordSocket.recv(): if result in (0, 1): diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index f4b0fd3c..0d55023d 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -12,13 +12,11 @@ from .utils.compat import * from .utils.cryptomath import * -from .utils.cipherfactory import createAES, createRC4, createTripleDES -from .utils.codec import * +from .utils.codec import Parser from .errors import * from .messages import * from .mathtls import * from .constants import * -from .utils.cryptomath import getRandomBytes from .recordlayer import RecordLayer import socket @@ -526,6 +524,7 @@ def _sendMsg(self, msg, randomizeFirstBlock = True): #an attacker from launching a chosen-plaintext attack based on #knowing the next IV (a la BEAST). # TODO don't reference private fields in _recordLayer + # to be fixed with proper message fragmentation implementation if not self.closed and randomizeFirstBlock and self.version <= (3,1) \ and self._recordLayer._writeState.encContext \ and self._recordLayer._writeState.encContext.isBlockCipher \ From a2d052338c8b444cc09afdaae4e07da6163466d8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 2 Jun 2015 13:09:06 +0200 Subject: [PATCH 049/574] add comments to MockSock --- unit_tests/mocksock.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/unit_tests/mocksock.py b/unit_tests/mocksock.py index bd05235a..5e3d282c 100644 --- a/unit_tests/mocksock.py +++ b/unit_tests/mocksock.py @@ -6,14 +6,22 @@ import errno class MockSocket(socket.socket): def __init__(self, buf, maxRet=None, maxWrite=None, blockEveryOther=False): + # current position in read buffer (buf) self.index = 0 + # read buffer self.buf = buf + # write buffer (data sent from application, to be asserted by test) self.sent = [] self.closed = False + # maximum number of bytes that socket will read/return at a time self.maxRet = maxRet + # maximum number of bytes that socket will write at a time self.maxWrite = maxWrite + # make socket rise errno.EWOULDBLOCK every other read or write self.blockEveryOther = blockEveryOther + # if next read will be blocked self.blockRead = False + # if next write will be blocked self.blockWrite = False def __repr__(self): @@ -24,6 +32,8 @@ def recv(self, size): if self.closed: raise ValueError("Read from closed socket") + # simulate a socket with full buffers, make it rise "Would block" + # every other call if self.blockEveryOther: if self.blockRead: self.blockRead = False @@ -31,16 +41,26 @@ def recv(self, size): else: self.blockRead = True + # return empty array if the caller asked for no data if size == 0: return bytearray(0) + + # limit returned data (if set) + # this will cause the socket to return just maxRet bytes, even if it + # has more in buf or was asked to return more in this call if self.maxRet is not None and self.maxRet < size: size = self.maxRet + + # don't allow reading past array end if len(self.buf[self.index:]) == 0: raise socket.error(errno.EWOULDBLOCK) + # if asked for more than we have prepared, return just as much as we + # have elif len(self.buf[self.index:]) < size: ret = self.buf[self.index:] self.index = len(self.buf) return ret + # regular call, return as much as was asked for else: ret = self.buf[self.index:self.index+size] self.index+=size @@ -50,6 +70,8 @@ def send(self, data): if self.closed: raise ValueError("Write to closed socket") + # simulate a socket with full buffer, raise "Would Block" every other + # call if self.blockEveryOther: if self.blockWrite: self.blockWrite = False @@ -57,10 +79,13 @@ def send(self, data): else: self.blockWrite = True + # regular write, just append to list of performed writes if self.maxWrite is None or len(data) < self.maxWrite: self.sent.append(data) return len(data) + # simulate a socket that won't write more data that it can + # (e.g. because the simulated buffers are mostly full) self.sent.append(data[:self.maxWrite]) return self.maxWrite From 7e4b64e558f65f6baf8a098a69a8ced4b76dfce7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 2 Jun 2015 13:43:12 +0200 Subject: [PATCH 050/574] clear up the use of TLSRecordLayer.version --- tlslite/tlsrecordlayer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 75c148c7..7c188c19 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -157,7 +157,13 @@ def version(self): @version.setter def version(self, value): - """Set the SSL protocol version of connection""" + """ + Set the SSL protocol version of connection + + The setter is a public method only for backwards compatibility. + Don't use it! See at HandshakeSettings for options to set desired + protocol version. + """ self._version = value self._recordSocket.version = value From 1cf55af7e28f3391bda787d594590f82f7147f54 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 31 Mar 2015 18:15:05 +0200 Subject: [PATCH 051/574] encryption using encrypt-then-MAC --- tlslite/recordlayer.py | 31 +++++++++- unit_tests/test_tlslite_recordlayer.py | 83 ++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 4827e89b..11e8de1a 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -221,6 +221,8 @@ class RecordLayer(object): @ivar version: the TLS version to use (tuple encoded as on the wire) @ivar sock: underlying socket @ivar client: whether the connection should use encryption + @ivar encryptThenMAC: use the encrypt-then-MAC mechanism for record + integrity """ def __init__(self, sock): @@ -236,6 +238,8 @@ def __init__(self, sock): self._pendingReadState = ConnectionState() self.fixedIVBlock = None + self.encryptThenMAC = False + @property def version(self): """Return the TLS version used by record layer""" @@ -329,6 +333,28 @@ def _macThenEncrypt(self, data, contentType): return data + def _encryptThenMAC(self, buf, contentType): + """Pad, encrypt and then MAC the data""" + if self._writeState.encContext: + # add IV for TLS1.1+ + if self.version >= (3, 2): + buf = self.fixedIVBlock + buf + + buf = self._addPadding(buf) + + buf = self._writeState.encContext.encrypt(buf) + + # add MAC + if self._writeState.macContext: + seqnumBytes = self._writeState.getSeqNumBytes() + mac = self._writeState.macContext.copy() + + # append MAC + macBytes = self._calculateMAC(mac, seqnumBytes, contentType, buf) + buf += macBytes + + return buf + # randomizeFirstBlock will get used once handling of fragmented # messages is implemented def sendMessage(self, msg, randomizeFirstBlock=True): @@ -343,7 +369,10 @@ def sendMessage(self, msg, randomizeFirstBlock=True): data = msg.write() contentType = msg.contentType - data = self._macThenEncrypt(data, contentType) + if self.encryptThenMAC: + data = self._encryptThenMAC(data, contentType) + else: + data = self._macThenEncrypt(data, contentType) encryptedMessage = Message(contentType, data) diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 502eb656..d54ef070 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -765,6 +765,89 @@ def test_sendMessage_with_slow_socket(self): bytearray(b'\x32'), bytearray(b'\x32')], mockSock.sent) + def test_sendMessage_with_encryptThenMAC_and_unset_crypto_state(self): + sock = MockSocket(bytearray(0)) + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 1) + recordLayer.encryptThenMAC = True + + app_data = ApplicationData().create(bytearray(b'test')) + + for result in recordLayer.sendMessage(app_data): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLS version + b'\x00\x04' + # length + b'test'), sock.sent[0]) + + def test_sendMessage_with_encryptThenMAC_in_TLSv1_0(self): + sock = MockSocket(bytearray(0)) + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 1) + recordLayer.encryptThenMAC = True + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + for result in recordLayer.sendMessage(app_data): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLS version + b'\x00\x24' + # length - 1 block + 20 bytes of MAC + b'\xc7\xd6\xaf:.MY\x80W\x81\xd2|5A#\xd5' + + b'X\xcd\xdc\'o\xb3I\xdd-\xfc\tneq~\x0f' + + b'd\xdb\xbdw'), sock.sent[0]) + + def test_sendMessage_with_encryptThenMAC_in_TLSv1_2(self): + patcher = mock.patch.object(os, + 'urandom', + lambda x: bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + sock = MockSocket(bytearray(0)) + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 3) + recordLayer.encryptThenMAC = True + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + for result in recordLayer.sendMessage(app_data): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(bytearray( + b'\x17' + # application data + b'\x03\x03' + # TLS version + b'\x00\x34' + # length - IV + 1 block + 20 bytes of MAC + b'H&\x1f\xc1\x9c\xde"\x92\xdd\xe4|\xfco)R\xd6' + + b'\x11~\xf2\xed\xa0l\x11\xb4\xb7\xbd\x1a- Date: Wed, 1 Apr 2015 15:44:10 +0200 Subject: [PATCH 052/574] decryption in encrypt-then-MAC --- tlslite/recordlayer.py | 61 +++- unit_tests/test_tlslite_recordlayer.py | 403 +++++++++++++++++++++++++ 2 files changed, 463 insertions(+), 1 deletion(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 11e8de1a..a9c33b12 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -447,6 +447,62 @@ def _decryptThenMAC(self, recordType, data): return data + def _macThenDecrypt(self, recordType, buf): + """ + Check MAC of data, then decrypt and remove padding + + @raise TLSBadRecordMAC: when the mac value is invalid + @raise TLSDecryptionFailed: when the data to decrypt has invalid size + """ + if self._readState.macContext: + macLength = self._readState.macContext.digest_size + if len(buf) < macLength: + raise TLSBadRecordMAC("Truncated data") + + checkBytes = buf[-macLength:] + buf = buf[:-macLength] + + seqnumBytes = self._readState.getSeqNumBytes() + mac = self._readState.macContext.copy() + + macBytes = self._calculateMAC(mac, seqnumBytes, recordType, buf) + + if macBytes != checkBytes: + raise TLSBadRecordMAC("MAC mismatch") + + if self._readState.encContext: + blockLength = self._readState.encContext.block_size + if len(buf) % blockLength != 0: + raise TLSDecryptionFailed("data length not multiple of "\ + "block size") + + buf = self._readState.encContext.decrypt(buf) + + # remove explicit IV + if self.version >= (3, 2): + buf = buf[blockLength:] + + # check padding + paddingLength = buf[-1] + if paddingLength + 1 > len(buf): + raise TLSBadRecordMAC("Invalid padding length") + + paddingGood = True + totalPaddingLength = paddingLength+1 + if self.version != (3, 0): + paddingBytes = buf[-totalPaddingLength:-1] + for byte in paddingBytes: + if byte != paddingLength: + paddingGood = False + + if not paddingGood: + raise TLSBadRecordMAC("Invalid padding byte values") + + # remove padding + buf = buf[:-totalPaddingLength] + + return buf + def recvMessage(self): """ Read, decrypt and check integrity of message @@ -466,7 +522,10 @@ def recvMessage(self): (header, data) = result - data = self._decryptThenMAC(header.type, data) + if self.encryptThenMAC: + data = self._macThenDecrypt(header.type, data) + else: + data = self._decryptThenMAC(header.type, data) yield (header, Parser(data)) diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index d54ef070..93f43097 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -1382,3 +1382,406 @@ def broken_padding(data): with self.assertRaises(TLSBadRecordMAC): next(gen) + + def test_recvMessage_with_encryptThenMAC_and_unset_crypto_state(self): + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLS version + b'\x00\x04' + # length + b'test')) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 1) + recordLayer.client = False + recordLayer.encryptThenMAC = True + + for result in recordLayer.recvMessage(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, parser = result + + self.assertEqual(parser.bytes, bytearray(b'test')) + + def test_recvMessage_with_encryptThenMAC_in_TLSv1_0(self): + # data from test_sendMessage_with_encryptThenMAC_in_TLSv1_0 + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLS version + b'\x00\x24' + # length - 1 block + 20 bytes of MAC + b'\xc7\xd6\xaf:.MY\x80W\x81\xd2|5A#\xd5' + + b'X\xcd\xdc\'o\xb3I\xdd-\xfc\tneq~\x0f' + + b'd\xdb\xbdw')) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 1) + recordLayer.encryptThenMAC = True + recordLayer.client = False + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + for result in recordLayer.recvMessage(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + header, parser = result + + self.assertEqual(parser.bytes, bytearray(b'test')) + + def test_recvMessage_with_encryptThenMAC_in_TLSv1_2(self): + + # data from test_sendMessage_with_encryptThenMAC_in_TLSv1_2 + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x03' + # TLS version + b'\x00\x34' + # length - IV + 1 block + 20 bytes of MAC + b'H&\x1f\xc1\x9c\xde"\x92\xdd\xe4|\xfco)R\xd6' + + b'\x11~\xf2\xed\xa0l\x11\xb4\xb7\xbd\x1a- Date: Wed, 1 Apr 2015 15:52:23 +0200 Subject: [PATCH 053/574] add useEncryptThenMAC to HandshakeSettings --- tlslite/handshakesettings.py | 5 +++++ unit_tests/test_tlslite_handshakesettings.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 942d13bb..6fe92d10 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -110,6 +110,7 @@ def __init__(self): self.maxVersion = (3,3) self.useExperimentalTackExtension = False self.sendFallbackSCSV = False + self.useEncryptThenMAC = True def validate(self): """ @@ -130,6 +131,7 @@ def validate(self): other.minVersion = self.minVersion other.maxVersion = self.maxVersion other.sendFallbackSCSV = self.sendFallbackSCSV + other.useEncryptThenMAC = self.useEncryptThenMAC if not cipherfactory.tripleDESPresent: other.cipherNames = [e for e in self.cipherNames if e != "3des"] @@ -180,6 +182,9 @@ def validate(self): # No sha256 pre TLS 1.2 other.macNames = [e for e in self.macNames if e != "sha256"] + if other.useEncryptThenMAC not in (True, False): + raise ValueError("useEncryptThenMAC can only be True or False") + return other def getCertificateTypes(self): diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index 31de7157..3733825c 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -152,3 +152,20 @@ def test_getCertificateTypes_with_unsupported_type(self): with self.assertRaises(AssertionError): hs.getCertificateTypes() + + def test_useEncryptThenMAC(self): + hs = HandshakeSettings() + self.assertTrue(hs.useEncryptThenMAC) + + hs.useEncryptThenMAC = False + + n_hs = hs.validate() + + self.assertFalse(n_hs.useEncryptThenMAC) + + def test_useEncryptThenMAC_with_wrong_value(self): + hs = HandshakeSettings() + hs.useEncryptThenMAC = None + + with self.assertRaises(ValueError): + hs.validate() From c90b99c2252e41f2e44889989ca12d203ea785b7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 1 Apr 2015 17:51:04 +0200 Subject: [PATCH 054/574] use encrypt-then-mac by default --- tests/tlstest.py | 56 ++++++++++++++++++++++++++++++++++----- tlslite/constants.py | 1 + tlslite/tlsconnection.py | 32 +++++++++++++++++++--- tlslite/tlsrecordlayer.py | 10 +++++++ 4 files changed, 88 insertions(+), 11 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index 85f8e9e8..efc4f424 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -117,6 +117,7 @@ def connect(): testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) assert(connection.session.serverName == address[0]) + assert connection.encryptThenMAC == True connection.close() print("Test 1.a - good X509, SSLv3") @@ -448,7 +449,31 @@ def connect(): raise connection.close() - print('Test 26 - good standard XMLRPC https client') + print("Test 26.a - no EtM server side") + synchro.recv(1) + connection = connect() + settings = HandshakeSettings() + assert settings.useEncryptThenMAC + connection.handshakeClientCert(serverName=address[0], settings=settings) + testConnClient(connection) + assert isinstance(connection.session.serverCertChain, X509CertChain) + assert connection.session.serverName == address[0] + assert not connection.encryptThenMAC + connection.close() + + print("Test 26.b - no EtM client side") + synchro.recv(1) + connection = connect() + settings = HandshakeSettings() + settings.useEncryptThenMAC = False + connection.handshakeClientCert(serverName=address[0], settings=settings) + testConnClient(connection) + assert isinstance(connection.session.serverCertChain, X509CertChain) + assert connection.session.serverName == address[0] + assert not connection.encryptThenMAC + connection.close() + + print('Test 27 - good standard XMLRPC https client') address = address[0], address[1]+1 synchro.recv(1) try: @@ -465,7 +490,7 @@ def connect(): synchro.recv(1) assert server.pow(2,4) == 16 - print('Test 27 - good tlslite XMLRPC client') + print('Test 28 - good tlslite XMLRPC client') transport = XMLRPCTransport(ignoreAbruptClose=True) server = xmlrpclib.Server('https://%s:%s' % address, transport) synchro.recv(1) @@ -473,22 +498,22 @@ def connect(): synchro.recv(1) assert server.pow(2,4) == 16 - print('Test 28 - good XMLRPC ignored protocol') + print('Test 29 - good XMLRPC ignored protocol') server = xmlrpclib.Server('http://%s:%s' % address, transport) synchro.recv(1) assert server.add(1,2) == 3 synchro.recv(1) assert server.pow(2,4) == 16 - print("Test 29 - Internet servers test") + print("Test 30 - Internet servers test") try: i = IMAP4_TLS("cyrus.andrew.cmu.edu") i.login("anonymous", "anonymous@anonymous.net") i.logout() - print("Test 30: IMAP4 good") + print("Test 31: IMAP4 good") p = POP3_TLS("pop.gmail.com") p.quit() - print("Test 31: POP3 good") + print("Test 32: POP3 good") except socket.error as e: print("Non-critical error: socket error trying to reach internet server: ", e) @@ -885,7 +910,24 @@ def server_bind(self): raise connection.close() - print("Tests 26-28 - XMLRPXC server") + print("Test 26.a - no EtM server side") + synchro.send(b'R') + connection = connect() + settings = HandshakeSettings() + settings.useEncryptThenMAC = False + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, + settings=settings) + testConnServer(connection) + connection.close() + + print("Test 26.b - no EtM client side") + synchro.send(b'R') + connection = connect() + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) + testConnServer(connection) + connection.close() + + print("Tests 27-29 - XMLRPXC server") address = address[0], address[1]+1 class Server(TLSXMLRPCServer): diff --git a/tlslite/constants.py b/tlslite/constants.py index 5445d93b..59ff6200 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -44,6 +44,7 @@ class ExtensionType: # RFC 6066 / 4366 server_name = 0 # RFC 6066 / 4366 srp = 12 # RFC 5054 cert_type = 9 # RFC 6091 + encrypt_then_mac = 22 # RFC 7366 tack = 0xF300 supports_npn = 13172 diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 10168a9f..7279a5c7 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -424,6 +424,10 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, # (string or None) nextProto = self._clientSelectNextProto(nextProtos, serverHello) + # Check if server selected encrypt-then-MAC + if serverHello.getExtension(ExtensionType.encrypt_then_mac): + self._recordLayer.encryptThenMAC = True + #If the server elected to resume the session, it is handled here. for result in self._clientResume(session, serverHello, clientHello.random, @@ -515,6 +519,13 @@ def _clientSendClientHello(self, settings, session, srpUsername, #Initialize acceptable certificate types certificateTypes = settings.getCertificateTypes() + + #Initialize TLS extensions + if settings.useEncryptThenMAC: + extensions = [TLSExtension().create(ExtensionType.encrypt_then_mac, + bytearray(0))] + else: + extensions = None #Either send ClientHello (with a resumable session)... if session and session.sessionID: @@ -530,7 +541,8 @@ def _clientSendClientHello(self, settings, session, srpUsername, certificateTypes, session.srpUsername, reqTack, nextProtos is not None, - session.serverName) + session.serverName, + extensions=extensions) #Or send ClientHello (without) else: @@ -540,7 +552,8 @@ def _clientSendClientHello(self, settings, session, srpUsername, certificateTypes, srpUsername, reqTack, nextProtos is not None, - serverName) + serverName, + extensions=extensions) for result in self._sendMsg(clientHello): yield result yield clientHello @@ -1149,10 +1162,21 @@ def _handshakeServerAsyncHelper(self, verifierDB, tackExt = TackExtension.create(tacks, activationFlags) else: tackExt = None + + # Prepare other extensions if requested + if settings.useEncryptThenMAC and \ + clientHello.getExtension(ExtensionType.encrypt_then_mac) and \ + cipherSuite not in CipherSuite.rc4Suites: + extensions = [TLSExtension().create(ExtensionType.encrypt_then_mac, + bytearray(0))] + self._recordLayer.encryptThenMAC = True + else: + extensions = None + serverHello = ServerHello() serverHello.create(self.version, getRandomBytes(32), sessionID, \ - cipherSuite, CertificateType.x509, tackExt, - nextProtos) + cipherSuite, CertificateType.x509, tackExt, + nextProtos, extensions=extensions) # Perform the SRP key exchange clientCertChain = None diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index b720eec6..4b22861b 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -81,6 +81,11 @@ class TLSRecordLayer(object): attacker truncating the connection, and only if necessary to avoid spurious errors. The default is False. + @type encryptThenMAC: bool + @ivear encryptThenMAC: Whether the connection uses the encrypt-then-MAC + construct for CBC cipher suites, will be False also if connection uses + RC4 or AEAD. + @sort: __init__, read, readAsync, write, writeAsync, close, closeAsync, getCipherImplementation, getCipherName """ @@ -148,6 +153,11 @@ def version(self, value): """ self._recordLayer.version = value + @property + def encryptThenMAC(self): + """Whether the connection uses Encrypt Then MAC (RFC 7366)""" + return self._recordLayer.encryptThenMAC + def clearReadBuffer(self): self._readBuffer = b'' From 4bf20ece59d7925a3fa70bedb73ec255ac3e0f63 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 1 Apr 2015 18:33:42 +0200 Subject: [PATCH 055/574] add support for resumption with EtM --- tests/tlstest.py | 87 ++++++++++++++++++++++++++++++++++++++++ tlslite/session.py | 12 +++++- tlslite/tlsconnection.py | 22 ++++++++-- 3 files changed, 116 insertions(+), 5 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index efc4f424..4f96ee85 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -473,6 +473,55 @@ def connect(): assert not connection.encryptThenMAC connection.close() + print("Test 26.c - resumption with EtM") + synchro.recv(1) + connection = connect() + connection.handshakeClientCert(serverName=address[0]) + testConnClient(connection) + assert isinstance(connection.session.serverCertChain, X509CertChain) + assert connection.session.serverName == address[0] + assert not connection.resumed + assert connection.encryptThenMAC + connection.close() + session = connection.session + + # resume + synchro.recv(1) + connection = connect() + connection.handshakeClientCert(serverName=address[0], session=session) + testConnClient(connection) + assert isinstance(connection.session.serverCertChain, X509CertChain) + assert connection.session.serverName == address[0] + assert connection.resumed + assert connection.encryptThenMAC + connection.close() + + print("Test 26.d - resumption with no EtM in 2nd handshake") + synchro.recv(1) + connection = connect() + connection.handshakeClientCert(serverName=address[0]) + testConnClient(connection) + assert isinstance(connection.session.serverCertChain, X509CertChain) + assert connection.session.serverName == address[0] + assert not connection.resumed + assert connection.encryptThenMAC + connection.close() + session = connection.session + + # resume + synchro.recv(1) + settings = HandshakeSettings() + settings.useEncryptThenMAC = False + connection = connect() + try: + connection.handshakeClientCert(serverName=address[0], session=session, + settings=settings) + except TLSRemoteAlert as e: + assert str(e) == "handshake_failure" + else: + raise AssertionError("No exception raised") + connection.close() + print('Test 27 - good standard XMLRPC https client') address = address[0], address[1]+1 synchro.recv(1) @@ -927,6 +976,44 @@ def server_bind(self): testConnServer(connection) connection.close() + print("Test 26.c - resumption with EtM") + synchro.send(b'R') + sessionCache = SessionCache() + connection = connect() + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, + sessionCache=sessionCache) + testConnServer(connection) + connection.close() + + # resume + synchro.send(b'R') + connection = connect() + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, + sessionCache=sessionCache) + testConnServer(connection) + connection.close() + + print("Test 26.d - resumption with no EtM in 2nd handshake") + synchro.send(b'R') + sessionCache = SessionCache() + connection = connect() + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, + sessionCache=sessionCache) + testConnServer(connection) + connection.close() + + # resume + synchro.send(b'R') + connection = connect() + try: + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, + sessionCache=sessionCache) + except TLSLocalAlert as e: + assert str(e) == "handshake_failure" + else: + raise AssertionError("no exception raised") + connection.close() + print("Tests 27-29 - XMLRPXC server") address = address[0], address[1]+1 class Server(TLSXMLRPCServer): diff --git a/tlslite/session.py b/tlslite/session.py index 6aadf58e..bc2715cb 100644 --- a/tlslite/session.py +++ b/tlslite/session.py @@ -41,7 +41,11 @@ class Session(object): @ivar tackExt: The server's TackExtension (or None). @type tackInHelloExt: L{bool} - @ivar tackInHelloExt: True if a TACK was presented via TLS Extension. + @ivar tackInHelloExt:True if a TACK was presented via TLS Extension. + + @type encryptThenMAC: bool + @ivar encryptThenMAC: True if connection uses CBC cipher in + encrypt-then-MAC mode """ def __init__(self): @@ -55,10 +59,12 @@ def __init__(self): self.tackInHelloExt = False self.serverName = "" self.resumable = False + self.encryptThenMAC = False def create(self, masterSecret, sessionID, cipherSuite, srpUsername, clientCertChain, serverCertChain, - tackExt, tackInHelloExt, serverName, resumable=True): + tackExt, tackInHelloExt, serverName, resumable=True, + encryptThenMAC=False): self.masterSecret = masterSecret self.sessionID = sessionID self.cipherSuite = cipherSuite @@ -69,6 +75,7 @@ def create(self, masterSecret, sessionID, cipherSuite, self.tackInHelloExt = tackInHelloExt self.serverName = serverName self.resumable = resumable + self.encryptThenMAC = encryptThenMAC def _clone(self): other = Session() @@ -82,6 +89,7 @@ def _clone(self): other.tackInHelloExt = self.tackInHelloExt other.serverName = self.serverName other.resumable = self.resumable + other.encryptThenMAC = self.encryptThenMAC return other def valid(self): diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 7279a5c7..6206dd9b 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -493,7 +493,8 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, self.session = Session() self.session.create(masterSecret, serverHello.session_id, cipherSuite, srpUsername, clientCertChain, serverCertChain, - tackExt, serverHello.tackExt!=None, serverName) + tackExt, serverHello.tackExt!=None, serverName, + encryptThenMAC=self._recordLayer.encryptThenMAC) self._handshakeDone(resumed=False) @@ -1232,7 +1233,8 @@ def _handshakeServerAsyncHelper(self, verifierDB, serverName = clientHello.server_name.decode("utf-8") self.session.create(masterSecret, serverHello.session_id, cipherSuite, srpUsername, clientCertChain, serverCertChain, - tackExt, serverHello.tackExt!=None, serverName) + tackExt, serverHello.tackExt!=None, serverName, + encryptThenMAC=self._recordLayer.encryptThenMAC) #Add the session object to the session cache if sessionCache and sessionID: @@ -1323,16 +1325,30 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, for result in self._sendError(\ AlertDescription.handshake_failure): yield result + if session.encryptThenMAC and \ + not clientHello.getExtension( + ExtensionType.encrypt_then_mac): + for result in self._sendError(\ + AlertDescription.handshake_failure): + yield result except KeyError: pass #If a session is found.. if session: #Send ServerHello + if session.encryptThenMAC: + self._recordLayer.encryptThenMAC = True + extensions = [TLSExtension().create( + ExtensionType.encrypt_then_mac, + bytearray(0))] + else: + extensions = None serverHello = ServerHello() serverHello.create(self.version, getRandomBytes(32), session.sessionID, session.cipherSuite, - CertificateType.x509, None, None) + CertificateType.x509, None, None, + extensions=extensions) for result in self._sendMsg(serverHello): yield result From b1d6ff4019d3d6b413ab1d7e75fb5f33a2040e2c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 1 Apr 2015 18:57:21 +0200 Subject: [PATCH 056/574] add EtM indication for the test client --- scripts/tls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/tls.py b/scripts/tls.py index 48035ce2..58c4edec 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -182,6 +182,7 @@ def printGoodConnection(connection, seconds): print(" TACK: %s" % emptyStr) print(str(connection.session.tackExt)) print(" Next-Protocol Negotiated: %s" % connection.next_proto) + print(" Encrypt-then-MAC: {0}".format(connection.encryptThenMAC)) def clientCmd(argv): From 8dba52267db83d7ec77d2d7b05a718a5d930cae6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 1 Jun 2015 15:34:16 +0200 Subject: [PATCH 057/574] fix pylint warnings fixes: C0330 (bad-continuation) C0326 (bad-whitespace) --- tlslite/session.py | 6 +++--- tlslite/tlsconnection.py | 20 +++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tlslite/session.py b/tlslite/session.py index bc2715cb..d8641a60 100644 --- a/tlslite/session.py +++ b/tlslite/session.py @@ -62,9 +62,9 @@ def __init__(self): self.encryptThenMAC = False def create(self, masterSecret, sessionID, cipherSuite, - srpUsername, clientCertChain, serverCertChain, - tackExt, tackInHelloExt, serverName, resumable=True, - encryptThenMAC=False): + srpUsername, clientCertChain, serverCertChain, + tackExt, tackInHelloExt, serverName, resumable=True, + encryptThenMAC=False): self.masterSecret = masterSecret self.sessionID = sessionID self.cipherSuite = cipherSuite diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 6206dd9b..c59e741f 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -492,9 +492,10 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, # Create the session object which is used for resumptions self.session = Session() self.session.create(masterSecret, serverHello.session_id, cipherSuite, - srpUsername, clientCertChain, serverCertChain, - tackExt, serverHello.tackExt!=None, serverName, - encryptThenMAC=self._recordLayer.encryptThenMAC) + srpUsername, clientCertChain, serverCertChain, + tackExt, (serverHello.tackExt is not None), + serverName, + encryptThenMAC=self._recordLayer.encryptThenMAC) self._handshakeDone(resumed=False) @@ -1232,9 +1233,10 @@ def _handshakeServerAsyncHelper(self, verifierDB, if clientHello.server_name: serverName = clientHello.server_name.decode("utf-8") self.session.create(masterSecret, serverHello.session_id, cipherSuite, - srpUsername, clientCertChain, serverCertChain, - tackExt, serverHello.tackExt!=None, serverName, - encryptThenMAC=self._recordLayer.encryptThenMAC) + srpUsername, clientCertChain, serverCertChain, + tackExt, (serverHello.tackExt is not None), + serverName, + encryptThenMAC=self._recordLayer.encryptThenMAC) #Add the session object to the session cache if sessionCache and sessionID: @@ -1339,9 +1341,9 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, #Send ServerHello if session.encryptThenMAC: self._recordLayer.encryptThenMAC = True - extensions = [TLSExtension().create( - ExtensionType.encrypt_then_mac, - bytearray(0))] + mte = TLSExtension().create(ExtensionType.encrypt_then_mac, + bytearray(0)) + extensions = [mte] else: extensions = None serverHello = ServerHello() From 3d0106a5355bf4d3ddd1b743404a5003087f8d3a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 1 Jun 2015 22:21:06 +0200 Subject: [PATCH 058/574] fixup typo in encryptThenMac description --- tlslite/tlsrecordlayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 4b22861b..ef3322c9 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -82,7 +82,7 @@ class TLSRecordLayer(object): spurious errors. The default is False. @type encryptThenMAC: bool - @ivear encryptThenMAC: Whether the connection uses the encrypt-then-MAC + @ivar encryptThenMAC: Whether the connection uses the encrypt-then-MAC construct for CBC cipher suites, will be False also if connection uses RC4 or AEAD. From 5da3d65494c4d2e3bb5b38a057dbb95427b167b7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 3 Jun 2015 16:22:24 +0200 Subject: [PATCH 059/574] keep assert format consistent the rest of file uses assert as a method, not a statement, so keep it consistent, even though it's not necessary --- tests/tlstest.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index 4f96ee85..38ca9c72 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -117,7 +117,7 @@ def connect(): testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) assert(connection.session.serverName == address[0]) - assert connection.encryptThenMAC == True + assert(connection.encryptThenMAC == True) connection.close() print("Test 1.a - good X509, SSLv3") @@ -453,12 +453,12 @@ def connect(): synchro.recv(1) connection = connect() settings = HandshakeSettings() - assert settings.useEncryptThenMAC + assert(settings.useEncryptThenMAC) connection.handshakeClientCert(serverName=address[0], settings=settings) testConnClient(connection) - assert isinstance(connection.session.serverCertChain, X509CertChain) - assert connection.session.serverName == address[0] - assert not connection.encryptThenMAC + assert(isinstance(connection.session.serverCertChain, X509CertChain)) + assert(connection.session.serverName == address[0]) + assert(not connection.encryptThenMAC) connection.close() print("Test 26.b - no EtM client side") @@ -468,9 +468,9 @@ def connect(): settings.useEncryptThenMAC = False connection.handshakeClientCert(serverName=address[0], settings=settings) testConnClient(connection) - assert isinstance(connection.session.serverCertChain, X509CertChain) - assert connection.session.serverName == address[0] - assert not connection.encryptThenMAC + assert(isinstance(connection.session.serverCertChain, X509CertChain)) + assert(connection.session.serverName == address[0]) + assert(not connection.encryptThenMAC) connection.close() print("Test 26.c - resumption with EtM") @@ -478,10 +478,10 @@ def connect(): connection = connect() connection.handshakeClientCert(serverName=address[0]) testConnClient(connection) - assert isinstance(connection.session.serverCertChain, X509CertChain) - assert connection.session.serverName == address[0] - assert not connection.resumed - assert connection.encryptThenMAC + assert(isinstance(connection.session.serverCertChain, X509CertChain)) + assert(connection.session.serverName == address[0]) + assert(not connection.resumed) + assert(connection.encryptThenMAC) connection.close() session = connection.session @@ -490,10 +490,10 @@ def connect(): connection = connect() connection.handshakeClientCert(serverName=address[0], session=session) testConnClient(connection) - assert isinstance(connection.session.serverCertChain, X509CertChain) - assert connection.session.serverName == address[0] - assert connection.resumed - assert connection.encryptThenMAC + assert(isinstance(connection.session.serverCertChain, X509CertChain)) + assert(connection.session.serverName == address[0]) + assert(connection.resumed) + assert(connection.encryptThenMAC) connection.close() print("Test 26.d - resumption with no EtM in 2nd handshake") @@ -501,10 +501,10 @@ def connect(): connection = connect() connection.handshakeClientCert(serverName=address[0]) testConnClient(connection) - assert isinstance(connection.session.serverCertChain, X509CertChain) - assert connection.session.serverName == address[0] - assert not connection.resumed - assert connection.encryptThenMAC + assert(isinstance(connection.session.serverCertChain, X509CertChain)) + assert(connection.session.serverName == address[0]) + assert(not connection.resumed) + assert(connection.encryptThenMAC) connection.close() session = connection.session @@ -517,7 +517,7 @@ def connect(): connection.handshakeClientCert(serverName=address[0], session=session, settings=settings) except TLSRemoteAlert as e: - assert str(e) == "handshake_failure" + assert(str(e) == "handshake_failure") else: raise AssertionError("No exception raised") connection.close() @@ -1009,7 +1009,7 @@ def server_bind(self): connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, sessionCache=sessionCache) except TLSLocalAlert as e: - assert str(e) == "handshake_failure" + assert(str(e) == "handshake_failure") else: raise AssertionError("no exception raised") connection.close() From 984e4c14ce3ce178e31f47224826823348aaf09e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 3 Jun 2015 19:21:19 +0200 Subject: [PATCH 060/574] mark as 0.5.0-alpha2 [ci skip] --- README.md | 19 +++++++++++++++---- setup.py | 10 +++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a6f063ce..382662b9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.5.0-alpha1 2015-05-28 +tlslite-ng version 0.5.0-alpha2 2015-06-03 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -68,13 +68,10 @@ Options: * If you have the M2Crypto interface to OpenSSL, this will be used for fast RSA operations and fast ciphers. - * If you have pycrypto this will be used for fast RSA operations and fast ciphers. - * If you have the GMPY interface to GMP, this will be used for fast RSA and SRP operations. - * These modules don't need to be present at installation - you can install them any time. @@ -516,6 +513,20 @@ RFC 7366. 12 History =========== + +0.5.0-alpha - xx/xx/xxxx - Hubert Kario + - Implement RFC 7366 - Encrypt-then-MAC + - generate minimal padding for CBC ciphers (David Benjamin - Chromium) + - implementation of `FALLBACK_SCSV` (David Benjamin - Chromium) + - fix issue with handling keys in session cache (Mirko Dziadzka) + - coverage measurement for unit tests + - introduced Continous Integration, targetting 2.6, 2.7, 3.2, 3.3 and 3.4 + - support PKCS#8 files with m2crypto installed for loading private keys + - fix Writer not to silently overflow integers + - fix Parser getFixBytes boundary checking + - big code refactors, mainly TLSRecordLayer and TLSConnection, lot of code put + under unit test coverage + 0.4.8 - 11/12/2014 - Added more acknowledgements and security considerations diff --git a/setup.py b/setup.py index 3ce281ae..a95ca3d6 100755 --- a/setup.py +++ b/setup.py @@ -5,11 +5,11 @@ from distutils.core import setup -setup(name="tlslite", - version="0.4.8", - author="Trevor Perrin", - author_email="tlslite@trevp.net", - url="http://trevp.net/tlslite/", +setup(name="tlslite-ng", + version="0.5.0-alpha2", + author="Hubert Kario", + author_email="hkario@redhat.com", + url="https://github.com/tomato42/tlslite-ng", description="tlslite implements SSL and TLS.", license="public domain and BSD", scripts=["scripts/tls.py", "scripts/tlsdb.py"], From c0ddc7ea4bb09e1b10ffc635869bc5e0958c1313 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Jun 2015 20:17:55 +0200 Subject: [PATCH 061/574] characterisation tests for TLSRecordLayer --- unit_tests/test_tlslite_tlsrecordlayer.py | 657 +++++++++++++++++++++- 1 file changed, 654 insertions(+), 3 deletions(-) diff --git a/unit_tests/test_tlslite_tlsrecordlayer.py b/unit_tests/test_tlslite_tlsrecordlayer.py index e311808c..90398738 100644 --- a/unit_tests/test_tlslite_tlsrecordlayer.py +++ b/unit_tests/test_tlslite_tlsrecordlayer.py @@ -18,9 +18,19 @@ import socket import errno from tlslite.tlsrecordlayer import TLSRecordLayer -from tlslite.constants import ContentType -from tlslite.errors import TLSAbruptCloseError, TLSLocalAlert -from tlslite.messages import Message +from tlslite.messages import Message, ClientHello, ServerHello, Certificate, \ + ServerHelloDone, ClientKeyExchange, ChangeCipherSpec, Finished, \ + RecordHeader3 +from tlslite.errors import TLSAbruptCloseError, TLSLocalAlert, \ + TLSAbruptCloseError +from tlslite.extensions import TLSExtension +from tlslite.constants import ContentType, HandshakeType, CipherSuite, \ + CertificateType +from tlslite.mathtls import calcMasterSecret, PRF_1_2 +from tlslite.x509 import X509 +from tlslite.x509certchain import X509CertChain +from tlslite.utils.keyfactory import parsePEMKey +from tlslite.utils.codec import Parser from unit_tests.mocksock import MockSocket class TestTLSRecordLayer(unittest.TestCase): @@ -239,6 +249,82 @@ def test__getNextRecord_with_SSL2_record_with_incomplete_header(self): self.assertEqual(0, result) + def test__getNextRecord_with_empty_handshake(self): + + mock_sock = MockSocket(bytearray( + b'\x16' + # handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x00' # length + )) + + record_layer = TLSRecordLayer(mock_sock) + + with self.assertRaises(TLSLocalAlert): + for result in record_layer._getNextRecord(): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + def test__getNextRecord_with_multiple_messages_in_single_record(self): + + mock_sock = MockSocket(bytearray( + b'\x16' + # handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x35' + # length + # server hello + b'\x02' + # type - server hello + b'\x00\x00\x26' + # length + b'\x03\x03' + # TLSv1.2 + b'\x01'*32 + # random + b'\x00' + # session ID length + b'\x00\x2f' + # cipher suite selected + b'\x00' + # compression method + # certificate + b'\x0b' + # type - certificate + b'\x00\x00\x03' # length + b'\x00\x00\x00' # length of certificates + # server hello done + b'\x0e' + # type - server hello done + b'\x00\x00\x00' # length + )) + + record_layer = TLSRecordLayer(mock_sock) + + results = [] + for result in record_layer._getNextRecord(): + if result in (0,1): + raise Exception("blocking") + else: + results.append(result) + if len(results) == 3: + break + + header, p = results[0] + + self.assertIsInstance(header, RecordHeader3) + self.assertEqual(ContentType.handshake, header.type) + self.assertEqual(42, len(p.bytes)) + self.assertEqual(HandshakeType.server_hello, p.bytes[0]) + + # XXX generator stops as soon as a message was read + self.assertEqual(1, len(results)) + return + + header, p = results[1] + + self.assertIsInstance(header, RecordHeader3) + self.assertEqual(ContentType.handshake, header.type) + self.assertEqual(7, len(p.bytes)) + self.assertEqual(HandshakeType.certificate, p.bytes[0]) + + header, p = results[2] + + self.assertIsInstance(header, RecordHeader3) + self.assertEqual(ContentType.handshake, header.type) + self.assertEqual(4, len(p.bytes)) + self.assertEqual(HandshakeType.server_hello_done, p.bytes[0]) + def test__sendMsg(self): mockSock = MockSocket(bytearray(0)) sock = TLSRecordLayer(mockSock) @@ -293,3 +379,568 @@ def test__sendMsg_with_errored_out_socket(self): with self.assertRaises(TLSAbruptCloseError): next(gen) + + def test__sendMsg_with_large_message(self): + + mock_sock = MockSocket(bytearray(0)) + + record_layer = TLSRecordLayer(mock_sock) + + client_hello = ClientHello().create((3,3), bytearray(32), bytearray(0), + [x for x in range(2**15-1)]) + + gen = record_layer._sendMsg(client_hello) + + # XXX should not happen, client hello messages larger than record + # layer maximum fragment size should get fragmented + with self.assertRaises(ValueError): + next(gen) + + return + + send_arg = mock_sock.send.call_args_list[0] + # The maximum length that can be sent in single record is 2**14 + # record layer adds 5 byte on top of that + self.assertTrue(len(send_arg[0][0]) <= 2**14 + 5) + self.assertEqual(2, mock_sock.send.call_count) + + def test__getMsg(self): + + mock_sock = MockSocket( + bytearray( + b'\x16' + # handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x3a' + # payload length + b'\x02' + # Server Hello + b'\x00\x00\x36' + # hello length + b'\x03\x03' + # TLSv1.2 + b'\x00'*32 + # random + b'\x00' + # session ID length + b'\x00\x2f' + # cipher suite selected (AES128-SHA) + b'\x00' + # compression null + b'\x00\x0e' + # extensions length + b'\xff\x01' + # renegotiation_info + b'\x00\x01' + # ext length + b'\x00' + # renegotiation info ext length - 0 + b'\x00\x23' + # session_ticket + b'\x00\x00' + # ext length + b'\x00\x0f' + # heartbeat extension + b'\x00\x01' + # ext length + b'\x01')) # peer is allowed to send requests + + record_layer = TLSRecordLayer(mock_sock) + + gen = record_layer._getMsg(ContentType.handshake, + HandshakeType.server_hello) + + message = next(gen) + + self.assertEqual(ServerHello, type(message)) + self.assertEqual((3,3), message.server_version) + self.assertEqual(0x002f, message.cipher_suite) + + def test__getMsg_with_fragmented_message(self): + + mock_sock = MockSocket( + bytearray( + b'\x16' + # handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x06' + # payload length + b'\x02' + # Server Hello + b'\x00\x00\x36' + # hello length + b'\x03\x03' + # TLSv1.2 + # fragment end + b'\x16' + # type - handshake + b'\x03\x03' + # TLSv1.2 + b'\x00\x34' + # payload length: + b'\x00'*32 + # random + b'\x00' + # session ID length + b'\x00\x2f' + # cipher suite selected (AES128-SHA) + b'\x00' + # compression null + b'\x00\x0e' + # extensions length + b'\xff\x01' + # renegotiation_info + b'\x00\x01' + # ext length + b'\x00' + # renegotiation info ext length - 0 + b'\x00\x23' + # session_ticket + b'\x00\x00' + # ext length + b'\x00\x0f' + # heartbeat extension + b'\x00\x01' + # ext length + b'\x01')) # peer is allowed to send requests + + record_layer = TLSRecordLayer(mock_sock) + + gen = record_layer._getMsg(ContentType.handshake, + HandshakeType.server_hello) + + # XXX record layer doesn't handle fragmented hello messages + with self.assertRaises(TLSLocalAlert): + message = next(gen) + + return + + if message in (0,1): + raise Exception("blocking") + + self.assertEqual(ServerHello, type(message)) + self.assertEqual((3,3), message.server_version) + self.assertEqual(0x002f, message.cipher_suite) + + def test__getMsg_with_oversized_message(self): + + mock_sock = MockSocket( + bytearray( + b'\x16' + # handshake + b'\x03\x03' + # TLSv1.2 + b'\x40\x01' + # payload length 2**14+1 + b'\x02' + # Server Hello + b'\x00\x3f\xfd' + # hello length 2**14+1-1-3 + b'\x03\x03' + # TLSv1.2 + b'\x00'*32 + # random + b'\x00' + # session ID length + b'\x00\x2f' + # cipher suite selected (AES128-SHA) + b'\x00' + # compression null + b'\x3f\xd5' + # extensions length: 2**14+1-1-3-2-32-6 + b'\xff\xff' + # extension type (padding) + b'\x3f\xd1' + # extension length: 2**14+1-1-3-2-32-6-4 + b'\x00'*16337 # value + )) + + record_layer = TLSRecordLayer(mock_sock) + + gen = record_layer._getMsg(ContentType.handshake, + HandshakeType.server_hello) + + # XXX decoder handles messages over the 2**14 limit! + # with self.assertRaises(TLSLocalAlert): + message = next(gen) + + # + # Temporary tests below + # + + def test_full_connection_with_RSA_kex(self): + + clnt_sock, srv_sock = socket.socketpair() + + # + # client part + # + record_layer = TLSRecordLayer(clnt_sock) + + record_layer._handshakeStart(client=True) + record_layer.version = (3,3) + + client_hello = ClientHello() + client_hello = client_hello.create((3,3), bytearray(32), + bytearray(0), [CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA], + None, None, False, False, None) + + for result in record_layer._sendMsg(client_hello): + if result in (0,1): + raise Exception("blocking socket") + + # + # server part + # + + srv_record_layer = TLSRecordLayer(srv_sock) + + srv_raw_certificate = str( + "-----BEGIN CERTIFICATE-----\n"\ + "MIIB9jCCAV+gAwIBAgIJAMyn9DpsTG55MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\n"\ + "BAMMCWxvY2FsaG9zdDAeFw0xNTAxMjExNDQzMDFaFw0xNTAyMjAxNDQzMDFaMBQx\n"\ + "EjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n"\ + "0QkEeakSyV/LMtTeARdRtX5pdbzVuUuqOIdz3lg7YOyRJ/oyLTPzWXpKxr//t4FP\n"\ + "QvYsSJiVOlPk895FNu6sNF/uJQyQGfFWYKkE6fzFifQ6s9kssskFlL1DVI/dD/Zn\n"\ + "7sgzua2P1SyLJHQTTs1MtMb170/fX2EBPkDz+2kYKN0CAwEAAaNQME4wHQYDVR0O\n"\ + "BBYEFJtvXbRmxRFXYVMOPH/29pXCpGmLMB8GA1UdIwQYMBaAFJtvXbRmxRFXYVMO\n"\ + "PH/29pXCpGmLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAkOgC7LP/\n"\ + "Rd6uJXY28HlD2K+/hMh1C3SRT855ggiCMiwstTHACGgNM+AZNqt6k8nSfXc6k1gw\n"\ + "5a7SGjzkWzMaZC3ChBeCzt/vIAGlMyXeqTRhjTCdc/ygRv3NPrhUKKsxUYyXRk5v\n"\ + "g/g6MwxzXfQP3IyFu3a9Jia/P89Z1rQCNRY=\n"\ + "-----END CERTIFICATE-----\n"\ + ) + + srv_raw_key = str( + "-----BEGIN RSA PRIVATE KEY-----\n"\ + "MIICXQIBAAKBgQDRCQR5qRLJX8sy1N4BF1G1fml1vNW5S6o4h3PeWDtg7JEn+jIt\n"\ + "M/NZekrGv/+3gU9C9ixImJU6U+Tz3kU27qw0X+4lDJAZ8VZgqQTp/MWJ9Dqz2Syy\n"\ + "yQWUvUNUj90P9mfuyDO5rY/VLIskdBNOzUy0xvXvT99fYQE+QPP7aRgo3QIDAQAB\n"\ + "AoGAVSLbE8HsyN+fHwDbuo4I1Wa7BRz33xQWLBfe9TvyUzOGm0WnkgmKn3LTacdh\n"\ + "GxgrdBZXSun6PVtV8I0im5DxyVaNdi33sp+PIkZU386f1VUqcnYnmgsnsUQEBJQu\n"\ + "fUZmgNM+bfR+Rfli4Mew8lQ0sorZ+d2/5fsM0g80Qhi5M3ECQQDvXeCyrcy0u/HZ\n"\ + "FNjIloyXaAIvavZ6Lc6gfznCSfHc5YwplOY7dIWp8FRRJcyXkA370l5dJ0EXj5Gx\n"\ + "udV9QQ43AkEA34+RxjRk4DT7Zo+tbM/Fkoi7jh1/0hFkU5NDHweJeH/mJseiHtsH\n"\ + "KOcPGtEGBBqT2KNPWVz4Fj19LiUmmjWXiwJBAIBs49O5/+ywMdAAqVblv0S0nweF\n"\ + "4fwne4cM+5ZMSiH0XsEojGY13EkTEon/N8fRmE8VzV85YmkbtFWgmPR85P0CQQCs\n"\ + "elWbN10EZZv3+q1wH7RsYzVgZX3yEhz3JcxJKkVzRCnKjYaUi6MweWN76vvbOq4K\n"\ + "G6Tiawm0Duh/K4ZmvyYVAkBppE5RRQqXiv1KF9bArcAJHvLm0vnHPpf1yIQr5bW6\n"\ + "njBuL4qcxlaKJVGRXT7yFtj2fj0gv3914jY2suWqp8XJ\n"\ + "-----END RSA PRIVATE KEY-----\n"\ + ) + + srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_cert_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + + srv_record_layer._handshakeStart(client=False) + + srv_record_layer.version = (3,3) + + for result in srv_record_layer._getMsg(ContentType.handshake, + HandshakeType.client_hello): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + srv_client_hello = result + self.assertEqual(ClientHello, type(srv_client_hello)) + + srv_cipher_suite = CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA + srv_session_id = bytearray(0) + + srv_server_hello = ServerHello().create( + (3,3), bytearray(32), srv_session_id, srv_cipher_suite, + CertificateType.x509, None, None) + + srv_msgs = [] + srv_msgs.append(srv_server_hello) + srv_msgs.append(Certificate(CertificateType.x509). + create(srv_cert_chain)) + srv_msgs.append(ServerHelloDone()) + for result in srv_record_layer._sendMsgs(srv_msgs): + if result in (0,1): + raise Exception("blocking socket") + else: + break + srv_record_layer._versionCheck = True + + # + # client part + # + + for result in record_layer._getMsg(ContentType.handshake, + HandshakeType.server_hello): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + server_hello = result + self.assertEqual(ServerHello, type(server_hello)) + + for result in record_layer._getMsg(ContentType.handshake, + HandshakeType.certificate, CertificateType.x509): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + server_certificate = result + self.assertEqual(Certificate, type(server_certificate)) + + for result in record_layer._getMsg(ContentType.handshake, + HandshakeType.server_hello_done): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + server_hello_done = result + self.assertEqual(ServerHelloDone, type(server_hello_done)) + + public_key = server_certificate.certChain.getEndEntityPublicKey() + + premasterSecret = bytearray(48) + premasterSecret[0] = 3 # 'cause we negotiatied TLSv1.2 + premasterSecret[1] = 3 + + encryptedPreMasterSecret = public_key.encrypt(premasterSecret) + + client_key_exchange = ClientKeyExchange( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + (3,3)) + client_key_exchange.createRSA(encryptedPreMasterSecret) + + for result in record_layer._sendMsg(client_key_exchange): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + master_secret = calcMasterSecret((3,3), premasterSecret, + client_hello.random, server_hello.random) + + record_layer._calcPendingStates( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + master_secret, client_hello.random, server_hello.random, + None) + + for result in record_layer._sendMsg(ChangeCipherSpec()): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + record_layer._changeWriteState() + + handshake_hashes = record_layer._handshake_sha256.digest() + verify_data = PRF_1_2(master_secret, b'client finished', + handshake_hashes, 12) + + finished = Finished((3,3)).create(verify_data) + for result in record_layer._sendMsg(finished): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + # + # server part + # + + for result in srv_record_layer._getMsg(ContentType.handshake, + HandshakeType.client_key_exchange, + srv_cipher_suite): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + srv_client_key_exchange = result + + srv_premaster_secret = srv_private_key.decrypt( + srv_client_key_exchange.encryptedPreMasterSecret) + + self.assertEqual(bytearray(b'\x03\x03' + b'\x00'*46), + srv_premaster_secret) + + srv_master_secret = calcMasterSecret(srv_record_layer.version, + srv_premaster_secret, srv_client_hello.random, + srv_server_hello.random) + + srv_record_layer._calcPendingStates(srv_cipher_suite, + srv_master_secret, srv_client_hello.random, + srv_server_hello.random, None) + + for result in srv_record_layer._getMsg(ContentType.change_cipher_spec): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + srv_change_cipher_spec = result + self.assertEqual(ChangeCipherSpec, type(srv_change_cipher_spec)) + + srv_record_layer._changeReadState() + + srv_handshakeHashes = srv_record_layer._handshake_sha256.digest() + srv_verify_data = PRF_1_2(srv_master_secret, b"client finished", + srv_handshakeHashes, 12) + + for result in srv_record_layer._getMsg(ContentType.handshake, + HandshakeType.finished): + if result in (0,1): + raise Exception("blocking socket") + else: + break + srv_finished = result + self.assertEqual(Finished, type(srv_finished)) + self.assertEqual(srv_verify_data, srv_finished.verify_data) + + for result in srv_record_layer._sendMsg(ChangeCipherSpec()): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + srv_record_layer._changeWriteState() + + srv_handshakeHashes = srv_record_layer._handshake_sha256.digest() + srv_verify_data = PRF_1_2(srv_master_secret, b"server finished", + srv_handshakeHashes, 12) + + for result in srv_record_layer._sendMsg(Finished((3,3)).create( + srv_verify_data)): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + srv_record_layer._handshakeDone(resumed=False) + + # + # client part + # + + for result in record_layer._getMsg(ContentType.change_cipher_spec): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + change_cipher_spec = result + self.assertEqual(ChangeCipherSpec, type(change_cipher_spec)) + + record_layer._changeReadState() + + handshake_hashes = record_layer._handshake_sha256.digest() + server_verify_data = PRF_1_2(master_secret, b'server finished', + handshake_hashes, 12) + + for result in record_layer._getMsg(ContentType.handshake, + HandshakeType.finished): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + server_finished = result + self.assertEqual(Finished, type(server_finished)) + self.assertEqual(server_verify_data, server_finished.verify_data) + + record_layer._handshakeDone(resumed=False) + + # try sending data + record_layer.write(bytearray(b'text\n')) + + # try recieving data + data = srv_record_layer.read(10) + self.assertEqual(data, bytearray(b'text\n')) + + record_layer.close() + srv_record_layer.close() + + @unittest.skip("needs external TLS server") + def test_full_connection_with_external_server(self): + + # TODO test is slow (100ms) move to integration test suite + # + # start a regular TLS server locally before running this test + # e.g.: openssl s_server -key localhost.key -cert localhost.crt + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(("127.0.0.1", 4433)) + + record_layer = TLSRecordLayer(sock) + + record_layer._handshakeStart(client=True) + record_layer.version = (3,3) + + client_hello = ClientHello() + client_hello = client_hello.create((3,3), bytearray(32), + bytearray(0), [CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA], + None, None, False, False, None) + + for result in record_layer._sendMsg(client_hello): + if result in (0,1): + raise Exception("blocking socket") + + for result in record_layer._getMsg(ContentType.handshake, + HandshakeType.server_hello): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + server_hello = result + self.assertEqual(ServerHello, type(server_hello)) + + for result in record_layer._getMsg(ContentType.handshake, + HandshakeType.certificate, CertificateType.x509): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + server_certificate = result + self.assertEqual(Certificate, type(server_certificate)) + + for result in record_layer._getMsg(ContentType.handshake, + HandshakeType.server_hello_done): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + server_hello_done = result + self.assertEqual(ServerHelloDone, type(server_hello_done)) + + public_key = server_certificate.certChain.getEndEntityPublicKey() + + premasterSecret = bytearray(48) + premasterSecret[0] = 3 # 'cause we negotiatied TLSv1.2 + premasterSecret[1] = 3 + + encryptedPreMasterSecret = public_key.encrypt(premasterSecret) + + client_key_exchange = ClientKeyExchange( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + (3,3)) + client_key_exchange.createRSA(encryptedPreMasterSecret) + + for result in record_layer._sendMsg(client_key_exchange): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + master_secret = calcMasterSecret((3,3), premasterSecret, + client_hello.random, server_hello.random) + + record_layer._calcPendingStates( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + master_secret, client_hello.random, server_hello.random, + None) + + for result in record_layer._sendMsg(ChangeCipherSpec()): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + record_layer._changeWriteState() + + handshake_hashes = record_layer._handshake_sha256.digest() + verify_data = PRF_1_2(master_secret, b'client finished', + handshake_hashes, 12) + + finished = Finished((3,3)).create(verify_data) + for result in record_layer._sendMsg(finished): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + for result in record_layer._getMsg(ContentType.change_cipher_spec): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + change_cipher_spec = result + self.assertEqual(ChangeCipherSpec, type(change_cipher_spec)) + + record_layer._changeReadState() + + handshake_hashes = record_layer._handshake_sha256.digest() + server_verify_data = PRF_1_2(master_secret, b'server finished', + handshake_hashes, 12) + + for result in record_layer._getMsg(ContentType.handshake, + HandshakeType.finished): + if result in (0,1): + raise Exception("blocking socket") + else: + break + + server_finished = result + self.assertEqual(Finished, type(server_finished)) + self.assertEqual(server_verify_data, server_finished.verify_data) + + record_layer._handshakeDone(resumed=False) + + record_layer.write(bytearray(b'text\n')) + + record_layer.close() + From 2806b2c34f9edd26e200340f1372f880c53bc431 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Jun 2015 15:48:32 +0200 Subject: [PATCH 062/574] add defragmenter for messages Since TLS messages are multiplexed in record layer, we need to reassemble them in specific order and only when they are fully read. Introduce a class to do that. --- tlslite/defragmenter.py | 116 +++++++++++ unit_tests/test_tlslite_defragmenter.py | 256 ++++++++++++++++++++++++ 2 files changed, 372 insertions(+) create mode 100644 tlslite/defragmenter.py create mode 100644 unit_tests/test_tlslite_defragmenter.py diff --git a/tlslite/defragmenter.py b/tlslite/defragmenter.py new file mode 100644 index 00000000..0bd61072 --- /dev/null +++ b/tlslite/defragmenter.py @@ -0,0 +1,116 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +""" Helper package for handling fragmentation of messages """ + +from __future__ import generators + +from .utils.codec import Parser + +class Defragmenter(object): + + """ + Class for demultiplexing TLS messages. + + Since the messages can be interleaved and fragmented between each other + we need to cache not complete ones and return in order of urgency. + + Supports messages with given size (like Alerts) or with a length header + in specific place (like Handshake messages). + + @ivar priorities: order in which messages from given types should be + returned. + @ivar buffers: data buffers for message types + @ivar decoders: functions which check buffers if a message of given type + is complete + """ + + def __init__(self): + """Set up empty defregmenter""" + self.priorities = [] + self.buffers = {} + self.decoders = {} + + def addStaticSize(self, msgType, size): + """Add a message type which all messages are of same length""" + if msgType in self.priorities: + raise ValueError("Message type already defined") + if size < 1: + raise ValueError("Message size must be positive integer") + + self.priorities += [msgType] + + self.buffers[msgType] = bytearray(0) + def sizeHandler(data): + """ + Size of message in parameter + + If complete message is present in parameter returns its size, + None otherwise. + """ + if len(data) < size: + return None + else: + return size + self.decoders[msgType] = sizeHandler + + def addDynamicSize(self, msgType, sizeOffset, sizeOfSize): + """Add a message type which has a dynamic size set in a header""" + if msgType in self.priorities: + raise ValueError("Message type already defined") + if sizeOfSize < 1: + raise ValueError("Size of size must be positive integer") + if sizeOffset < 0: + raise ValueError("Offset can't be negative") + + self.priorities += [msgType] + self.buffers[msgType] = bytearray(0) + + def sizeHandler(data): + """ + Size of message in parameter + + If complete message is present in parameter returns its size, + None otherwise. + """ + if len(data) < sizeOffset+sizeOfSize: + return None + else: + parser = Parser(data) + # skip the header + parser.getFixBytes(sizeOffset) + + payloadLength = parser.get(sizeOfSize) + if parser.getRemainingLength() < payloadLength: + # not enough bytes in buffer + return None + return sizeOffset + sizeOfSize + payloadLength + + self.decoders[msgType] = sizeHandler + + def addData(self, msgType, data): + """Adds data to buffers""" + if msgType not in self.priorities: + raise ValueError("Message type not defined") + + self.buffers[msgType] += data + + def getMessage(self): + """Extract the highest priority complete message from buffer""" + for msgType in self.priorities: + length = self.decoders[msgType](self.buffers[msgType]) + if length is None: + continue + + # extract message + data = self.buffers[msgType][:length] + # remove it from buffer + self.buffers[msgType] = self.buffers[msgType][length:] + return (msgType, data) + return None + + def clearBuffers(self): + """Remove all data from buffers""" + for key in self.buffers.keys(): + self.buffers[key] = bytearray(0) diff --git a/unit_tests/test_tlslite_defragmenter.py b/unit_tests/test_tlslite_defragmenter.py new file mode 100644 index 00000000..e76c5354 --- /dev/null +++ b/unit_tests/test_tlslite_defragmenter.py @@ -0,0 +1,256 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.defragmenter import Defragmenter + +class TestDefragmenter(unittest.TestCase): + def test___init__(self): + a = Defragmenter() + + self.assertIsNotNone(a) + + def test_getMessage(self): + a = Defragmenter() + + self.assertIsNone(a.getMessage()) + self.assertIsNone(a.getMessage()) + + def test_addStaticSize(self): + d = Defragmenter() + + d.addStaticSize(10, 2) + + d.addData(10, bytearray(b'\x03'*2)) + + ret = d.getMessage() + self.assertIsNotNone(ret) + msgType, data = ret + self.assertEqual(10, msgType) + self.assertEqual(bytearray(b'\x03'*2), data) + + def test_addStaticSize_with_already_defined_type(self): + d = Defragmenter() + + d.addStaticSize(10, 255) + + with self.assertRaises(ValueError): + d.addStaticSize(10, 2) + + def test_addStaticSize_with_uncomplete_message(self): + d = Defragmenter() + + d.addStaticSize(10, 2) + + d.addData(10, bytearray(b'\x10')) + + ret = d.getMessage() + self.assertIsNone(ret) + + d.addData(10, bytearray(b'\x11')) + + ret = d.getMessage() + self.assertIsNotNone(ret) + msgType, data = ret + self.assertEqual(10, msgType) + self.assertEqual(bytearray(b'\x10\x11'), data) + + ret = d.getMessage() + self.assertIsNone(ret) + + def test_addStaticSize_with_multiple_types(self): + d = Defragmenter() + + # types are added in order of priority... + d.addStaticSize(10, 2) + # so type 8 should be returned later than type 10 if both are in buffer + d.addStaticSize(8, 4) + + d.addData(8, bytearray(b'\x08'*4)) + d.addData(10, bytearray(b'\x10'*2)) + + ret = d.getMessage() + self.assertIsNotNone(ret) + msgType, data = ret + self.assertEqual(10, msgType) + self.assertEqual(bytearray(b'\x10'*2), data) + + ret = d.getMessage() + self.assertIsNotNone(ret) + msgType, data = ret + self.assertEqual(8, msgType) + self.assertEqual(bytearray(b'\x08'*4), data) + + ret = d.getMessage() + self.assertIsNone(ret) + + def test_addStaticSize_with_multiple_uncompleted_messages(self): + d = Defragmenter() + + d.addStaticSize(10, 2) + d.addStaticSize(8, 4) + + d.addData(8, bytearray(b'\x08'*3)) + d.addData(10, bytearray(b'\x10')) + + ret = d.getMessage() + self.assertIsNone(ret) + + d.addData(8, bytearray(b'\x09')) + + ret = d.getMessage() + self.assertIsNotNone(ret) + msgType, data = ret + self.assertEqual(8, msgType) + self.assertEqual(bytearray(b'\x08'*3 + b'\x09'), data) + + ret = d.getMessage() + self.assertIsNone(ret) + + def test_addDynamicSize(self): + d = Defragmenter() + + d.addDynamicSize(10, 2, 2) + + ret = d.getMessage() + self.assertIsNone(ret) + + d.addData(10, bytearray( + b'\xee\xee' + # header bytes + b'\x00\x00' + # remaining length + # next message + b'\xff\xff' + # header bytes + b'\x00\x01' + # remaining length + b'\xf0')) + + ret = d.getMessage() + self.assertIsNotNone(ret) + msgType, data = ret + self.assertEqual(10, msgType) + self.assertEqual(bytearray(b'\xee\xee\x00\x00'), data) + + ret = d.getMessage() + self.assertIsNotNone(ret) + msgType, data = ret + self.assertEqual(10, msgType) + self.assertEqual(bytearray(b'\xff\xff\x00\x01\xf0'), data) + + ret = d.getMessage() + self.assertIsNone(ret) + + def test_addDynamicSize_with_incomplete_header(self): + d = Defragmenter() + + d.addDynamicSize(10, 2, 2) + + d.addData(10, bytearray(b'\xee')) + + self.assertIsNone(d.getMessage()) + + d.addData(10, bytearray(b'\xee')) + + self.assertIsNone(d.getMessage()) + + d.addData(10, bytearray(b'\x00')) + + self.assertIsNone(d.getMessage()) + + d.addData(10, bytearray(b'\x00')) + + ret = d.getMessage() + self.assertIsNotNone(ret) + msgType, data = ret + self.assertEqual(10, msgType) + self.assertEqual(bytearray(b'\xee\xee\x00\x00'), data) + + def test_addDynamicSize_with_incomplete_payload(self): + d = Defragmenter() + + d.addDynamicSize(10, 2, 2) + + d.addData(10, bytearray(b'\xee\xee\x00\x01')) + + self.assertIsNone(d.getMessage()) + + d.addData(10, bytearray(b'\x99')) + + msgType, data = d.getMessage() + self.assertEqual(10, msgType) + self.assertEqual(bytearray(b'\xee\xee\x00\x01\x99'), data) + + def test_addDynamicSize_with_two_streams(self): + d = Defragmenter() + + d.addDynamicSize(9, 0, 3) + d.addDynamicSize(10, 2, 2) + + d.addData(10, bytearray(b'\x44\x44\x00\x04')) + d.addData(9, bytearray(b'\x00\x00\x02')) + + self.assertIsNone(d.getMessage()) + + d.addData(9, bytearray(b'\x09'*2)) + d.addData(10, bytearray(b'\x10'*4)) + + msgType, data = d.getMessage() + self.assertEqual(msgType, 9) + self.assertEqual(data, bytearray(b'\x00\x00\x02\x09\x09')) + + msgType, data = d.getMessage() + self.assertEqual(msgType, 10) + self.assertEqual(data, bytearray(b'\x44'*2 + b'\x00\x04' + b'\x10'*4)) + + def test_addStaticSize_with_zero_size(self): + d = Defragmenter() + + with self.assertRaises(ValueError): + d.addStaticSize(10, 0) + + def test_addStaticSize_with_invalid_size(self): + d = Defragmenter() + + with self.assertRaises(ValueError): + d.addStaticSize(10, -10) + + def test_addDynamicSize_with_double_type(self): + d = Defragmenter() + + d.addDynamicSize(1, 0, 1) + with self.assertRaises(ValueError): + d.addDynamicSize(1, 2, 2) + + def test_addDynamicSize_with_invalid_size(self): + d = Defragmenter() + + with self.assertRaises(ValueError): + d.addDynamicSize(1, 2, 0) + + def test_addDynamicSize_with_invalid_offset(self): + d = Defragmenter() + + with self.assertRaises(ValueError): + d.addDynamicSize(1, -1, 2) + + def test_addData_with_undefined_type(self): + d = Defragmenter() + + with self.assertRaises(ValueError): + d.addData(1, bytearray(10)) + + def test_clearBuffers(self): + d = Defragmenter() + + d.addStaticSize(10, 2) + + d.addData(10, bytearray(10)) + + d.clearBuffers() + + self.assertIsNone(d.getMessage()) From c37767889e2f0ca472497d921b4a715f401f8183 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Jun 2015 17:09:43 +0200 Subject: [PATCH 063/574] make record layer support fragmented handshake messages the new _getNextRecord() will continue reading from socket as long as the caller will ask it to, so the users need to be updated to bail out early when a message was read --- tlslite/tlsrecordlayer.py | 123 +++++++++++++--------- unit_tests/test_tlslite_tlsrecordlayer.py | 37 ++----- 2 files changed, 77 insertions(+), 83 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index ef3322c9..d1af6079 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -4,6 +4,7 @@ # Google - minimal padding # Martin von Loewis - python 3 port # Yngve Pettersen (ported by Paul Sokolovsky) - TLS 1.2 +# Hubert Kario # # See the LICENSE file for legal information regarding use of this file. @@ -18,6 +19,7 @@ from .mathtls import * from .constants import * from .recordlayer import RecordLayer +from .defragmenter import Defragmenter import socket import traceback @@ -98,7 +100,10 @@ def __init__(self, sock): self.session = None #Buffers for processing messages - self._handshakeBuffer = [] + self._defragmenter = Defragmenter() + self._defragmenter.addStaticSize(ContentType.change_cipher_spec, 1) + self._defragmenter.addStaticSize(ContentType.alert, 2) + self._defragmenter.addDynamicSize(ContentType.handshake, 1, 3) self.clearReadBuffer() self.clearWriteBuffer() @@ -589,6 +594,8 @@ def _sendMsg(self, msg, randomizeFirstBlock = True): for result in self._getNextRecord(): if result in (0, 1): yield result + else: + break # Closes the socket self._shutdown(False) @@ -618,6 +625,8 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): for result in self._getNextRecord(): if result in (0,1): yield result + else: + break recordHeader, p = result #If this is an empty application-data fragment, try again @@ -762,22 +771,58 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): formatExceptionTrace(e)): yield result - #Returns next record or next handshake message def _getNextRecord(self): + """read next message from socket, defragment message""" + + while True: + # support for fragmentation + # (RFC 5246 Section 6.2.1) + # Because the Record Layer is completely separate from the messages + # that traverse it, it should handle both application data and + # hadshake data in the same way. For that we buffer the handshake + # messages until they are completely read. + # This makes it possible to handle both handshake data not aligned + # to record boundary as well as handshakes longer than single + # record. + while True: + # empty message buffer + ret = self._defragmenter.getMessage() + if ret is None: + break + header = RecordHeader3().create(self.version, ret[0], 0) + yield header, Parser(ret[1]) - #If there's a handshake message waiting, return it - if self._handshakeBuffer: - recordHeader, b = self._handshakeBuffer[0] - self._handshakeBuffer = self._handshakeBuffer[1:] - yield (recordHeader, Parser(b)) - return + # when the message buffer is empty, read next record from socket + for result in self._getNextRecordFromSocket(): + if result in (0, 1): + yield result + else: + break + + header, parser = result + + # application data isn't made out of messages, pass it through + if header.type == ContentType.application_data: + yield (header, parser) + # If it's an SSLv2 ClientHello, we can return it as well, since + # it's the only ssl2 type we support + elif header.ssl2: + yield (header, parser) + else: + # other types need to be put into buffers + self._defragmenter.addData(header.type, parser.bytes) + + def _getNextRecordFromSocket(self): + """Read a record, handle errors""" try: + # otherwise... read the next record for result in self._recordLayer.recvMessage(): if result in (0, 1): yield result - else: break + else: + break except TLSRecordOverflow: for result in self._sendError(AlertDescription.record_overflow): yield result @@ -794,52 +839,26 @@ def _getNextRecord(self): AlertDescription.bad_record_mac, "MAC failure (or padding failure)"): yield result - (r, p) = result - b = p.bytes - #Decrypt the record + header, parser = result - #If it doesn't contain handshake messages, we can just return it - if r.type != ContentType.handshake: - yield (r, p) - #If it's an SSLv2 ClientHello, we can return it as well - elif r.ssl2: - yield (r, p) - else: - #Otherwise, we loop through and add the handshake messages to the - #handshake buffer - while 1: - if p.index == len(b): #If we're at the end - if not self._handshakeBuffer: - for result in self._sendError(\ - AlertDescription.decode_error, \ - "Received empty handshake record"): - yield result - break - #There needs to be at least 4 bytes to get a header - if p.index+4 > len(b): - for result in self._sendError(\ - AlertDescription.decode_error, - "A record has a partial handshake message (1)"): - yield result - p.get(1) # skip handshake type - msgLength = p.get(3) - if p.index+msgLength > len(b): - for result in self._sendError(\ - AlertDescription.decode_error, - "A record has a partial handshake message (2)"): - yield result - - handshakePair = (r, b[p.index-4 : p.index+msgLength]) - self._handshakeBuffer.append(handshakePair) - p.index += msgLength + # RFC5246 section 5.2.1: Implementations MUST NOT send + # zero-length fragments of content types other than Application + # Data. + if header.type != ContentType.application_data \ + and parser.getRemainingLength() == 0: + for result in self._sendError(\ + AlertDescription.decode_error, \ + "Received empty non-application data record"): + yield result - #We've moved at least one handshake message into the - #handshakeBuffer, return the first one - recordHeader, b = self._handshakeBuffer[0] - self._handshakeBuffer = self._handshakeBuffer[1:] - yield (recordHeader, Parser(b)) + if header.type not in ContentType.all: + for result in self._sendError(\ + AlertDescription.unexpected_message, \ + "Received record with unknown ContentType"): + yield result + yield (header, parser) def _handshakeStart(self, client): if not self.closed: @@ -848,7 +867,7 @@ def _handshakeStart(self, client): self._handshake_md5 = hashlib.md5() self._handshake_sha = hashlib.sha1() self._handshake_sha256 = hashlib.sha256() - self._handshakeBuffer = [] + self._defragmenter.clearBuffers() self.allegedSrpUsername = None self._refCount = 1 diff --git a/unit_tests/test_tlslite_tlsrecordlayer.py b/unit_tests/test_tlslite_tlsrecordlayer.py index 90398738..fc40f93e 100644 --- a/unit_tests/test_tlslite_tlsrecordlayer.py +++ b/unit_tests/test_tlslite_tlsrecordlayer.py @@ -48,6 +48,7 @@ def test__getNextRecord(self): b'\x00'*4 )) sock = TLSRecordLayer(mockSock) + sock.version = (3,3) # XXX using private method! for result in sock._getNextRecord(): @@ -61,29 +62,7 @@ def test__getNextRecord(self): self.assertEqual(data, bytearray(4)) self.assertEqual(header.type, ContentType.handshake) self.assertEqual(header.version, (3, 3)) - self.assertEqual(header.length, 4) - - def test__getNextRecord_stops_itelf(self): - mockSock = MockSocket(bytearray( - b'\x16' + # type - handshake - b'\x03\x03' + # TLSv1.2 - b'\x00\x04' + # length - b'\x00'*4 - )) - sock = TLSRecordLayer(mockSock) - - # XXX using private method! - for result in sock._getNextRecord(): - if result in (0, 1): - self.assertTrue(False, "blocking socket") - - header, data = result - data = data.bytes - - self.assertEqual(data, bytearray(4)) - self.assertEqual(header.type, ContentType.handshake) - self.assertEqual(header.version, (3, 3)) - self.assertEqual(header.length, 4) + self.assertEqual(header.length, 0) def test__getNextRecord_with_trickling_socket(self): mockSock = MockSocket(bytearray( @@ -308,8 +287,8 @@ def test__getNextRecord_with_multiple_messages_in_single_record(self): self.assertEqual(HandshakeType.server_hello, p.bytes[0]) # XXX generator stops as soon as a message was read - self.assertEqual(1, len(results)) - return + #self.assertEqual(1, len(results)) + #return header, p = results[1] @@ -472,11 +451,7 @@ def test__getMsg_with_fragmented_message(self): gen = record_layer._getMsg(ContentType.handshake, HandshakeType.server_hello) - # XXX record layer doesn't handle fragmented hello messages - with self.assertRaises(TLSLocalAlert): - message = next(gen) - - return + message = next(gen) if message in (0,1): raise Exception("blocking") @@ -511,7 +486,7 @@ def test__getMsg_with_oversized_message(self): HandshakeType.server_hello) # XXX decoder handles messages over the 2**14 limit! - # with self.assertRaises(TLSLocalAlert): + #with self.assertRaises(TLSLocalAlert): message = next(gen) # From 1f182cc018d523f7a19b6407ed5a118fa8cc3a00 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 1 Jun 2015 22:38:26 +0200 Subject: [PATCH 064/574] move fragmentation of messages to _sendMsg() Because TLS record layer fragmentation is not limited to application data it needs to be applied to all message types, thus handle it inside _sendMsg() not in writeAsync() --- tlslite/tlsrecordlayer.py | 45 ++++++++++++++--------- unit_tests/test_tlslite_tlsrecordlayer.py | 17 ++++----- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index d1af6079..901d0fb9 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -88,6 +88,17 @@ class TLSRecordLayer(object): construct for CBC cipher suites, will be False also if connection uses RC4 or AEAD. + @type blockSize: int + @ivar blockSize: maimum size of data to be sent in a single record layer + message. Note that after encryption is established (generally after + handshake protocol has finished) the actual amount of data written to + network socket will be larger because of the record layer header, padding + or encryption overhead. It can be set to low value (so that there is no + fragmentation on Ethernet, IP and TCP level) at the beginning of + connection to reduce latency and set to protocol max (2**14) to maximise + throughput after sending few kiB of data. Setting to values greater than + 2**14 will cause the connection to be dropped by RFC compliant peers. + @sort: __init__, read, readAsync, write, writeAsync, close, closeAsync, getCipherImplementation, getCipherName """ @@ -132,6 +143,9 @@ def __init__(self, sock): #Fault we will induce, for testing purposes self.fault = None + #Limit the size of outgoing records to following size + self.blockSize = 16384 # 2**14 + @property def _client(self): """Boolean stating if the endpoint acts as a client""" @@ -283,23 +297,10 @@ def writeAsync(self, s): if self.closed: raise TLSClosedConnectionError("attempt to write to closed connection") - index = 0 - blockSize = 16384 - randomizeFirstBlock = True - while 1: - startIndex = index * blockSize - endIndex = startIndex + blockSize - if startIndex >= len(s): - break - if endIndex > len(s): - endIndex = len(s) - block = bytearray(s[startIndex : endIndex]) - applicationData = ApplicationData().create(block) - for result in self._sendMsg(applicationData, \ - randomizeFirstBlock): - yield result - randomizeFirstBlock = False #only on 1st message - index += 1 + applicationData = ApplicationData().create(bytearray(s)) + for result in self._sendMsg(applicationData, \ + randomizeFirstBlock=True): + yield result except GeneratorExit: raise except Exception: @@ -564,6 +565,16 @@ def _sendMsg(self, msg, randomizeFirstBlock = True): contentType = msg.contentType + #Fragment big messages + while len(b) > self.blockSize: + newB = b[:self.blockSize] + b = b[self.blockSize:] + + msgFragment = Message(contentType, newB) + for result in self._sendMsg(msgFragment, + randomizeFirstBlock=False): + yield result + #Update handshake hashes if contentType == ContentType.handshake: self._handshake_md5.update(compat26Str(b)) diff --git a/unit_tests/test_tlslite_tlsrecordlayer.py b/unit_tests/test_tlslite_tlsrecordlayer.py index fc40f93e..6927a906 100644 --- a/unit_tests/test_tlslite_tlsrecordlayer.py +++ b/unit_tests/test_tlslite_tlsrecordlayer.py @@ -370,18 +370,17 @@ def test__sendMsg_with_large_message(self): gen = record_layer._sendMsg(client_hello) - # XXX should not happen, client hello messages larger than record - # layer maximum fragment size should get fragmented - with self.assertRaises(ValueError): - next(gen) - - return + for result in gen: + if result in (0, 1): + self.assertTrue(False, "blocking") + else: + break - send_arg = mock_sock.send.call_args_list[0] # The maximum length that can be sent in single record is 2**14 # record layer adds 5 byte on top of that - self.assertTrue(len(send_arg[0][0]) <= 2**14 + 5) - self.assertEqual(2, mock_sock.send.call_count) + self.assertEqual(len(mock_sock.sent), 5) + for msg in mock_sock.sent: + self.assertTrue(len(msg) <= 2**14 + 5) def test__getMsg(self): From fee2d0a782b2321a30a5007e361984f22d8ab7b3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Jun 2015 17:26:50 +0200 Subject: [PATCH 065/574] remove unused API since fragmentation will be handled on a higher level, there is no need to keep it in API of RecordLayer --- tlslite/recordlayer.py | 6 ++---- unit_tests/test_tlslite_recordlayer.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index a9c33b12..1d790938 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -310,7 +310,7 @@ def _calculateMAC(self, mac, seqnumBytes, contentType, data): return bytearray(mac.digest()) def _macThenEncrypt(self, data, contentType): - """MAC then encrypt data""" + """MAC, pad then encrypt data""" if self._writeState.macContext: seqnumBytes = self._writeState.getSeqNumBytes() mac = self._writeState.macContext.copy() @@ -355,9 +355,7 @@ def _encryptThenMAC(self, buf, contentType): return buf - # randomizeFirstBlock will get used once handling of fragmented - # messages is implemented - def sendMessage(self, msg, randomizeFirstBlock=True): + def sendMessage(self, msg): """ Encrypt, MAC and send message through socket. diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 93f43097..2827a91b 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -410,7 +410,7 @@ def test_sendMessage_with_encrypting_set_up_tls1_2(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data, False): + for result in recordLayer.sendMessage(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -452,7 +452,7 @@ def test_sendMessage_with_SHA256_tls1_2(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data, False): + for result in recordLayer.sendMessage(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -494,7 +494,7 @@ def test_sendMessage_with_encrypting_set_up_tls1_1(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data, False): + for result in recordLayer.sendMessage(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -529,7 +529,7 @@ def test_sendMessage_with_encrypting_set_up_tls1_0(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data, False): + for result in recordLayer.sendMessage(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -563,7 +563,7 @@ def test_sendMessage_with_stream_cipher_and_tls1_0(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data, False): + for result in recordLayer.sendMessage(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -597,7 +597,7 @@ def test_sendMessage_with_MD5_MAC_and_tls1_0(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data, False): + for result in recordLayer.sendMessage(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -632,7 +632,7 @@ def test_sendMessage_with_AES256_cipher_and_tls1_0(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data, False): + for result in recordLayer.sendMessage(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -669,7 +669,7 @@ def test_sendMessage_with_3DES_cipher_and_tls1_0(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data, False): + for result in recordLayer.sendMessage(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -703,7 +703,7 @@ def test_sendMessage_with_encrypting_set_up_ssl3(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data, False): + for result in recordLayer.sendMessage(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break From 5f0319b9933a25db33d6f2014e6f1dd0ddda3362 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Jun 2015 19:56:07 +0200 Subject: [PATCH 066/574] clean up fragmenting of messages fragmentation for large writes could lead to very deep recursion, refactor the code to use iterative algorithm, just move record sending outside introduces a isCBCMode() to RecordLayer to decide if 1/n-1 record splitting is necessary more test coverage for TLSRecordLayer (using private fields, but just to access public methods of RecordLayer) --- tlslite/recordlayer.py | 7 ++ tlslite/tlsrecordlayer.py | 58 ++++++++-------- unit_tests/test_tlslite_recordlayer.py | 2 + unit_tests/test_tlslite_tlsrecordlayer.py | 83 +++++++++++++++++++++++ 4 files changed, 119 insertions(+), 31 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 1d790938..613aba37 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -282,6 +282,13 @@ def shutdown(self): self._pendingWriteState = ConnectionState() self._pendingReadState = ConnectionState() + def isCBCMode(self): + """Returns true if cipher uses CBC mode""" + if self._writeState and self._writeState.encContext and \ + self._writeState.encContext.isBlockCipher: + return True + else: + return False # # sending messages # diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 901d0fb9..baa0b098 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -88,8 +88,8 @@ class TLSRecordLayer(object): construct for CBC cipher suites, will be False also if connection uses RC4 or AEAD. - @type blockSize: int - @ivar blockSize: maimum size of data to be sent in a single record layer + @type recordSize: int + @ivar recordSize: maimum size of data to be sent in a single record layer message. Note that after encryption is established (generally after handshake protocol has finished) the actual amount of data written to network socket will be larger because of the record layer header, padding @@ -144,7 +144,7 @@ def __init__(self, sock): self.fault = None #Limit the size of outgoing records to following size - self.blockSize = 16384 # 2**14 + self.recordSize = 16384 # 2**14 @property def _client(self): @@ -541,47 +541,43 @@ def _sendMsgs(self, msgs): randomizeFirstBlock = True def _sendMsg(self, msg, randomizeFirstBlock = True): + """Fragment and send message through socket""" #Whenever we're connected and asked to send an app data message, #we first send the first byte of the message. This prevents #an attacker from launching a chosen-plaintext attack based on #knowing the next IV (a la BEAST). - # TODO don't reference private fields in _recordLayer - # to be fixed with proper message fragmentation implementation - if not self.closed and randomizeFirstBlock and self.version <= (3,1) \ - and self._recordLayer._writeState.encContext \ - and self._recordLayer._writeState.encContext.isBlockCipher \ - and isinstance(msg, ApplicationData): + if randomizeFirstBlock and self.version <= (3, 1) \ + and self._recordLayer.isCBCMode() \ + and msg.contentType == ContentType.application_data: msgFirstByte = msg.splitFirstByte() - for result in self._sendMsg(msgFirstByte, - randomizeFirstBlock = False): - yield result + for result in self._sendMsgThroughSocket(msgFirstByte): + yield result + if len(msg.write()) == 0: + return - b = msg.write() - - # If a 1-byte message was passed in, and we "split" the - # first(only) byte off above, we may have a 0-length msg: - if len(b) == 0: - return - + buf = msg.write() contentType = msg.contentType + #Update handshake hashes + if contentType == ContentType.handshake: + self._handshake_md5.update(compat26Str(buf)) + self._handshake_sha.update(compat26Str(buf)) + self._handshake_sha256.update(compat26Str(buf)) #Fragment big messages - while len(b) > self.blockSize: - newB = b[:self.blockSize] - b = b[self.blockSize:] + while len(buf) > self.recordSize: + newB = buf[:self.recordSize] + buf = buf[self.recordSize:] msgFragment = Message(contentType, newB) - for result in self._sendMsg(msgFragment, - randomizeFirstBlock=False): + for result in self._sendMsgThroughSocket(msgFragment): yield result - #Update handshake hashes - if contentType == ContentType.handshake: - self._handshake_md5.update(compat26Str(b)) - self._handshake_sha.update(compat26Str(b)) - self._handshake_sha256.update(compat26Str(b)) + msgFragment = Message(contentType, buf) + for result in self._sendMsgThroughSocket(msgFragment): + yield result - msg = Message(contentType, b) + def _sendMsgThroughSocket(self, msg): + """Send message, handle errors""" try: for result in self._recordLayer.sendMessage(msg): @@ -598,7 +594,7 @@ def _sendMsg(self, msg, randomizeFirstBlock = True): # However, if we get here DURING handshaking, we take # it upon ourselves to see if the next message is an # Alert. - if contentType == ContentType.handshake: + if msg.contentType == ContentType.handshake: # See if there's an alert record # Could raise socket.error or TLSAbruptCloseError diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 2827a91b..0750ca52 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -329,6 +329,7 @@ def test___init__(self): self.assertIsNone(recordLayer.getCipherName()) self.assertIsNone(recordLayer.getCipherImplementation()) + self.assertFalse(recordLayer.isCBCMode()) def test_sendMessage(self): sock = MockSocket(bytearray(0)) @@ -365,6 +366,7 @@ def test_getCipherName(self): recordLayer.changeWriteState() self.assertEqual('aes128', recordLayer.getCipherName()) + self.assertTrue(recordLayer.isCBCMode()) def test_getCipherImplementation(self): sock = MockSocket(bytearray(0)) diff --git a/unit_tests/test_tlslite_tlsrecordlayer.py b/unit_tests/test_tlslite_tlsrecordlayer.py index 6927a906..753f2b38 100644 --- a/unit_tests/test_tlslite_tlsrecordlayer.py +++ b/unit_tests/test_tlslite_tlsrecordlayer.py @@ -382,6 +382,89 @@ def test__sendMsg_with_large_message(self): for msg in mock_sock.sent: self.assertTrue(len(msg) <= 2**14 + 5) + def test_write_with_BEAST_record_splitting(self): + mock_sock = MockSocket(bytearray(0)) + record_layer = TLSRecordLayer(mock_sock) + + record_layer.version = (3, 1) + record_layer.closed = False + record_layer._recordLayer.calcPendingStates( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), + bytearray(32), + bytearray(32), + None) + record_layer._recordLayer.changeWriteState() + + record_layer.write(bytearray(32)) + + self.assertEqual(len(mock_sock.sent), 2) + msg1 = mock_sock.sent[0] + self.assertEqual(bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLSv1.0 + b'\x00\x20' # length 32 bytes = data(1) + MAC(20) + padding(11) + ), msg1[:5]) + self.assertEqual(len(msg1[5:]), 32) + + msg2 = mock_sock.sent[1] + self.assertEqual(bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLSv1.0 + b'\x00\x40' # length 64 bytes = data(31) + MAC(20) + padding(13) + ), msg2[:5]) + self.assertEqual(len(msg2[5:]), 64) + + def test_write_with_BEAST_record_splitting_and_small_write(self): + mock_sock = MockSocket(bytearray(0)) + record_layer = TLSRecordLayer(mock_sock) + + record_layer.version = (3, 1) + record_layer.closed = False + record_layer._recordLayer.calcPendingStates( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), + bytearray(32), + bytearray(32), + None) + record_layer._recordLayer.changeWriteState() + + record_layer.write(bytearray(1)) + + self.assertEqual(len(mock_sock.sent), 1) + msg1 = mock_sock.sent[0] + self.assertEqual(bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLSv1.0 + b'\x00\x20' # length 32 bytes = data(1) + MAC(20) + padding(11) + ), msg1[:5]) + self.assertEqual(len(msg1[5:]), 32) + + def test_write_with_BEAST_record_splitting_and_empty_write(self): + mock_sock = MockSocket(bytearray(0)) + record_layer = TLSRecordLayer(mock_sock) + + record_layer.version = (3, 1) + record_layer.closed = False + record_layer._recordLayer.calcPendingStates( + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), + bytearray(32), + bytearray(32), + None) + record_layer._recordLayer.changeWriteState() + + record_layer.write(bytearray(0)) + + self.assertEqual(len(mock_sock.sent), 1) + msg1 = mock_sock.sent[0] + self.assertEqual(bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLSv1.0 + b'\x00\x20' # length 32 bytes = data(0) + MAC(20) + padding(12) + ), msg1[:5]) + self.assertEqual(len(msg1[5:]), 32) + def test__getMsg(self): mock_sock = MockSocket( From 73691332537c3cbc768c1ffb3178f52f07b6d211 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Jun 2015 20:04:35 +0200 Subject: [PATCH 067/574] update README with message fragmentation --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 382662b9..4487de11 100644 --- a/README.md +++ b/README.md @@ -515,6 +515,8 @@ RFC 7366. =========== 0.5.0-alpha - xx/xx/xxxx - Hubert Kario + - properly implement record layer fragmentation (previously worked just for + Application Data) - RFC 5246 Section 6.2.1 - Implement RFC 7366 - Encrypt-then-MAC - generate minimal padding for CBC ciphers (David Benjamin - Chromium) - implementation of `FALLBACK_SCSV` (David Benjamin - Chromium) From 1f52340eccff71e723c4163aed8c5b1bdeab5515 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 6 Jun 2015 22:58:53 +0200 Subject: [PATCH 068/574] document CipherSuite, introduce ietfNames provide human-readable names for supported ciphersuites describe which purpose have the different list of ciphers --- tlslite/constants.py | 76 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 59ff6200..a7fd0a0f 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -112,42 +112,106 @@ class AlertDescription: class CipherSuite: + + """ + Numeric values of ciphersuites and ciphersuite types + + @cvar tripleDESSuites: ciphersuties which use 3DES symmetric cipher in CBC + mode + @cvar aes128Suites: ciphersuites which use AES symmetric cipher in CBC mode + with 128 bit key + @cvar aes256Suites: ciphersuites which use AES symmetric cipher in CBC mode + with 128 bit key + @cvar rc4Suites: ciphersuites which use RC4 symmetric cipher with 128 bit + key + @cvar shaSuites: ciphersuites which use SHA-1 HMAC integrity mechanism + and protocol default Pseudo Random Function + @cvar sha256Suites: ciphersuites which use SHA-256 HMAC integrity mechanism + and SHA-256 Pseudo Random Function + @cvar md5Suites: ciphersuites which use MD-5 HMAC integrity mechanism and + protocol default Pseudo Random Function + @cvar srpSuites: ciphersuites which use Secure Remote Password (SRP) key + exchange protocol + @cvar srpCertSuites: ciphersuites which use Secure Remote Password (SRP) + key exchange protocol with RSA server authentication + @cvar srpAllSuites: all SRP ciphersuites, pure SRP and with RSA based + server authentication + @cvar certSuites: ciphersuites which use RSA key exchange with RSA server + authentication + @cvar certAllSuites: ciphersuites which use RSA server authentication + @cvar anonSuites: ciphersuites which use anonymous Finite Field + Diffie-Hellman key exchange + @cvar ietfNames: dictionary with string names of the ciphersuites + """ + + ietfNames = {} + # Weird pseudo-ciphersuite from RFC 5746 # Signals that "secure renegotiation" is supported # We actually don't do any renegotiation, but this # prevents renegotiation attacks TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF + ietfNames[0x00FF] = 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV' - # draft-ietf-tls-downgrade-scsv-03 + # RFC 7507 - Fallback Signaling Cipher Suite Value for Preventing Protocol + # Downgrade Attacks TLS_FALLBACK_SCSV = 0x5600 + ietfNames[0x5600] = 'TLS_FALLBACK_SCSV' + # RFC 5054 - Secure Remote Password (SRP) Protocol for TLS Authentication TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A + ietfNames[0xC01A] = 'TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA' TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D + ietfNames[0xC01D] = 'TLS_SRP_SHA_WITH_AES_128_CBC_SHA' TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020 + ietfNames[0xC020] = 'TLS_SRP_SHA_WITH_AES_256_CBC_SHA' + # RFC 5054 - Secure Remote Password (SRP) Protocol for TLS Authentication TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0xC01B + ietfNames[0xC01B] = 'TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA' TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0xC01E + ietfNames[0xC01E] = 'TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA' TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021 + ietfNames[0xC021] = 'TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA' + # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A + ietfNames[0x000A] = 'TLS_RSA_WITH_3DES_EDE_CBC_SHA' TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F + ietfNames[0x002F] = 'TLS_RSA_WITH_AES_128_CBC_SHA' TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 + ietfNames[0x0035] = 'TLS_RSA_WITH_AES_256_CBC_SHA' TLS_RSA_WITH_RC4_128_SHA = 0x0005 + ietfNames[0x0005] = 'TLS_RSA_WITH_RC4_128_SHA' + # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_RC4_128_MD5 = 0x0004 + ietfNames[0x0004] = 'TLS_RSA_WITH_RC4_128_MD5' + # RFC 5246 - TLS v1.2 Protocol TLS_DH_ANON_WITH_AES_128_CBC_SHA = 0x0034 + ietfNames[0x0034] = 'TLS_DH_ANON_WITH_AES_128_CBC_SHA' TLS_DH_ANON_WITH_AES_256_CBC_SHA = 0x003A + ietfNames[0x003A] = 'TLS_DH_ANON_WITH_AES_256_CBC_SHA' + # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C + ietfNames[0x003C] = 'TLS_RSA_WITH_AES_128_CBC_SHA256' TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D + ietfNames[0x003D] = 'TLS_RSA_WITH_AES_256_CBC_SHA256' + + # + # Define cipher suite families below + # + # 3DES CBC ciphers tripleDESSuites = [] 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) + # AES-128 CBC ciphers aes128Suites = [] aes128Suites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) @@ -155,6 +219,7 @@ class CipherSuite: aes128Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA256) + # AES-256 CBC ciphers aes256Suites = [] aes256Suites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) @@ -162,10 +227,12 @@ class CipherSuite: aes256Suites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) + # RC4-128 stream cipher rc4Suites = [] rc4Suites.append(TLS_RSA_WITH_RC4_128_SHA) rc4Suites.append(TLS_RSA_WITH_RC4_128_MD5) + # SHA-1 HMAC, protocol default PRF shaSuites = [] shaSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) shaSuites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) @@ -180,10 +247,12 @@ class CipherSuite: shaSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) shaSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) + # SHA-256 HMAC, SHA-256 PRF sha256Suites = [] sha256Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA256) sha256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) + # MD-5 HMAC, protocol default PRF md5Suites = [] md5Suites.append(TLS_RSA_WITH_RC4_128_MD5) @@ -211,6 +280,7 @@ def _filterSuites(suites, settings): return [s for s in suites if s in macSuites and s in cipherSuites] + # SRP key exchange srpSuites = [] srpSuites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA) srpSuites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) @@ -220,6 +290,7 @@ def _filterSuites(suites, settings): def getSrpSuites(settings): return CipherSuite._filterSuites(CipherSuite.srpSuites, settings) + # SRP key exchange, RSA authentication srpCertSuites = [] srpCertSuites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) srpCertSuites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) @@ -235,6 +306,7 @@ def getSrpCertSuites(settings): def getSrpAllSuites(settings): return CipherSuite._filterSuites(CipherSuite.srpAllSuites, settings) + # RSA key exchange, RSA authentication certSuites = [] certSuites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) certSuites.append(TLS_RSA_WITH_AES_128_CBC_SHA256) @@ -243,12 +315,14 @@ def getSrpAllSuites(settings): certSuites.append(TLS_RSA_WITH_3DES_EDE_CBC_SHA) certSuites.append(TLS_RSA_WITH_RC4_128_SHA) certSuites.append(TLS_RSA_WITH_RC4_128_MD5) + # RSA authentication certAllSuites = srpCertSuites + certSuites @staticmethod def getCertSuites(settings): return CipherSuite._filterSuites(CipherSuite.certSuites, settings) + # anon FFDHE key exchange anonSuites = [] anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) From 865fae9464e520c0383eda783fc93abc6253ca93 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 6 Jun 2015 23:46:59 +0200 Subject: [PATCH 069/574] test coverage and docs for ClientKeyExchange --- tlslite/messages.py | 63 ++++++++++++ unit_tests/test_tlslite_messages.py | 150 +++++++++++++++++++++++++++- 2 files changed, 212 insertions(+), 1 deletion(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 24fbb8d3..5ecdc9da 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1007,7 +1007,32 @@ def write(self): return self.postWrite(w) class ClientKeyExchange(HandshakeMsg): + + """ + Handling of TLS Handshake protocol ClientKeyExchange message + + @type cipherSuite: int + @ivar cipherSuite: the cipher suite id used for the connection + @type version: tuple(int, int) + @ivar version: TLS protocol version used for the connection + @type srp_A: int + @ivar srp_A: SRP protocol client answer value + @type dh_Yc: int + @ivar dh_Yc: client Finite Field Diffie-Hellman protocol key share + @type encryptedPreMasterSecret: bytearray + @ivar encryptedPreMasterSecret: client selected PremMaster secret encrypted + with server public key (from certificate) + """ + def __init__(self, cipherSuite, version=None): + """ + Initialise ClientKeyExchange for reading or writing + + @type cipherSuite: int + @param cipherSuite: id of the ciphersuite selected by server + @type version: tuple(int, int) + @param version: protocol version selected by server + """ HandshakeMsg.__init__(self, HandshakeType.client_key_exchange) self.cipherSuite = cipherSuite self.version = version @@ -1015,18 +1040,51 @@ def __init__(self, cipherSuite, version=None): self.encryptedPreMasterSecret = bytearray(0) def createSRP(self, srp_A): + """ + Set the SRP client answer + + returns self + + @type srp_A: int + @param srp_A: client SRP answer + @rtype: L{ClientKeyExchange} + """ self.srp_A = srp_A return self def createRSA(self, encryptedPreMasterSecret): + """ + Set the encrypted PreMaster Secret + + returns self + + @type encryptedPreMasterSecret: bytearray + @rtype: L{ClientKeyExchange} + """ self.encryptedPreMasterSecret = encryptedPreMasterSecret return self def createDH(self, dh_Yc): + """ + Set the client FFDH key share + + returns self + + @type dh_Yc: int + @rtype: L{ClientKeyExchange} + """ self.dh_Yc = dh_Yc return self def parse(self, p): + """ + Deserialise the message from L{Parser} + + returns self + + @type p: L{Parser} + @rtype: L{ClientKeyExchange} + """ p.startLengthCheck(3) if self.cipherSuite in CipherSuite.srpAllSuites: self.srp_A = bytesToNumber(p.getVarBytes(2)) @@ -1046,6 +1104,11 @@ def parse(self, p): return self def write(self): + """ + Serialise the object + + @rtype: bytearray + """ w = Writer() if self.cipherSuite in CipherSuite.srpAllSuites: w.addVarSeq(numberToByteArray(self.srp_A), 1, 2) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index ee4f6754..ba5f962a 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -8,7 +8,7 @@ except ImportError: import unittest from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ - RecordHeader2, Message + RecordHeader2, Message, ClientKeyExchange from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType @@ -1101,5 +1101,153 @@ def test_write(self): self.assertEqual(bytearray( b'\x02\x16'), alert.write()) +class TestClientKeyExchange(unittest.TestCase): + def test___init__(self): + cke = ClientKeyExchange(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA) + + self.assertIsNotNone(cke) + self.assertIsNone(cke.version) + self.assertEqual(0, cke.srp_A) + self.assertEqual(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + cke.cipherSuite) + self.assertEqual(bytearray(0), cke.encryptedPreMasterSecret) + + def test_createSRP(self): + cke = ClientKeyExchange(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA) + + cke.createSRP(2**128+3) + + bts = cke.write() + + self.assertEqual(bts, bytearray( + b'\x10' + # CKE + b'\x00\x00\x13' + # Handshake message length + b'\x00\x11' + # length of value + b'\x01' + # 2... + b'\x00'*15 + # ...**128... + b'\x03')) # ...+3 + + def test_createRSA(self): + cke = ClientKeyExchange(CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, + (3, 3)) + + cke.createRSA(bytearray(12)) + + bts = cke.write() + + self.assertEqual(bts, bytearray( + b'\x10' + # CKE + b'\x00\x00\x0e' + # Handshake message length + b'\x00\x0c' + # length of encrypted value + b'\x00'*12)) + + def test_createRSA_with_SSL3(self): + cke = ClientKeyExchange(CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, + (3, 0)) + + cke.createRSA(bytearray(12)) + + bts = cke.write() + + self.assertEqual(bts, bytearray( + b'\x10' + # CKE + b'\x00\x00\x0c' + # Handshake message length + b'\x00'*12)) + + def test_createDH(self): + cke = ClientKeyExchange(CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA) + + cke.createDH(2**64+3) + + bts = cke.write() + + self.assertEqual(bts, bytearray( + b'\x10' + + b'\x00\x00\x0b' + + b'\x00\x09' + + b'\x01' + b'\x00'*7 + b'\x03')) + + def test_createRSA_with_unset_protocol(self): + cke = ClientKeyExchange(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA) + + cke.createRSA(bytearray(12)) + + with self.assertRaises(AssertionError): + cke.write() + + def test_write_with_unknown_cipher_suite(self): + cke = ClientKeyExchange(0) + + with self.assertRaises(AssertionError): + cke.write() + + def test_parse_with_RSA(self): + cke = ClientKeyExchange(CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + (3, 1)) + + parser = Parser(bytearray( + b'\x00\x00\x0e' + + b'\x00\x0c' + + b'\x00'*12)) + + cke.parse(parser) + + self.assertEqual(bytearray(12), cke.encryptedPreMasterSecret) + + def test_parse_with_RSA_in_SSL3(self): + cke = ClientKeyExchange(CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + (3, 0)) + + parser = Parser(bytearray( + b'\x00\x00\x0c' + + b'\x00'*12)) + + cke.parse(parser) + + self.assertEqual(bytearray(12), cke.encryptedPreMasterSecret) + + def test_parse_with_RSA_and_unset_protocol(self): + cke = ClientKeyExchange(CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA) + + parser = Parser(bytearray( + b'\x00\x00\x0c' + + b'x\00'*12)) + + with self.assertRaises(AssertionError): + cke.parse(parser) + + def test_parse_with_SRP(self): + cke = ClientKeyExchange(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA) + + parser = Parser(bytearray( + b'\x00\x00\x0a' + + b'\x00\x08' + + b'\x00'*7 + b'\xff')) + + cke.parse(parser) + + self.assertEqual(255, cke.srp_A) + + def test_parse_with_DH(self): + cke = ClientKeyExchange(CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA) + + parser = Parser(bytearray( + b'\x00\x00\x0a' + + b'\x00\x08' + + b'\x01' + b'\x00'*7)) + + cke.parse(parser) + + self.assertEqual(2**56, cke.dh_Yc) + + def test_parse_with_unknown_cipher(self): + cke = ClientKeyExchange(0) + + parser = Parser(bytearray( + b'\x00\x00\x00')) + + with self.assertRaises(AssertionError): + cke.parse(parser) + if __name__ == '__main__': unittest.main() From 29f5cc72284906e8d99d941ab23530565642c7f8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 6 Jun 2015 23:48:33 +0200 Subject: [PATCH 070/574] define DH variable in CKE init --- tlslite/messages.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tlslite/messages.py b/tlslite/messages.py index 5ecdc9da..8afdbdc4 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1037,6 +1037,7 @@ def __init__(self, cipherSuite, version=None): self.cipherSuite = cipherSuite self.version = version self.srp_A = 0 + self.dh_Yc = 0 self.encryptedPreMasterSecret = bytearray(0) def createSRP(self, srp_A): From 3f7e57fe1b2a3f9a165612ed17fa70501f7f2dc2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 6 Jun 2015 23:53:51 +0200 Subject: [PATCH 071/574] cleanup CKE parser fix pylint C0103 (invalid-name) --- tlslite/messages.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 8afdbdc4..76c52050 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1077,31 +1077,31 @@ def createDH(self, dh_Yc): self.dh_Yc = dh_Yc return self - def parse(self, p): + def parse(self, parser): """ Deserialise the message from L{Parser} returns self - @type p: L{Parser} + @type parser: L{Parser} @rtype: L{ClientKeyExchange} """ - p.startLengthCheck(3) + parser.startLengthCheck(3) if self.cipherSuite in CipherSuite.srpAllSuites: - self.srp_A = bytesToNumber(p.getVarBytes(2)) + self.srp_A = bytesToNumber(parser.getVarBytes(2)) elif self.cipherSuite in CipherSuite.certSuites: if self.version in ((3,1), (3,2), (3,3)): - self.encryptedPreMasterSecret = p.getVarBytes(2) + self.encryptedPreMasterSecret = parser.getVarBytes(2) elif self.version == (3,0): self.encryptedPreMasterSecret = \ - p.getFixBytes(len(p.bytes)-p.index) + parser.getFixBytes(parser.getRemainingLength()) else: raise AssertionError() elif self.cipherSuite in CipherSuite.anonSuites: - self.dh_Yc = bytesToNumber(p.getVarBytes(2)) + self.dh_Yc = bytesToNumber(parser.getVarBytes(2)) else: raise AssertionError() - p.stopLengthCheck() + parser.stopLengthCheck() return self def write(self): From fa17fa895191a5d186f33be420089a6ff0e175aa Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Jun 2015 12:27:00 +0200 Subject: [PATCH 072/574] docs and test coverage for ServerKeyExchange --- tlslite/messages.py | 44 ++++++- unit_tests/test_tlslite_messages.py | 183 +++++++++++++++++++++++++++- 2 files changed, 225 insertions(+), 2 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 76c52050..cbcc89bc 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -922,7 +922,37 @@ def write(self): return self.postWrite(w) class ServerKeyExchange(HandshakeMsg): + + """ + Handling TLS Handshake protocol Server Key Exchange messages + + @type cipherSuite: int + @cvar cipherSuite: id of ciphersuite selected in Server Hello message + @type srp_N: int + @cvar srp_N: SRP protocol prime + @type srp_g: int + @cvar srp_g: SRP protocol generator + @type srp_s: bytearray + @cvar srp_s: SRP protocol salt value + @type srp_B: int + @cvar srp_B: SRP protocol server public value + @type dh_p: int + @cvar dh_p: FFDHE protocol prime + @type dh_g: int + @cvar dh_g: FFDHE protocol generator + @type dh_Ys: int + @cvar dh_Ys: FFDH protocol server key share + @type signature: bytearray + @cvar signature: signature performed over the parameters by server + """ + def __init__(self, cipherSuite): + """ + Initialise Server Key Exchange for reading or writing + + @type cipherSuite: int + @param cipherSuite: id of ciphersuite selected by server + """ HandshakeMsg.__init__(self, HandshakeType.server_key_exchange) self.cipherSuite = cipherSuite self.srp_N = 0 @@ -936,19 +966,26 @@ def __init__(self, cipherSuite): self.signature = bytearray(0) def createSRP(self, srp_N, srp_g, srp_s, srp_B): + """Set SRP protocol parameters""" self.srp_N = srp_N self.srp_g = srp_g self.srp_s = srp_s self.srp_B = srp_B return self - + def createDH(self, dh_p, dh_g, dh_Ys): + """Set FFDH protocol parameters""" self.dh_p = dh_p self.dh_g = dh_g self.dh_Ys = dh_Ys return self def parse(self, p): + """Deserialise message from L{Parser} + + @type p: L{Parser} + @param p: parser to read data from + """ p.startLengthCheck(3) if self.cipherSuite in CipherSuite.srpAllSuites: self.srp_N = bytesToNumber(p.getVarBytes(2)) @@ -965,6 +1002,10 @@ def parse(self, p): return self def write(self): + """Serialise message + + @rtype: bytearray + """ w = Writer() if self.cipherSuite in CipherSuite.srpAllSuites: w.addVarSeq(numberToByteArray(self.srp_N), 1, 2) @@ -982,6 +1023,7 @@ def write(self): return self.postWrite(w) def hash(self, clientRandom, serverRandom): + """Calculate the signature hash""" oldCipherSuite = self.cipherSuite self.cipherSuite = None try: diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index ba5f962a..89f9de62 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -8,7 +8,7 @@ except ImportError: import unittest from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ - RecordHeader2, Message, ClientKeyExchange + RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType @@ -1249,5 +1249,186 @@ def test_parse_with_unknown_cipher(self): with self.assertRaises(AssertionError): cke.parse(parser) +class TestServerKeyExchange(unittest.TestCase): + def test___init__(self): + ske = ServerKeyExchange(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA) + + self.assertIsNotNone(ske) + self.assertEqual(ske.cipherSuite, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA) + self.assertEqual(ske.srp_N, 0) + self.assertEqual(ske.srp_g, 0) + self.assertEqual(ske.srp_s, bytearray(0)) + self.assertEqual(ske.srp_B, 0) + self.assertEqual(ske.dh_p, 0) + self.assertEqual(ske.dh_g, 0) + self.assertEqual(ske.dh_Ys, 0) + self.assertEqual(ske.signature, bytearray(0)) + + def test_createSRP(self): + ske = ServerKeyExchange(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA) + + ske.createSRP(srp_N=1, + srp_g=2, + srp_s=bytearray(3), + srp_B=4) + + self.assertEqual(ske.write(), bytearray( + b'\x0c' + # message type + b'\x00\x00\x0d' + # overall length + b'\x00\x01' + # N parameter length + b'\x01' + # N value + b'\x00\x01' + # g parameter length + b'\x02' + # g value + b'\x03' + # s parameter length + b'\x00'*3 + # s value + b'\x00\x01' + # B parameter length + b'\x04' # B value + )) + + def test_createSRP_with_signature(self): + ske = ServerKeyExchange( + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) + + ske.createSRP(srp_N=1, + srp_g=2, + srp_s=bytearray(3), + srp_B=4) + ske.signature = bytearray(b'\xc0\xff\xee') + + self.assertEqual(ske.write(), bytearray( + b'\x0c' + # message type + b'\x00\x00\x12' + # overall length + b'\x00\x01' + # N parameter length + b'\x01' + # N value + b'\x00\x01' + # g parameter length + b'\x02' + # g value + b'\x03' + # s parameter length + b'\x00'*3 + # s value + b'\x00\x01' + # B parameter length + b'\x04' # B value + b'\x00\x03' + # signature length + b'\xc0\xff\xee' # signature value + )) + + def test_createDH(self): + ske = ServerKeyExchange(CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA) + + ske.createDH(dh_p=31, + dh_g=2, + dh_Ys=16) + + self.assertEqual(ske.write(), bytearray( + b'\x0c' + # message type + b'\x00\x00\x09' + # overall length + b'\x00\x01' + # p parameter length + b'\x1f' + # p value + b'\x00\x01' + # g parameter length + b'\x02' + # g value + b'\x00\x01' + # Ys parameter length + b'\x10' # Ys value + )) + + def test_parse_with_unknown_cipher(self): + ske = ServerKeyExchange(0) + + parser = Parser(bytearray( + b'\x00\x00\x03' + + b'\x00\x01' + + b'\xff' + )) + + with self.assertRaises(SyntaxError): + ske.parse(parser) + + def test_parse_with_SRP(self): + ske = ServerKeyExchange(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA) + + parser = Parser(bytearray( + b'\x00\x00\x0d' + # overall length + b'\x00\x01' + # N parameter length + b'\x01' + # N value + b'\x00\x01' + # g parameter length + b'\x02' + # g value + b'\x03' + # s parameter length + b'\x00'*3 + # s value + b'\x00\x01' + # B parameter length + b'\x04' # B value + )) + + ske.parse(parser) + + self.assertEqual(ske.srp_N, 1) + self.assertEqual(ske.srp_g, 2) + self.assertEqual(ske.srp_s, bytearray(3)) + self.assertEqual(ske.srp_B, 4) + + def test_parser_with_SRP_RSA(self): + ske = ServerKeyExchange( + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) + + parser = Parser(bytearray( + b'\x00\x00\x12' + # overall length + b'\x00\x01' + # N parameter length + b'\x01' + # N value + b'\x00\x01' + # g parameter length + b'\x02' + # g value + b'\x03' + # s parameter length + b'\x00'*3 + # s value + b'\x00\x01' + # B parameter length + b'\x04' # B value + b'\x00\x03' + # signature length + b'\xc0\xff\xee' # signature value + )) + + ske.parse(parser) + + self.assertEqual(ske.srp_N, 1) + self.assertEqual(ske.srp_g, 2) + self.assertEqual(ske.srp_s, bytearray(3)) + self.assertEqual(ske.srp_B, 4) + self.assertEqual(ske.signature, bytearray(b'\xc0\xff\xee')) + + def test_parser_with_DH(self): + ske = ServerKeyExchange(CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA) + + parser = Parser(bytearray( + b'\x00\x00\x09' + # overall length + b'\x00\x01' + # p parameter length + b'\x1f' + # p value + b'\x00\x01' + # g parameter length + b'\x02' + # g value + b'\x00\x01' + # Ys parameter length + b'\x10' # Ys value + )) + + ske.parse(parser) + + self.assertEqual(ske.dh_p, 31) + self.assertEqual(ske.dh_g, 2) + self.assertEqual(ske.dh_Ys, 16) + + def test_hash(self): + ske = ServerKeyExchange( + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) + + ske.createSRP(srp_N=1, + srp_g=2, + srp_s=bytearray(3), + srp_B=4) + + hash1 = ske.hash(bytearray(32), bytearray(32)) + + ske2 = ServerKeyExchange(0) + hash2 = ske2.hash(bytearray(32), bytearray(32)) + self.assertEqual(len(hash2), 36) + self.assertEqual(hash2, bytearray( + b';]<} ~7\xdc\xee\xed\xd3\x01\xe3^.X' + + b'\xc8\xd7\xd0\xef\x0e\xed\xfa\x82\xd2\xea\x1a\xa5\x92\x84[\x9a' + + b'mK\x02\xb7')) + + # XXX seriously?! + self.assertEqual(hash1, hash2) + if __name__ == '__main__': unittest.main() From e50653794d11855546908d8dd59e2fb1827df2bb Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Jun 2015 12:33:43 +0200 Subject: [PATCH 073/574] cleanup ServerKeyExchange fixes pylint invalid-name and redefined-builtin --- tlslite/messages.py | 48 ++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index cbcc89bc..4e72d165 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -980,25 +980,25 @@ def createDH(self, dh_p, dh_g, dh_Ys): self.dh_Ys = dh_Ys return self - def parse(self, p): + def parse(self, parser): """Deserialise message from L{Parser} - @type p: L{Parser} - @param p: parser to read data from + @type parser: L{Parser} + @param parser: parser to read data from """ - p.startLengthCheck(3) + parser.startLengthCheck(3) if self.cipherSuite in CipherSuite.srpAllSuites: - self.srp_N = bytesToNumber(p.getVarBytes(2)) - self.srp_g = bytesToNumber(p.getVarBytes(2)) - self.srp_s = p.getVarBytes(1) - self.srp_B = bytesToNumber(p.getVarBytes(2)) + self.srp_N = bytesToNumber(parser.getVarBytes(2)) + self.srp_g = bytesToNumber(parser.getVarBytes(2)) + self.srp_s = parser.getVarBytes(1) + self.srp_B = bytesToNumber(parser.getVarBytes(2)) if self.cipherSuite in CipherSuite.srpCertSuites: - self.signature = p.getVarBytes(2) + self.signature = parser.getVarBytes(2) elif self.cipherSuite in CipherSuite.anonSuites: - self.dh_p = bytesToNumber(p.getVarBytes(2)) - self.dh_g = bytesToNumber(p.getVarBytes(2)) - self.dh_Ys = bytesToNumber(p.getVarBytes(2)) - p.stopLengthCheck() + self.dh_p = bytesToNumber(parser.getVarBytes(2)) + self.dh_g = bytesToNumber(parser.getVarBytes(2)) + self.dh_Ys = bytesToNumber(parser.getVarBytes(2)) + parser.stopLengthCheck() return self def write(self): @@ -1006,21 +1006,21 @@ def write(self): @rtype: bytearray """ - w = Writer() + writer = 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) + writer.addVarSeq(numberToByteArray(self.srp_N), 1, 2) + writer.addVarSeq(numberToByteArray(self.srp_g), 1, 2) + writer.addVarSeq(self.srp_s, 1, 1) + writer.addVarSeq(numberToByteArray(self.srp_B), 1, 2) if self.cipherSuite in CipherSuite.srpCertSuites: - w.addVarSeq(self.signature, 1, 2) + writer.addVarSeq(self.signature, 1, 2) elif self.cipherSuite in CipherSuite.anonSuites: - w.addVarSeq(numberToByteArray(self.dh_p), 1, 2) - w.addVarSeq(numberToByteArray(self.dh_g), 1, 2) - w.addVarSeq(numberToByteArray(self.dh_Ys), 1, 2) + writer.addVarSeq(numberToByteArray(self.dh_p), 1, 2) + writer.addVarSeq(numberToByteArray(self.dh_g), 1, 2) + writer.addVarSeq(numberToByteArray(self.dh_Ys), 1, 2) if self.cipherSuite in []: # TODO support for signed_params - w.addVarSeq(self.signature, 1, 2) - return self.postWrite(w) + writer.addVarSeq(self.signature, 1, 2) + return self.postWrite(writer) def hash(self, clientRandom, serverRandom): """Calculate the signature hash""" From 340544797db60eae52bca8a91cdefa3c813883a1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Jun 2015 13:17:01 +0200 Subject: [PATCH 074/574] fix SRP_SHA_RSA - actually sign SRP parameters because the deserialise will write nothing if the cipherSuite is set to None, we need to actually set it to one of the anonymous SRP ciphersuites first note that the TLSv1.2 signature handling is still incorrect! --- tlslite/messages.py | 11 ++++++++--- unit_tests/test_tlslite_messages.py | 8 ++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 4e72d165..4dc38374 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1025,10 +1025,15 @@ def write(self): def hash(self, clientRandom, serverRandom): """Calculate the signature hash""" oldCipherSuite = self.cipherSuite - self.cipherSuite = None + # temporarily change so serialiser omits signature + if self.cipherSuite in CipherSuite.srpAllSuites: + self.cipherSuite = CipherSuite.srpSuites[0] try: - bytes = clientRandom + serverRandom + self.write()[4:] - return MD5(bytes) + SHA1(bytes) + # skip message type(1 byte) and length(3 bytes) + payloadBytes = self.write()[4:] + bytesToMAC = clientRandom + serverRandom + payloadBytes + # TODO should use protocol dependant values (invalid in TLSv1.2) + return MD5(bytesToMAC) + SHA1(bytesToMAC) finally: self.cipherSuite = oldCipherSuite diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 89f9de62..1ae02608 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1419,6 +1419,11 @@ def test_hash(self): hash1 = ske.hash(bytearray(32), bytearray(32)) + self.assertEqual(hash1, bytearray( + b'\xcb\xe6\xd3=\x8b$\xff\x97e&\xb2\x89\x1dA\xab>' + + b'\x8e?YW\xcd\xad\xc6\x83\x91\x1d.fe,\x17y' + + b'=\xc4T\x89')) + ske2 = ServerKeyExchange(0) hash2 = ske2.hash(bytearray(32), bytearray(32)) self.assertEqual(len(hash2), 36) @@ -1427,8 +1432,7 @@ def test_hash(self): b'\xc8\xd7\xd0\xef\x0e\xed\xfa\x82\xd2\xea\x1a\xa5\x92\x84[\x9a' + b'mK\x02\xb7')) - # XXX seriously?! - self.assertEqual(hash1, hash2) + self.assertNotEqual(hash1, hash2) if __name__ == '__main__': unittest.main() From e9f6a4801e3493e6525651456c1014ca96ae8383 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Jun 2015 13:33:54 +0200 Subject: [PATCH 075/574] update README with fixes --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4487de11..f32deb59 100644 --- a/README.md +++ b/README.md @@ -515,6 +515,7 @@ RFC 7366. =========== 0.5.0-alpha - xx/xx/xxxx - Hubert Kario + - fix SRP_SHA_RSA ciphersuites - properly implement record layer fragmentation (previously worked just for Application Data) - RFC 5246 Section 6.2.1 - Implement RFC 7366 - Encrypt-then-MAC From b77aa3e68ef516607b58469456c84a89f573bd75 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Jun 2015 21:26:33 +0200 Subject: [PATCH 076/574] remove trailing whitespace in CipherSuite fixes pylint C0303 (trailing-whitespace) --- tlslite/constants.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index a7fd0a0f..1319cd1b 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -157,7 +157,7 @@ class CipherSuite: # Downgrade Attacks TLS_FALLBACK_SCSV = 0x5600 ietfNames[0x5600] = 'TLS_FALLBACK_SCSV' - + # RFC 5054 - Secure Remote Password (SRP) Protocol for TLS Authentication TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A ietfNames[0xC01A] = 'TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA' @@ -184,7 +184,7 @@ class CipherSuite: ietfNames[0x0035] = 'TLS_RSA_WITH_AES_256_CBC_SHA' TLS_RSA_WITH_RC4_128_SHA = 0x0005 ietfNames[0x0005] = 'TLS_RSA_WITH_RC4_128_SHA' - + # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_RC4_128_MD5 = 0x0004 ietfNames[0x0004] = 'TLS_RSA_WITH_RC4_128_MD5' @@ -231,7 +231,7 @@ class CipherSuite: rc4Suites = [] rc4Suites.append(TLS_RSA_WITH_RC4_128_SHA) rc4Suites.append(TLS_RSA_WITH_RC4_128_MD5) - + # SHA-1 HMAC, protocol default PRF shaSuites = [] shaSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) @@ -246,7 +246,7 @@ class CipherSuite: shaSuites.append(TLS_RSA_WITH_RC4_128_SHA) shaSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) shaSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) - + # SHA-256 HMAC, SHA-256 PRF sha256Suites = [] sha256Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA256) @@ -285,7 +285,7 @@ def _filterSuites(suites, settings): srpSuites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA) srpSuites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) srpSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) - + @staticmethod def getSrpSuites(settings): return CipherSuite._filterSuites(CipherSuite.srpSuites, settings) @@ -295,7 +295,7 @@ def getSrpSuites(settings): srpCertSuites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) srpCertSuites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) srpCertSuites.append(TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) - + @staticmethod def getSrpCertSuites(settings): return CipherSuite._filterSuites(CipherSuite.srpCertSuites, settings) @@ -317,7 +317,7 @@ def getSrpAllSuites(settings): certSuites.append(TLS_RSA_WITH_RC4_128_MD5) # RSA authentication certAllSuites = srpCertSuites + certSuites - + @staticmethod def getCertSuites(settings): return CipherSuite._filterSuites(CipherSuite.certSuites, settings) @@ -326,7 +326,7 @@ def getCertSuites(settings): anonSuites = [] anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) - + @staticmethod def getAnonSuites(settings): return CipherSuite._filterSuites(CipherSuite.anonSuites, settings) From e0f0fee0b2f137cb8fb47fab37659fb91c422264 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Jun 2015 21:30:12 +0200 Subject: [PATCH 077/574] remove leftover docs --- tlslite/recordlayer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 613aba37..96085c96 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -368,8 +368,6 @@ def sendMessage(self, msg): @param msg: TLS message to send @type msg: ApplicationData, HandshakeMessage, etc. - @param randomizeFirstBlock: set to perform 1/n-1 record splitting in - SSLv3 and TLSv1.0 in application data """ data = msg.write() contentType = msg.contentType From 431f2e22f5f62779b585d92d3115167fa15e2465 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Jun 2015 15:40:36 +0200 Subject: [PATCH 078/574] test coverage for CertificateRequest --- unit_tests/test_tlslite_messages.py | 110 +++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 1ae02608..167db519 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -8,10 +8,11 @@ except ImportError: import unittest from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ - RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange + RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ + CertificateRequest from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ - AlertLevel, AlertDescription, ExtensionType + AlertLevel, AlertDescription, ExtensionType, ClientCertificateType from tlslite.extensions import SNIExtension, ClientCertTypeExtension, \ SRPExtension, TLSExtension from tlslite.errors import TLSInternalError @@ -1434,5 +1435,110 @@ def test_hash(self): self.assertNotEqual(hash1, hash2) +class TestCertificateRequest(unittest.TestCase): + def test___init__(self): + cr = CertificateRequest((3, 0)) + + self.assertIsNotNone(cr) + self.assertEqual(cr.version, (3, 0)) + # XXX unset + self.assertEqual(cr.certificate_types, [ClientCertificateType.rsa_sign]) + self.assertEqual(cr.certificate_authorities, []) + self.assertEqual(cr.supported_signature_algs, []) + + def test_create(self): + cr = CertificateRequest((3, 0)) + cr.create([ClientCertificateType.rsa_sign], []) + + self.assertEqual(cr.certificate_authorities, []) + self.assertEqual(cr.certificate_types, [ClientCertificateType.rsa_sign]) + + # XXX type change from array! + self.assertEqual(cr.supported_signature_algs, tuple()) + + def test_parse(self): + cr = CertificateRequest((3, 1)) + + parser = Parser(bytearray( + b'\x00\x00\x04' + # overall length + b'\x01' + # length of certificate types + b'\x01' + # type rsa_sign + b'\x00\x00' # length of CA list + )) + + cr.parse(parser) + + self.assertEqual(cr.certificate_authorities, []) + self.assertEqual(cr.certificate_types, + [ClientCertificateType.rsa_sign]) + + def test_parse_with_TLSv1_2(self): + cr = CertificateRequest((3, 3)) + + parser = Parser(bytearray( + b'\x00\x00\x1a' + # overall length + b'\x01' + # length of certificate types + b'\x01' + # type rsa_sign + b'\x00\x0a' + # length of signature types + b'\x06\x01' + # SHA512+RSA + b'\x05\x01' + # SHA384+RSA + b'\x04\x01' + # SHA256+RSA + b'\x03\x01' + # SHA224+RSA + b'\x02\x01' + # SHA1+RSA + b'\x00\x0a' + # length of CA list + b'\x00'*10 # opaque data type + )) + + cr.parse(parser) + + self.assertEqual(cr.certificate_types, [ClientCertificateType.rsa_sign]) + self.assertEqual(cr.supported_signature_algs, + # XXX should be an array of tuples + [0x0601, + 0x0501, + 0x0401, + 0x0301, + 0x0201]) + + self.assertEqual(len(cr.certificate_authorities), 5) + for cert_auth in cr.certificate_authorities: + self.assertEqual(cert_auth, bytearray(0)) + + def test_write(self): + cr = CertificateRequest((3, 1)) + cr.create([ClientCertificateType.rsa_sign], [bytearray(b'\xff\xff')]) + + self.assertEqual(cr.write(), bytearray( + b'\x0d' + # type + b'\x00\x00\x08' + # overall length + b'\x01' + # length of certificate types + b'\x01' + # type rsa sign + b'\x00\x04' + # length of CA list + b'\x00\x02' + # length of entry + b'\xff\xff' # opaque + )) + + def test_write_in_TLS_v1_2(self): + cr = CertificateRequest((3, 3)) + self.assertEqual(cr.version, (3, 3)) + cr.create([ClientCertificateType.rsa_sign], + [], + # XXX should be an array of tuples + [0x0601, 0x0401, 0x0201], + # XXX version set for the second time! + version=(3, 3)) + + self.assertEqual(cr.write(), bytearray( + b'\x0d' + # type + b'\x00\x00\x0c' + # overall length + b'\x01' + # length of certificate types + b'\x01' + # type rsa sign + b'\x00\x06' + # signature types + b'\x06\x01' + # SHA512+RSA + b'\x04\x01' + # SHA256+RSA + b'\x02\x01' + # SHA1+RSA + b'\x00\x00' # length of CA list + )) + if __name__ == '__main__': unittest.main() From 21b635094e3f32d9977061f6eda7d2e6d92584c1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Jun 2015 15:48:22 +0200 Subject: [PATCH 079/574] test coverage for CertificateVerify --- unit_tests/test_tlslite_messages.py | 41 ++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 167db519..fd81a327 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -9,7 +9,7 @@ import unittest from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ - CertificateRequest + CertificateRequest, CertificateVerify from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType @@ -1540,5 +1540,44 @@ def test_write_in_TLS_v1_2(self): b'\x00\x00' # length of CA list )) +class TestCertificateVerify(unittest.TestCase): + def test___init__(self): + cv = CertificateVerify() + + self.assertIsNotNone(cv) + self.assertEqual(cv.signature, bytearray(0)) + + def test_create(self): + cv = CertificateVerify() + + cv.create(bytearray(b'\xf0\x0f')) + + self.assertEqual(cv.signature, bytearray(b'\xf0\x0f')) + + def test_write(self): + cv = CertificateVerify() + + cv.create(bytearray(b'\xf0\x0f')) + + self.assertEqual(cv.write(), bytearray( + b'\x0f' + # type + b'\x00\x00\x04' + # overall length + b'\x00\x02' + # length of signature + b'\xf0\x0f' # signature + )) + + def test_parse(self): + cv = CertificateVerify() + + parser = Parser(bytearray( + b'\x00\x00\x04' + # length + b'\x00\x02' + # length of signature + b'\xf0\x0f' # signature + )) + + cv.parse(parser) + + self.assertEqual(cv.signature, bytearray(b'\xf0\x0f')) + if __name__ == '__main__': unittest.main() From 871809da9ef5859923997f92299904e2a6a7e031 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Sat, 25 Apr 2015 21:43:35 -0400 Subject: [PATCH 080/574] Fix CertificateRequest initialization. Adapted from a Chromium patch. This matches the other messages; __init__ gets passed parameters necessary to determine the behavior of parse (version, cipherSuite), while the fields for outgoing messages are set in create. --- tlslite/messages.py | 7 ++----- tlslite/tlsconnection.py | 12 +++++++----- unit_tests/test_tlslite_messages.py | 7 ++----- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 4dc38374..73433e24 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -877,17 +877,14 @@ def write(self): class CertificateRequest(HandshakeMsg): def __init__(self, version): HandshakeMsg.__init__(self, HandshakeType.certificate_request) - #Apple's Secure Transport library rejects empty certificate_types, so - #default to rsa_sign. - self.certificate_types = [ClientCertificateType.rsa_sign] + self.certificate_types = [] self.certificate_authorities = [] self.version = version self.supported_signature_algs = [] - def create(self, certificate_types, certificate_authorities, sig_algs=(), version=(3,0)): + def create(self, certificate_types, certificate_authorities, sig_algs=()): self.certificate_types = certificate_types self.certificate_authorities = certificate_authorities - self.version = version self.supported_signature_algs = sig_algs return self diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index c59e741f..5455b1bb 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1485,11 +1485,13 @@ def _serverCertKeyExchange(self, clientHello, serverHello, msgs.append(serverHello) msgs.append(Certificate(CertificateType.x509).create(serverCertChain)) - if reqCert and reqCAs: - msgs.append(CertificateRequest().create(\ - [ClientCertificateType.rsa_sign], reqCAs)) - elif reqCert: - msgs.append(CertificateRequest(self.version)) + if reqCert: + #Apple's Secure Transport library rejects empty certificate_types, + #and only RSA certificates are supported. + reqCAs = reqCAs or [] + reqCertTypes = [ClientCertificateType.rsa_sign] + msgs.append(CertificateRequest(self.version).create(reqCertTypes, + reqCAs)) msgs.append(ServerHelloDone()) for result in self._sendMsgs(msgs): yield result diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index fd81a327..62e98ce9 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1441,8 +1441,7 @@ def test___init__(self): self.assertIsNotNone(cr) self.assertEqual(cr.version, (3, 0)) - # XXX unset - self.assertEqual(cr.certificate_types, [ClientCertificateType.rsa_sign]) + self.assertEqual(cr.certificate_types, []) self.assertEqual(cr.certificate_authorities, []) self.assertEqual(cr.supported_signature_algs, []) @@ -1524,9 +1523,7 @@ def test_write_in_TLS_v1_2(self): cr.create([ClientCertificateType.rsa_sign], [], # XXX should be an array of tuples - [0x0601, 0x0401, 0x0201], - # XXX version set for the second time! - version=(3, 3)) + [0x0601, 0x0401, 0x0201]) self.assertEqual(cr.write(), bytearray( b'\x0d' + # type From 0567977d296347c6bd6174087c1c897ad2e9d2f1 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Sat, 25 Apr 2015 21:43:46 -0400 Subject: [PATCH 081/574] If ignoreAbrubtClose is set, write failures don't break sessions. From Chromium. If an abrubt close happens while we're in the middle of writing data, don't invalidate the session. --- tlslite/tlsrecordlayer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index baa0b098..42759602 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -304,7 +304,9 @@ def writeAsync(self, s): except GeneratorExit: raise except Exception: - self._shutdown(False) + # Don't invalidate the session on write failure if abrupt closes are + # okay. + self._shutdown(self.ignoreAbruptClose) raise def close(self): From 308b821a25fe03f641a4d0f89edeb7ed4bca2646 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Sat, 25 Apr 2015 21:43:54 -0400 Subject: [PATCH 082/574] Fix client auth for TLS 1.2. Client auth now participates in signature algorithms. Test-wise, this was already covered by test 14, but since both sides implemented it wrong the test passed. Add a test at TLS 1.1 so coverage of the hash-less codepath isn't lost. From Chromium. --- tests/tlstest.py | 26 +++++++++++- tlslite/constants.py | 23 ++++++++++- tlslite/messages.py | 21 ++++++++-- tlslite/tlsconnection.py | 19 ++++++--- tlslite/tlsrecordlayer.py | 2 +- tlslite/utils/codec.py | 19 +++++++++ tlslite/utils/rsakey.py | 12 +++--- unit_tests/test_tlslite_messages.py | 55 ++++++++++++++++++++------ unit_tests/test_tlslite_utils_codec.py | 29 ++++++++++++++ 9 files changed, 174 insertions(+), 32 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index 38ca9c72..40a91173 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -234,7 +234,18 @@ def connect(): assert(isinstance(connection.session.serverCertChain, X509CertChain)) connection.close() - print("Test 14.a - good mutual X509, SSLv3") + print("Test 14.a - good mutual X509, TLSv1.1") + synchro.recv(1) + connection = connect() + settings = HandshakeSettings() + settings.minVersion = (3,2) + settings.maxVersion = (3,2) + connection.handshakeClientCert(x509Chain, x509Key, settings=settings) + testConnClient(connection) + assert(isinstance(connection.session.serverCertChain, X509CertChain)) + connection.close() + + print("Test 14.b - good mutual X509, SSLv3") synchro.recv(1) connection = connect() settings = HandshakeSettings() @@ -744,7 +755,18 @@ def connect(): assert(isinstance(connection.session.serverCertChain, X509CertChain)) connection.close() - print("Test 14a - good mutual X.509, SSLv3") + print("Test 14a - good mutual X.509, TLSv1.1") + synchro.send(b'R') + connection = connect() + settings = HandshakeSettings() + settings.minVersion = (3,2) + settings.maxVersion = (3,2) + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, reqCert=True, settings=settings) + testConnServer(connection) + assert(isinstance(connection.session.serverCertChain, X509CertChain)) + connection.close() + + print("Test 14b - good mutual X.509, SSLv3") synchro.send(b'R') connection = connect() settings = HandshakeSettings() diff --git a/tlslite/constants.py b/tlslite/constants.py index 1319cd1b..a7a5f1bd 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -47,7 +47,28 @@ class ExtensionType: # RFC 6066 / 4366 encrypt_then_mac = 22 # RFC 7366 tack = 0xF300 supports_npn = 13172 - + +class HashAlgorithm: + + """Hash algorithm IDs used in TLSv1.2""" + + none = 0 + md5 = 1 + sha1 = 2 + sha224 = 3 + sha256 = 4 + sha384 = 5 + sha512 = 6 + +class SignatureAlgorithm: + + """Signing algorithms used in TLSv1.2""" + + anonymous = 0 + rsa = 1 + dsa = 2 + ecdsa = 3 + class NameType: host_name = 0 diff --git a/tlslite/messages.py b/tlslite/messages.py index 73433e24..b9690cc3 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -892,7 +892,7 @@ def parse(self, p): p.startLengthCheck(3) self.certificate_types = p.getVarList(1, 1) if self.version >= (3,3): - self.supported_signature_algs = p.getVarList(2, 2) + self.supported_signature_algs = p.getVarTupleList(1, 2, 2) ca_list_length = p.get(2) index = 0 self.certificate_authorities = [] @@ -907,7 +907,12 @@ def write(self): w = Writer() w.addVarSeq(self.certificate_types, 1, 1) if self.version >= (3,3): - w.addVarSeq(self.supported_signature_algs, 2, 2) + w2 = Writer() + for (hash_alg, signature) in self.supported_signature_algs: + w2.add(hash_alg, 1) + w2.add(signature, 1) + w.add(len(w2.bytes), 2) + w.bytes += w2.bytes caLength = 0 #determine length for ca_dn in self.certificate_authorities: @@ -1171,22 +1176,30 @@ def write(self): return self.postWrite(w) class CertificateVerify(HandshakeMsg): - def __init__(self): + def __init__(self, version): HandshakeMsg.__init__(self, HandshakeType.certificate_verify) + self.version = version + self.signature_algorithm = None self.signature = bytearray(0) - def create(self, signature): + def create(self, signature, signature_algorithm=None): + self.signature_algorithm = signature_algorithm self.signature = signature return self def parse(self, p): p.startLengthCheck(3) + if self.version >= (3,3): + self.signature_algorithm = (p.get(1), p.get(1)) self.signature = p.getVarBytes(2) p.stopLengthCheck() return self def write(self): w = Writer() + if self.version >= (3,3): + w.add(self.signature_algorithm[0], 1) + w.add(self.signature_algorithm[1], 1) w.addVarSeq(self.signature, 1, 2) return self.postWrite(w) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 5455b1bb..386627df 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -24,6 +24,7 @@ from .mathtls import * from .handshakesettings import HandshakeSettings from .utils.tackwrapper import * +from .utils.rsakey import RSAKey class TLSConnection(TLSRecordLayer): @@ -871,6 +872,7 @@ def _clientRSAKeyExchange(self, settings, cipherSuite, #If client authentication was requested and we have a #private key, send CertificateVerify if certificateRequest and privateKey: + signatureAlgorithm = None if self.version == (3,0): masterSecret = calcMasterSecret(self.version, premasterSecret, @@ -881,12 +883,15 @@ def _clientRSAKeyExchange(self, settings, cipherSuite, verifyBytes = self._handshake_md5.digest() + \ self._handshake_sha.digest() elif self.version == (3,3): - verifyBytes = self._handshake_sha256.digest() + # TODO: Signature algorithm negotiation not supported. + signatureAlgorithm = (HashAlgorithm.sha1, SignatureAlgorithm.rsa) + verifyBytes = self._handshake_sha.digest() + verifyBytes = RSAKey.addPKCS1SHA1Prefix(verifyBytes) if self.fault == Fault.badVerifyMessage: verifyBytes[0] = ((verifyBytes[0]+1) % 256) signedBytes = privateKey.sign(verifyBytes) - certificateVerify = CertificateVerify() - certificateVerify.create(signedBytes) + certificateVerify = CertificateVerify(self.version) + certificateVerify.create(signedBytes, signatureAlgorithm) for result in self._sendMsg(certificateVerify): yield result yield (premasterSecret, serverCertChain, clientCertChain, tackExt) @@ -1490,8 +1495,11 @@ def _serverCertKeyExchange(self, clientHello, serverHello, #and only RSA certificates are supported. reqCAs = reqCAs or [] reqCertTypes = [ClientCertificateType.rsa_sign] + #Only SHA-1 + RSA is supported. + sigAlgs = [(HashAlgorithm.sha1, SignatureAlgorithm.rsa)] msgs.append(CertificateRequest(self.version).create(reqCertTypes, - reqCAs)) + reqCAs, + sigAlgs)) msgs.append(ServerHelloDone()) for result in self._sendMsgs(msgs): yield result @@ -1568,7 +1576,8 @@ def _serverCertKeyExchange(self, clientHello, serverHello, verifyBytes = self._handshake_md5.digest() + \ self._handshake_sha.digest() elif self.version == (3,3): - verifyBytes = self._handshake_sha256.digest() + verifyBytes = self._handshake_sha.digest() + verifyBytes = RSAKey.addPKCS1SHA1Prefix(verifyBytes) for result in self._getMsg(ContentType.handshake, HandshakeType.certificate_verify): if result in (0,1): yield result diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 42759602..34a5335a 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -759,7 +759,7 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): elif subType == HandshakeType.certificate_request: yield CertificateRequest(self.version).parse(p) elif subType == HandshakeType.certificate_verify: - yield CertificateVerify().parse(p) + yield CertificateVerify(self.version).parse(p) elif subType == HandshakeType.server_key_exchange: yield ServerKeyExchange(constructorType).parse(p) elif subType == HandshakeType.server_hello_done: diff --git a/tlslite/utils/codec.py b/tlslite/utils/codec.py index b1f69345..33761cdc 100644 --- a/tlslite/utils/codec.py +++ b/tlslite/utils/codec.py @@ -70,6 +70,25 @@ def getVarList(self, length, lengthLength): l[x] = self.get(length) return l + def getVarTupleList(self, elemLength, elemNum, lengthLength): + """Read a variable length list of same sized tuples + + @param elemLength: length in bytes of single tuple element + @param elemNum: number of elements in tuple + @param lengthLength: length in bytes of the list length variable + """ + lengthList = self.get(lengthLength) + if lengthList % (elemLength * elemNum) != 0: + raise SyntaxError() + tupleCount = lengthList // (elemLength * elemNum) + tupleList = [] + for _ in range(tupleCount): + currentTuple = [] + for _ in range(elemNum): + currentTuple.append(self.get(elemLength)) + tupleList.append(tuple(currentTuple)) + return tupleList + def startLengthCheck(self, lengthLength): self.lengthCheck = self.get(lengthLength) self.indexCheck = self.index diff --git a/tlslite/utils/rsakey.py b/tlslite/utils/rsakey.py index 3f2100eb..fb022cc6 100644 --- a/tlslite/utils/rsakey.py +++ b/tlslite/utils/rsakey.py @@ -60,7 +60,7 @@ def hashAndSign(self, bytes): @return: A PKCS1-SHA1 signature on the passed-in data. """ hashBytes = SHA1(bytearray(bytes)) - prefixedHashBytes = self._addPKCS1SHA1Prefix(hashBytes) + prefixedHashBytes = self.addPKCS1SHA1Prefix(hashBytes) sigBytes = self.sign(prefixedHashBytes) return sigBytes @@ -81,8 +81,8 @@ def hashAndVerify(self, sigBytes, bytes): hashBytes = SHA1(bytearray(bytes)) # Try it with/without the embedded NULL - prefixedHashBytes1 = self._addPKCS1SHA1Prefix(hashBytes, False) - prefixedHashBytes2 = self._addPKCS1SHA1Prefix(hashBytes, True) + prefixedHashBytes1 = self.addPKCS1SHA1Prefix(hashBytes, False) + prefixedHashBytes2 = self.addPKCS1SHA1Prefix(hashBytes, True) result1 = self.verify(sigBytes, prefixedHashBytes1) result2 = self.verify(sigBytes, prefixedHashBytes2) return (result1 or result2) @@ -221,7 +221,8 @@ def generate(bits): # Helper Functions for RSA Keys # ************************************************************************** - def _addPKCS1SHA1Prefix(self, bytes, withNULL=True): + @staticmethod + def addPKCS1SHA1Prefix(bytes, withNULL=True): # There is a long history of confusion over whether the SHA1 # algorithmIdentifier should be encoded with a NULL parameter or # with the parameter omitted. While the original intention was @@ -229,8 +230,7 @@ def _addPKCS1SHA1Prefix(self, bytes, withNULL=True): # specifies the NULL should be included, and this behavior is also # mandated in recent versions of PKCS #1, and is what tlslite has # always implemented. Anyways, verification code should probably - # accept both. However, nothing uses this code yet, so this is - # all fairly moot. + # accept both. if not withNULL: prefixBytes = bytearray(\ [0x30,0x1f,0x30,0x07,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x04,0x14]) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 62e98ce9..b887470f 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -12,7 +12,8 @@ CertificateRequest, CertificateVerify from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ - AlertLevel, AlertDescription, ExtensionType, ClientCertificateType + AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ + HashAlgorithm, SignatureAlgorithm from tlslite.extensions import SNIExtension, ClientCertTypeExtension, \ SRPExtension, TLSExtension from tlslite.errors import TLSInternalError @@ -1492,12 +1493,11 @@ def test_parse_with_TLSv1_2(self): self.assertEqual(cr.certificate_types, [ClientCertificateType.rsa_sign]) self.assertEqual(cr.supported_signature_algs, - # XXX should be an array of tuples - [0x0601, - 0x0501, - 0x0401, - 0x0301, - 0x0201]) + [(HashAlgorithm.sha512, SignatureAlgorithm.rsa), + (HashAlgorithm.sha384, SignatureAlgorithm.rsa), + (HashAlgorithm.sha256, SignatureAlgorithm.rsa), + (HashAlgorithm.sha224, SignatureAlgorithm.rsa), + (HashAlgorithm.sha1, SignatureAlgorithm.rsa)]) self.assertEqual(len(cr.certificate_authorities), 5) for cert_auth in cr.certificate_authorities: @@ -1522,8 +1522,7 @@ def test_write_in_TLS_v1_2(self): self.assertEqual(cr.version, (3, 3)) cr.create([ClientCertificateType.rsa_sign], [], - # XXX should be an array of tuples - [0x0601, 0x0401, 0x0201]) + [(6, 1), (4, 1), (2, 1)]) self.assertEqual(cr.write(), bytearray( b'\x0d' + # type @@ -1539,20 +1538,20 @@ def test_write_in_TLS_v1_2(self): class TestCertificateVerify(unittest.TestCase): def test___init__(self): - cv = CertificateVerify() + cv = CertificateVerify((3, 1)) self.assertIsNotNone(cv) self.assertEqual(cv.signature, bytearray(0)) def test_create(self): - cv = CertificateVerify() + cv = CertificateVerify((3, 1)) cv.create(bytearray(b'\xf0\x0f')) self.assertEqual(cv.signature, bytearray(b'\xf0\x0f')) def test_write(self): - cv = CertificateVerify() + cv = CertificateVerify((3, 1)) cv.create(bytearray(b'\xf0\x0f')) @@ -1564,7 +1563,7 @@ def test_write(self): )) def test_parse(self): - cv = CertificateVerify() + cv = CertificateVerify((3, 1)) parser = Parser(bytearray( b'\x00\x00\x04' + # length @@ -1576,5 +1575,35 @@ def test_parse(self): self.assertEqual(cv.signature, bytearray(b'\xf0\x0f')) + def test_parse_with_TLSv1_2(self): + cv = CertificateVerify((3, 3)) + + parser = Parser(bytearray( + b'\x00\x00\x06' + # length + b'\x02\x01' + # SHA1 + RSA + b'\x00\x02' + # length of signature + b'\xab\xcd' # signature + )) + + cv.parse(parser) + + self.assertEqual(cv.signature, bytearray(b'\xab\xcd')) + self.assertEqual(cv.signature_algorithm, (HashAlgorithm.sha1, + SignatureAlgorithm.rsa)) + + def test_write_with_TLSv1_2(self): + cv = CertificateVerify((3, 3)) + + cv.create(bytearray(b'\xff\xba'), (HashAlgorithm.sha512, + SignatureAlgorithm.rsa)) + + self.assertEqual(cv.write(), bytearray( + b'\x0f' + # type + b'\x00\x00\x06' + # overall length + b'\x06\x01' + # SHA512+RSA + b'\x00\x02' + # signature length + b'\xff\xba' # signature + )) + if __name__ == '__main__': unittest.main() diff --git a/unit_tests/test_tlslite_utils_codec.py b/unit_tests/test_tlslite_utils_codec.py index 999a30f0..276ee0a6 100644 --- a/unit_tests/test_tlslite_utils_codec.py +++ b/unit_tests/test_tlslite_utils_codec.py @@ -169,6 +169,35 @@ def test_getRemainingLength(self): self.assertEqual(5, p.get(1)) self.assertEqual(0, p.getRemainingLength()) + def test_getVarTupleList(self): + p = Parser(bytearray( + b'\x00\x06' + # length of list + b'\x01\x00' + # first tuple + b'\x01\x05' + # second tuple + b'\x04\x00' # third tuple + )) + + self.assertEqual(p.getVarTupleList(1, 2, 2), + [(1, 0), + (1, 5), + (4, 0)]) + + def test_getVarTupleList_with_missing_elements(self): + p = Parser(bytearray( + b'\x00\x02' + + b'\x00')) + + with self.assertRaises(SyntaxError): + p.getVarTupleList(1, 2, 2) + + def test_getVarTupleList_with_incorrect_length(self): + p = Parser(bytearray( + b'\x00\x03' + + b'\x00'*3)) + + with self.assertRaises(SyntaxError): + p.getVarTupleList(1, 2, 2) + class TestWriter(unittest.TestCase): def test___init__(self): w = Writer() From 0d140ded43b9c6e4bafe4558356e37b2fed3b41a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 20 Jun 2015 14:32:09 +0200 Subject: [PATCH 083/574] check for client cert in tests Since we're checking on server side, we need to check for client certificate, not our (server) certificate --- tests/tlstest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index 40a91173..6733e4bf 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -752,7 +752,7 @@ def connect(): connection = connect() connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, reqCert=True) testConnServer(connection) - assert(isinstance(connection.session.serverCertChain, X509CertChain)) + assert(isinstance(connection.session.clientCertChain, X509CertChain)) connection.close() print("Test 14a - good mutual X.509, TLSv1.1") @@ -763,7 +763,7 @@ def connect(): settings.maxVersion = (3,2) connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, reqCert=True, settings=settings) testConnServer(connection) - assert(isinstance(connection.session.serverCertChain, X509CertChain)) + assert(isinstance(connection.session.clientCertChain, X509CertChain)) connection.close() print("Test 14b - good mutual X.509, SSLv3") @@ -774,7 +774,7 @@ def connect(): settings.maxVersion = (3,0) connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, reqCert=True, settings=settings) testConnServer(connection) - assert(isinstance(connection.session.serverCertChain, X509CertChain)) + assert(isinstance(connection.session.clientCertChain, X509CertChain)) connection.close() print("Test 15 - mutual X.509 faults") From d2f21d7af0a9e2fc2e4144b84d96495d51b938ec Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 20 Jun 2015 15:14:31 +0200 Subject: [PATCH 084/574] test coverage for RSAKey --- unit_tests/test_tlslite_utils_rsakey.py | 118 ++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 unit_tests/test_tlslite_utils_rsakey.py diff --git a/unit_tests/test_tlslite_utils_rsakey.py b/unit_tests/test_tlslite_utils_rsakey.py new file mode 100644 index 00000000..a185c269 --- /dev/null +++ b/unit_tests/test_tlslite_utils_rsakey.py @@ -0,0 +1,118 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.utils.rsakey import RSAKey +from tlslite.utils.python_rsakey import Python_RSAKey + +# because RSAKey is an abstract class... +class TestRSAKey(unittest.TestCase): + + # random RSA parameters + N = int("101394340507163232476731540998223559348384567842249950630680016" + "729829651735259973644737329194901739140557378171784099933376993" + "53519793819698299093375577631") + e = 65537 + d = int("141745721972918790698280063566067268498148845185400775263435953" + "111621933337897734637889622802200979017278309730638712431978569" + "771023240787627463565420833") + p = int("903614668974112441151570413608036278756730123846327797584414732" + "71561046135679") + q = int("112209710608480690748363491355148749700390327497055102381924341" + "581861552321889") + dP = int("37883511062045429960298073888481933556799848761465588242411735" + "654811958185817") + dQ = int("62620473256245674709410658602365234471246407950887183034263101" + "286525236349249") + qInv = int("479278327226690415958629934820002183615697717603796111150941" + "44623120451328875") + + + def test___init__(self): + rsa = Python_RSAKey() + + self.assertIsNotNone(rsa) + + def test___init___with_values(self): + rsa = Python_RSAKey(self.N, self.e, self.d, self.p, self.q, self.dP, + self.dQ, self.qInv) + + self.assertIsNotNone(rsa) + + def test_hashAndSign(self): + rsa = Python_RSAKey(self.N, self.e, self.d, self.p, self.q, self.dP, + self.dQ, self.qInv) + + sigBytes = rsa.hashAndSign(bytearray(b'text to sign')) + + self.assertEqual(bytearray( + b'K\x7f\xf2\xca\x81\xf0A1\x95\xb1\x19\xe3\xd7QTL*Q|\xb6\x04' + + b'\xbdG\x88H\x12\xc3\xe2\xb3\x97\xd2\xcd\xd8\xe8^Zn^\x8f\x1a' + + b'\xae\x9a\x0b)\xb5K\xe8\x98|R\xac\xdc\xdc\n\x7f\x8b\xe7\xe6' + + b'HQ\xc3hS\x19'), sigBytes) + + def test_hashAndVerify(self): + rsa = Python_RSAKey(self.N, self.e) + + sigBytes = bytearray( + b'K\x7f\xf2\xca\x81\xf0A1\x95\xb1\x19\xe3\xd7QTL*Q|\xb6\x04' + + b'\xbdG\x88H\x12\xc3\xe2\xb3\x97\xd2\xcd\xd8\xe8^Zn^\x8f\x1a' + + b'\xae\x9a\x0b)\xb5K\xe8\x98|R\xac\xdc\xdc\n\x7f\x8b\xe7\xe6' + + b'HQ\xc3hS\x19') + + self.assertTrue(rsa.hashAndVerify(sigBytes, + bytearray(b'text to sign'))) + + def test_hashAndVerify_without_NULL_encoding_of_SHA1(self): + rsa = Python_RSAKey(self.N, self.e) + + sigBytes = bytearray( + b'F\xe7\x8a>\x8a<;Cj\xdd\xea\x7f\x9d\x0c\xfd\xa7r\xd8\xa1O' + + b'\xe1\xf5\x174\x0bR\xad:+\xc9C\x06\xf4\x88n\tp\x14FJ=\xfa' + + b'\x8b\xefc\xe2\xdf\x00e\xc1\x1e\xe8\xd2\x97@\x8a\x96\xe2' + + b'\x039Y_\x9c\xc9') + + self.assertTrue(rsa.hashAndVerify(sigBytes, + bytearray(b'text to sign'))) + + def test_hashAndVerify_with_invalid_signature(self): + rsa = Python_RSAKey(self.N, self.e) + + sigBytes = bytearray(64) + + self.assertFalse(rsa.hashAndVerify(sigBytes, + bytearray(b'text to sign'))) + + def test_hashAndVerify_with_slightly_wrong_signature(self): + rsa = Python_RSAKey(self.N, self.e) + + sigBytes = bytearray( + b'K\x7f\xf2\xca\x81\xf0A1\x95\xb1\x19\xe3\xd7QTL*Q|\xb6\x04' + + b'\xbdG\x88H\x12\xc3\xe2\xb3\x97\xd2\xcd\xd8\xe8^Zn^\x8f\x1a' + + b'\xae\x9a\x0b)\xb5K\xe8\x98|R\xac\xdc\xdc\n\x7f\x8b\xe7\xe6' + + b'HQ\xc3hS\x19') + sigBytes[0] = 255 + + self.assertFalse(rsa.hashAndVerify(sigBytes, + bytearray(b'text to sign'))) + + def test_addPKCS1SHA1Prefix(self): + data = bytearray(b' sha-1 hash of data ') + + self.assertEqual(RSAKey.addPKCS1SHA1Prefix(data), bytearray( + b'0!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14' + + b' sha-1 hash of data ')) + + def test_addPKCS1SHA1Prefix_without_NULL(self): + data = bytearray(b' sha-1 hash of data ') + + self.assertEqual(RSAKey.addPKCS1SHA1Prefix(data, False), bytearray( + b'0\x1f0\x07\x06\x05+\x0e\x03\x02\x1a\x04\x14' + + b' sha-1 hash of data ')) From 255a68303f4605163f34b39408264dbc2152fba0 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Sat, 25 Apr 2015 21:44:08 -0400 Subject: [PATCH 085/574] Fix Fault-based test framework. Enabling any faults just no-ops the handshake right now, so those tests so they were meaningless. The tests with the assert() lines were passing only because a bare expect will catch everything, including AssertionError. --- tests/tlstest.py | 26 +++++------------------ tlslite/tlsconnection.py | 45 ++++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 44 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index 6733e4bf..281e087b 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -707,11 +707,7 @@ def connect(): synchro.send(b'R') connection = connect() connection.fault = fault - try: - connection.handshakeServer(verifierDB=verifierDB) - assert() - except: - pass + connection.handshakeServer(verifierDB=verifierDB) connection.close() print("Test 6 - good SRP: with X.509 cert") @@ -727,12 +723,8 @@ def connect(): synchro.send(b'R') connection = connect() connection.fault = fault - try: - connection.handshakeServer(verifierDB=verifierDB, \ - certChain=x509Chain, privateKey=x509Key) - assert() - except: - pass + connection.handshakeServer(verifierDB=verifierDB, \ + certChain=x509Chain, privateKey=x509Key) connection.close() print("Test 11 - X.509 faults") @@ -740,11 +732,7 @@ def connect(): synchro.send(b'R') connection = connect() connection.fault = fault - try: - connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) - assert() - except: - pass + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) connection.close() print("Test 14 - good mutual X.509") @@ -782,11 +770,7 @@ def connect(): synchro.send(b'R') connection = connect() connection.fault = fault - try: - connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, reqCert=True) - assert() - except: - pass + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, reqCert=True) connection.close() print("Test 18 - good SRP, prepare to resume") diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 386627df..8c594be6 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1779,28 +1779,27 @@ def _calcFinished(self, masterSecret, send=True): def _handshakeWrapperAsync(self, handshaker, checker): - if not self.fault: - try: - for result in handshaker: - yield result - if checker: - try: - checker(self) - except TLSAuthenticationError: - alert = Alert().create(AlertDescription.close_notify, - AlertLevel.fatal) - for result in self._sendMsg(alert): - yield result - raise - except GeneratorExit: - raise - except TLSAlert as alert: - if not self.fault: + try: + for result in handshaker: + yield result + if checker: + try: + checker(self) + except TLSAuthenticationError: + alert = Alert().create(AlertDescription.close_notify, + AlertLevel.fatal) + for result in self._sendMsg(alert): + yield result raise - if alert.description not in Fault.faultAlerts[self.fault]: - raise TLSFaultError(str(alert)) - else: - pass - except: - self._shutdown(False) + except GeneratorExit: + raise + except TLSAlert as alert: + if not self.fault: raise + if alert.description not in Fault.faultAlerts[self.fault]: + raise TLSFaultError(str(alert)) + else: + pass + except: + self._shutdown(False) + raise From 4d0df2b2cc3d2161158131977aca47a0699d822e Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Sat, 25 Apr 2015 21:44:12 -0400 Subject: [PATCH 086/574] Correctly enforce cipher suite version constraints. Adapted and then rewritten from part of a Chromium patch. Adds a test which uses a Fault which disables this check on either peer. --- tlslite/constants.py | 8 ++ tlslite/tlsconnection.py | 12 ++- unit_tests/test_tlslite_tlsconnection.py | 128 +++++++++++++++++++++++ 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 unit_tests/test_tlslite_tlsconnection.py diff --git a/tlslite/constants.py b/tlslite/constants.py index a7a5f1bd..de822730 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -277,6 +277,14 @@ class CipherSuite: md5Suites = [] md5Suites.append(TLS_RSA_WITH_RC4_128_MD5) + @staticmethod + def filterForVersion(suites, minVersion, maxVersion): + """Return a copy of suites without ciphers incompatible with version""" + excludeSuites = [] + if maxVersion < (3, 3): + excludeSuites += CipherSuite.sha256Suites + return [s for s in suites if s not in excludeSuites] + @staticmethod def _filterSuites(suites, settings): macNames = settings.macNames diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 8c594be6..1df2b21d 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -584,7 +584,11 @@ def _clientGetServerHello(self, settings, clientHello): AlertDescription.protocol_version, "Too new version: %s" % str(serverHello.server_version)): yield result - if serverHello.cipher_suite not in clientHello.cipher_suites: + serverVer = serverHello.server_version + cipherSuites = CipherSuite.filterForVersion(clientHello.cipher_suites, + minVersion=serverVer, + maxVersion=serverVer) + if serverHello.cipher_suite not in cipherSuites: for result in self._sendError(\ AlertDescription.illegal_parameter, "Server responded with incorrect ciphersuite"): @@ -1301,6 +1305,12 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, AlertDescription.inappropriate_fallback): yield result + #Now that the version is known, limit to only the ciphers available to + #that version. + cipherSuites = CipherSuite.filterForVersion(cipherSuites, + minVersion=self.version, + maxVersion=self.version) + #If resumption was requested and we have a session cache... if clientHello.session_id and sessionCache: session = None diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py new file mode 100644 index 00000000..bce83557 --- /dev/null +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -0,0 +1,128 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.handshakesettings import HandshakeSettings +from tlslite.recordlayer import RecordLayer +from tlslite.messages import ServerHello, Certificate, ServerHelloDone, \ + ClientHello +from tlslite.constants import CipherSuite, CertificateType, AlertDescription +from tlslite.tlsconnection import TLSConnection +from tlslite.errors import TLSLocalAlert +from tlslite.x509 import X509 +from tlslite.x509certchain import X509CertChain +from tlslite.utils.keyfactory import parsePEMKey + +from unit_tests.mocksock import MockSocket + +class TestTLSConnection(unittest.TestCase): + + srv_raw_certificate = str( + "-----BEGIN CERTIFICATE-----\n"\ + "MIIB9jCCAV+gAwIBAgIJAMyn9DpsTG55MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\n"\ + "BAMMCWxvY2FsaG9zdDAeFw0xNTAxMjExNDQzMDFaFw0xNTAyMjAxNDQzMDFaMBQx\n"\ + "EjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n"\ + "0QkEeakSyV/LMtTeARdRtX5pdbzVuUuqOIdz3lg7YOyRJ/oyLTPzWXpKxr//t4FP\n"\ + "QvYsSJiVOlPk895FNu6sNF/uJQyQGfFWYKkE6fzFifQ6s9kssskFlL1DVI/dD/Zn\n"\ + "7sgzua2P1SyLJHQTTs1MtMb170/fX2EBPkDz+2kYKN0CAwEAAaNQME4wHQYDVR0O\n"\ + "BBYEFJtvXbRmxRFXYVMOPH/29pXCpGmLMB8GA1UdIwQYMBaAFJtvXbRmxRFXYVMO\n"\ + "PH/29pXCpGmLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAkOgC7LP/\n"\ + "Rd6uJXY28HlD2K+/hMh1C3SRT855ggiCMiwstTHACGgNM+AZNqt6k8nSfXc6k1gw\n"\ + "5a7SGjzkWzMaZC3ChBeCzt/vIAGlMyXeqTRhjTCdc/ygRv3NPrhUKKsxUYyXRk5v\n"\ + "g/g6MwxzXfQP3IyFu3a9Jia/P89Z1rQCNRY=\n"\ + "-----END CERTIFICATE-----\n"\ + ) + + srv_raw_key = str( + "-----BEGIN RSA PRIVATE KEY-----\n"\ + "MIICXQIBAAKBgQDRCQR5qRLJX8sy1N4BF1G1fml1vNW5S6o4h3PeWDtg7JEn+jIt\n"\ + "M/NZekrGv/+3gU9C9ixImJU6U+Tz3kU27qw0X+4lDJAZ8VZgqQTp/MWJ9Dqz2Syy\n"\ + "yQWUvUNUj90P9mfuyDO5rY/VLIskdBNOzUy0xvXvT99fYQE+QPP7aRgo3QIDAQAB\n"\ + "AoGAVSLbE8HsyN+fHwDbuo4I1Wa7BRz33xQWLBfe9TvyUzOGm0WnkgmKn3LTacdh\n"\ + "GxgrdBZXSun6PVtV8I0im5DxyVaNdi33sp+PIkZU386f1VUqcnYnmgsnsUQEBJQu\n"\ + "fUZmgNM+bfR+Rfli4Mew8lQ0sorZ+d2/5fsM0g80Qhi5M3ECQQDvXeCyrcy0u/HZ\n"\ + "FNjIloyXaAIvavZ6Lc6gfznCSfHc5YwplOY7dIWp8FRRJcyXkA370l5dJ0EXj5Gx\n"\ + "udV9QQ43AkEA34+RxjRk4DT7Zo+tbM/Fkoi7jh1/0hFkU5NDHweJeH/mJseiHtsH\n"\ + "KOcPGtEGBBqT2KNPWVz4Fj19LiUmmjWXiwJBAIBs49O5/+ywMdAAqVblv0S0nweF\n"\ + "4fwne4cM+5ZMSiH0XsEojGY13EkTEon/N8fRmE8VzV85YmkbtFWgmPR85P0CQQCs\n"\ + "elWbN10EZZv3+q1wH7RsYzVgZX3yEhz3JcxJKkVzRCnKjYaUi6MweWN76vvbOq4K\n"\ + "G6Tiawm0Duh/K4ZmvyYVAkBppE5RRQqXiv1KF9bArcAJHvLm0vnHPpf1yIQr5bW6\n"\ + "njBuL4qcxlaKJVGRXT7yFtj2fj0gv3914jY2suWqp8XJ\n"\ + "-----END RSA PRIVATE KEY-----\n"\ + ) + + def test_client_with_server_responing_with_SHA256_on_TLSv1_1(self): + # socket to generate the faux response + gen_sock = MockSocket(bytearray(0)) + + gen_record_layer = RecordLayer(gen_sock) + gen_record_layer.version = (3, 2) + + server_hello = ServerHello().create( + version=(3, 2), + random=bytearray(32), + session_id=bytearray(0), + cipher_suite=CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + certificate_type=None, + tackExt=None, + next_protos_advertised=None) + + for res in gen_record_layer.sendMessage(server_hello): + if res in (0, 1): + self.assertTrue(False, "Blocking socket") + else: + break + + # test proper + sock = MockSocket(gen_sock.sent[0]) + + conn = TLSConnection(sock) + + with self.assertRaises(TLSLocalAlert) as err: + conn.handshakeClientCert() + + self.assertEqual(err.exception.description, + AlertDescription.illegal_parameter) + + def test_server_with_client_proposing_SHA256_on_TLSv1_1(self): + gen_sock = MockSocket(bytearray(0)) + + gen_record_layer = RecordLayer(gen_sock) + gen_record_layer.version = (3, 0) + + ciphers = [CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256, + 0x88, # TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA + CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV] + + client_hello = ClientHello().create(version=(3, 2), + random=bytearray(32), + session_id=bytearray(0), + cipher_suites=ciphers) + + for res in gen_record_layer.sendMessage(client_hello): + if res in (0, 1): + self.assertTrue(False, "Blocking socket") + else: + break + + # test proper + sock = MockSocket(gen_sock.sent[0]) + + conn = TLSConnection(sock) + + srv_private_key = parsePEMKey(self.srv_raw_key, private=True) + srv_cert_chain = X509CertChain([X509().parse(self.srv_raw_certificate)]) + with self.assertRaises(TLSLocalAlert) as err: + conn.handshakeServer(certChain=srv_cert_chain, + privateKey=srv_private_key) + + self.assertEqual(err.exception.description, + AlertDescription.handshake_failure) From cd3df68a1a400ef7eec9857ee82f53e39109ce98 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 20 Jun 2015 15:45:57 +0200 Subject: [PATCH 087/574] make filterForVersion a positive not negative test Since it's safer to include what we know than the other way round, change the filter to output only ciphers we know includes test coverage for the method --- tlslite/constants.py | 19 +++++++-- unit_tests/test_tlslite_constants.py | 59 ++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 unit_tests/test_tlslite_constants.py diff --git a/tlslite/constants.py b/tlslite/constants.py index de822730..5ea384cc 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -277,13 +277,24 @@ class CipherSuite: md5Suites = [] md5Suites.append(TLS_RSA_WITH_RC4_128_MD5) + # SSL3, TLS1.0, TLS1.1 and TLS1.2 compatible ciphers + ssl3Suites = [] + ssl3Suites.extend(shaSuites) + ssl3Suites.extend(md5Suites) + + # TLS1.2 specific ciphersuites + tls12Suites = [] + tls12Suites.extend(sha256Suites) + @staticmethod def filterForVersion(suites, minVersion, maxVersion): """Return a copy of suites without ciphers incompatible with version""" - excludeSuites = [] - if maxVersion < (3, 3): - excludeSuites += CipherSuite.sha256Suites - return [s for s in suites if s not in excludeSuites] + includeSuites = set([]) + if (3, 0) <= minVersion <= (3, 3): + includeSuites.update(CipherSuite.ssl3Suites) + if maxVersion == (3, 3): + includeSuites.update(CipherSuite.tls12Suites) + return [s for s in suites if s in includeSuites] @staticmethod def _filterSuites(suites, settings): diff --git a/unit_tests/test_tlslite_constants.py b/unit_tests/test_tlslite_constants.py new file mode 100644 index 00000000..b48599cf --- /dev/null +++ b/unit_tests/test_tlslite_constants.py @@ -0,0 +1,59 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.constants import CipherSuite + +class TestCipherSuite(unittest.TestCase): + + def test___init__(self): + cipherSuites = CipherSuite() + + self.assertIsNotNone(cipherSuites) + + def test_filterForVersion_with_SSL3_ciphers(self): + suites = [CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_RC4_128_MD5] + + filtered = CipherSuite.filterForVersion(suites, (3, 0), (3, 0)) + + self.assertEqual(filtered, + [CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_RC4_128_MD5]) + + filtered = CipherSuite.filterForVersion(suites, (3, 3), (3, 3)) + + self.assertEqual(filtered, + [CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_RC4_128_MD5]) + + def test_filterForVersion_with_unknown_ciphers(self): + suites = [0, 0xfffe] + + filtered = CipherSuite.filterForVersion(suites, (3, 0), (3, 3)) + + self.assertEqual(filtered, []) + + def test_filterForVersion_with_TLS_1_2_ciphers(self): + suites = [CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_RC4_128_MD5, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256] + + filtered = CipherSuite.filterForVersion(suites, (3, 2), (3, 2)) + + self.assertEqual(filtered, + [CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_RC4_128_MD5]) From 7d238574408ac8e2f7484c548e40a545aefe3fc9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 18 Mar 2015 18:40:49 +0100 Subject: [PATCH 088/574] tests for PRF_1_2 and calcMasterSecret --- unit_tests/test_tlslite_mathtls.py | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 unit_tests/test_tlslite_mathtls.py diff --git a/unit_tests/test_tlslite_mathtls.py b/unit_tests/test_tlslite_mathtls.py new file mode 100644 index 00000000..164c5e6e --- /dev/null +++ b/unit_tests/test_tlslite_mathtls.py @@ -0,0 +1,35 @@ +# Copyright (c) 2014, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.mathtls import PRF_1_2, calcMasterSecret + +class TestCalcMasterSecret(unittest.TestCase): + def test_with_empty_values(self): + ret = calcMasterSecret((3, 3), bytearray(48), bytearray(32), + bytearray(32)) + + self.assertEqual(bytearray( + b'I\xcf\xae\xe5[\x86\x92\xd3\xbbm\xd6\xeekSo/' + + b'\x17\xaf\xbc\x84\x18\tGc\xbc\xb5\xbe\xd6\xb0\x05\xad\xf8' + + b'\x88\xd0`\xe4\x8c^\xb2&ls\xcb\x1a=-Kh' + ), ret) + self.assertEqual(48, len(ret)) + +class TestPRF1_2(unittest.TestCase): + def test_with_bogus_values(self): + ret = PRF_1_2(bytearray(1), b"key expansion", bytearray(1), 10) + + self.assertEqual(bytearray(b'\xaa2\xca\r\x8b\x85N\xad?\xab'), ret) + + def test_with_realistic_values(self): + ret = PRF_1_2(bytearray(48), b"key expansion", bytearray(64), 16) + + self.assertEqual(bytearray(b'S\xb5\xdb\xc8T }u)BxuB\xe4\xeb\xeb'), ret) From 58b15d4a2028aa3878d135f396768c3bb3a58cea Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Fri, 30 Jan 2015 00:46:28 -0500 Subject: [PATCH 089/574] Implement DHE_RSA key exchanges. From Chromium. --- tlslite/constants.py | 60 ++++++++++- tlslite/handshakesettings.py | 12 ++- tlslite/messages.py | 65 +++++++----- tlslite/tlsconnection.py | 156 ++++++++++++++++++++++------ tlslite/tlsrecordlayer.py | 3 +- unit_tests/test_tlslite_messages.py | 104 ++++++++++++++++--- 6 files changed, 321 insertions(+), 79 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 5ea384cc..d2c78e3f 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -210,6 +210,14 @@ class CipherSuite: TLS_RSA_WITH_RC4_128_MD5 = 0x0004 ietfNames[0x0004] = 'TLS_RSA_WITH_RC4_128_MD5' + # RFC 5246 - TLS v1.2 Protocol + TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016 + ietfNames[0x0016] = 'TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA' + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 + ietfNames[0x0016] = 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA' + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 + ietfNames[0x0039] = 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA' + # RFC 5246 - TLS v1.2 Protocol TLS_DH_ANON_WITH_AES_128_CBC_SHA = 0x0034 ietfNames[0x0034] = 'TLS_DH_ANON_WITH_AES_128_CBC_SHA' @@ -222,6 +230,12 @@ class CipherSuite: TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D ietfNames[0x003D] = 'TLS_RSA_WITH_AES_256_CBC_SHA256' + # RFC 5246 - TLS v1.2 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 + ietfNames[0x0067] = 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA256' + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B + ietfNames[0x006B] = 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA256' + # # Define cipher suite families below # @@ -231,14 +245,17 @@ 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) # AES-128 CBC ciphers 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) aes128Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA256) + aes128Suites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) # AES-256 CBC ciphers aes256Suites = [] @@ -246,7 +263,9 @@ class CipherSuite: aes256Suites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) + aes256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) + aes256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) # RC4-128 stream cipher rc4Suites = [] @@ -265,6 +284,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) @@ -272,6 +294,9 @@ class CipherSuite: sha256Suites = [] sha256Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA256) sha256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) + sha256Suites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) + sha256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) + # MD-5 HMAC, protocol default PRF md5Suites = [] @@ -300,6 +325,7 @@ def filterForVersion(suites, minVersion, maxVersion): def _filterSuites(suites, settings): macNames = settings.macNames cipherNames = settings.cipherNames + keyExchangeNames = settings.keyExchangeNames macSuites = [] if "sha" in macNames: macSuites += CipherSuite.shaSuites @@ -318,7 +344,20 @@ def _filterSuites(suites, settings): 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] # SRP key exchange srpSuites = [] @@ -355,13 +394,26 @@ def getSrpAllSuites(settings): certSuites.append(TLS_RSA_WITH_3DES_EDE_CBC_SHA) certSuites.append(TLS_RSA_WITH_RC4_128_SHA) certSuites.append(TLS_RSA_WITH_RC4_128_MD5) - # RSA authentication - certAllSuites = srpCertSuites + certSuites @staticmethod def getCertSuites(settings): return CipherSuite._filterSuites(CipherSuite.certSuites, settings) + # FFDHE key exchange, RSA authentication + dheCertSuites = [] + dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) + dheCertSuites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) + dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA) + dheCertSuites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA) + dheCertSuites.append(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) + + @staticmethod + def getDheCertSuites(settings): + return CipherSuite._filterSuites(CipherSuite.dheCertSuites, settings) + + # RSA authentication + certAllSuites = srpCertSuites + certSuites + dheCertSuites + # anon FFDHE key exchange anonSuites = [] anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) @@ -371,6 +423,8 @@ def getCertSuites(settings): 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/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 6fe92d10..4a73c4f3 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -14,7 +14,9 @@ # 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", "sha256"] # "md5" is allowed +MAC_NAMES = ["sha", "sha256"] # Don't allow "md5" by default. +ALL_MAC_NAMES = ["sha", "sha256", "md5"] +KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "srp_sha", "srp_sha_rsa", "dh_anon"] CIPHER_IMPLEMENTATIONS = ["openssl", "pycrypto", "python"] CERTIFICATE_TYPES = ["x509"] @@ -104,6 +106,7 @@ def __init__(self): 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,1) @@ -126,6 +129,7 @@ def validate(self): 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 @@ -162,6 +166,12 @@ def validate(self): 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/tlslite/messages.py b/tlslite/messages.py index b9690cc3..d9232fd8 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -946,9 +946,13 @@ class ServerKeyExchange(HandshakeMsg): @cvar dh_Ys: FFDH protocol server key share @type signature: bytearray @cvar signature: signature performed over the parameters by server + @type hashAlg: int + @cvar hashAlg: id of hash algorithm used for signature + @type signAlg: int + @cvar signAlg: id of signature algorithm used for signature """ - def __init__(self, cipherSuite): + def __init__(self, cipherSuite, version): """ Initialise Server Key Exchange for reading or writing @@ -957,6 +961,7 @@ def __init__(self, cipherSuite): """ HandshakeMsg.__init__(self, HandshakeType.server_key_exchange) self.cipherSuite = cipherSuite + self.version = version self.srp_N = 0 self.srp_g = 0 self.srp_s = bytearray(0) @@ -965,7 +970,11 @@ def __init__(self, cipherSuite): self.dh_p = 0 self.dh_g = 0 self.dh_Ys = 0 + # signature for certificate authenticated ciphersuites self.signature = bytearray(0) + # signature hash algorithm and signing algorithm for TLSv1.2 + self.hashAlg = 0 + self.signAlg = 0 def createSRP(self, srp_N, srp_g, srp_s, srp_B): """Set SRP protocol parameters""" @@ -994,16 +1003,23 @@ def parse(self, parser): self.srp_g = bytesToNumber(parser.getVarBytes(2)) self.srp_s = parser.getVarBytes(1) self.srp_B = bytesToNumber(parser.getVarBytes(2)) - if self.cipherSuite in CipherSuite.srpCertSuites: - self.signature = parser.getVarBytes(2) elif self.cipherSuite in CipherSuite.anonSuites: self.dh_p = bytesToNumber(parser.getVarBytes(2)) self.dh_g = bytesToNumber(parser.getVarBytes(2)) self.dh_Ys = bytesToNumber(parser.getVarBytes(2)) + else: + raise AssertionError() + + if self.cipherSuite in CipherSuite.certAllSuites: + if self.version == (3, 3): + self.hashAlg = parser.get(1) + self.signAlg = parser.get(1) + self.signature = parser.getVarBytes(2) + parser.stopLengthCheck() return self - def write(self): + def write_params(self): """Serialise message @rtype: bytearray @@ -1014,30 +1030,31 @@ def write(self): writer.addVarSeq(numberToByteArray(self.srp_g), 1, 2) writer.addVarSeq(self.srp_s, 1, 1) writer.addVarSeq(numberToByteArray(self.srp_B), 1, 2) - if self.cipherSuite in CipherSuite.srpCertSuites: - writer.addVarSeq(self.signature, 1, 2) - elif self.cipherSuite in CipherSuite.anonSuites: + elif self.cipherSuite in CipherSuite.dhAllSuites: writer.addVarSeq(numberToByteArray(self.dh_p), 1, 2) writer.addVarSeq(numberToByteArray(self.dh_g), 1, 2) writer.addVarSeq(numberToByteArray(self.dh_Ys), 1, 2) - if self.cipherSuite in []: # TODO support for signed_params - writer.addVarSeq(self.signature, 1, 2) - return self.postWrite(writer) + else: + assert(False) + return writer.bytes + + def write(self): + w = Writer() + w.bytes += self.write_params() + if self.cipherSuite in CipherSuite.certAllSuites: + if self.version >= (3,3): + # TODO: Signature algorithm negotiation not supported. + w.add(HashAlgorithm.sha1, 1) + w.add(SignatureAlgorithm.rsa, 1) + w.addVarSeq(self.signature, 1, 2) + return self.postWrite(w) def hash(self, clientRandom, serverRandom): - """Calculate the signature hash""" - oldCipherSuite = self.cipherSuite - # temporarily change so serialiser omits signature - if self.cipherSuite in CipherSuite.srpAllSuites: - self.cipherSuite = CipherSuite.srpSuites[0] - try: - # skip message type(1 byte) and length(3 bytes) - payloadBytes = self.write()[4:] - bytesToMAC = clientRandom + serverRandom + payloadBytes - # TODO should use protocol dependant values (invalid in TLSv1.2) - return MD5(bytesToMAC) + SHA1(bytesToMAC) - finally: - self.cipherSuite = oldCipherSuite + bytes = clientRandom + serverRandom + self.write_params() + if self.version >= (3,3): + # TODO: Signature algorithm negotiation not supported. + return SHA1(bytes) + return MD5(bytes) + SHA1(bytes) class ServerHelloDone(HandshakeMsg): def __init__(self): @@ -1146,7 +1163,7 @@ def parse(self, parser): parser.getFixBytes(parser.getRemainingLength()) else: raise AssertionError() - elif self.cipherSuite in CipherSuite.anonSuites: + elif self.cipherSuite in CipherSuite.dhAllSuites: self.dh_Yc = bytesToNumber(parser.getVarBytes(2)) else: raise AssertionError() diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 1df2b21d..8dc4e966 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -13,6 +13,7 @@ MAIN CLASS FOR TLS LITE (START HERE!). """ +from __future__ import division import socket from .utils.compat import formatExceptionTrace from .tlsrecordlayer import TLSRecordLayer @@ -26,6 +27,89 @@ from .utils.tackwrapper import * from .utils.rsakey import RSAKey +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 + +class DHE_RSAKeyExchange(KeyExchange): + # 2048-bit MODP Group (RFC 3526, Section 3) + dh_g, dh_p = goodGroupParameters[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) + + version = self.serverHello.server_version + serverKeyExchange = ServerKeyExchange(self.cipherSuite, version) + serverKeyExchange.createDH(self.dh_p, self.dh_g, dh_Ys) + hashBytes = serverKeyExchange.hash(self.clientHello.random, + self.serverHello.random) + if version >= (3,3): + # TODO: Signature algorithm negotiation not supported. + hashBytes = RSAKey.addPKCS1SHA1Prefix(hashBytes) + serverKeyExchange.signature = self.privateKey.sign(hashBytes) + 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): """ @@ -508,6 +592,8 @@ def _clientSendClientHello(self, settings, session, srpUsername, if srpParams: cipherSuites += CipherSuite.getSrpAllSuites(settings) elif certParams: + # TODO: Client DHE_RSA not supported. + # cipherSuites += CipherSuite.getDheCertSuites(settings) cipherSuites += CipherSuite.getCertSuites(settings) elif anonParams: cipherSuites += CipherSuite.getAnonSuites(settings) @@ -1199,10 +1285,23 @@ def _handshakeServerAsyncHelper(self, verifierDB, else: break premasterSecret = result - # Perform the RSA key exchange - elif cipherSuite in CipherSuite.certSuites: + # Perform a certificate-based 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): if result in (0,1): yield result @@ -1264,6 +1363,7 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, CipherSuite.getSrpCertSuites(settings) cipherSuites += CipherSuite.getSrpSuites(settings) elif certChain: + cipherSuites += CipherSuite.getDheCertSuites(settings) cipherSuites += CipherSuite.getCertSuites(settings) elif anon: cipherSuites += CipherSuite.getAnonSuites(settings) @@ -1443,7 +1543,7 @@ def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, B = (powMod(g, b, N) + (k*v)) % N #Create ServerKeyExchange, signing it if necessary - serverKeyExchange = ServerKeyExchange(cipherSuite) + serverKeyExchange = ServerKeyExchange(cipherSuite, self.version) serverKeyExchange.createSRP(N, g, s, B) if cipherSuite in CipherSuite.srpCertSuites: hashBytes = serverKeyExchange.hash(clientHello.random, @@ -1488,11 +1588,11 @@ def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, def _serverCertKeyExchange(self, clientHello, serverHello, - serverCertChain, privateKey, + serverCertChain, keyExchange, reqCert, reqCAs, cipherSuite, settings): - #Send ServerHello, Certificate[, CertificateRequest], - #ServerHelloDone + #Send ServerHello, Certificate[, ServerKeyExchange] + #[, CertificateRequest], ServerHelloDone msgs = [] # If we verify a client cert chain, return it @@ -1500,16 +1600,14 @@ def _serverCertKeyExchange(self, clientHello, serverHello, msgs.append(serverHello) msgs.append(Certificate(CertificateType.x509).create(serverCertChain)) - if reqCert: - #Apple's Secure Transport library rejects empty certificate_types, - #and only RSA certificates are supported. - reqCAs = reqCAs or [] - reqCertTypes = [ClientCertificateType.rsa_sign] - #Only SHA-1 + RSA is supported. - sigAlgs = [(HashAlgorithm.sha1, SignatureAlgorithm.rsa)] - msgs.append(CertificateRequest(self.version).create(reqCertTypes, - reqCAs, - sigAlgs)) + serverKeyExchange = keyExchange.makeServerKeyExchange() + if serverKeyExchange is not None: + msgs.append(serverKeyExchange) + if reqCert and reqCAs: + msgs.append(CertificateRequest().create(\ + [ClientCertificateType.rsa_sign], reqCAs)) + elif reqCert: + msgs.append(CertificateRequest(self.version)) msgs.append(ServerHelloDone()) for result in self._sendMsgs(msgs): yield result @@ -1560,21 +1658,13 @@ def _serverCertKeyExchange(self, clientHello, serverHello, 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 as alert: + for result in self._sendError(alert.description, alert.message): + yield result #Get and check CertificateVerify, if relevant if clientCertChain: @@ -1623,7 +1713,7 @@ def _serverAnonKeyExchange(self, clientHello, serverHello, cipherSuite, dh_Ys = powMod(dh_g, dh_Xs, dh_p) #Create ServerKeyExchange - serverKeyExchange = ServerKeyExchange(cipherSuite) + serverKeyExchange = ServerKeyExchange(cipherSuite, self.version) serverKeyExchange.createDH(dh_p, dh_g, dh_Ys) #Send ServerHello[, Certificate], ServerKeyExchange, diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 34a5335a..f9410eb2 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -761,7 +761,8 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): elif subType == HandshakeType.certificate_verify: yield CertificateVerify(self.version).parse(p) elif subType == HandshakeType.server_key_exchange: - yield ServerKeyExchange(constructorType).parse(p) + yield ServerKeyExchange(constructorType, + self.version).parse(p) elif subType == HandshakeType.server_hello_done: yield ServerHelloDone().parse(p) elif subType == HandshakeType.client_key_exchange: diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index b887470f..96f09cbd 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1253,7 +1253,8 @@ def test_parse_with_unknown_cipher(self): class TestServerKeyExchange(unittest.TestCase): def test___init__(self): - ske = ServerKeyExchange(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA) + ske = ServerKeyExchange(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + (3, 1)) self.assertIsNotNone(ske) self.assertEqual(ske.cipherSuite, @@ -1266,9 +1267,11 @@ def test___init__(self): self.assertEqual(ske.dh_g, 0) self.assertEqual(ske.dh_Ys, 0) self.assertEqual(ske.signature, bytearray(0)) + self.assertEqual(ske.version, (3, 1)) def test_createSRP(self): - ske = ServerKeyExchange(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA) + ske = ServerKeyExchange(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA, + (3, 3)) ske.createSRP(srp_N=1, srp_g=2, @@ -1290,7 +1293,8 @@ def test_createSRP(self): def test_createSRP_with_signature(self): ske = ServerKeyExchange( - CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, + (3, 1)) ske.createSRP(srp_N=1, srp_g=2, @@ -1313,8 +1317,36 @@ def test_createSRP_with_signature(self): b'\xc0\xff\xee' # signature value )) + def test_createSRP_with_signature_in_TLS_v1_2(self): + ske = ServerKeyExchange( + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, + (3, 3)) + + ske.createSRP(srp_N=1, + srp_g=2, + srp_s=bytearray(3), + srp_B=4) + ske.signature = bytearray(b'\xc0\xff\xee') + + self.assertEqual(ske.write(), bytearray( + b'\x0c' + # message type + b'\x00\x00\x14' + # overall length + b'\x00\x01' + # N parameter length + b'\x01' + # N value + b'\x00\x01' + # g parameter length + b'\x02' + # g value + b'\x03' + # s parameter length + b'\x00'*3 + # s value + b'\x00\x01' + # B parameter length + b'\x04' + # B value + b'\x02\x01' + # SHA1+RSA + b'\x00\x03' + # signature length + b'\xc0\xff\xee' # signature value + )) + def test_createDH(self): - ske = ServerKeyExchange(CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA) + ske = ServerKeyExchange(CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA, + (3, 3)) ske.createDH(dh_p=31, dh_g=2, @@ -1332,7 +1364,7 @@ def test_createDH(self): )) def test_parse_with_unknown_cipher(self): - ske = ServerKeyExchange(0) + ske = ServerKeyExchange(0, (3, 1)) parser = Parser(bytearray( b'\x00\x00\x03' + @@ -1340,11 +1372,33 @@ def test_parse_with_unknown_cipher(self): b'\xff' )) + with self.assertRaises(AssertionError): + ske.parse(parser) + + def test_parse_with_read_past_message_end(self): + ske = ServerKeyExchange(\ + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, + (3, 1)) + + parser = Parser(bytearray( + b'\x00\x00\x13' + # overall length + b'\x00\x01' + # N parameter length + b'\x01' + # N value + b'\x00\x01' + # g parameter length + b'\x02' + # g value + b'\x03' + # s parameter length + b'\x00'*3 + # s value + b'\x00\x01' + # B parameter length + b'\x04' + # B value + b'\x00\x06' + # signature length + b'\xff'*4)) + with self.assertRaises(SyntaxError): ske.parse(parser) def test_parse_with_SRP(self): - ske = ServerKeyExchange(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA) + ske = ServerKeyExchange(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA, + (3, 3)) parser = Parser(bytearray( b'\x00\x00\x0d' + # overall length @@ -1367,7 +1421,8 @@ def test_parse_with_SRP(self): def test_parser_with_SRP_RSA(self): ske = ServerKeyExchange( - CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, + (3, 1)) parser = Parser(bytearray( b'\x00\x00\x12' + # overall length @@ -1392,7 +1447,8 @@ def test_parser_with_SRP_RSA(self): self.assertEqual(ske.signature, bytearray(b'\xc0\xff\xee')) def test_parser_with_DH(self): - ske = ServerKeyExchange(CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA) + ske = ServerKeyExchange(CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA, + (3, 3)) parser = Parser(bytearray( b'\x00\x00\x09' + # overall length @@ -1412,7 +1468,8 @@ def test_parser_with_DH(self): def test_hash(self): ske = ServerKeyExchange( - CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, + (3, 1)) ske.createSRP(srp_N=1, srp_g=2, @@ -1426,15 +1483,28 @@ def test_hash(self): b'\x8e?YW\xcd\xad\xc6\x83\x91\x1d.fe,\x17y' + b'=\xc4T\x89')) - ske2 = ServerKeyExchange(0) - hash2 = ske2.hash(bytearray(32), bytearray(32)) - self.assertEqual(len(hash2), 36) - self.assertEqual(hash2, bytearray( - b';]<} ~7\xdc\xee\xed\xd3\x01\xe3^.X' + - b'\xc8\xd7\xd0\xef\x0e\xed\xfa\x82\xd2\xea\x1a\xa5\x92\x84[\x9a' + - b'mK\x02\xb7')) + def test_hash_with_invalid_ciphersuite(self): + ske = ServerKeyExchange(0, (3, 1)) + + with self.assertRaises(AssertionError): + ske.hash(bytearray(32), bytearray(32)) + + def test_hash_with_default_hash_algorithm_for_TLS_v1_2(self): + ske = ServerKeyExchange( + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, + (3, 3)) + + ske.createSRP(srp_N=1, + srp_g=2, + srp_s=bytearray(3), + srp_B=4) + + hash1 = ske.hash(bytearray(32), bytearray(32)) + + self.assertEqual(hash1, bytearray( + b'\x8e?YW\xcd\xad\xc6\x83\x91\x1d.fe,\x17y=\xc4T\x89' + )) - self.assertNotEqual(hash1, hash2) class TestCertificateRequest(unittest.TestCase): def test___init__(self): From 082b20352af62734299defc3b5288a9e28c98221 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 10 Jun 2015 20:15:50 +0200 Subject: [PATCH 090/574] client side DHE_RSA --- tlslite/messages.py | 14 +- tlslite/tlsconnection.py | 193 +++++++++++++++++-- unit_tests/test_tlslite_handshakesettings.py | 14 ++ unit_tests/test_tlslite_messages.py | 46 ++++- 4 files changed, 239 insertions(+), 28 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index d9232fd8..36a943d0 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1003,7 +1003,7 @@ def parse(self, parser): self.srp_g = bytesToNumber(parser.getVarBytes(2)) self.srp_s = parser.getVarBytes(1) self.srp_B = bytesToNumber(parser.getVarBytes(2)) - elif self.cipherSuite in CipherSuite.anonSuites: + elif self.cipherSuite in CipherSuite.dhAllSuites: self.dh_p = bytesToNumber(parser.getVarBytes(2)) self.dh_g = bytesToNumber(parser.getVarBytes(2)) self.dh_Ys = bytesToNumber(parser.getVarBytes(2)) @@ -1042,10 +1042,10 @@ def write(self): w = Writer() w.bytes += self.write_params() if self.cipherSuite in CipherSuite.certAllSuites: - if self.version >= (3,3): - # TODO: Signature algorithm negotiation not supported. - w.add(HashAlgorithm.sha1, 1) - w.add(SignatureAlgorithm.rsa, 1) + if self.version >= (3, 3): + assert self.hashAlg != 0 and self.signAlg != 0 + w.add(self.hashAlg, 1) + w.add(self.signAlg, 1) w.addVarSeq(self.signature, 1, 2) return self.postWrite(w) @@ -1186,8 +1186,8 @@ def write(self): w.addFixSeq(self.encryptedPreMasterSecret, 1) else: raise AssertionError() - elif self.cipherSuite in CipherSuite.anonSuites: - w.addVarSeq(numberToByteArray(self.dh_Yc), 1, 2) + elif self.cipherSuite in CipherSuite.dhAllSuites: + w.addVarSeq(numberToByteArray(self.dh_Yc), 1, 2) else: raise AssertionError() return self.postWrite(w) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 8dc4e966..3e2e5b4c 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -96,6 +96,8 @@ def makeServerKeyExchange(self): if version >= (3,3): # TODO: Signature algorithm negotiation not supported. hashBytes = RSAKey.addPKCS1SHA1Prefix(hashBytes) + serverKeyExchange.signAlg = SignatureAlgorithm.rsa + serverKeyExchange.hashAlg = HashAlgorithm.sha1 serverKeyExchange.signature = self.privateKey.sign(hashBytes) return serverKeyExchange @@ -539,12 +541,16 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, #If the server selected an anonymous ciphersuite, the client #finishes reading the post-ServerHello messages. - elif cipherSuite in CipherSuite.anonSuites: - for result in self._clientAnonKeyExchange(settings, cipherSuite, + elif cipherSuite in CipherSuite.dhAllSuites: + for result in self._clientDHEKeyExchange(settings, cipherSuite, + clientCertChain, privateKey, + serverHello.certificate_type, + serverHello.tackExt, clientHello.random, serverHello.random): if result in (0,1): yield result else: break - (premasterSecret, serverCertChain, tackExt) = result + (premasterSecret, serverCertChain, clientCertChain, + tackExt) = result #If the server selected a certificate-based RSA ciphersuite, #the client finishes reading the post-ServerHello messages. If @@ -592,8 +598,7 @@ def _clientSendClientHello(self, settings, session, srpUsername, if srpParams: cipherSuites += CipherSuite.getSrpAllSuites(settings) elif certParams: - # TODO: Client DHE_RSA not supported. - # cipherSuites += CipherSuite.getDheCertSuites(settings) + cipherSuites += CipherSuite.getDheCertSuites(settings) cipherSuites += CipherSuite.getCertSuites(settings) elif anonParams: cipherSuites += CipherSuite.getAnonSuites(settings) @@ -865,7 +870,7 @@ def _clientSRPKeyExchange(self, settings, cipherSuite, certificateType, #Send ClientKeyExchange for result in self._sendMsg(\ - ClientKeyExchange(cipherSuite).createSRP(A)): + ClientKeyExchange(cipherSuite, self.version).createSRP(A)): yield result yield (premasterSecret, serverCertChain, tackExt) @@ -986,8 +991,23 @@ def _clientRSAKeyExchange(self, settings, cipherSuite, yield result yield (premasterSecret, serverCertChain, clientCertChain, tackExt) - def _clientAnonKeyExchange(self, settings, cipherSuite, clientRandom, - serverRandom): + def _clientDHEKeyExchange(self, settings, cipherSuite, + clientCertChain, privateKey, + certificateType, + tackExt, clientRandom, serverRandom): + #TODO: check if received messages match cipher suite + # (abort if CertfificateRequest and ADH) + + # if server chose DHE_RSA cipher get the certificate + if cipherSuite in CipherSuite.dheCertSuites: + for result in self._getMsg(ContentType.handshake, + HandshakeType.certificate, + certificateType): + if result in (0, 1): + yield result + else: break + serverCertificate = result + # get rest of handshake messages for result in self._getMsg(ContentType.handshake, HandshakeType.server_key_exchange, cipherSuite): if result in (0,1): yield result @@ -995,29 +1015,153 @@ def _clientAnonKeyExchange(self, settings, cipherSuite, clientRandom, serverKeyExchange = result for result in self._getMsg(ContentType.handshake, - HandshakeType.server_hello_done): + (HandshakeType.certificate_request, + HandshakeType.server_hello_done)): if result in (0,1): yield result else: break + + certificateRequest = None + if isinstance(result, CertificateRequest): + certificateRequest = result + # we got CertificateRequest so now we'll get ServerHelloDone + for result in self._getMsg(ContentType.handshake, + HandshakeType.server_hello_done): + if result in (0, 1): + yield result + else: break serverHelloDone = result - + + #Check the server's signature, if the server chose an DHE_RSA suite + serverCertChain = None + if cipherSuite in CipherSuite.dheCertSuites: + #Check if the signature algorithm is sane in TLSv1.2 + if self.version == (3, 3): + if serverKeyExchange.hashAlg != HashAlgorithm.sha1 or \ + serverKeyExchange.signAlg != SignatureAlgorithm.rsa: + for result in self._sendError(\ + AlertDescription.illegal_parameter, + "Server selected not advertised " + "signature algorithm"): + yield result + + hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) + + sigBytes = serverKeyExchange.signature + if len(sigBytes) == 0: + for result in self._sendError(\ + AlertDescription.illegal_parameter, + "Server sent DHE ServerKeyExchange message " + "without a signature"): + yield result + + for result in self._clientGetKeyFromChain(serverCertificate, + settings, + tackExt): + if result in (0, 1): + yield result + else: + break + publicKey, serverCertChain, tackExt = result + + # actually a check for hashAlg == sha1 and signAlg == rsa + if self.version == (3, 3): + hashBytes = publicKey.addPKCS1SHA1Prefix(hashBytes) + + if not publicKey.verify(sigBytes, hashBytes): + for result in self._sendError( + AlertDescription.decrypt_error, + "signature failed to verify"): + yield result + + # TODO: make it changeable + if 2**1023 > serverKeyExchange.dh_p: + for result in self._sendError( + AlertDescription.insufficient_security, + "Server sent a DHE key exchange with very small prime"): + yield result + + #Send Certificate if we were asked for it + if certificateRequest: + + # if a peer doesn't advertise support for any algorithm in TLSv1.2, + # support for SHA1+RSA can be assumed + if self.version == (3, 3) and \ + (HashAlgorithm.sha1, SignatureAlgorithm.rsa) \ + not in certificateRequest.supported_signature_algs and\ + len(certificateRequest.supported_signature_algs) > 0: + for result in self._sendError(\ + AlertDescription.handshake_failure, + "Server doesn't accept any sigalgs we support: " + + str(certificateRequest.supported_signature_algs)): + yield result + clientCertificate = Certificate(certificateType) + + if clientCertChain: + #Check to make sure we have the same type of + #certificates the server requested + wrongType = False + if certificateType == CertificateType.x509: + if not isinstance(clientCertChain, X509CertChain): + wrongType = True + if wrongType: + for result in self._sendError(\ + AlertDescription.handshake_failure, + "Client certificate is of wrong type"): + yield result + + clientCertificate.create(clientCertChain) + # we need to send the message even if we don't have a certificate + for result in self._sendMsg(clientCertificate): + yield result + else: + #Server didn't ask for cer, zeroise so session doesn't store them + privateKey = None + clientCertChain = None + #calculate Yc dh_p = serverKeyExchange.dh_p dh_g = serverKeyExchange.dh_g dh_Xc = bytesToNumber(getRandomBytes(32)) dh_Ys = serverKeyExchange.dh_Ys dh_Yc = powMod(dh_g, dh_Xc, dh_p) - + #Send ClientKeyExchange for result in self._sendMsg(\ ClientKeyExchange(cipherSuite, self.version).createDH(dh_Yc)): yield result - + #Calculate premaster secret S = powMod(dh_Ys, dh_Xc, dh_p) premasterSecret = numberToByteArray(S) - - yield (premasterSecret, None, None) - + + #if client auth was requested and we have a private key, send a + #CertificateVerify + if certificateRequest and privateKey: + signatureAlgorithm = None + if self.version == (3,0): + masterSecret = calcMasterSecret(self.version, + premasterSecret, + clientRandom, + serverRandom) + verifyBytes = self._calcSSLHandshakeHash(masterSecret, b"") + elif self.version in ((3,1), (3,2)): + verifyBytes = self._handshake_md5.digest() + \ + self._handshake_sha.digest() + else: # self.version == (3,3): + # TODO: Signature algorithm negotiation not supported. + signatureAlgorithm = (HashAlgorithm.sha1, SignatureAlgorithm.rsa) + verifyBytes = self._handshake_sha.digest() + verifyBytes = RSAKey.addPKCS1SHA1Prefix(verifyBytes) + if self.fault == Fault.badVerifyMessage: + verifyBytes[0] = ((verifyBytes[0]+1) % 256) + signedBytes = privateKey.sign(verifyBytes) + certificateVerify = CertificateVerify(self.version) + certificateVerify.create(signedBytes, signatureAlgorithm) + for result in self._sendMsg(certificateVerify): + yield result + + yield (premasterSecret, serverCertChain, clientCertChain, tackExt) + def _clientFinished(self, premasterSecret, clientRandom, serverRandom, cipherSuite, cipherImplementations, nextProto): @@ -1549,6 +1693,10 @@ def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, hashBytes = serverKeyExchange.hash(clientHello.random, serverHello.random) serverKeyExchange.signature = privateKey.sign(hashBytes) + if self.version == (3, 3): + # TODO signing algorithm not negotiatied + serverKeyExchange.signAlg = SignatureAlgorithm.rsa + serverKeyExchange.hashAlg = HashAlgorithm.sha1 #Send ServerHello[, Certificate], ServerKeyExchange, #ServerHelloDone @@ -1603,11 +1751,16 @@ def _serverCertKeyExchange(self, clientHello, serverHello, serverKeyExchange = keyExchange.makeServerKeyExchange() if serverKeyExchange is not None: msgs.append(serverKeyExchange) - if reqCert and reqCAs: - msgs.append(CertificateRequest().create(\ - [ClientCertificateType.rsa_sign], reqCAs)) - elif reqCert: - msgs.append(CertificateRequest(self.version)) + if reqCert: + certificateRequest = CertificateRequest(self.version) + if not reqCAs: + reqCAs = [] + # TODO add support for more HashAlgorithms + certificateRequest.create([ClientCertificateType.rsa_sign], + reqCAs, + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + msgs.append(certificateRequest) msgs.append(ServerHelloDone()) for result in self._sendMsgs(msgs): yield result diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index 3733825c..d06c8f24 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -169,3 +169,17 @@ def test_useEncryptThenMAC_with_wrong_value(self): with self.assertRaises(ValueError): hs.validate() + + def test_invalid_MAC(self): + hs = HandshakeSettings() + hs.macNames = ['sha1', 'whirpool'] + + with self.assertRaises(ValueError): + hs.validate() + + def test_invalid_KEX(self): + hs = HandshakeSettings() + hs.keyExchangeNames = ['rsa', 'ecdhe_rsa', 'gost'] + + with self.assertRaises(ValueError): + hs.validate() diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 96f09cbd..a7ce7567 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1183,6 +1183,18 @@ def test_write_with_unknown_cipher_suite(self): with self.assertRaises(AssertionError): cke.write() + def test_write_with_DHE_RSA(self): + cke = ClientKeyExchange(CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + (3, 1)) + + cke.createDH(2**64+3) + + self.assertEqual(cke.write(), bytearray( + b'\x10' + + b'\x00\x00\x0b' + + b'\x00\x09' + + b'\x01' + b'\x00'*7 + b'\x03')) + def test_parse_with_RSA(self): cke = ClientKeyExchange(CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, (3, 1)) @@ -1326,6 +1338,8 @@ def test_createSRP_with_signature_in_TLS_v1_2(self): srp_g=2, srp_s=bytearray(3), srp_B=4) + ske.hashAlg = HashAlgorithm.sha512 + ske.signAlg = SignatureAlgorithm.rsa ske.signature = bytearray(b'\xc0\xff\xee') self.assertEqual(ske.write(), bytearray( @@ -1339,7 +1353,7 @@ def test_createSRP_with_signature_in_TLS_v1_2(self): b'\x00'*3 + # s value b'\x00\x01' + # B parameter length b'\x04' + # B value - b'\x02\x01' + # SHA1+RSA + b'\x06\x01' + # SHA512+RSA b'\x00\x03' + # signature length b'\xc0\xff\xee' # signature value )) @@ -1446,6 +1460,36 @@ def test_parser_with_SRP_RSA(self): self.assertEqual(ske.srp_B, 4) self.assertEqual(ske.signature, bytearray(b'\xc0\xff\xee')) + def test_parser_with_SRP_RSA_in_TLS_v1_2(self): + ske = ServerKeyExchange( + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, + (3, 3)) + + parser = Parser(bytearray( + b'\x00\x00\x14' + # overall length + b'\x00\x01' + # N parameter length + b'\x01' + # N value + b'\x00\x01' + # g parameter length + b'\x02' + # g value + b'\x03' + # s parameter length + b'\x00'*3 + # s value + b'\x00\x01' + # B parameter length + b'\x04' # B value + b'\x06\x01' + # SHA512+RSA + b'\x00\x03' + # signature length + b'\xc0\xff\xee' # signature value + )) + + ske.parse(parser) + + self.assertEqual(ske.srp_N, 1) + self.assertEqual(ske.srp_g, 2) + self.assertEqual(ske.srp_s, bytearray(3)) + self.assertEqual(ske.srp_B, 4) + self.assertEqual(ske.signature, bytearray(b'\xc0\xff\xee')) + self.assertEqual(ske.hashAlg, HashAlgorithm.sha512) + self.assertEqual(ske.signAlg, SignatureAlgorithm.rsa) + def test_parser_with_DH(self): ske = ServerKeyExchange(CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA, (3, 3)) From 7724598cbfd9af43de16b89809e7db5a61270b77 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 10 Jun 2015 20:16:07 +0200 Subject: [PATCH 091/574] repr() for ServerKeyExchange --- tlslite/messages.py | 21 +++++++++++ unit_tests/test_tlslite_messages.py | 56 +++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/tlslite/messages.py b/tlslite/messages.py index 36a943d0..ce2058a1 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -976,6 +976,27 @@ def __init__(self, cipherSuite, version): self.hashAlg = 0 self.signAlg = 0 + def __repr__(self): + + ret = "ServerKeyExchange(cipherSuite=CipherSuite.{0}, version={1}"\ + "".format(CipherSuite.ietfNames[self.cipherSuite], self.version) + + if self.srp_N != 0: + ret += ", srp_N={0}, srp_g={1}, srp_s={2!r}, srp_B={3}".format(\ + self.srp_N, self.srp_g, self.srp_s, self.srp_B) + if self.dh_p != 0: + ret += ", dh_p={0}, dh_g={1}, dh_Ys={2}".format(\ + self.dh_p, self.dh_g, self.dh_Ys) + if self.signAlg != 0: + ret += ", hashAlg={0}, signAlg={1}".format(\ + self.hashAlg, self.signAlg) + if self.signature != bytearray(0): + ret += ", signature={0!r}".format(self.signature) + ret += ")" + + return ret + + def createSRP(self, srp_N, srp_g, srp_s, srp_B): """Set SRP protocol parameters""" self.srp_N = srp_N diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index a7ce7567..9226f19a 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1281,6 +1281,62 @@ def test___init__(self): self.assertEqual(ske.signature, bytearray(0)) self.assertEqual(ske.version, (3, 1)) + def test___repr__(self): + ske = ServerKeyExchange(CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA, + (3, 1)) + + self.assertEqual("ServerKeyExchange(" + "cipherSuite=CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA, " + "version=(3, 1))", + repr(ske)) + + def test__repr___with_ADH(self): + ske = ServerKeyExchange(CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA, + (3, 1)) + + ske.createDH(dh_p=31, + dh_g=2, + dh_Ys=16) + + self.assertEqual("ServerKeyExchange(" + "cipherSuite=CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA, " + "version=(3, 1), dh_p=31, dh_g=2, dh_Ys=16)", + repr(ske)) + + def test___repr___with_DHE(self): + ske = ServerKeyExchange(CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + (3, 3)) + + ske.createDH(dh_p=31, + dh_g=2, + dh_Ys=16) + ske.signature = bytearray(b'\xff'*4) + ske.signAlg = SignatureAlgorithm.rsa + ske.hashAlg = HashAlgorithm.sha384 + + self.assertEqual("ServerKeyExchange(" + "cipherSuite=CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, " + "version=(3, 3), dh_p=31, dh_g=2, dh_Ys=16, " + "hashAlg=5, signAlg=1, " + "signature=bytearray(b'\\xff\\xff\\xff\\xff'))", + repr(ske)) + + def test___repr___with_SRP(self): + ske = ServerKeyExchange(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA, + (3, 3)) + + ske.createSRP(srp_N=1, + srp_g=2, + srp_s=bytearray(3), + srp_B=4) + + self.assertEqual("ServerKeyExchange(" + "cipherSuite=CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA, " + "version=(3, 3), " + "srp_N=1, srp_g=2, " + "srp_s=bytearray(b'\\x00\\x00\\x00'), srp_B=4)", + repr(ske)) + def test_createSRP(self): ske = ServerKeyExchange(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA, (3, 3)) From 1e760b28da5a0a9a6485662dce190dcfdbbf13cd Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Fri, 30 Jan 2015 01:02:22 -0500 Subject: [PATCH 092/574] Implement AES-GCM and fix server-side TLS-1.2-only check. From Chromium. --- tests/tlstest.py | 8 +- tlslite/constants.py | 53 ++++++--- tlslite/handshakesettings.py | 13 +-- tlslite/recordlayer.py | 115 +++++++++++++++--- tlslite/tlsconnection.py | 28 +++-- tlslite/utils/aes.py | 3 +- tlslite/utils/aesgcm.py | 194 +++++++++++++++++++++++++++++++ tlslite/utils/cipherfactory.py | 23 +++- tlslite/utils/pycrypto_aesgcm.py | 16 +++ tlslite/utils/python_aesgcm.py | 10 ++ tlslite/utils/rc4.py | 3 +- tlslite/utils/tripledes.py | 1 + 12 files changed, 408 insertions(+), 59 deletions(-) create mode 100644 tlslite/utils/aesgcm.py create mode 100644 tlslite/utils/pycrypto_aesgcm.py create mode 100644 tlslite/utils/python_aesgcm.py diff --git a/tests/tlstest.py b/tests/tlstest.py index 281e087b..f1d4be4d 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -355,9 +355,11 @@ def connect(): print("Test 23 - throughput test") for implementation in implementations: - for cipher in ["aes128", "aes256", "3des", "rc4"]: + for cipher in ["aes128gcm", "aes128", "aes256", "3des", "rc4"]: if cipher == "3des" and implementation not in ("openssl", "pycrypto"): continue + if cipher == "aes128gcm" and implementation not in ("pycrypto", "python"): + continue print("Test 23:", end=' ') synchro.recv(1) @@ -864,9 +866,11 @@ def server_bind(self): print("Test 23 - throughput test") for implementation in implementations: - for cipher in ["aes128", "aes256", "3des", "rc4"]: + for cipher in ["aes128gcm", "aes128", "aes256", "3des", "rc4"]: if cipher == "3des" and implementation not in ("openssl", "pycrypto"): continue + if cipher == "aes128gcm" and implementation not in ("pycrypto", "python"): + continue print("Test 23:", end=' ') synchro.send(b'R') diff --git a/tlslite/constants.py b/tlslite/constants.py index d2c78e3f..1a414190 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -236,6 +236,12 @@ class CipherSuite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B ietfNames[0x006B] = 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA256' + # RFC 5288 - AES-GCM ciphers for TLSv1.2 + TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C + ietfNames[0x009C] = 'TLS_RSA_WITH_AES_128_GCM_SHA256' + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E + ietfNames[0x009E] = 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256' + # # Define cipher suite families below # @@ -267,7 +273,12 @@ class CipherSuite: aes256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) aes256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) - # RC4-128 stream cipher + # AES-128 GCM ciphers + aes128GcmSuites = [] + aes128GcmSuites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) + aes128GcmSuites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) + + # RC4 128 stream cipher rc4Suites = [] rc4Suites.append(TLS_RSA_WITH_RC4_128_SHA) rc4Suites.append(TLS_RSA_WITH_RC4_128_MD5) @@ -296,7 +307,10 @@ class CipherSuite: sha256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) sha256Suites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) sha256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) + sha256Suites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) + sha256Suites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) + aeadSuites = aes128GcmSuites # MD-5 HMAC, protocol default PRF md5Suites = [] @@ -310,6 +324,7 @@ class CipherSuite: # TLS1.2 specific ciphersuites tls12Suites = [] tls12Suites.extend(sha256Suites) + tls12Suites.extend(aeadSuites) @staticmethod def filterForVersion(suites, minVersion, maxVersion): @@ -322,19 +337,25 @@ def filterForVersion(suites, minVersion, maxVersion): return [s for s in suites if s in includeSuites] @staticmethod - def _filterSuites(suites, settings): + def _filterSuites(suites, settings, version=None): + if version is None: + version = settings.maxVersion macNames = settings.macNames cipherNames = settings.cipherNames keyExchangeNames = settings.keyExchangeNames macSuites = [] if "sha" in macNames: macSuites += CipherSuite.shaSuites - if "sha256" in macNames: + if "sha256" in macNames and version >= (3,3): macSuites += CipherSuite.sha256Suites if "md5" in macNames: macSuites += CipherSuite.md5Suites + if "aead" in macNames and version >= (3,3): + macSuites += CipherSuite.aeadSuites cipherSuites = [] + if "aes128gcm" in cipherNames and version >= (3,3): + cipherSuites += CipherSuite.aes128GcmSuites if "aes128" in cipherNames: cipherSuites += CipherSuite.aes128Suites if "aes256" in cipherNames: @@ -366,8 +387,8 @@ def _filterSuites(suites, settings): srpSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) @staticmethod - def getSrpSuites(settings): - return CipherSuite._filterSuites(CipherSuite.srpSuites, settings) + def getSrpSuites(settings, version=None): + return CipherSuite._filterSuites(CipherSuite.srpSuites, settings, version) # SRP key exchange, RSA authentication srpCertSuites = [] @@ -376,17 +397,18 @@ def getSrpSuites(settings): srpCertSuites.append(TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) @staticmethod - def getSrpCertSuites(settings): - return CipherSuite._filterSuites(CipherSuite.srpCertSuites, settings) + def getSrpCertSuites(settings, version=None): + return CipherSuite._filterSuites(CipherSuite.srpCertSuites, settings, version) srpAllSuites = srpSuites + srpCertSuites @staticmethod - def getSrpAllSuites(settings): - return CipherSuite._filterSuites(CipherSuite.srpAllSuites, settings) + def getSrpAllSuites(settings, version=None): + return CipherSuite._filterSuites(CipherSuite.srpAllSuites, settings, version) # RSA key exchange, RSA authentication certSuites = [] + certSuites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) certSuites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) certSuites.append(TLS_RSA_WITH_AES_128_CBC_SHA256) certSuites.append(TLS_RSA_WITH_AES_256_CBC_SHA) @@ -396,11 +418,12 @@ def getSrpAllSuites(settings): certSuites.append(TLS_RSA_WITH_RC4_128_MD5) @staticmethod - def getCertSuites(settings): - return CipherSuite._filterSuites(CipherSuite.certSuites, settings) + def getCertSuites(settings, version=None): + return CipherSuite._filterSuites(CipherSuite.certSuites, settings, version) # FFDHE key exchange, RSA authentication dheCertSuites = [] + dheCertSuites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA) @@ -408,8 +431,8 @@ def getCertSuites(settings): dheCertSuites.append(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) @staticmethod - def getDheCertSuites(settings): - return CipherSuite._filterSuites(CipherSuite.dheCertSuites, settings) + def getDheCertSuites(settings, version=None): + return CipherSuite._filterSuites(CipherSuite.dheCertSuites, settings, version) # RSA authentication certAllSuites = srpCertSuites + certSuites + dheCertSuites @@ -420,8 +443,8 @@ def getDheCertSuites(settings): anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) @staticmethod - def getAnonSuites(settings): - return CipherSuite._filterSuites(CipherSuite.anonSuites, settings) + def getAnonSuites(settings, version=None): + return CipherSuite._filterSuites(CipherSuite.anonSuites, settings, version) dhAllSuites = dheCertSuites + anonSuites diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 4a73c4f3..6ba2ffb4 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -11,11 +11,9 @@ from .utils import cryptomath 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", "sha256"] # Don't allow "md5" by default. -ALL_MAC_NAMES = ["sha", "sha256", "md5"] +CIPHER_NAMES = ["aes128gcm", "rc4", "aes256", "aes128", "3des"] +MAC_NAMES = ["sha", "sha256", "aead"] # Don't allow "md5" by default. +ALL_MAC_NAMES = MAC_NAMES + ["md5"] KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "srp_sha", "srp_sha_rsa", "dh_anon"] CIPHER_IMPLEMENTATIONS = ["openssl", "pycrypto", "python"] CERTIFICATE_TYPES = ["x509"] @@ -41,7 +39,7 @@ class HandshakeSettings(object): The default is 8193. @type cipherNames: list - @ivar cipherNames: The allowed ciphers, in order of preference. + @ivar cipherNames: The allowed ciphers. The allowed values in this list are 'aes256', 'aes128', '3des', and 'rc4'. If these settings are used with a client handshake, they @@ -67,8 +65,7 @@ class HandshakeSettings(object): @type certificateTypes: list - @ivar certificateTypes: The allowed certificate types, in order of - preference. + @ivar certificateTypes: The allowed certificate types. The only allowed certificate type is 'x509'. This list is only used with a client handshake. The client will advertise to the server which certificate diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 96085c96..df6c1c42 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -9,7 +9,8 @@ import hashlib from .constants import ContentType, CipherSuite from .messages import RecordHeader3, RecordHeader2, Message -from .utils.cipherfactory import createAES, createRC4, createTripleDES +from .utils.cipherfactory import createAESGCM, createAES, createRC4, \ + createTripleDES from .utils.codec import Parser, Writer from .utils.compat import compatHMAC from .utils.cryptomath import getRandomBytes @@ -362,6 +363,28 @@ def _encryptThenMAC(self, buf, contentType): return buf + def _encryptThenSeal(self, buf, contentType): + """Encrypt with AEAD cipher""" + #Assemble the authenticated data. + seqNumBytes = self._writeState.getSeqNumBytes() + authData = seqNumBytes + bytearray([contentType, + self.version[0], + self.version[1], + len(buf)//256, + len(buf)%256]) + + #The nonce is always the fixed nonce and the sequence number. + nonce = self._writeState.fixedNonce + seqNumBytes + assert len(nonce) == self._writeState.encContext.nonceLength + + buf = self._writeState.encContext.seal(nonce, buf, authData) + + #The only AEAD supported, AES-GCM, has an explicit variable + #nonce. + buf = seqNumBytes + buf + + return buf + def sendMessage(self, msg): """ Encrypt, MAC and send message through socket. @@ -372,7 +395,11 @@ def sendMessage(self, msg): data = msg.write() contentType = msg.contentType - if self.encryptThenMAC: + if self._writeState and \ + self._writeState.encContext and \ + self._writeState.encContext.isAEAD: + data = self._encryptThenSeal(data, contentType) + elif self.encryptThenMAC: data = self._encryptThenMAC(data, contentType) else: data = self._macThenEncrypt(data, contentType) @@ -506,6 +533,34 @@ def _macThenDecrypt(self, recordType, buf): return buf + def _decryptAndUnseal(self, recordType, buf): + """Decrypt AEAD encrypted data""" + #The only AEAD supported, AES-GCM, has an explicit variable + #nonce. + explicitNonceLength = 8 + if explicitNonceLength > len(buf): + #Publicly invalid. + raise TLSBadRecordMAC("Truncated nonce") + nonce = self._readState.fixedNonce + buf[:explicitNonceLength] + buf = buf[8:] + + if self._readState.encContext.tagLength > len(buf): + #Publicly invalid. + raise TLSBadRecordMAC("Truncated tag") + + #Assemble the authenticated data. + seqnumBytes = self._readState.getSeqNumBytes() + plaintextLen = len(buf) - self._readState.encContext.tagLength + authData = seqnumBytes + bytearray([recordType, self.version[0], + self.version[1], + plaintextLen//256, + plaintextLen%256]) + + buf = self._readState.encContext.open(nonce, buf, authData) + if buf is None: + raise TLSBadRecordMAC("Invalid tag, decryption failure") + return buf + def recvMessage(self): """ Read, decrypt and check integrity of message @@ -525,7 +580,11 @@ def recvMessage(self): (header, data) = result - if self.encryptThenMAC: + if self._readState and \ + self._readState.encContext and \ + self._readState.encContext.isAEAD: + data = self._decryptAndUnseal(header.type, data) + elif self.encryptThenMAC: data = self._macThenDecrypt(header.type, data) else: data = self._decryptThenMAC(header.type, data) @@ -559,7 +618,11 @@ def changeReadState(self): def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, serverRandom, implementations): """Create pending states for encryption and decryption.""" - if cipherSuite in CipherSuite.aes128Suites: + if cipherSuite in CipherSuite.aes128GcmSuites: + keyLength = 16 + ivLength = 4 + createCipherFunc = createAESGCM + elif cipherSuite in CipherSuite.aes128Suites: keyLength = 16 ivLength = 16 createCipherFunc = createAES @@ -578,7 +641,10 @@ def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, else: raise AssertionError() - if cipherSuite in CipherSuite.shaSuites: + if cipherSuite in CipherSuite.aeadSuites: + macLength = 0 + digestmod = None + elif cipherSuite in CipherSuite.shaSuites: macLength = 20 digestmod = hashlib.sha1 elif cipherSuite in CipherSuite.sha256Suites: @@ -587,8 +653,12 @@ def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, elif cipherSuite in CipherSuite.md5Suites: macLength = 16 digestmod = hashlib.md5 + else: + raise AssertionError() - if self.version == (3, 0): + if not digestmod: + createMACFunc = None + elif self.version == (3, 0): createMACFunc = createMAC_SSL elif self.version in ((3, 1), (3, 2), (3, 3)): createMACFunc = createHMAC @@ -623,16 +693,29 @@ def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, serverKeyBlock = parser.getFixBytes(keyLength) clientIVBlock = parser.getFixBytes(ivLength) serverIVBlock = parser.getFixBytes(ivLength) - clientPendingState.macContext = createMACFunc( - compatHMAC(clientMACBlock), digestmod=digestmod) - serverPendingState.macContext = createMACFunc( - compatHMAC(serverMACBlock), digestmod=digestmod) - clientPendingState.encContext = createCipherFunc(clientKeyBlock, - clientIVBlock, - implementations) - serverPendingState.encContext = createCipherFunc(serverKeyBlock, - serverIVBlock, - implementations) + + if digestmod: + # Legacy cipher + clientPendingState.macContext = createMACFunc( + compatHMAC(clientMACBlock), digestmod=digestmod) + serverPendingState.macContext = createMACFunc( + compatHMAC(serverMACBlock), digestmod=digestmod) + clientPendingState.encContext = createCipherFunc(clientKeyBlock, + clientIVBlock, + implementations) + serverPendingState.encContext = createCipherFunc(serverKeyBlock, + serverIVBlock, + implementations) + else: + # AEAD + clientPendingState.macContext = None + serverPendingState.macContext = None + clientPendingState.encContext = createCipherFunc(clientKeyBlock, + implementations) + serverPendingState.encContext = createCipherFunc(serverKeyBlock, + implementations) + clientPendingState.fixedNonce = clientIVBlock + serverPendingState.fixedNonce = serverIVBlock #Assign new connection states to pending states if self.client: diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 3e2e5b4c..f9423a39 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1499,21 +1499,6 @@ def _handshakeServerAsyncHelper(self, verifierDB, def _serverGetClientHello(self, settings, certChain, verifierDB, sessionCache, anon): - #Initialize acceptable cipher suites - cipherSuites = [] - if verifierDB: - if certChain: - cipherSuites += \ - CipherSuite.getSrpCertSuites(settings) - cipherSuites += CipherSuite.getSrpSuites(settings) - elif certChain: - cipherSuites += CipherSuite.getDheCertSuites(settings) - cipherSuites += CipherSuite.getCertSuites(settings) - elif anon: - cipherSuites += CipherSuite.getAnonSuites(settings) - else: - assert(False) - #Tentatively set version to most-desirable version, so if an error #occurs parsing the ClientHello, this is what we'll use for the #error alert @@ -1551,6 +1536,19 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, #Now that the version is known, limit to only the ciphers available to #that version. + cipherSuites = [] + if verifierDB: + if certChain: + cipherSuites += \ + CipherSuite.getSrpCertSuites(settings, self.version) + cipherSuites += CipherSuite.getSrpSuites(settings, self.version) + elif certChain: + cipherSuites += CipherSuite.getDheCertSuites(settings, self.version) + cipherSuites += CipherSuite.getCertSuites(settings, self.version) + elif anon: + cipherSuites += CipherSuite.getAnonSuites(settings, self.version) + else: + assert(False) cipherSuites = CipherSuite.filterForVersion(cipherSuites, minVersion=self.version, maxVersion=self.version) diff --git a/tlslite/utils/aes.py b/tlslite/utils/aes.py index 95afaa34..5a038fb9 100644 --- a/tlslite/utils/aes.py +++ b/tlslite/utils/aes.py @@ -12,6 +12,7 @@ def __init__(self, key, mode, IV, implementation): if len(IV) != 16: raise AssertionError() self.isBlockCipher = True + self.isAEAD = False self.block_size = 16 self.implementation = implementation if len(key)==16: @@ -31,4 +32,4 @@ def encrypt(self, plaintext): #CBC-Mode decryption, returns plaintext #WARNING: *MAY* modify the input as well def decrypt(self, ciphertext): - assert(len(ciphertext) % 16 == 0) \ No newline at end of file + assert(len(ciphertext) % 16 == 0) diff --git a/tlslite/utils/aesgcm.py b/tlslite/utils/aesgcm.py new file mode 100644 index 00000000..80826a0e --- /dev/null +++ b/tlslite/utils/aesgcm.py @@ -0,0 +1,194 @@ +# Author: Google +# See the LICENSE file for legal information regarding use of this file. + +# GCM derived from Go's implementation in crypto/cipher. +# +# https://golang.org/src/crypto/cipher/gcm.go + +# GCM works over elements of the field GF(2^128), each of which is a 128-bit +# polynomial. Throughout this implementation, polynomials are represented as +# Python integers with the low-order terms at the most significant bits. So a +# 128-bit polynomial is an integer from 0 to 2^128-1 with the most significant +# bit representing the x^0 term and the least significant bit representing the +# x^127 term. This bit reversal also applies to polynomials used as indices in a +# look-up table. + +from __future__ import division +from .cryptomath import bytesToNumber, numberToByteArray + +class AESGCM(object): + """ + AES-GCM implementation. Note: this implementation does not attempt + to be side-channel resistant. It's also rather slow. + """ + + def __init__(self, key, implementation, rawAesEncrypt): + self.isBlockCipher = False + self.isAEAD = True + self.nonceLength = 12 + self.tagLength = 16 + self.implementation = implementation + if len(key) == 16: + self.name = "aes128gcm" + elif len(key) == 32: + self.name = "aes256gcm" + else: + raise AssertionError() + + self._rawAesEncrypt = rawAesEncrypt + + # The GCM key is AES(0). + h = bytesToNumber(self._rawAesEncrypt(bytearray(16))) + + # Pre-compute all 4-bit multiples of h. Note that bits are reversed + # because our polynomial representation places low-order terms at the + # most significant bit. Thus x^0 * h = h is at index 0b1000 = 8 and + # x^1 * h is at index 0b0100 = 4. + self._productTable = [0] * 16 + self._productTable[_reverseBits(1)] = h + for i in range(2, 16, 2): + self._productTable[_reverseBits(i)] = \ + _gcmShift(self._productTable[_reverseBits(i//2)]) + self._productTable[_reverseBits(i+1)] = \ + _gcmAdd(self._productTable[_reverseBits(i)], h) + + def _rawAesCtrEncrypt(self, counter, inp): + """ + Encrypts (or decrypts) plaintext with AES-CTR. counter is modified. + """ + out = bytearray(len(inp)) + for i in range(0, len(out), 16): + mask = self._rawAesEncrypt(counter) + for j in range(i, min(len(out), i + 16)): + out[j] = inp[j] ^ mask[j-i] + _inc32(counter) + return out + + def _auth(self, ciphertext, ad, tagMask): + y = 0 + y = self._update(y, ad) + y = self._update(y, ciphertext) + y ^= (len(ad) << (3 + 64)) | (len(ciphertext) << 3) + y = self._mul(y) + y ^= bytesToNumber(tagMask) + return numberToByteArray(y, 16) + + def _update(self, y, data): + for i in range(0, len(data) // 16): + y ^= bytesToNumber(data[16*i:16*i+16]) + y = self._mul(y) + extra = len(data) % 16 + if extra != 0: + block = bytearray(16) + block[:extra] = data[-extra:] + y ^= bytesToNumber(block) + y = self._mul(y) + return y + + def _mul(self, y): + """ Returns y*H, where H is the GCM key. """ + ret = 0 + # Multiply H by y 4 bits at a time, starting with the highest power + # terms. + for i in range(0, 128, 4): + # Multiply by x^4. The reduction for the top four terms is + # precomputed. + retHigh = ret & 0xf + ret >>= 4 + ret ^= (_gcmReductionTable[retHigh] << (128-16)) + + # Add in y' * H where y' are the next four terms of y, shifted down + # to the x^0..x^4. This is one of the pre-computed multiples of + # H. The multiplication by x^4 shifts them back into place. + ret ^= self._productTable[y & 0xf] + y >>= 4 + assert y == 0 + return ret + + def seal(self, nonce, plaintext, data): + """ + Encrypts and authenticates plaintext using nonce and data. Returns the + ciphertext, consisting of the encrypted plaintext and tag concatenated. + """ + + if len(nonce) != 12: + raise ValueError("Bad nonce length") + + # The initial counter value is the nonce, followed by a 32-bit counter + # that starts at 1. It's used to compute the tag mask. + counter = bytearray(16) + counter[:12] = nonce + counter[-1] = 1 + tagMask = self._rawAesEncrypt(counter) + + # The counter starts at 2 for the actual encryption. + counter[-1] = 2 + ciphertext = self._rawAesCtrEncrypt(counter, plaintext) + + tag = self._auth(ciphertext, data, tagMask) + + return ciphertext + tag + + def open(self, nonce, ciphertext, data): + """ + Decrypts and authenticates ciphertext using nonce and data. If the + tag is valid, the plaintext is returned. If the tag is invalid, + returns None. + """ + + if len(nonce) != 12: + raise ValueError("Bad nonce length") + if len(ciphertext) < 16: + return None + + tag = ciphertext[-16:] + ciphertext = ciphertext[:-16] + + # The initial counter value is the nonce, followed by a 32-bit counter + # that starts at 1. It's used to compute the tag mask. + counter = bytearray(16) + counter[:12] = nonce + counter[-1] = 1 + tagMask = self._rawAesEncrypt(counter) + + if tag != self._auth(ciphertext, data, tagMask): + return None + + # The counter starts at 2 for the actual decryption. + counter[-1] = 2 + return self._rawAesCtrEncrypt(counter, ciphertext) + +def _reverseBits(i): + assert i < 16 + i = ((i << 2) & 0xc) | ((i >> 2) & 0x3) + i = ((i << 1) & 0xa) | ((i >> 1) & 0x5) + return i + +def _gcmAdd(x, y): + return x ^ y + +def _gcmShift(x): + # Multiplying by x is a right shift, due to bit order. + highTermSet = x & 1 + x >>= 1 + if highTermSet: + # The x^127 term was shifted up to x^128, so subtract a 1+x+x^2+x^7 + # term. This is 0b11100001 or 0xe1 when represented as an 8-bit + # polynomial. + x ^= 0xe1 << (128-8) + return x + +def _inc32(counter): + for i in range(len(counter)-1, len(counter)-5, -1): + counter[i] = (counter[i] + 1) % 256 + if counter[i] != 0: + break + return counter + +# _gcmReductionTable[i] is i * (1+x+x^2+x^7) for all 4-bit polynomials i. The +# result is stored as a 16-bit polynomial. This is used in the reduction step to +# multiply elements of GF(2^128) by x^4. +_gcmReductionTable = [ + 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0, + 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0, +] diff --git a/tlslite/utils/cipherfactory.py b/tlslite/utils/cipherfactory.py index 20e20f11..d5256441 100644 --- a/tlslite/utils/cipherfactory.py +++ b/tlslite/utils/cipherfactory.py @@ -6,6 +6,7 @@ import os from tlslite.utils import python_aes +from tlslite.utils import python_aesgcm from tlslite.utils import python_rc4 from tlslite.utils import cryptomath @@ -20,6 +21,7 @@ if cryptomath.pycryptoLoaded: from tlslite.utils import pycrypto_aes + from tlslite.utils import pycrypto_aesgcm from tlslite.utils import pycrypto_rc4 from tlslite.utils import pycrypto_tripledes tripleDESPresent = True @@ -52,6 +54,25 @@ def createAES(key, IV, implList=None): return python_aes.new(key, 2, IV) raise NotImplementedError() +def createAESGCM(key, implList=None): + """Create a new AESGCM object. + + @type key: bytearray + @param key: A 16 or 32 byte byte array. + + @rtype: L{tlslite.utils.AESGCM} + @return: An AESGCM object. + """ + if implList == None: + implList = ["pycrypto", "python"] + + for impl in implList: + if impl == "pycrypto" and cryptomath.pycryptoLoaded: + return pycrypto_aesgcm.new(key) + if impl == "python": + return python_aesgcm.new(key) + raise NotImplementedError() + def createRC4(key, IV, implList=None): """Create a new RC4 object. @@ -99,4 +120,4 @@ def createTripleDES(key, IV, implList=None): return openssl_tripledes.new(key, 2, IV) elif impl == "pycrypto" and cryptomath.pycryptoLoaded: return pycrypto_tripledes.new(key, 2, IV) - raise NotImplementedError() \ No newline at end of file + raise NotImplementedError() diff --git a/tlslite/utils/pycrypto_aesgcm.py b/tlslite/utils/pycrypto_aesgcm.py new file mode 100644 index 00000000..ee187eea --- /dev/null +++ b/tlslite/utils/pycrypto_aesgcm.py @@ -0,0 +1,16 @@ +# Author: Google +# See the LICENSE file for legal information regarding use of this file. + +"""PyCrypto AES-GCM implementation.""" + +from .cryptomath import * +from .aesgcm import AESGCM + +if pycryptoLoaded: + import Crypto.Cipher.AES + + def new(key): + cipher = Crypto.Cipher.AES.new(bytes(key)) + def encrypt(plaintext): + return bytearray(cipher.encrypt(bytes(plaintext))) + return AESGCM(key, "pycrypto", encrypt) diff --git a/tlslite/utils/python_aesgcm.py b/tlslite/utils/python_aesgcm.py new file mode 100644 index 00000000..80a5fd57 --- /dev/null +++ b/tlslite/utils/python_aesgcm.py @@ -0,0 +1,10 @@ +# Author: Google +# See the LICENSE file for legal information regarding use of this file. + +"""Pure-Python AES-GCM implementation.""" + +from .aesgcm import AESGCM +from .rijndael import rijndael + +def new(key): + return AESGCM(key, "python", rijndael(key, 16).encrypt) diff --git a/tlslite/utils/rc4.py b/tlslite/utils/rc4.py index 809026a2..3853f5be 100644 --- a/tlslite/utils/rc4.py +++ b/tlslite/utils/rc4.py @@ -9,6 +9,7 @@ def __init__(self, keyBytes, implementation): if len(keyBytes) < 16 or len(keyBytes) > 256: raise ValueError() self.isBlockCipher = False + self.isAEAD = False self.name = "rc4" self.implementation = implementation @@ -16,4 +17,4 @@ def encrypt(self, plaintext): raise NotImplementedError() def decrypt(self, ciphertext): - raise NotImplementedError() \ No newline at end of file + raise NotImplementedError() diff --git a/tlslite/utils/tripledes.py b/tlslite/utils/tripledes.py index 0b4d0759..ddcdcad3 100644 --- a/tlslite/utils/tripledes.py +++ b/tlslite/utils/tripledes.py @@ -12,6 +12,7 @@ def __init__(self, key, mode, IV, implementation): if len(IV) != 8: raise ValueError() self.isBlockCipher = True + self.isAEAD = False self.block_size = 8 self.implementation = implementation self.name = "3des" From fcb5c419299f7b28123cb90f6e3ef4238c760512 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 20 Jun 2015 17:44:30 +0200 Subject: [PATCH 093/574] test coverage for AESGCM --- unit_tests/test_tlslite_utils_aesgcm.py | 256 ++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 unit_tests/test_tlslite_utils_aesgcm.py diff --git a/unit_tests/test_tlslite_utils_aesgcm.py b/unit_tests/test_tlslite_utils_aesgcm.py new file mode 100644 index 00000000..67900831 --- /dev/null +++ b/unit_tests/test_tlslite_utils_aesgcm.py @@ -0,0 +1,256 @@ +# Copyright (c) 2014, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.utils.rijndael import rijndael +from tlslite.utils.aesgcm import AESGCM + +class TestAESGCM(unittest.TestCase): + def test___init__(self): + key = bytearray(16) + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + self.assertIsNotNone(aesGCM) + + def test___init___with_invalid_key(self): + key = bytearray(8) + + with self.assertRaises(AssertionError): + aesGCM = AESGCM(key, "python", rijndael(bytearray(16), 16).encrypt) + + def test_seal(self): + key = bytearray(b'\x01'*16) + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + nonce = bytearray(b'\x02'*12) + + plaintext = bytearray(b'text to encrypt.') + self.assertEqual(len(plaintext), 16) + + encData = aesGCM.seal(nonce, plaintext, bytearray(0)) + + self.assertEqual(bytearray( + b'\'\x81h\x17\xe6Z)\\\xf2\x8emF\xcb\x91\x0eu' + b'z1:\xf6}\xa7\\@\xba\x11\xd8r\xdf#K\xd4'), encData) + + def test_seal_with_invalid_nonce(self): + key = bytearray(b'\x01'*16) + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + nonce = bytearray(b'\x02'*11) + + plaintext = bytearray(b'text to encrypt.') + self.assertEqual(len(plaintext), 16) + + with self.assertRaises(ValueError): + aesGCM.seal(nonce, plaintext, bytearray(0)) + + def test_open(self): + key = bytearray(b'\x01'*16) + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + nonce = bytearray(b'\x02'*12) + + ciphertext = bytearray( + b'\'\x81h\x17\xe6Z)\\\xf2\x8emF\xcb\x91\x0eu' + b'z1:\xf6}\xa7\\@\xba\x11\xd8r\xdf#K\xd4') + + plaintext = aesGCM.open(nonce, ciphertext, bytearray(0)) + + self.assertEqual(plaintext, bytearray(b'text to encrypt.')) + + def test_open_with_incorrect_key(self): + key = bytearray(b'\x01'*15 + b'\x00') + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + nonce = bytearray(b'\x02'*12) + + ciphertext = bytearray( + b'\'\x81h\x17\xe6Z)\\\xf2\x8emF\xcb\x91\x0eu' + b'z1:\xf6}\xa7\\@\xba\x11\xd8r\xdf#K\xd4') + + plaintext = aesGCM.open(nonce, ciphertext, bytearray(0)) + + self.assertIsNone(plaintext) + + def test_open_with_incorrect_nonce(self): + key = bytearray(b'\x01'*16) + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + nonce = bytearray(b'\x02'*11 + b'\x01') + + ciphertext = bytearray( + b'\'\x81h\x17\xe6Z)\\\xf2\x8emF\xcb\x91\x0eu' + b'z1:\xf6}\xa7\\@\xba\x11\xd8r\xdf#K\xd4') + + plaintext = aesGCM.open(nonce, ciphertext, bytearray(0)) + + self.assertIsNone(plaintext) + + def test_open_with_invalid_nonce(self): + key = bytearray(b'\x01'*16) + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + nonce = bytearray(b'\x02'*11) + + ciphertext = bytearray( + b'\'\x81h\x17\xe6Z)\\\xf2\x8emF\xcb\x91\x0eu' + b'z1:\xf6}\xa7\\@\xba\x11\xd8r\xdf#K\xd4') + + with self.assertRaises(ValueError): + aesGCM.open(nonce, ciphertext, bytearray(0)) + + def test_open_with_invalid_ciphertext(self): + key = bytearray(b'\x01'*16) + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + nonce = bytearray(b'\x02'*12) + + ciphertext = bytearray( + b'\xff'*15) + + self.assertIsNone(aesGCM.open(nonce, ciphertext, bytearray(0))) + + def test_seal_with_test_vector_1(self): + key = bytearray(b'\x00'*16) + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + nonce = bytearray(b'\x00'*12) + + plaintext = bytearray(b'') + self.assertEqual(len(plaintext), 0) + + encData = aesGCM.seal(nonce, plaintext, bytearray(0)) + + self.assertEqual(bytearray( + b'\x58\xe2\xfc\xce\xfa\x7e\x30\x61' + + b'\x36\x7f\x1d\x57\xa4\xe7\x45\x5a'), encData) + + def test_seal_with_test_vector_2(self): + key = bytearray(b'\x00'*16) + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + nonce = bytearray(b'\x00'*12) + + plaintext = bytearray(b'\x00'*16) + self.assertEqual(len(plaintext), 16) + + encData = aesGCM.seal(nonce, plaintext, bytearray(0)) + + self.assertEqual(bytearray( + b'\x03\x88\xda\xce\x60\xb6\xa3\x92' + + b'\xf3\x28\xc2\xb9\x71\xb2\xfe\x78' + + b'\xab\x6e\x47\xd4\x2c\xec\x13\xbd' + + b'\xf5\x3a\x67\xb2\x12\x57\xbd\xdf'), encData) + + def test_seal_with_test_vector_3(self): + key = bytearray(b'\xfe\xff\xe9\x92\x86\x65\x73\x1c' + + b'\x6d\x6a\x8f\x94\x67\x30\x83\x08') + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + nonce = bytearray(b'\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88') + + plaintext = bytearray(b'\xd9\x31\x32\x25\xf8\x84\x06\xe5' + + b'\xa5\x59\x09\xc5\xaf\xf5\x26\x9a' + + b'\x86\xa7\xa9\x53\x15\x34\xf7\xda' + + b'\x2e\x4c\x30\x3d\x8a\x31\x8a\x72' + + b'\x1c\x3c\x0c\x95\x95\x68\x09\x53' + + b'\x2f\xcf\x0e\x24\x49\xa6\xb5\x25' + + b'\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57' + + b'\xba\x63\x7b\x39\x1a\xaf\xd2\x55') + + self.assertEqual(len(plaintext), 4*16) + + encData = aesGCM.seal(nonce, plaintext, bytearray(0)) + + self.assertEqual(bytearray( + b'\x42\x83\x1e\xc2\x21\x77\x74\x24' + + b'\x4b\x72\x21\xb7\x84\xd0\xd4\x9c' + + b'\xe3\xaa\x21\x2f\x2c\x02\xa4\xe0' + + b'\x35\xc1\x7e\x23\x29\xac\xa1\x2e' + + b'\x21\xd5\x14\xb2\x54\x66\x93\x1c' + + b'\x7d\x8f\x6a\x5a\xac\x84\xaa\x05' + + b'\x1b\xa3\x0b\x39\x6a\x0a\xac\x97' + + b'\x3d\x58\xe0\x91\x47\x3f\x59\x85' + + b'\x4d\x5c\x2a\xf3\x27\xcd\x64\xa6' + + b'\x2c\xf3\x5a\xbd\x2b\xa6\xfa\xb4' + ), encData) + + def test_seal_with_test_vector_4(self): + key = bytearray(b'\xfe\xff\xe9\x92\x86\x65\x73\x1c' + + b'\x6d\x6a\x8f\x94\x67\x30\x83\x08') + + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + nonce = bytearray(b'\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88') + + plaintext = bytearray(b'\xd9\x31\x32\x25\xf8\x84\x06\xe5' + + b'\xa5\x59\x09\xc5\xaf\xf5\x26\x9a' + + b'\x86\xa7\xa9\x53\x15\x34\xf7\xda' + + b'\x2e\x4c\x30\x3d\x8a\x31\x8a\x72' + + b'\x1c\x3c\x0c\x95\x95\x68\x09\x53' + + b'\x2f\xcf\x0e\x24\x49\xa6\xb5\x25' + + b'\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57' + + b'\xba\x63\x7b\x39') + + data = bytearray(b'\xfe\xed\xfa\xce\xde\xad\xbe\xef' + + b'\xfe\xed\xfa\xce\xde\xad\xbe\xef' + + b'\xab\xad\xda\xd2') + + encData = aesGCM.seal(nonce, plaintext, data) + + self.assertEqual(bytearray( + b'\x42\x83\x1e\xc2\x21\x77\x74\x24' + + b'\x4b\x72\x21\xb7\x84\xd0\xd4\x9c' + + b'\xe3\xaa\x21\x2f\x2c\x02\xa4\xe0' + + b'\x35\xc1\x7e\x23\x29\xac\xa1\x2e' + + b'\x21\xd5\x14\xb2\x54\x66\x93\x1c' + + b'\x7d\x8f\x6a\x5a\xac\x84\xaa\x05' + + b'\x1b\xa3\x0b\x39\x6a\x0a\xac\x97' + + b'\x3d\x58\xe0\x91' + + b'\x5b\xc9\x4f\xbc\x32\x21\xa5\xdb' + + b'\x94\xfa\xe9\x5a\xe7\x12\x1a\x47'), encData) + + def test_seal_with_test_vector_13(self): + key = bytearray(32) + + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + self.assertEqual(aesGCM.name, "aes256gcm") + + nonce = bytearray(12) + data = bytearray(0) + + encData = aesGCM.seal(nonce, data, data) + + self.assertEqual(bytearray( + b'\x53\x0f\x8a\xfb\xc7\x45\x36\xb9' + + b'\xa9\x63\xb4\xf1\xc4\xcb\x73\x8b' + ), encData) + + def test_seal_with_test_vector_14(self): + key = bytearray(32) + + aesGCM = AESGCM(key, "python", rijndael(key, 16).encrypt) + + self.assertEqual(aesGCM.name, "aes256gcm") + + nonce = bytearray(12) + plaintext = bytearray(16) + data = bytearray(0) + + encData = aesGCM.seal(nonce, plaintext, data) + + self.assertEqual(bytearray( + b'\xce\xa7\x40\x3d\x4d\x60\x6b\x6e' + + b'\x07\x4e\xc5\xd3\xba\xf3\x9d\x18' + + b'\xd0\xd1\xc8\xa7\x99\x99\x6b\xf0' + + b'\x26\x5b\x98\xb5\xd4\x8a\xb9\x19' + ), encData) From 76140230bdbc3763e895866c31a3d2b066a14656 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 20 Jun 2015 17:51:27 +0200 Subject: [PATCH 094/574] clean up AESGCM no logic changes, just make the methods static --- tlslite/utils/aesgcm.py | 86 +++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/tlslite/utils/aesgcm.py b/tlslite/utils/aesgcm.py index 80826a0e..54b48faa 100644 --- a/tlslite/utils/aesgcm.py +++ b/tlslite/utils/aesgcm.py @@ -45,12 +45,12 @@ def __init__(self, key, implementation, rawAesEncrypt): # most significant bit. Thus x^0 * h = h is at index 0b1000 = 8 and # x^1 * h is at index 0b0100 = 4. self._productTable = [0] * 16 - self._productTable[_reverseBits(1)] = h + self._productTable[self._reverseBits(1)] = h for i in range(2, 16, 2): - self._productTable[_reverseBits(i)] = \ - _gcmShift(self._productTable[_reverseBits(i//2)]) - self._productTable[_reverseBits(i+1)] = \ - _gcmAdd(self._productTable[_reverseBits(i)], h) + self._productTable[self._reverseBits(i)] = \ + self._gcmShift(self._productTable[self._reverseBits(i//2)]) + self._productTable[self._reverseBits(i+1)] = \ + self._gcmAdd(self._productTable[self._reverseBits(i)], h) def _rawAesCtrEncrypt(self, counter, inp): """ @@ -61,7 +61,7 @@ def _rawAesCtrEncrypt(self, counter, inp): mask = self._rawAesEncrypt(counter) for j in range(i, min(len(out), i + 16)): out[j] = inp[j] ^ mask[j-i] - _inc32(counter) + self._inc32(counter) return out def _auth(self, ciphertext, ad, tagMask): @@ -95,7 +95,7 @@ def _mul(self, y): # precomputed. retHigh = ret & 0xf ret >>= 4 - ret ^= (_gcmReductionTable[retHigh] << (128-16)) + ret ^= (AESGCM._gcmReductionTable[retHigh] << (128-16)) # Add in y' * H where y' are the next four terms of y, shifted down # to the x^0..x^4. This is one of the pre-computed multiples of @@ -158,37 +158,41 @@ def open(self, nonce, ciphertext, data): counter[-1] = 2 return self._rawAesCtrEncrypt(counter, ciphertext) -def _reverseBits(i): - assert i < 16 - i = ((i << 2) & 0xc) | ((i >> 2) & 0x3) - i = ((i << 1) & 0xa) | ((i >> 1) & 0x5) - return i - -def _gcmAdd(x, y): - return x ^ y - -def _gcmShift(x): - # Multiplying by x is a right shift, due to bit order. - highTermSet = x & 1 - x >>= 1 - if highTermSet: - # The x^127 term was shifted up to x^128, so subtract a 1+x+x^2+x^7 - # term. This is 0b11100001 or 0xe1 when represented as an 8-bit - # polynomial. - x ^= 0xe1 << (128-8) - return x - -def _inc32(counter): - for i in range(len(counter)-1, len(counter)-5, -1): - counter[i] = (counter[i] + 1) % 256 - if counter[i] != 0: - break - return counter - -# _gcmReductionTable[i] is i * (1+x+x^2+x^7) for all 4-bit polynomials i. The -# result is stored as a 16-bit polynomial. This is used in the reduction step to -# multiply elements of GF(2^128) by x^4. -_gcmReductionTable = [ - 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0, - 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0, -] + @staticmethod + def _reverseBits(i): + assert i < 16 + i = ((i << 2) & 0xc) | ((i >> 2) & 0x3) + i = ((i << 1) & 0xa) | ((i >> 1) & 0x5) + return i + + @staticmethod + def _gcmAdd(x, y): + return x ^ y + + @staticmethod + def _gcmShift(x): + # Multiplying by x is a right shift, due to bit order. + highTermSet = x & 1 + x >>= 1 + if highTermSet: + # The x^127 term was shifted up to x^128, so subtract a 1+x+x^2+x^7 + # term. This is 0b11100001 or 0xe1 when represented as an 8-bit + # polynomial. + x ^= 0xe1 << (128-8) + return x + + @staticmethod + def _inc32(counter): + for i in range(len(counter)-1, len(counter)-5, -1): + counter[i] = (counter[i] + 1) % 256 + if counter[i] != 0: + break + return counter + + # _gcmReductionTable[i] is i * (1+x+x^2+x^7) for all 4-bit polynomials i. The + # result is stored as a 16-bit polynomial. This is used in the reduction step to + # multiply elements of GF(2^128) by x^4. + _gcmReductionTable = [ + 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0, + 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0, + ] From 66e781f89e25e4a9f1e559523b970e41bed1b0b3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 20 Jun 2015 18:17:38 +0200 Subject: [PATCH 095/574] clean up messages.py provide documentation, don't redefine builtins, use consistent naming scheme --- tlslite/messages.py | 82 ++++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index ce2058a1..b0d41cd7 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1040,8 +1040,8 @@ def parse(self, parser): parser.stopLengthCheck() return self - def write_params(self): - """Serialise message + def writeParams(self): + """Serialise the key exchange parameters @rtype: bytearray """ @@ -1060,22 +1060,32 @@ def write_params(self): return writer.bytes def write(self): - w = Writer() - w.bytes += self.write_params() + """ + Serialise complete message + + @rtype: bytearray + """ + writer = Writer() + writer.bytes += self.writeParams() if self.cipherSuite in CipherSuite.certAllSuites: if self.version >= (3, 3): assert self.hashAlg != 0 and self.signAlg != 0 - w.add(self.hashAlg, 1) - w.add(self.signAlg, 1) - w.addVarSeq(self.signature, 1, 2) - return self.postWrite(w) + writer.add(self.hashAlg, 1) + writer.add(self.signAlg, 1) + writer.addVarSeq(self.signature, 1, 2) + return self.postWrite(writer) def hash(self, clientRandom, serverRandom): - bytes = clientRandom + serverRandom + self.write_params() - if self.version >= (3,3): + """ + Calculate hash of paramters to sign + + @rtype: bytearray + """ + bytesToHash = clientRandom + serverRandom + self.writeParams() + if self.version >= (3, 3): # TODO: Signature algorithm negotiation not supported. - return SHA1(bytes) - return MD5(bytes) + SHA1(bytes) + return SHA1(bytesToHash) + return MD5(bytesToHash) + SHA1(bytesToHash) class ServerHelloDone(HandshakeMsg): def __init__(self): @@ -1214,32 +1224,56 @@ def write(self): return self.postWrite(w) class CertificateVerify(HandshakeMsg): + + """Serializer for TLS handshake protocol Certificate Verify message""" + def __init__(self, version): + """Create message + + @param version: TLS protocol version in use + """ HandshakeMsg.__init__(self, HandshakeType.certificate_verify) self.version = version self.signature_algorithm = None self.signature = bytearray(0) def create(self, signature, signature_algorithm=None): + """ + Provide data for serialisation of message + + @param signature: signature carried in the message + @param signature_algorithm: signature algorithm used to make the + signature (TLSv1.2 only) + """ self.signature_algorithm = signature_algorithm self.signature = signature return self - def parse(self, p): - p.startLengthCheck(3) - if self.version >= (3,3): - self.signature_algorithm = (p.get(1), p.get(1)) - self.signature = p.getVarBytes(2) - p.stopLengthCheck() + def parse(self, parser): + """ + Deserialize message from parser + + @param parser: parser with data to read + """ + parser.startLengthCheck(3) + if self.version >= (3, 3): + self.signature_algorithm = (parser.get(1), parser.get(1)) + self.signature = parser.getVarBytes(2) + parser.stopLengthCheck() return self def write(self): - w = Writer() - if self.version >= (3,3): - w.add(self.signature_algorithm[0], 1) - w.add(self.signature_algorithm[1], 1) - w.addVarSeq(self.signature, 1, 2) - return self.postWrite(w) + """ + Serialize the data to bytearray + + @rtype: bytearray + """ + writer = Writer() + if self.version >= (3, 3): + writer.add(self.signature_algorithm[0], 1) + writer.add(self.signature_algorithm[1], 1) + writer.addVarSeq(self.signature, 1, 2) + return self.postWrite(writer) class ChangeCipherSpec(object): def __init__(self): From 5580256362f6fd4c8b029a3535ec79d5e3e8c9a3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 20 Jun 2015 18:18:46 +0200 Subject: [PATCH 096/574] fix comparisions to None --- tlslite/utils/cipherfactory.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tlslite/utils/cipherfactory.py b/tlslite/utils/cipherfactory.py index d5256441..6de3ceb6 100644 --- a/tlslite/utils/cipherfactory.py +++ b/tlslite/utils/cipherfactory.py @@ -42,7 +42,7 @@ def createAES(key, IV, implList=None): @rtype: L{tlslite.utils.AES} @return: An AES object. """ - if implList == None: + if implList is None: implList = ["openssl", "pycrypto", "python"] for impl in implList: @@ -63,7 +63,7 @@ def createAESGCM(key, implList=None): @rtype: L{tlslite.utils.AESGCM} @return: An AESGCM object. """ - if implList == None: + if implList is None: implList = ["pycrypto", "python"] for impl in implList: @@ -85,7 +85,7 @@ def createRC4(key, IV, implList=None): @rtype: L{tlslite.utils.RC4} @return: An RC4 object. """ - if implList == None: + if implList is None: implList = ["openssl", "pycrypto", "python"] if len(IV) != 0: @@ -112,7 +112,7 @@ def createTripleDES(key, IV, implList=None): @rtype: L{tlslite.utils.TripleDES} @return: A 3DES object. """ - if implList == None: + if implList is None: implList = ["openssl", "pycrypto"] for impl in implList: From b40105ee5e607adefce67320b1f0a0fd558160e0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 20 Jun 2015 18:31:33 +0200 Subject: [PATCH 097/574] update changelog entries --- README.md | 10 +++++++++- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f32deb59..656eeebe 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.5.0-alpha2 2015-06-03 +tlslite-ng version 0.5.0-alpha3 2015-06-20 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -515,6 +515,14 @@ RFC 7366. =========== 0.5.0-alpha - xx/xx/xxxx - Hubert Kario + - implement AES-GCM cipher and AES-128-GCM ciphersuites (David Benjamin - + Chromium) + - implement client side DHE_RSA key exchange and DHE with certificate based + client authentication + - implement server side DHE_RSA key exchange (David Benjamin - Chromium) + - don't use TLSv1.2 ciphers in earlier protocols (David Benjamin - Chromium) + - fix certificate-based client authentication in TLSv1.2 (David Benjamin - + Chromium) - fix SRP_SHA_RSA ciphersuites - properly implement record layer fragmentation (previously worked just for Application Data) - RFC 5246 Section 6.2.1 diff --git a/setup.py b/setup.py index a95ca3d6..745a0009 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from distutils.core import setup setup(name="tlslite-ng", - version="0.5.0-alpha2", + version="0.5.0-alpha3", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 853f8e67..0d05bc94 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.4.8 +@version: 0.5.0-alpha3 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index fa6a18c0..dfa29382 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.4.8" +__version__ = "0.5.0-alpha3" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 1f4047da9625f1dba9b80ce5b80bfd62e8d8ba07 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 20 Jun 2015 18:33:35 +0200 Subject: [PATCH 098/574] remove also python3 cache files --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index f738f3c8..cccf82e2 100644 --- a/Makefile +++ b/Makefile @@ -18,10 +18,14 @@ install: .PHONY : clean clean: + rm -rf tlslite/__pycache__ + rm -rf tlslite/integration/__pycache__ + rm -rf tlslite/utils/__pycache__ rm -rf tlslite/*.pyc rm -rf tlslite/utils/*.pyc rm -rf tlslite/integration/*.pyc rm -rf unit_tests/*.pyc + rm -rf unit_tests/__pycache__ rm -rf dist rm -rf docs rm -rf build From c33e0acd7216f0049bffbbc35ec3b0cd3da7f79c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 11 Jun 2015 00:06:35 +0200 Subject: [PATCH 099/574] ciphersuite name in tls.py --- scripts/tls.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/tls.py b/scripts/tls.py index 58c4edec..8efebf25 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -28,6 +28,7 @@ raise "This must be run as a command, not used as a module!" from tlslite.api import * +from tlslite.constants import CipherSuite from tlslite import __version__ try: @@ -164,6 +165,8 @@ def printGoodConnection(connection, seconds): print(" Version: %s" % connection.getVersionName()) print(" Cipher: %s %s" % (connection.getCipherName(), connection.getCipherImplementation())) + print(" Ciphersuite: {0}".\ + format(CipherSuite.ietfNames[connection.session.cipherSuite])) if connection.session.srpUsername: print(" Client SRP username: %s" % connection.session.srpUsername) if connection.session.clientCertChain: From bb6142caaa0635872cafeacc3d310157538c4cdd Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 21 Jun 2015 11:39:44 +0200 Subject: [PATCH 100/574] update Lucky 13 status --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 656eeebe..37594f65 100644 --- a/README.md +++ b/README.md @@ -505,10 +505,10 @@ tlslite-ng does NOT verify certificates by default. tlslite-ng's pure-python ciphers are probably vulnerable to timing attacks. -tlslite-ng is probably vulnerable to the "Lucky 13" timing attack if AES or 3DES +tlslite-ng **is** vulnerable to the "Lucky 13" timing attack if AES or 3DES are used, or the weak cipher RC4 otherwise. This unhappy situation will remain until tlslite-ng implements authenticated-encryption ciphersuites (like GCM), or -RFC 7366. +RFC 7366 and allows refusing connections which don't use them. 12 History From f716c2f3ce57b3c46f2c7fcc3083ac75e79e0ea7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 01:03:55 +0200 Subject: [PATCH 101/574] actually support reqCert option in tls.py server the option is documented, is parsed from command line parameters but it's not used, fix this error --- scripts/tls.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/tls.py b/scripts/tls.py index 8efebf25..6a7e5482 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -172,6 +172,8 @@ def printGoodConnection(connection, seconds): if connection.session.clientCertChain: print(" Client X.509 SHA1 fingerprint: %s" % connection.session.clientCertChain.getFingerprint()) + else: + print(" No client certificate provided by peer") if connection.session.serverCertChain: print(" Server X.509 SHA1 fingerprint: %s" % connection.session.serverCertChain.getFingerprint()) @@ -266,6 +268,8 @@ def serverCmd(argv): print("Using verifier DB...") if tacks: print("Using Tacks...") + if reqCert: + print("Asking for client certificates...") ############# sessionCache = SessionCache() @@ -291,7 +295,8 @@ def handshake(self, connection): activationFlags=activationFlags, sessionCache=sessionCache, settings=settings, - nextProtos=[b"http/1.1"]) + nextProtos=[b"http/1.1"], + reqCert=reqCert) # As an example (does not work here): #nextProtos=[b"spdy/3", b"spdy/2", b"http/1.1"]) stop = time.clock() From f41f5c284c6e525d73e3c6809e731be32e04b88b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 11 Jun 2015 13:44:30 +0200 Subject: [PATCH 102/574] make ServerHello not require providing optional parameters --- tlslite/messages.py | 6 ++++-- unit_tests/test_tlslite_messages.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index b0d41cd7..8666ec62 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -769,8 +769,9 @@ def next_protos_advertised(self, val): self.next_protos = val def create(self, version, random, session_id, cipher_suite, - certificate_type, tackExt, next_protos_advertised, + certificate_type=None, tackExt=None, next_protos_advertised=None, extensions=None): + """Initialize the object for deserialisation""" self.extensions = extensions self.server_version = version self.random = random @@ -778,7 +779,8 @@ def create(self, version, random, session_id, cipher_suite, self.cipher_suite = cipher_suite self.certificate_type = certificate_type self.compression_method = 0 - self.tackExt = tackExt + if tackExt is not None: + self.tackExt = tackExt self.next_protos_advertised = next_protos_advertised return self diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 9226f19a..8af9c083 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -634,6 +634,24 @@ def test_create(self): self.assertEqual(None, server_hello.tackExt) self.assertEqual(None, server_hello.next_protos_advertised) + def test_create_with_minimal_options(self): + server_hello = ServerHello().create( + (3, 3), # server version + bytearray(b'\x02'*31+b'\x01'), # random + bytearray(4), # session ID + CipherSuite.TLS_RSA_WITH_RC4_128_MD5) # ciphersuite + + self.assertEqual(bytearray( + b'\x02' + # type of message + b'\x00\x00\x2a' + # length + b'\x03\x03' + # server version + b'\x02'*31+b'\x01' + # random + b'\x04' + # Session ID length + b'\x00'*4 + # session id + b'\x00\x04' + # selected ciphersuite + b'\x00' # selected compression method + ), server_hello.write()) + def test_parse(self): p = Parser(bytearray( # don't include type of message as it is handled by the hello From 3408d6058e616ec823f1e7f6678167bf28bd60dd Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 13 Jun 2015 15:12:13 +0200 Subject: [PATCH 103/574] remove whitespace in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index cccf82e2..304f143a 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ clean: rm -rf tlslite/utils/__pycache__ rm -rf tlslite/*.pyc rm -rf tlslite/utils/*.pyc - rm -rf tlslite/integration/*.pyc + rm -rf tlslite/integration/*.pyc rm -rf unit_tests/*.pyc rm -rf unit_tests/__pycache__ rm -rf dist From 2e5413d97de23cd9551c296e9a91ef55cb364c9f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 13 Jun 2015 15:25:35 +0200 Subject: [PATCH 104/574] rename sendMessage to sendRecord Because the RecordLayer method sendMessage actually sends a record as-is without any fragmentation or length checks (with the exception of the implicit length limit enforced by two-byte length field), a better name for it is "sendRecord" rename allowed since the API was not part of a stable release yet --- tlslite/recordlayer.py | 7 +- tlslite/tlsrecordlayer.py | 2 +- unit_tests/test_tlslite_recordlayer.py | 88 ++++++++++++------------ unit_tests/test_tlslite_tlsconnection.py | 4 +- 4 files changed, 52 insertions(+), 49 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index df6c1c42..59909867 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -385,9 +385,12 @@ def _encryptThenSeal(self, buf, contentType): return buf - def sendMessage(self, msg): + def sendRecord(self, msg): """ - Encrypt, MAC and send message through socket. + Encrypt, MAC and send arbitrary message as-is through socket. + + Note that if the message was not fragmented to below 2**14 bytes + it will be rejected by the other connection side. @param msg: TLS message to send @type msg: ApplicationData, HandshakeMessage, etc. diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index f9410eb2..cadb0630 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -582,7 +582,7 @@ def _sendMsgThroughSocket(self, msg): """Send message, handle errors""" try: - for result in self._recordLayer.sendMessage(msg): + for result in self._recordLayer.sendRecord(msg): if result in (0, 1): yield result except socket.error: diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 0750ca52..21fa2751 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -331,13 +331,13 @@ def test___init__(self): self.assertIsNone(recordLayer.getCipherImplementation()) self.assertFalse(recordLayer.isCBCMode()) - def test_sendMessage(self): + def test_sendRecord(self): sock = MockSocket(bytearray(0)) recordLayer = RecordLayer(sock) hello = Message(ContentType.handshake, bytearray(10)) - for result in recordLayer.sendMessage(hello): + for result in recordLayer.sendRecord(hello): if result in (0, 1): self.assertTrue(False, "Blocking write") else: @@ -388,7 +388,7 @@ def test_getCipherImplementation(self): else: self.assertEqual('python', recordLayer.getCipherImplementation()) - def test_sendMessage_with_encrypting_set_up_tls1_2(self): + def test_sendRecord_with_encrypting_set_up_tls1_2(self): patcher = mock.patch.object(os, 'urandom', lambda x: bytearray(x)) @@ -412,7 +412,7 @@ def test_sendMessage_with_encrypting_set_up_tls1_2(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data): + for result in recordLayer.sendRecord(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -429,7 +429,7 @@ def test_sendMessage_with_encrypting_set_up_tls1_2(self): b'\x9f\x73\xec\xa9\xa6\x82\x55\x8e\x3a\x8c\x94\x96\xda\x06\x09\x8d' ), sock.sent[0][5:]) - def test_sendMessage_with_SHA256_tls1_2(self): + def test_sendRecord_with_SHA256_tls1_2(self): patcher = mock.patch.object(os, 'urandom', lambda x: bytearray(x)) @@ -454,7 +454,7 @@ def test_sendMessage_with_SHA256_tls1_2(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data): + for result in recordLayer.sendRecord(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -472,7 +472,7 @@ def test_sendMessage_with_SHA256_tls1_2(self): b'\xe0"c:7\xa9\xd7}X\x00[\x88\xce\xfe|\t' ), sock.sent[0][5:]) - def test_sendMessage_with_encrypting_set_up_tls1_1(self): + def test_sendRecord_with_encrypting_set_up_tls1_1(self): patcher = mock.patch.object(os, 'urandom', lambda x: bytearray(x)) @@ -496,7 +496,7 @@ def test_sendMessage_with_encrypting_set_up_tls1_1(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data): + for result in recordLayer.sendRecord(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -513,7 +513,7 @@ def test_sendMessage_with_encrypting_set_up_tls1_1(self): b'\xa8\x8c!k\xab\x03\x03\x19.\x1dFMt\x08h^' ), sock.sent[0][5:]) - def test_sendMessage_with_encrypting_set_up_tls1_0(self): + def test_sendRecord_with_encrypting_set_up_tls1_0(self): sock = MockSocket(bytearray(0)) recordLayer = RecordLayer(sock) @@ -531,7 +531,7 @@ def test_sendMessage_with_encrypting_set_up_tls1_0(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data): + for result in recordLayer.sendRecord(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -547,7 +547,7 @@ def test_sendMessage_with_encrypting_set_up_tls1_0(self): b'D\xe9\xec\x8d\xdfd\xed\x94\x9f\xe6K\x08(\x08\xf6\xb7' )) - def test_sendMessage_with_stream_cipher_and_tls1_0(self): + def test_sendRecord_with_stream_cipher_and_tls1_0(self): sock = MockSocket(bytearray(0)) recordLayer = RecordLayer(sock) @@ -565,7 +565,7 @@ def test_sendMessage_with_stream_cipher_and_tls1_0(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data): + for result in recordLayer.sendRecord(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -581,7 +581,7 @@ def test_sendMessage_with_stream_cipher_and_tls1_0(self): b'\xa1\xaf]Q%y5\x1e' )) - def test_sendMessage_with_MD5_MAC_and_tls1_0(self): + def test_sendRecord_with_MD5_MAC_and_tls1_0(self): sock = MockSocket(bytearray(0)) recordLayer = RecordLayer(sock) @@ -599,7 +599,7 @@ def test_sendMessage_with_MD5_MAC_and_tls1_0(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data): + for result in recordLayer.sendRecord(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -616,7 +616,7 @@ def test_sendMessage_with_MD5_MAC_and_tls1_0(self): )) - def test_sendMessage_with_AES256_cipher_and_tls1_0(self): + def test_sendRecord_with_AES256_cipher_and_tls1_0(self): sock = MockSocket(bytearray(0)) recordLayer = RecordLayer(sock) @@ -634,7 +634,7 @@ def test_sendMessage_with_AES256_cipher_and_tls1_0(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data): + for result in recordLayer.sendRecord(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -653,7 +653,7 @@ def test_sendMessage_with_AES256_cipher_and_tls1_0(self): # tlslite has no pure python implementation of 3DES @unittest.skipUnless(cryptomath.m2cryptoLoaded or cryptomath.pycryptoLoaded, "requires native 3DES implementation") - def test_sendMessage_with_3DES_cipher_and_tls1_0(self): + def test_sendRecord_with_3DES_cipher_and_tls1_0(self): sock = MockSocket(bytearray(0)) recordLayer = RecordLayer(sock) @@ -671,7 +671,7 @@ def test_sendMessage_with_3DES_cipher_and_tls1_0(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data): + for result in recordLayer.sendRecord(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -687,7 +687,7 @@ def test_sendMessage_with_3DES_cipher_and_tls1_0(self): b'\x8d\x16\x8b\xa3N\xe6\xfa\x14\xa9\xb9\xc7\x08w\xf2V\xe2' )) - def test_sendMessage_with_encrypting_set_up_ssl3(self): + def test_sendRecord_with_encrypting_set_up_ssl3(self): sock = MockSocket(bytearray(0)) recordLayer = RecordLayer(sock) @@ -705,7 +705,7 @@ def test_sendMessage_with_encrypting_set_up_ssl3(self): self.assertIsNotNone(app_data) self.assertTrue(len(app_data.write()) > 3) - for result in recordLayer.sendMessage(app_data): + for result in recordLayer.sendRecord(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -721,7 +721,7 @@ def test_sendMessage_with_encrypting_set_up_ssl3(self): b'\xff\x842\xc7\xa2\x0byd\xab\x1a\xfd\xaf\x05\xd6\xba\x89' )) - def test_sendMessage_with_wrong_SSL_version(self): + def test_sendRecord_with_wrong_SSL_version(self): sock = MockSocket(bytearray(0)) recordLayer = RecordLayer(sock) @@ -734,7 +734,7 @@ def test_sendMessage_with_wrong_SSL_version(self): bytearray(32), # server random None) - def test_sendMessage_with_invalid_ciphersuite(self): + def test_sendRecord_with_invalid_ciphersuite(self): sock = MockSocket(bytearray(0)) recordLayer = RecordLayer(sock) @@ -747,14 +747,14 @@ def test_sendMessage_with_invalid_ciphersuite(self): bytearray(32), # server random None) - def test_sendMessage_with_slow_socket(self): + def test_sendRecord_with_slow_socket(self): mockSock = MockSocket(bytearray(0), maxWrite=1, blockEveryOther=True) sock = RecordLayer(mockSock) msg = Message(ContentType.handshake, bytearray(b'\x32'*2)) gotRetry = False - for result in sock.sendMessage(msg): + for result in sock.sendRecord(msg): if result in (0, 1): gotRetry = True else: break @@ -767,7 +767,7 @@ def test_sendMessage_with_slow_socket(self): bytearray(b'\x32'), bytearray(b'\x32')], mockSock.sent) - def test_sendMessage_with_encryptThenMAC_and_unset_crypto_state(self): + def test_sendRecord_with_encryptThenMAC_and_unset_crypto_state(self): sock = MockSocket(bytearray(0)) recordLayer = RecordLayer(sock) recordLayer.version = (3, 1) @@ -775,7 +775,7 @@ def test_sendMessage_with_encryptThenMAC_and_unset_crypto_state(self): app_data = ApplicationData().create(bytearray(b'test')) - for result in recordLayer.sendMessage(app_data): + for result in recordLayer.sendRecord(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -787,7 +787,7 @@ def test_sendMessage_with_encryptThenMAC_and_unset_crypto_state(self): b'\x00\x04' + # length b'test'), sock.sent[0]) - def test_sendMessage_with_encryptThenMAC_in_TLSv1_0(self): + def test_sendRecord_with_encryptThenMAC_in_TLSv1_0(self): sock = MockSocket(bytearray(0)) recordLayer = RecordLayer(sock) recordLayer.version = (3, 1) @@ -801,7 +801,7 @@ def test_sendMessage_with_encryptThenMAC_in_TLSv1_0(self): app_data = ApplicationData().create(bytearray(b'test')) - for result in recordLayer.sendMessage(app_data): + for result in recordLayer.sendRecord(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -815,7 +815,7 @@ def test_sendMessage_with_encryptThenMAC_in_TLSv1_0(self): b'X\xcd\xdc\'o\xb3I\xdd-\xfc\tneq~\x0f' + b'd\xdb\xbdw'), sock.sent[0]) - def test_sendMessage_with_encryptThenMAC_in_TLSv1_2(self): + def test_sendRecord_with_encryptThenMAC_in_TLSv1_2(self): patcher = mock.patch.object(os, 'urandom', lambda x: bytearray(x)) @@ -835,7 +835,7 @@ def test_sendMessage_with_encryptThenMAC_in_TLSv1_2(self): app_data = ApplicationData().create(bytearray(b'test')) - for result in recordLayer.sendMessage(app_data): + for result in recordLayer.sendRecord(app_data): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -904,7 +904,7 @@ def test_recvMessage_with_encrypted_content_TLS1_1(self): b'\x17' + # application data b'\x03\x02' + # TLSv1.1 b'\x00\x30' + # length - # data from test_sendMessage_with_encrypting_set_up_tls1_1 + # data from test_sendRecord_with_encrypting_set_up_tls1_1 b'b\x8e\xee\xddV\\W=\x810\xd5\x0c\xae \x84\xa8' + b'^\x91\xa4d[\xe4\xde\x90\xee{f\xbb\xcd_\x1ao' + b'\xa8\x8c!k\xab\x03\x03\x19.\x1dFMt\x08h^' @@ -938,7 +938,7 @@ def test_recvMessage_with_encrypted_content_SSLv3(self): b'\x17' + # application data b'\x03\x00' + # SSLv3 b'\x00\x20' + # length - # data from test_sendMessage_with_encrypting_set_up_ssl3 + # data from test_sendRecord_with_encrypting_set_up_ssl3 b'\xc5\x16y\xf9\ra\xd9=\xec\x8b\x93\'\xb7\x05\xe6\xad' + b'\xff\x842\xc7\xa2\x0byd\xab\x1a\xfd\xaf\x05\xd6\xba\x89' )) @@ -971,7 +971,7 @@ def test_recvMessage_with_stream_cipher_and_tls1_0(self): b'\x17' + # application data b'\x03\x01' + # TLSv1.0 b'\x00\x18' + # length (24 bytes) - # data from test_sendMessage_with_stream_cipher_and_tls1_0 + # data from test_sendRecord_with_stream_cipher_and_tls1_0 b'B\xb8H\xc6\xd7\\\x01\xe27\xa9\x86\xf2\xfdm!\x1d' + b'\xa1\xaf]Q%y5\x1e' )) @@ -1060,7 +1060,7 @@ def broken_padding(data): msg = ApplicationData().create(bytearray(b'test')) # create the data - for result in sendingRecordLayer.sendMessage(msg): + for result in sendingRecordLayer.sendRecord(msg): if result in (0, 1): self.assertTrue(False, "Blocking socket") else: @@ -1138,7 +1138,7 @@ def broken_padding(data): msg = ApplicationData().create(bytearray(b'test')) # create the bad data - for result in sendingRecordLayer.sendMessage(msg): + for result in sendingRecordLayer.sendRecord(msg): if result in (0, 1): self.assertTrue(False, "Blocking socket") else: @@ -1210,7 +1210,7 @@ def broken_padding(data): msg = ApplicationData().create(bytearray(b'test')) # create the bad data - for result in sendingRecordLayer.sendMessage(msg): + for result in sendingRecordLayer.sendRecord(msg): if result in (0, 1): self.assertTrue(False, "Blocking socket") else: @@ -1281,7 +1281,7 @@ def broken_padding(data): msg = ApplicationData().create(bytearray(b'test')) # create the bad data - for result in sendingRecordLayer.sendMessage(msg): + for result in sendingRecordLayer.sendRecord(msg): if result in (0, 1): self.assertTrue(False, "Blocking socket") else: @@ -1352,7 +1352,7 @@ def broken_padding(data): msg = ApplicationData().create(bytearray(b'test')) # create the bad data - for result in sendingRecordLayer.sendMessage(msg): + for result in sendingRecordLayer.sendRecord(msg): if result in (0, 1): self.assertTrue(False, "Blocking socket") else: @@ -1407,7 +1407,7 @@ def test_recvMessage_with_encryptThenMAC_and_unset_crypto_state(self): self.assertEqual(parser.bytes, bytearray(b'test')) def test_recvMessage_with_encryptThenMAC_in_TLSv1_0(self): - # data from test_sendMessage_with_encryptThenMAC_in_TLSv1_0 + # data from test_sendRecord_with_encryptThenMAC_in_TLSv1_0 sock = MockSocket(bytearray( b'\x17' + # application data b'\x03\x01' + # TLS version @@ -1438,7 +1438,7 @@ def test_recvMessage_with_encryptThenMAC_in_TLSv1_0(self): def test_recvMessage_with_encryptThenMAC_in_TLSv1_2(self): - # data from test_sendMessage_with_encryptThenMAC_in_TLSv1_2 + # data from test_sendRecord_with_encryptThenMAC_in_TLSv1_2 sock = MockSocket(bytearray( b'\x17' + # application data b'\x03\x03' + # TLS version @@ -1550,7 +1550,7 @@ def broken_encrypt(buf): msg = ApplicationData().create(bytearray(b'test')) # create the bad data - for result in sendingRecordLayer.sendMessage(msg): + for result in sendingRecordLayer.sendRecord(msg): if result in (0, 1): self.assertTrue(False, "Blocking socket") else: @@ -1623,7 +1623,7 @@ def broken_padding(data): msg = ApplicationData().create(bytearray(b'test')) # create the bad data - for result in sendingRecordLayer.sendMessage(msg): + for result in sendingRecordLayer.sendRecord(msg): if result in (0, 1): self.assertTrue(False, "Blocking socket") else: @@ -1676,7 +1676,7 @@ def test_recvMessage_with_encryptThenMAC_and_SSLv3(self): msg = ApplicationData().create(bytearray(b'test')) # create the data - for result in sendingRecordLayer.sendMessage(msg): + for result in sendingRecordLayer.sendRecord(msg): if result in (0, 1): self.assertTrue(False, "Blocking socket") else: @@ -1754,7 +1754,7 @@ def broken_padding(data): msg = ApplicationData().create(bytearray(b'test')) # create the bad data - for result in sendingRecordLayer.sendMessage(msg): + for result in sendingRecordLayer.sendRecord(msg): if result in (0, 1): self.assertTrue(False, "Blocking socket") else: diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index bce83557..b119fc68 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -74,7 +74,7 @@ def test_client_with_server_responing_with_SHA256_on_TLSv1_1(self): tackExt=None, next_protos_advertised=None) - for res in gen_record_layer.sendMessage(server_hello): + for res in gen_record_layer.sendRecord(server_hello): if res in (0, 1): self.assertTrue(False, "Blocking socket") else: @@ -107,7 +107,7 @@ def test_server_with_client_proposing_SHA256_on_TLSv1_1(self): session_id=bytearray(0), cipher_suites=ciphers) - for res in gen_record_layer.sendMessage(client_hello): + for res in gen_record_layer.sendRecord(client_hello): if res in (0, 1): self.assertTrue(False, "Blocking socket") else: From 7b068300c2b1f26470c191079356e30421130f9d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 13 Jun 2015 16:13:48 +0200 Subject: [PATCH 105/574] rename recvMessage to recvRecord Because the RecordLayer method recvMessage actually reads a record and returns it to caller as-is without any defragmentation or message splitting, a better name for it is "recvRecord" rename allowed since the API was not part of a stable release yet --- tlslite/recordlayer.py | 4 +- tlslite/tlsrecordlayer.py | 2 +- unit_tests/test_tlslite_recordlayer.py | 80 +++++++++++++------------- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 59909867..dd305d55 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -564,9 +564,9 @@ def _decryptAndUnseal(self, recordType, buf): raise TLSBadRecordMAC("Invalid tag, decryption failure") return buf - def recvMessage(self): + def recvRecord(self): """ - Read, decrypt and check integrity of message + Read, decrypt and check integrity of a single record @rtype: tuple @return: message header and decrypted message payload diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index cadb0630..fe7111be 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -828,7 +828,7 @@ def _getNextRecordFromSocket(self): try: # otherwise... read the next record - for result in self._recordLayer.recvMessage(): + for result in self._recordLayer.recvRecord(): if result in (0, 1): yield result else: diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 21fa2751..3c4cab3d 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -850,7 +850,7 @@ def test_sendRecord_with_encryptThenMAC_in_TLSv1_2(self): b'\xa4\x9bH}T\xcbT\x9d2\xed\xc5\xe1|\x82T\xf1' + b'\xf6\x19\xfcw'), sock.sent[0]) - def test_recvMessage(self): + def test_recvRecord(self): sock = MockSocket(bytearray( b'\x16' + # handshake b'\x03\x03' + # TLSv1.2 @@ -860,7 +860,7 @@ def test_recvMessage(self): )) recordLayer = RecordLayer(sock) - for result in recordLayer.recvMessage(): + for result in recordLayer.recvRecord(): if result in (0, 1): self.assertTrue(False, "Blocking read") else: @@ -873,7 +873,7 @@ def test_recvMessage(self): self.assertEqual((3, 3), header.version) self.assertEqual(bytearray(b'\x0e' + b'\x00'*3), parser.bytes) - def test_recvMessage_with_slow_socket(self): + def test_recvRecord_with_slow_socket(self): sock = MockSocket(bytearray( b'\x16' + # handshake b'\x03\x03' + # TLSv1.2 @@ -884,7 +884,7 @@ def test_recvMessage_with_slow_socket(self): recordLayer = RecordLayer(sock) wasBlocked = False - for result in recordLayer.recvMessage(): + for result in recordLayer.recvRecord(): if result in (0, 1): wasBlocked = True else: @@ -899,7 +899,7 @@ def test_recvMessage_with_slow_socket(self): self.assertEqual(bytearray(b'\x0e' + b'\x00'*3), parser.bytes) - def test_recvMessage_with_encrypted_content_TLS1_1(self): + def test_recvRecord_with_encrypted_content_TLS1_1(self): sock = MockSocket(bytearray( b'\x17' + # application data b'\x03\x02' + # TLSv1.1 @@ -920,7 +920,7 @@ def test_recvMessage_with_encrypted_content_TLS1_1(self): None) recordLayer.changeReadState() - for result in recordLayer.recvMessage(): + for result in recordLayer.recvRecord(): if result in (0, 1): self.assertTrue(False, "Blocking read") else: @@ -933,7 +933,7 @@ def test_recvMessage_with_encrypted_content_TLS1_1(self): self.assertEqual((3, 2), header.version) self.assertEqual(bytearray(b'test'), parser.bytes) - def test_recvMessage_with_encrypted_content_SSLv3(self): + def test_recvRecord_with_encrypted_content_SSLv3(self): sock = MockSocket(bytearray( b'\x17' + # application data b'\x03\x00' + # SSLv3 @@ -953,7 +953,7 @@ def test_recvMessage_with_encrypted_content_SSLv3(self): None) recordLayer.changeReadState() - for result in recordLayer.recvMessage(): + for result in recordLayer.recvRecord(): if result in (0, 1): self.assertTrue(False, "Blocking read") else: @@ -966,7 +966,7 @@ def test_recvMessage_with_encrypted_content_SSLv3(self): self.assertEqual((3, 0), header.version) self.assertEqual(bytearray(b'test'), parser.bytes) - def test_recvMessage_with_stream_cipher_and_tls1_0(self): + def test_recvRecord_with_stream_cipher_and_tls1_0(self): sock = MockSocket(bytearray( b'\x17' + # application data b'\x03\x01' + # TLSv1.0 @@ -986,7 +986,7 @@ def test_recvMessage_with_stream_cipher_and_tls1_0(self): None) recordLayer.changeReadState() - for result in recordLayer.recvMessage(): + for result in recordLayer.recvRecord(): if result in (0, 1): self.assertTrue(False, "Blocking read") else: @@ -999,7 +999,7 @@ def test_recvMessage_with_stream_cipher_and_tls1_0(self): self.assertEqual((3, 1), header.version) self.assertEqual(bytearray(b'test'), parser.bytes) - def test_recvMessage_with_invalid_length_payload(self): + def test_recvRecord_with_invalid_length_payload(self): sock = MockSocket(bytearray( b'\x17' + # application data b'\x03\x02' + # TLSv1.1 @@ -1019,12 +1019,12 @@ def test_recvMessage_with_invalid_length_payload(self): None) recordLayer.changeReadState() - gen = recordLayer.recvMessage() + gen = recordLayer.recvRecord() with self.assertRaises(TLSDecryptionFailed): next(gen) - def test_recvMessage_with_zero_filled_padding_in_SSLv3(self): + def test_recvRecord_with_zero_filled_padding_in_SSLv3(self): # make sure the IV is predictible (all zero) patcher = mock.patch.object(os, 'urandom', @@ -1088,7 +1088,7 @@ def broken_padding(data): None) recordLayer.changeReadState() - for result in recordLayer.recvMessage(): + for result in recordLayer.recvRecord(): if result in (0, 1): self.assertTrue(False, "Blocking socket") else: break @@ -1100,7 +1100,7 @@ def broken_padding(data): self.assertEqual((3, 0), header.version) self.assertEqual(bytearray(b'test'), parser.bytes) - def test_recvMessage_with_invalid_last_byte_in_padding(self): + def test_recvRecord_with_invalid_last_byte_in_padding(self): # make sure the IV is predictible (all zero) patcher = mock.patch.object(os, 'urandom', @@ -1166,12 +1166,12 @@ def broken_padding(data): None) recordLayer.changeReadState() - gen = recordLayer.recvMessage() + gen = recordLayer.recvRecord() with self.assertRaises(TLSBadRecordMAC): next(gen) - def test_recvMessage_with_invalid_middle_byte_in_padding(self): + def test_recvRecord_with_invalid_middle_byte_in_padding(self): # make sure the IV is predictible (all zero) patcher = mock.patch.object(os, 'urandom', @@ -1238,12 +1238,12 @@ def broken_padding(data): None) recordLayer.changeReadState() - gen = recordLayer.recvMessage() + gen = recordLayer.recvRecord() with self.assertRaises(TLSBadRecordMAC): next(gen) - def test_recvMessage_with_truncated_MAC(self): + def test_recvRecord_with_truncated_MAC(self): # make sure the IV is predictible (all zero) patcher = mock.patch.object(os, 'urandom', @@ -1309,12 +1309,12 @@ def broken_padding(data): None) recordLayer.changeReadState() - gen = recordLayer.recvMessage() + gen = recordLayer.recvRecord() with self.assertRaises(TLSBadRecordMAC): next(gen) - def test_recvMessage_with_invalid_MAC(self): + def test_recvRecord_with_invalid_MAC(self): # make sure the IV is predictible (all zero) patcher = mock.patch.object(os, 'urandom', @@ -1380,12 +1380,12 @@ def broken_padding(data): None) recordLayer.changeReadState() - gen = recordLayer.recvMessage() + gen = recordLayer.recvRecord() with self.assertRaises(TLSBadRecordMAC): next(gen) - def test_recvMessage_with_encryptThenMAC_and_unset_crypto_state(self): + def test_recvRecord_with_encryptThenMAC_and_unset_crypto_state(self): sock = MockSocket(bytearray( b'\x17' + # application data b'\x03\x01' + # TLS version @@ -1397,7 +1397,7 @@ def test_recvMessage_with_encryptThenMAC_and_unset_crypto_state(self): recordLayer.client = False recordLayer.encryptThenMAC = True - for result in recordLayer.recvMessage(): + for result in recordLayer.recvRecord(): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -1406,7 +1406,7 @@ def test_recvMessage_with_encryptThenMAC_and_unset_crypto_state(self): self.assertEqual(parser.bytes, bytearray(b'test')) - def test_recvMessage_with_encryptThenMAC_in_TLSv1_0(self): + def test_recvRecord_with_encryptThenMAC_in_TLSv1_0(self): # data from test_sendRecord_with_encryptThenMAC_in_TLSv1_0 sock = MockSocket(bytearray( b'\x17' + # application data @@ -1427,7 +1427,7 @@ def test_recvMessage_with_encryptThenMAC_in_TLSv1_0(self): None) recordLayer.changeReadState() - for result in recordLayer.recvMessage(): + for result in recordLayer.recvRecord(): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -1436,7 +1436,7 @@ def test_recvMessage_with_encryptThenMAC_in_TLSv1_0(self): self.assertEqual(parser.bytes, bytearray(b'test')) - def test_recvMessage_with_encryptThenMAC_in_TLSv1_2(self): + def test_recvRecord_with_encryptThenMAC_in_TLSv1_2(self): # data from test_sendRecord_with_encryptThenMAC_in_TLSv1_2 sock = MockSocket(bytearray( @@ -1459,7 +1459,7 @@ def test_recvMessage_with_encryptThenMAC_in_TLSv1_2(self): None) recordLayer.changeReadState() - for result in recordLayer.recvMessage(): + for result in recordLayer.recvRecord(): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -1468,7 +1468,7 @@ def test_recvMessage_with_encryptThenMAC_in_TLSv1_2(self): self.assertEqual(parser.bytes, bytearray(b'test')) - def test_recvMessage_with_encryptThenMAC_and_too_short_MAC(self): + def test_recvRecord_with_encryptThenMAC_and_too_short_MAC(self): sock = MockSocket(bytearray( b'\x17' + # application data @@ -1487,12 +1487,12 @@ def test_recvMessage_with_encryptThenMAC_and_too_short_MAC(self): None) recordLayer.changeReadState() - gen = recordLayer.recvMessage() + gen = recordLayer.recvRecord() with self.assertRaises(TLSBadRecordMAC): next(gen) - def test_recvMessage_with_encryptThenMAC_with_modified_MAC(self): + def test_recvRecord_with_encryptThenMAC_with_modified_MAC(self): sock = MockSocket(bytearray( b'\x17' + # application data @@ -1514,12 +1514,12 @@ def test_recvMessage_with_encryptThenMAC_with_modified_MAC(self): None) recordLayer.changeReadState() - gen = recordLayer.recvMessage() + gen = recordLayer.recvRecord() with self.assertRaises(TLSBadRecordMAC): next(gen) - def test_recvMessage_with_encryptThenMAC_and_bad_size_encrypted_data(self): + def test_recvRecord_with_encryptThenMAC_and_bad_size_encrypted_data(self): # make sure the IV is predictible (all zero) patcher = mock.patch.object(os, 'urandom', @@ -1579,12 +1579,12 @@ def broken_encrypt(buf): None) recordLayer.changeReadState() - gen = recordLayer.recvMessage() + gen = recordLayer.recvRecord() with self.assertRaises(TLSDecryptionFailed): next(gen) - def test_recvMessage_with_encryptThenMAC_and_bad_last_padding_byte(self): + def test_recvRecord_with_encryptThenMAC_and_bad_last_padding_byte(self): # make sure the IV is predictible (all zero) patcher = mock.patch.object(os, 'urandom', @@ -1652,12 +1652,12 @@ def broken_padding(data): None) recordLayer.changeReadState() - gen = recordLayer.recvMessage() + gen = recordLayer.recvRecord() with self.assertRaises(TLSBadRecordMAC): next(gen) - def test_recvMessage_with_encryptThenMAC_and_SSLv3(self): + def test_recvRecord_with_encryptThenMAC_and_SSLv3(self): # constructor for the data sendingSocket = MockSocket(bytearray()) @@ -1705,7 +1705,7 @@ def test_recvMessage_with_encryptThenMAC_and_SSLv3(self): None) recordLayer.changeReadState() - for result in recordLayer.recvMessage(): + for result in recordLayer.recvRecord(): if result in (0, 1): self.assertTrue(False, "blocking socket") else: break @@ -1714,7 +1714,7 @@ def test_recvMessage_with_encryptThenMAC_and_SSLv3(self): self.assertEqual(parser.bytes, bytearray(b'test')) - def test_recvMessage_with_encryptThenMAC_and_bad_padding_byte(self): + def test_recvRecord_with_encryptThenMAC_and_bad_padding_byte(self): # make sure the IV is predictible (all zero) patcher = mock.patch.object(os, 'urandom', @@ -1783,7 +1783,7 @@ def broken_padding(data): None) recordLayer.changeReadState() - gen = recordLayer.recvMessage() + gen = recordLayer.recvRecord() with self.assertRaises(TLSBadRecordMAC): next(gen) From 0ac3e0160b9215fb8a8219770a79ad22f9c43da8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 13 Jun 2015 18:09:07 +0200 Subject: [PATCH 106/574] implement MessageSocket add a wrapper around RecordLayer that makes it easier to handle sending and receiving messages through it, especially aimed at handling handshake messages --- tlslite/messagesocket.py | 185 ++++++++++ unit_tests/test_tlslite_messagesocket.py | 444 +++++++++++++++++++++++ 2 files changed, 629 insertions(+) create mode 100644 tlslite/messagesocket.py create mode 100644 unit_tests/test_tlslite_messagesocket.py diff --git a/tlslite/messagesocket.py b/tlslite/messagesocket.py new file mode 100644 index 00000000..67c55e6a --- /dev/null +++ b/tlslite/messagesocket.py @@ -0,0 +1,185 @@ +# vim: set fileencoding=utf8 +# +# Copyright © 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +"""Wrapper of TLS RecordLayer providing message-level abstraction""" + +from .recordlayer import RecordLayer +from .constants import ContentType +from .messages import RecordHeader3, Message +from .utils.codec import Parser + +class MessageSocket(RecordLayer): + + """TLS Record Layer socket that provides Message level abstraction + + Because the record layer has a hard size limit on sent messages, they need + to be fragmented before sending. Similarly, a single record layer record + can include multiple handshake protocol messages (very common with + ServerHello, Certificate and ServerHelloDone), as such, the user of + RecordLayer needs to fragment those records into multiple messages. + Unfortunately, fragmentation of messages requires some degree of + knowledge about the messages passed and as such is outside scope of pure + record layer implementation. + + This class tries to provide a useful abstraction for handling Handshake + protocol messages. + + @type recordSize: int + @ivar recordSize: maximum size of records sent through socket. Messages + bigger than this size will be fragmented to smaller chunks. Setting it + to higher value than the default 2^14 will make the implementation + non RFC compliant and likely not interoperable with other peers. + + @type defragmenter: L{Defragmenter} + @ivar defragmenter: defragmenter used for read records + + @type unfragmentedDataTypes: tuple + @ivar unfragmentedDataTypes: data types which will be passed as-read, + TLS application_data by default + """ + + def __init__(self, sock, defragmenter): + """Apply TLS Record Layer abstraction to raw network socket. + + @type sock: L{socket.socket} + @param sock: network socket to wrap + @type defragmenter: L{Defragmenter} + @param defragmenter: defragmenter to apply on the records read + """ + super(MessageSocket, self).__init__(sock) + + self.defragmenter = defragmenter + self.unfragmentedDataTypes = tuple((ContentType.application_data, )) + self._lastRecordVersion = (0, 0) + + self._sendBuffer = bytearray(0) + self._sendBufferType = None + + self.recordSize = 2**14 + + def recvMessage(self): + """ + Read next message in queue + + will return a 0 or 1 if the read is blocking, a tuple of + L{RecordHeader3} and L{Parser} in case a message was received. + + @rtype: generator + """ + while True: + while True: + ret = self.defragmenter.getMessage() + if ret is None: + break + header = RecordHeader3().create(self._lastRecordVersion, + ret[0], + 0) + yield header, Parser(ret[1]) + + for ret in self.recvRecord(): + if ret in (0, 1): + yield ret + else: + break + + header, parser = ret + if header.type in self.unfragmentedDataTypes: + yield ret + # TODO probably needs a bit better handling... + if header.ssl2: + yield ret + + self.defragmenter.addData(header.type, parser.bytes) + self._lastRecordVersion = header.version + + def recvMessageBlocking(self): + """Blocking variant of L{recvMessage}""" + for res in self.recvMessage(): + if res in (0, 1): + pass + else: + return res + + def flush(self): + """ + Empty the queue of messages to write + + Will fragment the messages and write them in as little records as + possible. + + @rtype: generator + """ + while len(self._sendBuffer) > 0: + recordPayload = self._sendBuffer[:self.recordSize] + self._sendBuffer = self._sendBuffer[self.recordSize:] + msg = Message(self._sendBufferType, recordPayload) + for res in self.sendRecord(msg): + yield res + + assert len(self._sendBuffer) == 0 + self._sendBufferType = None + + def flushBlocking(self): + """Blocking variant of L{flush}""" + for _ in self.flush(): + pass + + def queueMessage(self, msg): + """ + Queue message for sending + + If the message is of same type as messages in queue, the message is + just added to queue. + + If the message is of different type as messages in queue, the queue is + flushed and then the message is queued. + + @rtype: generator + """ + if self._sendBufferType is None: + self._sendBufferType = msg.contentType + + if msg.contentType == self._sendBufferType: + self._sendBuffer += msg.write() + return + + for res in self.flush(): + yield res + + assert self._sendBufferType is None + self._sendBufferType = msg.contentType + self._sendBuffer += msg.write() + + def queueMessageBlocking(self, msg): + """Blocking variant of L{queueMessage}""" + for _ in self.queueMessage(msg): + pass + + def sendMessage(self, msg): + """ + Fragment and send a message. + + If a messages already of same type reside in queue, the message if + first added to it and then the queue is flushed. + + If the message is of different type than the queue, the queue is + flushed, the message is added to queue and the queue is flushed again. + + Use the sendRecord() message if you want to send a message outside + the queue, or a message of zero size. + + @rtype: generator + """ + for res in self.queueMessage(msg): + yield res + + for res in self.flush(): + yield res + + def sendMessageBlocking(self, msg): + """Blocking variant of L{sendMessage}""" + for _ in self.sendMessage(msg): + pass diff --git a/unit_tests/test_tlslite_messagesocket.py b/unit_tests/test_tlslite_messagesocket.py new file mode 100644 index 00000000..c0256ac3 --- /dev/null +++ b/unit_tests/test_tlslite_messagesocket.py @@ -0,0 +1,444 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from unit_tests.mocksock import MockSocket +from tlslite.messagesocket import MessageSocket +from tlslite.defragmenter import Defragmenter +from tlslite.messages import Message +from tlslite.constants import ContentType + +class TestMessageSocket(unittest.TestCase): + def test___init__(self): + msgSock = MessageSocket(None, None) + + self.assertIsNotNone(msgSock) + + def test_recvMessage(self): + defragmenter = Defragmenter() + defragmenter.addStaticSize(21, 2) + + sock = MockSocket(bytearray( + b'\x15' + # message type + b'\x03\x03' + # TLS version + b'\x00\x04' + # payload length + b'\xff\xff' + # first message + b'\xbb\xbb' # second message + )) + + msgSock = MessageSocket(sock, defragmenter) + + for res in msgSock.recvMessage(): + if res in (0, 1): + self.assertTrue(False, "Blocking read") + else: + break + + self.assertIsNotNone(res) + + header, parser = res + + self.assertEqual(header.type, 21) + self.assertEqual(header.version, (3, 3)) + self.assertEqual(header.length, 0) + self.assertEqual(parser.bytes, bytearray(b'\xff\xff')) + + res = None + + for res in msgSock.recvMessage(): + if res in (0, 1): + self.assertTrue(False, "Blocking read") + else: + break + + self.assertIsNotNone(res) + + header, parser = res + + self.assertEqual(header.type, 21) + self.assertEqual(header.version, (3, 3)) + self.assertEqual(header.length, 0) + self.assertEqual(parser.bytes, bytearray(b'\xbb\xbb')) + + def test_recvMessage_with_unfragmentable_type(self): + defragmenter = Defragmenter() + defragmenter.addStaticSize(21, 2) + + sock = MockSocket(bytearray( + b'\x17' + # message type + b'\x03\x03' + # TLS version + b'\x00\x06' + # payload length + b'\x00\x04' + + b'\xff'*4 + )) + + msgSock = MessageSocket(sock, defragmenter) + + for res in msgSock.recvMessage(): + if res in (0, 1): + self.assertTrue(False, "Blocking read") + else: + break + + self.assertIsNotNone(res) + + header, parser = res + + self.assertEqual(header.type, 23) + self.assertEqual(header.version, (3, 3)) + self.assertEqual(header.length, 6) + self.assertEqual(parser.bytes, bytearray(b'\x00\x04' + b'\xff'*4)) + + def test_recvMessage_with_blocking_socket(self): + defragmenter = Defragmenter() + defragmenter.addStaticSize(21, 2) + + sock = MockSocket(bytearray( + b'\x15' + # message type + b'\x03\x03' + # TLS version + b'\x00\x02' + # payload length + b'\xff\xff' # message + ), + blockEveryOther=True, + maxRet=1) + + msgSock = MessageSocket(sock, defragmenter) + + gotBlocked = False + for res in msgSock.recvMessage(): + if res in (0, 1): + gotBlocked = True + else: + break + + self.assertTrue(gotBlocked) + self.assertIsNotNone(res) + + header, parser = res + + self.assertEqual(header.type, 21) + self.assertEqual(header.version, (3, 3)) + self.assertEqual(parser.bytes, bytearray(b'\xff\xff')) + + def test_recvMessageBlocking(self): + defragmenter = Defragmenter() + defragmenter.addStaticSize(21, 2) + + sock = MockSocket(bytearray( + b'\x15' + # message type + b'\x03\x03' + # TLS version + b'\x00\x02' + # payload length + b'\xff\xff' # message + ), + blockEveryOther=True, + maxRet=1) + + msgSock = MessageSocket(sock, defragmenter) + + res = msgSock.recvMessageBlocking() + + self.assertIsNotNone(res) + + header, parser = res + + self.assertEqual(header.type, 21) + self.assertEqual(parser.bytes, bytearray(b'\xff\xff')) + + def test_flush(self): + sock = MockSocket(bytearray()) + + msgSock = MessageSocket(sock, None) + + for res in msgSock.flush(): + if res in (0, 1): + self.assertTrue(False, "Blocking flush") + else: + break + + self.assertEqual(len(sock.sent), 0) + + for res in msgSock.flush(): + if res in (0, 1): + self.assertTrue(False, "Blocking flush") + else: + break + + self.assertEqual(len(sock.sent), 0) + + def test_queueMessage(self): + sock = MockSocket(bytearray()) + + msgSocket = MessageSocket(sock, None) + + msg = Message(ContentType.alert, bytearray(b'\xff\xbb')) + + for res in msgSocket.queueMessage(msg): + if res in (0, 1): + self.assertTrue(False, "Blocking queue") + else: + break + + self.assertEqual(len(sock.sent), 0) + + msg = Message(ContentType.alert, bytearray(b'\xff\xaa')) + + for res in msgSocket.queueMessage(msg): + if res in (0, 1): + self.assertTrue(False, "Blocking queue") + else: + break + + self.assertEqual(len(sock.sent), 0) + + for res in msgSocket.flush(): + if res in (0, 1): + self.assertTrue(False, "Blocking flush") + else: + break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0], bytearray( + b'\x15' + + b'\x00\x00' + + b'\x00\x04' + + b'\xff\xbb' + + b'\xff\xaa')) + + def test_queueMessage_with_conflicting_types(self): + sock = MockSocket(bytearray()) + + msgSock = MessageSocket(sock, None) + msgSock.version = (3, 3) + + msg = Message(ContentType.handshake, bytearray(b'\xaa\xaa\xaa')) + + for res in msgSock.queueMessage(msg): + if res in (0, 1): + self.assertTrue(False, "Blocking queue") + else: + break + + self.assertEqual(len(sock.sent), 0) + + msg = Message(ContentType.alert, bytearray(b'\x02\x01')) + + for res in msgSock.queueMessage(msg): + if res in (0, 1): + self.assertTrue(False, "Blocking queue") + else: + break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(bytearray( + b'\x16' + + b'\x03\x03' + + b'\x00\x03' + + b'\xaa'*3), sock.sent[0]) + + for res in msgSock.flush(): + if res in (0, 1): + self.assertTrue(False, "Blocking flush") + else: + break + + self.assertEqual(len(sock.sent), 2) + self.assertEqual(bytearray( + b'\x15' + + b'\x03\x03' + + b'\x00\x02' + + b'\x02\x01'), sock.sent[1]) + + def test_queueMessage_with_conflicting_types_and_blocking_socket(self): + sock = MockSocket(bytearray(), blockEveryOther=True) + sock.blockWrite = True + + msgSock = MessageSocket(sock, None) + msgSock.version = (3, 3) + + msg = Message(ContentType.handshake, bytearray(b'\xaa\xaa\xaa')) + + blocked = False + for res in msgSock.queueMessage(msg): + if res in (0, 1): + blocked = True + else: + break + + # no write so no blocking + self.assertFalse(blocked) + self.assertEqual(len(sock.sent), 0) + + msg = Message(ContentType.alert, bytearray(b'\x02\x01')) + + blocked = False + for res in msgSock.queueMessage(msg): + if res in (0, 1): + blocked = True + else: + break + + # blocked once, so one write + self.assertTrue(blocked) + self.assertEqual(len(sock.sent), 1) + self.assertEqual(bytearray( + b'\x16' + + b'\x03\x03' + + b'\x00\x03' + + b'\xaa'*3), sock.sent[0]) + + sock.blockWrite = True + + blocked = False + for res in msgSock.flush(): + if res in (0, 1): + blocked = True + else: + break + + # blocked once, so one write + self.assertTrue(blocked) + self.assertEqual(len(sock.sent), 2) + self.assertEqual(bytearray( + b'\x15' + + b'\x03\x03' + + b'\x00\x02' + + b'\x02\x01'), sock.sent[1]) + + def test_sendMessage(self): + sock = MockSocket(bytearray(), blockEveryOther=True) + sock.blockWrite = True + + msgSock = MessageSocket(sock, None) + msgSock.version = (3, 3) + + msg = Message(ContentType.handshake, bytearray(b'\xaa\xaa\xaa')) + + blocked = False + for res in msgSock.queueMessage(msg): + if res in (0, 1): + blocked = True + else: + break + + # no write so no blocking + self.assertFalse(blocked) + self.assertEqual(len(sock.sent), 0) + + msg = Message(ContentType.alert, bytearray(b'\x02\x01')) + + blocked = False + for res in msgSock.sendMessage(msg): + if res in (0, 1): + blocked = True + else: + break + + self.assertTrue(blocked) + self.assertEqual(len(sock.sent), 2) + self.assertEqual(bytearray( + b'\x16' + + b'\x03\x03' + + b'\x00\x03' + + b'\xaa'*3), sock.sent[0]) + self.assertEqual(bytearray( + b'\x15' + + b'\x03\x03' + + b'\x00\x02' + + b'\x02\x01'), sock.sent[1]) + + def test_sendMessageBlocking(self): + sock = MockSocket(bytearray(), blockEveryOther=True) + sock.blockWrite = True + + msgSock = MessageSocket(sock, None) + msgSock.version = (3, 3) + + msg = Message(ContentType.handshake, bytearray(b'\xaa\xaa\xaa')) + + blocked = False + for res in msgSock.queueMessage(msg): + if res in (0, 1): + blocked = True + else: + break + + # no write so no blocking + self.assertFalse(blocked) + self.assertEqual(len(sock.sent), 0) + + msg = Message(ContentType.alert, bytearray(b'\x02\x01')) + + msgSock.sendMessageBlocking(msg) + + self.assertEqual(len(sock.sent), 2) + self.assertEqual(bytearray( + b'\x16' + + b'\x03\x03' + + b'\x00\x03' + + b'\xaa'*3), sock.sent[0]) + self.assertEqual(bytearray( + b'\x15' + + b'\x03\x03' + + b'\x00\x02' + + b'\x02\x01'), sock.sent[1]) + + def test_queueMessageBlocking(self): + sock = MockSocket(bytearray(), blockEveryOther=True) + sock.blockWrite = True + + msgSock = MessageSocket(sock, None) + msgSock.version = (3, 3) + + msg = Message(ContentType.handshake, bytearray(b'\xaa\xaa\xaa')) + + msgSock.queueMessageBlocking(msg) + + self.assertEqual(len(sock.sent), 0) + + msg = Message(ContentType.alert, bytearray(b'\x02\x01')) + + msgSock.queueMessageBlocking(msg) + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(bytearray( + b'\x16' + + b'\x03\x03' + + b'\x00\x03' + + b'\xaa'*3), sock.sent[0]) + + def test_flushBlocking(self): + sock = MockSocket(bytearray()) + msgSock = MessageSocket(sock, None) + + msgSock.flushBlocking() + + self.assertEqual(len(sock.sent), 0) + + def test_flushBlocking_with_data(self): + sock = MockSocket(bytearray(), blockEveryOther=True) + sock.blockWrite = True + + msgSock = MessageSocket(sock, None) + msgSock.version = (3, 3) + + msg = Message(ContentType.handshake, bytearray(b'\xaa\xaa\xaa')) + + msgSock.queueMessageBlocking(msg) + + self.assertEqual(len(sock.sent), 0) + + msgSock.flushBlocking() + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(bytearray( + b'\x16' + + b'\x03\x03' + + b'\x00\x03' + + b'\xaa'*3), sock.sent[0]) From f0f5ee9777056daab559889921acf8bdf6cb69b0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 11:35:02 +0200 Subject: [PATCH 107/574] recordlayer AEAD test coverage --- unit_tests/test_tlslite_recordlayer.py | 140 +++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 3c4cab3d..708928e3 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -650,6 +650,146 @@ def test_sendRecord_with_AES256_cipher_and_tls1_0(self): b'}\xcct\x84 Date: Wed, 24 Jun 2015 19:38:10 +0200 Subject: [PATCH 108/574] test coverage for TLSExtension --- unit_tests/test_tlslite_extensions.py | 106 ++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 4d3a7d75..9097d21a 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -79,8 +79,97 @@ def test_parse_with_sni_ext(self): tls_extension = TLSExtension().parse(p) + self.assertIsInstance(tls_extension, SNIExtension) + self.assertEqual(bytearray(b'example.com'), tls_extension.host_names[0]) + def test_parse_with_SRP_ext(self): + p = Parser(bytearray( + b'\x00\x0c' + # ext type - 12 + b'\x00\x09' + # overall length + b'\x08' + # name length + b'username' # name + )) + + ext = TLSExtension().parse(p) + + self.assertIsInstance(ext, SRPExtension) + + self.assertEqual(ext.identity, b'username') + + def test_parse_with_NPN_ext(self): + p = Parser(bytearray( + b'\x33\x74' + # type of extension - NPN + b'\x00\x09' + # overall length + b'\x08' + # first name length + b'http/1.1' + )) + + ext = TLSExtension().parse(p) + + self.assertIsInstance(ext, NPNExtension) + + self.assertEqual(ext.protocols, [b'http/1.1']) + + def test_parse_with_elliptic_curves(self): + p = Parser(bytearray( + b'\x00\x0a' + # type of extension + b'\x00\x08' + # overall length + b'\x00\x06' + # length of array + b'\x00\x17' + # secp256r1 + b'\x00\x18' + # secp384r1 + b'\x00\x19' # secp521r1 + )) + + ext = TLSExtension().parse(p) + + # XXX not supported + self.assertIsInstance(ext, TLSExtension) + + #self.assertEqual(ext.curves, [EllipticCurves.secp256r1, + # EllipticCurves.secp384r1, + # EllipticCurves.secp521r1]) + + def test_parse_with_ec_point_formats(self): + p = Parser(bytearray( + b'\x00\x0b' + # type of extension + b'\x00\x02' + # overall length + b'\x01' + # length of array + b'\x00' # type - uncompressed + )) + + ext = TLSExtension().parse(p) + + # XXX unsupported + self.assertIsInstance(ext, TLSExtension) + + #self.assertEqual(ext.formats, [PointFormats.uncompressed]) + + def test_parse_with_signature_algorithms(self): + p = Parser(bytearray( + b'\x00\x0d' + # type of extension + b'\x00\x1c' + # overall length + b'\x00\x1a' + # length of array + b'\x04\x01' + # SHA256+RSA + b'\x04\x02' + # SHA256+DSA + b'\x04\x03' + # SHA256+ECDSA + b'\x05\x01' + # SHA384+RSA + b'\x05\x03' + # SHA384+ECDSA + b'\x06\x01' + # SHA512+RSA + b'\x06\x03' + # SHA512+ECDSA + b'\x03\x01' + # SHA224+RSA + b'\x03\x02' + # SHA224+DSA + b'\x03\x03' + # SHA224+ECDSA + b'\x02\x01' + # SHA1+RSA + b'\x02\x02' + # SHA1+DSA + b'\x02\x03' # SHA1+ECDSA + )) + + ext = TLSExtension().parse(p) + + # XXX unsupported + self.assertIsInstance(ext, TLSExtension) + def test_equality(self): a = TLSExtension().create(0, bytearray(0)) b = SNIExtension().create() @@ -114,8 +203,25 @@ def test_parse_of_server_hello_extension(self): ext = ext.parse(p) + self.assertIsInstance(ext, ServerCertTypeExtension) + self.assertEqual(1, ext.cert_type) + def test_parse_with_client_cert_type_extension(self): + ext = TLSExtension() + + p = Parser(bytearray( + b'\x00\x09' + # ext type + b'\x00\x02' + # ext length + b'\x01' + # length of array + b'\x01')) # type - opengpg (1) + + ext = ext.parse(p) + + self.assertIsInstance(ext, ClientCertTypeExtension) + + self.assertEqual([1], ext.cert_types) + def test___repr__(self): ext = TLSExtension() ext = ext.create(0, bytearray(b'\x00\x00')) From 229ea4f5857bd27da057fe0f1c39966c0468ac0e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 20 Jun 2015 20:37:39 +0200 Subject: [PATCH 109/574] add class for handling handshake protocol hashes --- tlslite/handshakehashes.py | 99 ++++++++++++++++++++++ unit_tests/test_tlslite_handshakehashes.py | 98 +++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 tlslite/handshakehashes.py create mode 100644 unit_tests/test_tlslite_handshakehashes.py diff --git a/tlslite/handshakehashes.py b/tlslite/handshakehashes.py new file mode 100644 index 00000000..d3114398 --- /dev/null +++ b/tlslite/handshakehashes.py @@ -0,0 +1,99 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. +"""Handling cryptographic hashes for handshake protocol""" + +from .utils.compat import compat26Str, compatHMAC +from .utils.cryptomath import MD5, SHA1 +import hashlib + +class HandshakeHashes(object): + + """ + Store and calculate necessary hashes for handshake protocol + + Calculates message digests of messages exchanged in handshake protocol + of SSLv3 and TLS. + """ + + def __init__(self): + """Create instance""" + self._handshakeMD5 = hashlib.md5() + self._handshakeSHA = hashlib.sha1() + self._handshakeSHA256 = hashlib.sha256() + + def update(self, data): + """ + Add L{data} to hash input. + + @type data: bytearray + @param data: serialized TLS handshake message + """ + text = compat26Str(data) + self._handshakeMD5.update(text) + self._handshakeSHA.update(text) + self._handshakeSHA256.update(text) + + def digest(self, digest=None): + """ + Calculate and return digest for the already consumed data. + + Used for Finished and CertificateVerify messages. + + @type digest: str + @param digest: name of digest to return + """ + if digest is None: + return self._handshakeMD5.digest() + self._handshakeSHA.digest() + elif digest == 'md5': + return self._handshakeMD5.digest() + elif digest == 'sha1': + return self._handshakeSHA.digest() + elif digest == 'sha256': + return self._handshakeSHA256.digest() + else: + raise ValueError("Unknown digest name") + + def digestSSL(self, masterSecret, label): + """ + Calculate and return digest for already consumed data (SSLv3 version) + + Used for Finished and CertificateVerify messages. + + @type masterSecret: bytearray + @param masterSecret: value of the master secret + @type label: bytearray + @param label: label to include in the calculation + """ + #pylint: disable=maybe-no-member + imacMD5 = self._handshakeMD5.copy() + imacSHA = self._handshakeSHA.copy() + #pylint: enable=maybe-no-member + + # the below difference in input for MD5 and SHA-1 is why we can't reuse + # digest() method + imacMD5.update(compatHMAC(label + masterSecret + bytearray([0x36]*48))) + imacSHA.update(compatHMAC(label + masterSecret + bytearray([0x36]*40))) + + md5Bytes = MD5(masterSecret + bytearray([0x5c]*48) + \ + bytearray(imacMD5.digest())) + shaBytes = SHA1(masterSecret + bytearray([0x5c]*40) + \ + bytearray(imacSHA.digest())) + + return md5Bytes + shaBytes + + #pylint: disable=protected-access, maybe-no-member + def copy(self): + """ + Copy object + + Return a copy of the object with all the hashes in the same state + as the source object. + + @rtype: HandshakeHashes + """ + other = HandshakeHashes() + other._handshakeMD5 = self._handshakeMD5.copy() + other._handshakeSHA = self._handshakeSHA.copy() + other._handshakeSHA256 = self._handshakeSHA256.copy() + return other diff --git a/unit_tests/test_tlslite_handshakehashes.py b/unit_tests/test_tlslite_handshakehashes.py new file mode 100644 index 00000000..4892d0a9 --- /dev/null +++ b/unit_tests/test_tlslite_handshakehashes.py @@ -0,0 +1,98 @@ +# Copyright (c) 2014, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.handshakehashes import HandshakeHashes + +class TestHandshakeHashes(unittest.TestCase): + def test___init__(self): + hh = HandshakeHashes() + + self.assertIsNotNone(hh) + + def test_update(self): + hh = HandshakeHashes() + hh.update(bytearray(10)) + + def test_update_with_str(self): + hh = HandshakeHashes() + hh.update(b'text') + + def test_digest_SSL3(self): + hh = HandshakeHashes() + + self.assertEqual(bytearray( + b'\xb5Q\x15\xa4\xcd\xff\xfdF\xa6\x9c\xe2\x0f\x83~\x948\xc3\xb5'\ + b'\xc1\x8d\xb6|\x10n@a\x97\xccG\xfeI\xa8s T\\'), + hh.digestSSL(bytearray(48), b'')) + + def test_digest_TLS1_0(self): + hh = HandshakeHashes() + + self.assertEqual( + b'\xd4\x1d\x8c\xd9\x8f\x00\xb2\x04\xe9\x80\t\x98\xec\xf8B~\xda'\ + b'9\xa3\xee^kK\r2U\xbf\xef\x95`\x18\x90\xaf\xd8\x07\t', + hh.digest()) + + def test_copy(self): + hh = HandshakeHashes() + hh.update(b'text') + + hh2 = hh.copy() + + self.assertEqual(hh2.digest(), hh.digest()) + + def test_digest_md5(self): + hh = HandshakeHashes() + + self.assertEqual( + b"\xd4\x1d\x8c\xd9\x8f\x00\xb2\x04\xe9\x80\t\x98\xec\xf8B~", + hh.digest('md5')) + + def test_digest_sha1(self): + hh = HandshakeHashes() + + self.assertEqual( + b"\xda9\xa3\xee^kK\r2U\xbf\xef\x95`\x18\x90\xaf\xd8\x07\t", + hh.digest('sha1')) + + def test_digest_sha256(self): + hh = HandshakeHashes() + + self.assertEqual( + b"\xe3\xb0\xc4B\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99o\xb9$'\xae"\ + b"A\xe4d\x9b\x93L\xa4\x95\x99\x1bxR\xb8U", + hh.digest('sha256')) + + def test_digest_with_partial_writes(self): + hh = HandshakeHashes() + hh.update(b'text') + + hh2 = HandshakeHashes() + hh2.update(b'te') + hh2.update(b'xt') + + self.assertEqual(hh.digest(), hh2.digest()) + + def test_digest_with_invalid_hash(self): + hh = HandshakeHashes() + + with self.assertRaises(ValueError): + hh.digest('md2') + + def test_digest_with_repeated_calls(self): + hh = HandshakeHashes() + hh.update(b'text') + + self.assertEqual(hh.digest(), hh.digest()) + + hh.update(b'ext') + + self.assertEqual(hh.digest('sha256'), hh.digest('sha256')) From e71a7a90191eacf1333fdc3438b00b0be182452f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 01:16:49 +0200 Subject: [PATCH 110/574] use HandshakeHashes in TLSConnection --- tlslite/tlsconnection.py | 28 +++++++++---------- tlslite/tlsrecordlayer.py | 33 ++++------------------- unit_tests/test_tlslite_tlsrecordlayer.py | 12 ++++----- 3 files changed, 23 insertions(+), 50 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index f9423a39..5bc2a78f 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -973,14 +973,13 @@ def _clientRSAKeyExchange(self, settings, cipherSuite, premasterSecret, clientRandom, serverRandom) - verifyBytes = self._calcSSLHandshakeHash(masterSecret, b"") + verifyBytes = self._handshake_hash.digestSSL(masterSecret, b"") elif self.version in ((3,1), (3,2)): - verifyBytes = self._handshake_md5.digest() + \ - self._handshake_sha.digest() + verifyBytes = self._handshake_hash.digest() elif self.version == (3,3): # TODO: Signature algorithm negotiation not supported. signatureAlgorithm = (HashAlgorithm.sha1, SignatureAlgorithm.rsa) - verifyBytes = self._handshake_sha.digest() + verifyBytes = self._handshake_hash.digest('sha1') verifyBytes = RSAKey.addPKCS1SHA1Prefix(verifyBytes) if self.fault == Fault.badVerifyMessage: verifyBytes[0] = ((verifyBytes[0]+1) % 256) @@ -1143,14 +1142,13 @@ def _clientDHEKeyExchange(self, settings, cipherSuite, premasterSecret, clientRandom, serverRandom) - verifyBytes = self._calcSSLHandshakeHash(masterSecret, b"") + verifyBytes = self._handshake_hash.digestSSL(masterSecret, b"") elif self.version in ((3,1), (3,2)): - verifyBytes = self._handshake_md5.digest() + \ - self._handshake_sha.digest() + verifyBytes = self._handshake_hash.digest() else: # self.version == (3,3): # TODO: Signature algorithm negotiation not supported. signatureAlgorithm = (HashAlgorithm.sha1, SignatureAlgorithm.rsa) - verifyBytes = self._handshake_sha.digest() + verifyBytes = self._handshake_hash.digest('sha1') verifyBytes = RSAKey.addPKCS1SHA1Prefix(verifyBytes) if self.fault == Fault.badVerifyMessage: verifyBytes[0] = ((verifyBytes[0]+1) % 256) @@ -1822,12 +1820,11 @@ def _serverCertKeyExchange(self, clientHello, serverHello, if self.version == (3,0): masterSecret = calcMasterSecret(self.version, premasterSecret, clientHello.random, serverHello.random) - verifyBytes = self._calcSSLHandshakeHash(masterSecret, b"") + verifyBytes = self._handshake_hash.digestSSL(masterSecret, b"") elif self.version in ((3,1), (3,2)): - verifyBytes = self._handshake_md5.digest() + \ - self._handshake_sha.digest() + verifyBytes = self._handshake_hash.digest() elif self.version == (3,3): - verifyBytes = self._handshake_sha.digest() + verifyBytes = self._handshake_hash.digest('sha1') verifyBytes = RSAKey.addPKCS1SHA1Prefix(verifyBytes) for result in self._getMsg(ContentType.handshake, HandshakeType.certificate_verify): @@ -2003,7 +2000,7 @@ def _calcFinished(self, masterSecret, send=True): else: senderStr = b"\x53\x52\x56\x52" - verifyData = self._calcSSLHandshakeHash(masterSecret, senderStr) + verifyData = self._handshake_hash.digestSSL(masterSecret, senderStr) return verifyData elif self.version in ((3,1), (3,2)): @@ -2012,8 +2009,7 @@ def _calcFinished(self, masterSecret, send=True): else: label = b"server finished" - handshakeHashes = self._handshake_md5.digest() + \ - self._handshake_sha.digest() + handshakeHashes = self._handshake_hash.digest() verifyData = PRF(masterSecret, label, handshakeHashes, 12) return verifyData elif self.version == (3,3): @@ -2022,7 +2018,7 @@ def _calcFinished(self, masterSecret, send=True): else: label = b"server finished" - handshakeHashes = self._handshake_sha256.digest() + handshakeHashes = self._handshake_hash.digest('sha256') verifyData = PRF_1_2(masterSecret, label, handshakeHashes, 12) return verifyData else: diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index fe7111be..a0e7bd0d 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -20,6 +20,7 @@ from .constants import * from .recordlayer import RecordLayer from .defragmenter import Defragmenter +from .handshakehashes import HandshakeHashes import socket import traceback @@ -119,9 +120,7 @@ def __init__(self, sock): self.clearWriteBuffer() #Handshake digests - self._handshake_md5 = hashlib.md5() - self._handshake_sha = hashlib.sha1() - self._handshake_sha256 = hashlib.sha256() + self._handshake_hash = HandshakeHashes() #Is the connection open? self.closed = True #read-only @@ -561,9 +560,7 @@ def _sendMsg(self, msg, randomizeFirstBlock = True): contentType = msg.contentType #Update handshake hashes if contentType == ContentType.handshake: - self._handshake_md5.update(compat26Str(buf)) - self._handshake_sha.update(compat26Str(buf)) - self._handshake_sha256.update(compat26Str(buf)) + self._handshake_hash.update(buf) #Fragment big messages while len(buf) > self.recordSize: @@ -745,9 +742,7 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): yield result #Update handshake hashes - self._handshake_md5.update(compat26Str(p.bytes)) - self._handshake_sha.update(compat26Str(p.bytes)) - self._handshake_sha256.update(compat26Str(p.bytes)) + self._handshake_hash.update(p.bytes) #Parse based on handshake type if subType == HandshakeType.client_hello: @@ -874,9 +869,7 @@ def _handshakeStart(self, client): if not self.closed: raise ValueError("Renegotiation disallowed for security reasons") self._client = client - self._handshake_md5 = hashlib.md5() - self._handshake_sha = hashlib.sha1() - self._handshake_sha256 = hashlib.sha256() + self._handshake_hash = HandshakeHashes() self._defragmenter.clearBuffers() self.allegedSrpUsername = None self._refCount = 1 @@ -896,19 +889,3 @@ def _changeWriteState(self): def _changeReadState(self): self._recordLayer.changeReadState() - - #Used for Finished messages and CertificateVerify messages in SSL v3 - def _calcSSLHandshakeHash(self, masterSecret, label): - imac_md5 = self._handshake_md5.copy() - imac_sha = self._handshake_sha.copy() - - imac_md5.update(compatHMAC(label + masterSecret + bytearray([0x36]*48))) - imac_sha.update(compatHMAC(label + masterSecret + bytearray([0x36]*40))) - - md5Bytes = MD5(masterSecret + bytearray([0x5c]*48) + \ - bytearray(imac_md5.digest())) - shaBytes = SHA1(masterSecret + bytearray([0x5c]*40) + \ - bytearray(imac_sha.digest())) - - return md5Bytes + shaBytes - diff --git a/unit_tests/test_tlslite_tlsrecordlayer.py b/unit_tests/test_tlslite_tlsrecordlayer.py index 753f2b38..ed8c17a9 100644 --- a/unit_tests/test_tlslite_tlsrecordlayer.py +++ b/unit_tests/test_tlslite_tlsrecordlayer.py @@ -741,7 +741,7 @@ def test_full_connection_with_RSA_kex(self): record_layer._changeWriteState() - handshake_hashes = record_layer._handshake_sha256.digest() + handshake_hashes = record_layer._handshake_hash.digest('sha256') verify_data = PRF_1_2(master_secret, b'client finished', handshake_hashes, 12) @@ -791,7 +791,7 @@ def test_full_connection_with_RSA_kex(self): srv_record_layer._changeReadState() - srv_handshakeHashes = srv_record_layer._handshake_sha256.digest() + srv_handshakeHashes = srv_record_layer._handshake_hash.digest('sha256') srv_verify_data = PRF_1_2(srv_master_secret, b"client finished", srv_handshakeHashes, 12) @@ -813,7 +813,7 @@ def test_full_connection_with_RSA_kex(self): srv_record_layer._changeWriteState() - srv_handshakeHashes = srv_record_layer._handshake_sha256.digest() + srv_handshakeHashes = srv_record_layer._handshake_hash.digest('sha256') srv_verify_data = PRF_1_2(srv_master_secret, b"server finished", srv_handshakeHashes, 12) @@ -841,7 +841,7 @@ def test_full_connection_with_RSA_kex(self): record_layer._changeReadState() - handshake_hashes = record_layer._handshake_sha256.digest() + handshake_hashes = record_layer._handshake_hash.digest('sha256') server_verify_data = PRF_1_2(master_secret, b'server finished', handshake_hashes, 12) @@ -958,7 +958,7 @@ def test_full_connection_with_external_server(self): record_layer._changeWriteState() - handshake_hashes = record_layer._handshake_sha256.digest() + handshake_hashes = record_layer._handshake_hash.digest('sha256') verify_data = PRF_1_2(master_secret, b'client finished', handshake_hashes, 12) @@ -980,7 +980,7 @@ def test_full_connection_with_external_server(self): record_layer._changeReadState() - handshake_hashes = record_layer._handshake_sha256.digest() + handshake_hashes = record_layer._handshake_hash.digest('sha256') server_verify_data = PRF_1_2(master_secret, b'server finished', handshake_hashes, 12) From 6922504a3203fca248f7dfbe8d3ea96ffe2520e3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 14:44:31 +0200 Subject: [PATCH 111/574] use big primes for ADH it's useless to use ephemeral parameters if they are 32 bit in size reuse one of the well known good groups --- tlslite/tlsconnection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 5bc2a78f..0b1074d2 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1855,9 +1855,9 @@ def _serverCertKeyExchange(self, clientHello, serverHello, def _serverAnonKeyExchange(self, clientHello, serverHello, cipherSuite, settings): # Calculate DH p, g, Xs, Ys - dh_p = getRandomSafePrime(32, False) - dh_g = getRandomNumber(2, dh_p) - dh_Xs = bytesToNumber(getRandomBytes(32)) + # TODO make configurable + dh_g, dh_p = goodGroupParameters[2] + dh_Xs = bytesToNumber(getRandomBytes(32)) dh_Ys = powMod(dh_g, dh_Xs, dh_p) #Create ServerKeyExchange From b5f2c17ec81160c25ab5540a2ce438e21e4f1cfd Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 10:43:49 +0200 Subject: [PATCH 112/574] add SHA384 PRF ciphersutes add two basic ciphersuites that require use of SHA384 PRF: TLS_RSA_WITH_AES_256_GCM_SHA384 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 --- README.md | 1 + tests/tlstest.py | 20 +++-- tlslite/constants.py | 51 +++++++++++-- tlslite/handshakehashes.py | 5 ++ tlslite/handshakesettings.py | 7 +- tlslite/mathtls.py | 23 +++++- tlslite/recordlayer.py | 23 ++++-- tlslite/tlsconnection.py | 80 +++++++++++++------- tlslite/utils/cryptomath.py | 5 ++ unit_tests/test_tlslite_handshakesettings.py | 2 +- unit_tests/test_tlslite_mathtls.py | 2 +- unit_tests/test_tlslite_tlsrecordlayer.py | 20 +++-- 12 files changed, 177 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 656eeebe..f5003182 100644 --- a/README.md +++ b/README.md @@ -515,6 +515,7 @@ RFC 7366. =========== 0.5.0-alpha - xx/xx/xxxx - Hubert Kario + - implement AES-256-GCM ciphersuites and SHA384 PRF - implement AES-GCM cipher and AES-128-GCM ciphersuites (David Benjamin - Chromium) - implement client side DHE_RSA key exchange and DHE with certificate based diff --git a/tests/tlstest.py b/tests/tlstest.py index f1d4be4d..42d7e379 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -355,10 +355,14 @@ def connect(): print("Test 23 - throughput test") for implementation in implementations: - for cipher in ["aes128gcm", "aes128", "aes256", "3des", "rc4"]: - if cipher == "3des" and implementation not in ("openssl", "pycrypto"): + for cipher in ["aes128gcm", "aes256gcm", "aes128", "aes256", "3des", + "rc4"]: + if cipher == "3des" and implementation not in ("openssl", + "pycrypto"): continue - if cipher == "aes128gcm" and implementation not in ("pycrypto", "python"): + if cipher in ("aes128gcm", "aes256gcm") and \ + implementation not in ("pycrypto", + "python"): continue print("Test 23:", end=' ') @@ -866,10 +870,14 @@ def server_bind(self): print("Test 23 - throughput test") for implementation in implementations: - for cipher in ["aes128gcm", "aes128", "aes256", "3des", "rc4"]: - if cipher == "3des" and implementation not in ("openssl", "pycrypto"): + for cipher in ["aes128gcm", "aes256gcm", "aes128", "aes256", "3des", + "rc4"]: + if cipher == "3des" and implementation not in ("openssl", + "pycrypto"): continue - if cipher == "aes128gcm" and implementation not in ("pycrypto", "python"): + if cipher in ("aes128gcm", "aes256gcm") and \ + implementation not in ("pycrypto", + "python"): continue print("Test 23:", end=' ') diff --git a/tlslite/constants.py b/tlslite/constants.py index 1a414190..2473f61f 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -241,6 +241,10 @@ class CipherSuite: ietfNames[0x009C] = 'TLS_RSA_WITH_AES_128_GCM_SHA256' TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E ietfNames[0x009E] = 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256' + TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D + ietfNames[0x009D] = 'TLS_RSA_WITH_AES_256_GCM_SHA384' + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F + ietfNames[0x009F] = 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384' # # Define cipher suite families below @@ -278,6 +282,11 @@ class CipherSuite: aes128GcmSuites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) aes128GcmSuites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) + # AES-256-GCM ciphers (implicit SHA384, see sha384PrfSuites) + aes256GcmSuites = [] + aes256GcmSuites.append(TLS_RSA_WITH_AES_256_GCM_SHA384) + aes256GcmSuites.append(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) + # RC4 128 stream cipher rc4Suites = [] rc4Suites.append(TLS_RSA_WITH_RC4_128_SHA) @@ -310,7 +319,18 @@ class CipherSuite: sha256Suites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) sha256Suites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) - aeadSuites = aes128GcmSuites + # SHA-384 HMAC, SHA-384 PRF + sha384Suites = [] + + # AEAD integrity, any PRF + aeadSuites = [] + aeadSuites.extend(aes128GcmSuites) + aeadSuites.extend(aes256GcmSuites) + + # TLS1.2 with SHA384 PRF + sha384PrfSuites = [] + sha384PrfSuites.extend(sha384Suites) + sha384PrfSuites.extend(aes256GcmSuites) # MD-5 HMAC, protocol default PRF md5Suites = [] @@ -324,6 +344,7 @@ class CipherSuite: # TLS1.2 specific ciphersuites tls12Suites = [] tls12Suites.extend(sha256Suites) + tls12Suites.extend(sha384Suites) tls12Suites.extend(aeadSuites) @staticmethod @@ -346,16 +367,20 @@ def _filterSuites(suites, settings, version=None): macSuites = [] if "sha" in macNames: macSuites += CipherSuite.shaSuites - if "sha256" in macNames and version >= (3,3): + if "sha256" in macNames and version >= (3, 3): macSuites += CipherSuite.sha256Suites + if "sha384" in macNames and version >= (3, 3): + macSuites += CipherSuite.sha384Suites if "md5" in macNames: macSuites += CipherSuite.md5Suites - if "aead" in macNames and version >= (3,3): + if "aead" in macNames and version >= (3, 3): macSuites += CipherSuite.aeadSuites cipherSuites = [] - if "aes128gcm" in cipherNames and version >= (3,3): + if "aes128gcm" in cipherNames and version >= (3, 3): cipherSuites += CipherSuite.aes128GcmSuites + if "aes256gcm" in cipherNames and version >= (3, 3): + cipherSuites += CipherSuite.aes256GcmSuites if "aes128" in cipherNames: cipherSuites += CipherSuite.aes128Suites if "aes256" in cipherNames: @@ -408,6 +433,7 @@ def getSrpAllSuites(settings, version=None): # RSA key exchange, RSA authentication certSuites = [] + certSuites.append(TLS_RSA_WITH_AES_256_GCM_SHA384) certSuites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) certSuites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) certSuites.append(TLS_RSA_WITH_AES_128_CBC_SHA256) @@ -423,6 +449,7 @@ def getCertSuites(settings, version=None): # FFDHE key exchange, RSA authentication dheCertSuites = [] + dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) @@ -450,8 +477,12 @@ def getAnonSuites(settings, version=None): @staticmethod def canonicalCipherName(ciphersuite): - "Return the canonical name of the cipher whose number is provided." - if ciphersuite in CipherSuite.aes128Suites: + """Return the canonical name of the cipher whose number is provided.""" + if ciphersuite in CipherSuite.aes128GcmSuites: + return "aes128gcm" + elif ciphersuite in CipherSuite.aes256GcmSuites: + return "aes256gcm" + elif ciphersuite in CipherSuite.aes128Suites: return "aes128" elif ciphersuite in CipherSuite.aes256Suites: return "aes256" @@ -464,8 +495,12 @@ def canonicalCipherName(ciphersuite): @staticmethod def canonicalMacName(ciphersuite): - "Return the canonical name of the MAC whose number is provided." - if ciphersuite in CipherSuite.shaSuites: + """Return the canonical name of the MAC whose number is provided.""" + if ciphersuite in CipherSuite.sha384Suites: + return "sha384" + elif ciphersuite in CipherSuite.sha256Suites: + return "sha256" + elif ciphersuite in CipherSuite.shaSuites: return "sha" elif ciphersuite in CipherSuite.md5Suites: return "md5" diff --git a/tlslite/handshakehashes.py b/tlslite/handshakehashes.py index d3114398..6718e2a2 100644 --- a/tlslite/handshakehashes.py +++ b/tlslite/handshakehashes.py @@ -21,6 +21,7 @@ def __init__(self): self._handshakeMD5 = hashlib.md5() self._handshakeSHA = hashlib.sha1() self._handshakeSHA256 = hashlib.sha256() + self._handshakeSHA384 = hashlib.sha384() def update(self, data): """ @@ -33,6 +34,7 @@ def update(self, data): self._handshakeMD5.update(text) self._handshakeSHA.update(text) self._handshakeSHA256.update(text) + self._handshakeSHA384.update(text) def digest(self, digest=None): """ @@ -51,6 +53,8 @@ def digest(self, digest=None): return self._handshakeSHA.digest() elif digest == 'sha256': return self._handshakeSHA256.digest() + elif digest == 'sha384': + return self._handshakeSHA384.digest() else: raise ValueError("Unknown digest name") @@ -96,4 +100,5 @@ def copy(self): other._handshakeMD5 = self._handshakeMD5.copy() other._handshakeSHA = self._handshakeSHA.copy() other._handshakeSHA256 = self._handshakeSHA256.copy() + other._handshakeSHA384 = self._handshakeSHA384.copy() return other diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 6ba2ffb4..e7bcc109 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -11,7 +11,7 @@ from .utils import cryptomath from .utils import cipherfactory -CIPHER_NAMES = ["aes128gcm", "rc4", "aes256", "aes128", "3des"] +CIPHER_NAMES = ["aes256gcm", "aes128gcm", "rc4", "aes256", "aes128", "3des"] MAC_NAMES = ["sha", "sha256", "aead"] # Don't allow "md5" by default. ALL_MAC_NAMES = MAC_NAMES + ["md5"] KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "srp_sha", "srp_sha_rsa", "dh_anon"] @@ -186,8 +186,9 @@ def validate(self): raise ValueError("maxVersion set incorrectly") if other.maxVersion < (3,3): - # No sha256 pre TLS 1.2 - other.macNames = [e for e in self.macNames if e != "sha256"] + # No sha-2 and AEAD pre TLS 1.2 + other.macNames = [e for e in self.macNames if \ + e == "sha" or e == "md5"] if other.useEncryptThenMAC not in (True, False): raise ValueError("useEncryptThenMAC can only be True or False") diff --git a/tlslite/mathtls.py b/tlslite/mathtls.py index 60a331ab..411d534d 100644 --- a/tlslite/mathtls.py +++ b/tlslite/mathtls.py @@ -2,6 +2,7 @@ # Trevor Perrin # Dave Baggett (Arcode Corporation) - MD5 support for MAC_SSL # Yngve Pettersen (ported by Paul Sokolovsky) - TLS 1.2 +# Hubert Kario - SHA384 PRF # # See the LICENSE file for legal information regarding use of this file. @@ -9,6 +10,7 @@ from .utils.compat import * from .utils.cryptomath import * +from .constants import CipherSuite import hmac @@ -51,8 +53,13 @@ def PRF(secret, label, seed, length): return p_md5 def PRF_1_2(secret, label, seed, length): + """Pseudo Random Function for TLS1.2 ciphers that use SHA256""" return P_hash(HMAC_SHA256, secret, label + seed, length) +def PRF_1_2_SHA384(secret, label, seed, length): + """Pseudo Random Function for TLS1.2 ciphers that use SHA384""" + return P_hash(HMAC_SHA384, secret, label + seed, length) + def PRF_SSL(secret, seed, length): bytes = bytearray(length) index = 0 @@ -67,7 +74,9 @@ def PRF_SSL(secret, seed, length): index += 1 return bytes -def calcMasterSecret(version, premasterSecret, clientRandom, serverRandom): +def calcMasterSecret(version, cipherSuite, premasterSecret, clientRandom, + serverRandom): + """Derive Master Secret from premaster secret and random values""" if version == (3,0): masterSecret = PRF_SSL(premasterSecret, clientRandom + serverRandom, 48) @@ -75,8 +84,16 @@ def calcMasterSecret(version, premasterSecret, clientRandom, serverRandom): masterSecret = PRF(premasterSecret, b"master secret", clientRandom + serverRandom, 48) elif version == (3,3): - masterSecret = PRF_1_2(premasterSecret, b"master secret", - clientRandom + serverRandom, 48) + if cipherSuite in CipherSuite.sha384PrfSuites: + masterSecret = PRF_1_2_SHA384(premasterSecret, + b"master secret", + clientRandom + serverRandom, + 48) + else: + masterSecret = PRF_1_2(premasterSecret, + b"master secret", + clientRandom + serverRandom, + 48) else: raise AssertionError() return masterSecret diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index dd305d55..c40a6501 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -16,7 +16,8 @@ from .utils.cryptomath import getRandomBytes from .errors import TLSRecordOverflow, TLSIllegalParameterException,\ TLSAbruptCloseError, TLSDecryptionFailed, TLSBadRecordMAC -from .mathtls import createMAC_SSL, createHMAC, PRF_SSL, PRF, PRF_1_2 +from .mathtls import createMAC_SSL, createHMAC, PRF_SSL, PRF, PRF_1_2, \ + PRF_1_2_SHA384 class RecordSocket(object): @@ -621,7 +622,11 @@ def changeReadState(self): def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, serverRandom, implementations): """Create pending states for encryption and decryption.""" - if cipherSuite in CipherSuite.aes128GcmSuites: + if cipherSuite in CipherSuite.aes256GcmSuites: + keyLength = 32 + ivLength = 4 + createCipherFunc = createAESGCM + elif cipherSuite in CipherSuite.aes128GcmSuites: keyLength = 16 ivLength = 4 createCipherFunc = createAESGCM @@ -679,10 +684,16 @@ def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, serverRandom + clientRandom, outputLength) elif self.version == (3, 3): - keyBlock = PRF_1_2(masterSecret, - b"key expansion", - serverRandom + clientRandom, - outputLength) + if cipherSuite in CipherSuite.sha384PrfSuites: + keyBlock = PRF_1_2_SHA384(masterSecret, + b"key expansion", + serverRandom + clientRandom, + outputLength) + else: + keyBlock = PRF_1_2(masterSecret, + b"key expansion", + serverRandom + clientRandom, + outputLength) else: raise AssertionError() diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 0b1074d2..7e848dad 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -750,9 +750,12 @@ def _clientResume(self, session, serverHello, clientRandom, cipherImplementations) #Exchange ChangeCipherSpec and Finished messages - for result in self._getFinished(session.masterSecret): + for result in self._getFinished(session.masterSecret, + session.cipherSuite): yield result - for result in self._sendFinished(session.masterSecret, nextProto): + for result in self._sendFinished(session.masterSecret, + session.cipherSuite, + nextProto): yield result #Set the session for this connection @@ -970,9 +973,10 @@ def _clientRSAKeyExchange(self, settings, cipherSuite, signatureAlgorithm = None if self.version == (3,0): masterSecret = calcMasterSecret(self.version, - premasterSecret, - clientRandom, - serverRandom) + cipherSuite, + premasterSecret, + clientRandom, + serverRandom) verifyBytes = self._handshake_hash.digestSSL(masterSecret, b"") elif self.version in ((3,1), (3,2)): verifyBytes = self._handshake_hash.digest() @@ -1139,9 +1143,10 @@ def _clientDHEKeyExchange(self, settings, cipherSuite, signatureAlgorithm = None if self.version == (3,0): masterSecret = calcMasterSecret(self.version, - premasterSecret, - clientRandom, - serverRandom) + cipherSuite, + premasterSecret, + clientRandom, + serverRandom) verifyBytes = self._handshake_hash.digestSSL(masterSecret, b"") elif self.version in ((3,1), (3,2)): verifyBytes = self._handshake_hash.digest() @@ -1163,16 +1168,21 @@ def _clientDHEKeyExchange(self, settings, cipherSuite, def _clientFinished(self, premasterSecret, clientRandom, serverRandom, cipherSuite, cipherImplementations, nextProto): - masterSecret = calcMasterSecret(self.version, premasterSecret, - clientRandom, serverRandom) + masterSecret = calcMasterSecret(self.version, + cipherSuite, + premasterSecret, + clientRandom, + serverRandom) self._calcPendingStates(cipherSuite, masterSecret, clientRandom, serverRandom, cipherImplementations) #Exchange ChangeCipherSpec and Finished messages - for result in self._sendFinished(masterSecret, nextProto): + for result in self._sendFinished(masterSecret, cipherSuite, nextProto): yield result - for result in self._getFinished(masterSecret, nextProto=nextProto): + for result in self._getFinished(masterSecret, + cipherSuite, + nextProto=nextProto): yield result yield masterSecret @@ -1617,9 +1627,11 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, settings.cipherImplementations) #Exchange ChangeCipherSpec and Finished messages - for result in self._sendFinished(session.masterSecret): + for result in self._sendFinished(session.masterSecret, + session.cipherSuite): yield result - for result in self._getFinished(session.masterSecret): + for result in self._getFinished(session.masterSecret, + session.cipherSuite): yield result #Set the session @@ -1818,8 +1830,11 @@ def _serverCertKeyExchange(self, clientHello, serverHello, #Get and check CertificateVerify, if relevant if clientCertChain: if self.version == (3,0): - masterSecret = calcMasterSecret(self.version, premasterSecret, - clientHello.random, serverHello.random) + masterSecret = calcMasterSecret(self.version, + cipherSuite, + premasterSecret, + clientHello.random, + serverHello.random) verifyBytes = self._handshake_hash.digestSSL(masterSecret, b"") elif self.version in ((3,1), (3,2)): verifyBytes = self._handshake_hash.digest() @@ -1899,8 +1914,11 @@ def _serverAnonKeyExchange(self, clientHello, serverHello, cipherSuite, def _serverFinished(self, premasterSecret, clientRandom, serverRandom, cipherSuite, cipherImplementations, nextProtos): - masterSecret = calcMasterSecret(self.version, premasterSecret, - clientRandom, serverRandom) + masterSecret = calcMasterSecret(self.version, + cipherSuite, + premasterSecret, + clientRandom, + serverRandom) #Calculate pending connection states self._calcPendingStates(cipherSuite, masterSecret, @@ -1908,11 +1926,12 @@ def _serverFinished(self, premasterSecret, clientRandom, serverRandom, cipherImplementations) #Exchange ChangeCipherSpec and Finished messages - for result in self._getFinished(masterSecret, - expect_next_protocol=nextProtos is not None): + for result in self._getFinished(masterSecret, + cipherSuite, + expect_next_protocol=nextProtos is not None): yield result - for result in self._sendFinished(masterSecret): + for result in self._sendFinished(masterSecret, cipherSuite): yield result yield masterSecret @@ -1923,7 +1942,7 @@ def _serverFinished(self, premasterSecret, clientRandom, serverRandom, #********************************************************* - def _sendFinished(self, masterSecret, nextProto=None): + def _sendFinished(self, masterSecret, cipherSuite=None, nextProto=None): #Send ChangeCipherSpec for result in self._sendMsg(ChangeCipherSpec()): yield result @@ -1937,7 +1956,7 @@ def _sendFinished(self, masterSecret, nextProto=None): yield result #Calculate verification data - verifyData = self._calcFinished(masterSecret, True) + verifyData = self._calcFinished(masterSecret, cipherSuite, True) if self.fault == Fault.badFinished: verifyData[0] = (verifyData[0]+1)%256 @@ -1946,7 +1965,8 @@ def _sendFinished(self, masterSecret, nextProto=None): for result in self._sendMsg(finished): yield result - def _getFinished(self, masterSecret, expect_next_protocol=False, nextProto=None): + def _getFinished(self, masterSecret, cipherSuite=None, + expect_next_protocol=False, nextProto=None): #Get and check ChangeCipherSpec for result in self._getMsg(ContentType.change_cipher_spec): if result in (0,1): @@ -1980,7 +2000,7 @@ def _getFinished(self, masterSecret, expect_next_protocol=False, nextProto=None) self.next_proto = nextProto #Calculate verification data - verifyData = self._calcFinished(masterSecret, False) + verifyData = self._calcFinished(masterSecret, cipherSuite, False) #Get and check Finished message under new state for result in self._getMsg(ContentType.handshake, @@ -1993,7 +2013,7 @@ def _getFinished(self, masterSecret, expect_next_protocol=False, nextProto=None) "Finished message is incorrect"): yield result - def _calcFinished(self, masterSecret, send=True): + def _calcFinished(self, masterSecret, cipherSuite, send=True): if self.version == (3,0): if (self._client and send) or (not self._client and not send): senderStr = b"\x43\x4C\x4E\x54" @@ -2018,8 +2038,12 @@ def _calcFinished(self, masterSecret, send=True): else: label = b"server finished" - handshakeHashes = self._handshake_hash.digest('sha256') - verifyData = PRF_1_2(masterSecret, label, handshakeHashes, 12) + if cipherSuite in CipherSuite.sha384PrfSuites: + handshakeHashes = self._handshake_hash.digest('sha384') + verifyData = PRF_1_2_SHA384(masterSecret, label, handshakeHashes, 12) + else: + handshakeHashes = self._handshake_hash.digest('sha256') + verifyData = PRF_1_2(masterSecret, label, handshakeHashes, 12) return verifyData else: raise AssertionError() diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index f5024135..0332ce42 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -89,6 +89,11 @@ def HMAC_SHA256(k, b): b = compatHMAC(b) return bytearray(hmac.new(k, b, hashlib.sha256).digest()) +def HMAC_SHA384(k, b): + k = compatHMAC(k) + b = compatHMAC(b) + return bytearray(hmac.new(k, b, hashlib.sha384).digest()) + # ************************************************************************** # Converter Functions # ************************************************************************** diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index d06c8f24..c63339b2 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -69,7 +69,7 @@ def test_cipherNames_with_unknown_name(self): def test_cipherNames_with_unknown_name(self): hs = HandshakeSettings() - hs.cipherNames = ["aes256gcm", "aes256"] + hs.cipherNames = ["camellia256gcm", "aes256"] with self.assertRaises(ValueError): hs.validate() diff --git a/unit_tests/test_tlslite_mathtls.py b/unit_tests/test_tlslite_mathtls.py index 164c5e6e..9068288d 100644 --- a/unit_tests/test_tlslite_mathtls.py +++ b/unit_tests/test_tlslite_mathtls.py @@ -13,7 +13,7 @@ class TestCalcMasterSecret(unittest.TestCase): def test_with_empty_values(self): - ret = calcMasterSecret((3, 3), bytearray(48), bytearray(32), + ret = calcMasterSecret((3, 3), 0, bytearray(48), bytearray(32), bytearray(32)) self.assertEqual(bytearray( diff --git a/unit_tests/test_tlslite_tlsrecordlayer.py b/unit_tests/test_tlslite_tlsrecordlayer.py index ed8c17a9..1a95aff5 100644 --- a/unit_tests/test_tlslite_tlsrecordlayer.py +++ b/unit_tests/test_tlslite_tlsrecordlayer.py @@ -725,8 +725,11 @@ def test_full_connection_with_RSA_kex(self): else: break - master_secret = calcMasterSecret((3,3), premasterSecret, - client_hello.random, server_hello.random) + master_secret = calcMasterSecret((3,3), + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + premasterSecret, + client_hello.random, + server_hello.random) record_layer._calcPendingStates( CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, @@ -773,8 +776,10 @@ def test_full_connection_with_RSA_kex(self): srv_premaster_secret) srv_master_secret = calcMasterSecret(srv_record_layer.version, - srv_premaster_secret, srv_client_hello.random, - srv_server_hello.random) + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + srv_premaster_secret, + srv_client_hello.random, + srv_server_hello.random) srv_record_layer._calcPendingStates(srv_cipher_suite, srv_master_secret, srv_client_hello.random, @@ -942,8 +947,11 @@ def test_full_connection_with_external_server(self): else: break - master_secret = calcMasterSecret((3,3), premasterSecret, - client_hello.random, server_hello.random) + master_secret = calcMasterSecret((3,3), + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + premasterSecret, + client_hello.random, + server_hello.random) record_layer._calcPendingStates( CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, From 835da2f159814da9c18740215288ce5b43c86047 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 12:01:48 +0200 Subject: [PATCH 113/574] recordlayer test coverage for AES-256-GCM --- unit_tests/test_tlslite_recordlayer.py | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 708928e3..7ad079b2 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -789,6 +789,37 @@ def test_recvRecord_with_AES128GCM_invalid_side(self): else: break + def test_sendRecord_with_AES256GCM(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 3) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsNotNone(app_data) + + for result in recordLayer.sendRecord(app_data): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x03' + # TLSv1.2 + b'\x00\x1c' # length + )) + self.assertEqual(sock.sent[0][5:], bytearray( + b'\x00\x00\x00\x00\x00\x00\x00\x00\xb5c\x15\x8c' + + b'\xe3\x92H6l\x90\x19\xef\x96\xbfT}\xe8\xbaE\xa3')) # tlslite has no pure python implementation of 3DES @unittest.skipUnless(cryptomath.m2cryptoLoaded or cryptomath.pycryptoLoaded, From c394ccd0a67df23fe399e3aceb3d6978819913df Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 12:15:18 +0200 Subject: [PATCH 114/574] clean up recordlayer break up calcPendingStates --- tlslite/recordlayer.py | 52 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index c40a6501..e239ba88 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -206,6 +206,7 @@ def __init__(self): """Create an instance with empty encryption and MACing contexts""" self.macContext = None self.encContext = None + self.fixedNonce = None self.seqnum = 0 def getSeqNumBytes(self): @@ -619,9 +620,9 @@ def changeReadState(self): self._readState = self._pendingReadState self._pendingReadState = ConnectionState() - def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, - serverRandom, implementations): - """Create pending states for encryption and decryption.""" + @staticmethod + def _getCipherSettings(cipherSuite): + """Get the settings for cipher suite used""" if cipherSuite in CipherSuite.aes256GcmSuites: keyLength = 32 ivLength = 4 @@ -649,6 +650,11 @@ def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, else: raise AssertionError() + return (keyLength, ivLength, createCipherFunc) + + @staticmethod + def _getMacSettings(cipherSuite): + """Get settings for HMAC used""" if cipherSuite in CipherSuite.aeadSuites: macLength = 0 digestmod = None @@ -664,16 +670,22 @@ def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, else: raise AssertionError() - if not digestmod: - createMACFunc = None - elif self.version == (3, 0): + return macLength, digestmod + + @staticmethod + def _getHMACMethod(version): + """Get the HMAC method""" + assert version in ((3, 0), (3, 1), (3, 2), (3, 3)) + if version == (3, 0): createMACFunc = createMAC_SSL - elif self.version in ((3, 1), (3, 2), (3, 3)): + elif version in ((3, 1), (3, 2), (3, 3)): createMACFunc = createHMAC - outputLength = (macLength*2) + (keyLength*2) + (ivLength*2) + return createMACFunc - #Calculate Keying Material from Master Secret + def _calcKeyBlock(self, cipherSuite, masterSecret, clientRandom, + serverRandom, outputLength): + """Calculate the overall key to slice up""" if self.version == (3, 0): keyBlock = PRF_SSL(masterSecret, serverRandom + clientRandom, @@ -697,6 +709,28 @@ def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, else: raise AssertionError() + return keyBlock + + def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, + serverRandom, implementations): + """Create pending states for encryption and decryption.""" + + keyLength, ivLength, createCipherFunc = \ + self._getCipherSettings(cipherSuite) + + macLength, digestmod = self._getMacSettings(cipherSuite) + + if not digestmod: + createMACFunc = None + else: + createMACFunc = self._getHMACMethod(self.version) + + outputLength = (macLength*2) + (keyLength*2) + (ivLength*2) + + #Calculate Keying Material from Master Secret + keyBlock = self._calcKeyBlock(cipherSuite, masterSecret, clientRandom, + serverRandom, outputLength) + #Slice up Keying Material clientPendingState = ConnectionState() serverPendingState = ConnectionState() From e8e433aed335cd0f4c7de26868f61a8565141ad4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 13:47:09 +0200 Subject: [PATCH 115/574] add TLS_DH_ANON_WITH_RC4_128_MD5 --- tlslite/constants.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 2473f61f..af904e70 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -219,6 +219,8 @@ class CipherSuite: ietfNames[0x0039] = 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA' # RFC 5246 - TLS v1.2 Protocol + TLS_DH_ANON_WITH_RC4_128_MD5 = 0x0018 + ietfNames[0x0018] = 'TLS_DH_ANON_WITH_RC4_128_MD5' TLS_DH_ANON_WITH_AES_128_CBC_SHA = 0x0034 ietfNames[0x0034] = 'TLS_DH_ANON_WITH_AES_128_CBC_SHA' TLS_DH_ANON_WITH_AES_256_CBC_SHA = 0x003A @@ -289,6 +291,7 @@ class CipherSuite: # RC4 128 stream cipher rc4Suites = [] + rc4Suites.append(TLS_DH_ANON_WITH_RC4_128_MD5) rc4Suites.append(TLS_RSA_WITH_RC4_128_SHA) rc4Suites.append(TLS_RSA_WITH_RC4_128_MD5) @@ -334,6 +337,7 @@ class CipherSuite: # MD-5 HMAC, protocol default PRF md5Suites = [] + md5Suites.append(TLS_DH_ANON_WITH_RC4_128_MD5) md5Suites.append(TLS_RSA_WITH_RC4_128_MD5) # SSL3, TLS1.0, TLS1.1 and TLS1.2 compatible ciphers @@ -468,6 +472,7 @@ def getDheCertSuites(settings, version=None): anonSuites = [] anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) + anonSuites.append(TLS_DH_ANON_WITH_RC4_128_MD5) @staticmethod def getAnonSuites(settings, version=None): From 3ecec63e063b7c789c8c071aa19229e3b5d62660 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 13:49:35 +0200 Subject: [PATCH 116/574] add TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA --- tlslite/constants.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index af904e70..f4e28219 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -221,6 +221,8 @@ class CipherSuite: # RFC 5246 - TLS v1.2 Protocol TLS_DH_ANON_WITH_RC4_128_MD5 = 0x0018 ietfNames[0x0018] = 'TLS_DH_ANON_WITH_RC4_128_MD5' + TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA = 0x001B + ietfNames[0x001B] = 'TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA' TLS_DH_ANON_WITH_AES_128_CBC_SHA = 0x0034 ietfNames[0x0034] = 'TLS_DH_ANON_WITH_AES_128_CBC_SHA' TLS_DH_ANON_WITH_AES_256_CBC_SHA = 0x003A @@ -258,6 +260,7 @@ class CipherSuite: 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) + tripleDESSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) # AES-128 CBC ciphers aes128Suites = [] @@ -312,6 +315,7 @@ class CipherSuite: 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) + shaSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) # SHA-256 HMAC, SHA-256 PRF sha256Suites = [] @@ -473,6 +477,7 @@ def getDheCertSuites(settings, version=None): anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) anonSuites.append(TLS_DH_ANON_WITH_RC4_128_MD5) + anonSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) @staticmethod def getAnonSuites(settings, version=None): From f0f58fc93b5748a6bb55e034d2b40d348a9aabb5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 14:21:30 +0200 Subject: [PATCH 117/574] add support for null ciphers - TLS_RSA_WITH_NULL_MD5 --- tlslite/constants.py | 13 +++++++++++++ tlslite/handshakesettings.py | 3 ++- tlslite/recordlayer.py | 19 +++++++++++++------ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index f4e28219..628e1353 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -195,6 +195,9 @@ class CipherSuite: TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021 ietfNames[0xC021] = 'TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA' + # RFC 5246 - TLS v1.2 Protocol + TLS_RSA_WITH_NULL_MD5 = 0x0001 + ietfNames[0x0001] = 'TLS_RSA_WITH_NULL_MD5' # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A @@ -298,6 +301,10 @@ class CipherSuite: rc4Suites.append(TLS_RSA_WITH_RC4_128_SHA) rc4Suites.append(TLS_RSA_WITH_RC4_128_MD5) + # no encryption + nullSuites = [] + nullSuites.append(TLS_RSA_WITH_NULL_MD5) + # SHA-1 HMAC, protocol default PRF shaSuites = [] shaSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) @@ -343,6 +350,7 @@ class CipherSuite: md5Suites = [] md5Suites.append(TLS_DH_ANON_WITH_RC4_128_MD5) md5Suites.append(TLS_RSA_WITH_RC4_128_MD5) + md5Suites.append(TLS_RSA_WITH_NULL_MD5) # SSL3, TLS1.0, TLS1.1 and TLS1.2 compatible ciphers ssl3Suites = [] @@ -397,6 +405,8 @@ def _filterSuites(suites, settings, version=None): cipherSuites += CipherSuite.tripleDESSuites if "rc4" in cipherNames: cipherSuites += CipherSuite.rc4Suites + if "null" in cipherNames: + cipherSuites += CipherSuite.nullSuites keyExchangeSuites = [] if "rsa" in keyExchangeNames: @@ -450,6 +460,7 @@ def getSrpAllSuites(settings, version=None): certSuites.append(TLS_RSA_WITH_3DES_EDE_CBC_SHA) certSuites.append(TLS_RSA_WITH_RC4_128_SHA) certSuites.append(TLS_RSA_WITH_RC4_128_MD5) + certSuites.append(TLS_RSA_WITH_NULL_MD5) @staticmethod def getCertSuites(settings, version=None): @@ -500,6 +511,8 @@ def canonicalCipherName(ciphersuite): return "rc4" elif ciphersuite in CipherSuite.tripleDESSuites: return "3des" + elif ciphersuite in CipherSuite.nullSuites: + return "null" else: return None diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index e7bcc109..5094f26f 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -12,6 +12,7 @@ from .utils import cipherfactory CIPHER_NAMES = ["aes256gcm", "aes128gcm", "rc4", "aes256", "aes128", "3des"] +ALL_CIPHER_NAMES = CIPHER_NAMES + ["null"] # require encryption by default MAC_NAMES = ["sha", "sha256", "aead"] # Don't allow "md5" by default. ALL_MAC_NAMES = MAC_NAMES + ["md5"] KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "srp_sha", "srp_sha_rsa", "dh_anon"] @@ -161,7 +162,7 @@ def validate(self): if other.maxKeySize < other.minKeySize: raise ValueError("maxKeySize smaller than minKeySize") for s in other.cipherNames: - if s not in CIPHER_NAMES: + if s not in ALL_CIPHER_NAMES: raise ValueError("Unknown cipher name: '%s'" % s) for s in other.macNames: if s not in ALL_MAC_NAMES: diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index e239ba88..673e4b5a 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -325,10 +325,10 @@ def _macThenEncrypt(self, data, contentType): seqnumBytes = self._writeState.getSeqNumBytes() mac = self._writeState.macContext.copy() macBytes = self._calculateMAC(mac, seqnumBytes, contentType, data) + data += macBytes #Encrypt for Block or Stream Cipher if self._writeState.encContext: - data += macBytes #Add padding (for Block Cipher): if self._writeState.encContext.isBlockCipher: @@ -420,6 +420,8 @@ def sendRecord(self, msg): def _decryptThenMAC(self, recordType, data): """Decrypt data, check padding and MAC""" + totalPaddingLength = 0 + paddingGood = True if self._readState.encContext: assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) @@ -450,10 +452,9 @@ def _decryptThenMAC(self, recordType, data): #Decrypt if it's a stream cipher else: - paddingGood = True data = self._readState.encContext.decrypt(data) - totalPaddingLength = 0 + if self._readState.macContext: #Check MAC macGood = True macLength = self._readState.macContext.digest_size @@ -647,6 +648,10 @@ def _getCipherSettings(cipherSuite): keyLength = 24 ivLength = 8 createCipherFunc = createTripleDES + elif cipherSuite in CipherSuite.nullSuites: + keyLength = 0 + ivLength = 0 + createCipherFunc = None else: raise AssertionError() @@ -714,7 +719,6 @@ def _calcKeyBlock(self, cipherSuite, masterSecret, clientRandom, def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, serverRandom, implementations): """Create pending states for encryption and decryption.""" - keyLength, ivLength, createCipherFunc = \ self._getCipherSettings(cipherSuite) @@ -748,10 +752,13 @@ def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, compatHMAC(clientMACBlock), digestmod=digestmod) serverPendingState.macContext = createMACFunc( compatHMAC(serverMACBlock), digestmod=digestmod) - clientPendingState.encContext = createCipherFunc(clientKeyBlock, + if createCipherFunc is not None: + clientPendingState.encContext = \ + createCipherFunc(clientKeyBlock, clientIVBlock, implementations) - serverPendingState.encContext = createCipherFunc(serverKeyBlock, + serverPendingState.encContext = \ + createCipherFunc(serverKeyBlock, serverIVBlock, implementations) else: From b6053ddaa2d5c4537c3c308ea575c6847278e01b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 14:24:14 +0200 Subject: [PATCH 118/574] add TLS_RSA_WITH_NULL_SHA --- tlslite/constants.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 628e1353..743dddde 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -198,6 +198,8 @@ class CipherSuite: # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_NULL_MD5 = 0x0001 ietfNames[0x0001] = 'TLS_RSA_WITH_NULL_MD5' + TLS_RSA_WITH_NULL_SHA = 0x0002 + ietfNames[0x0002] = 'TLS_RSA_WITH_NULL_SHA' # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A @@ -304,6 +306,7 @@ class CipherSuite: # no encryption nullSuites = [] nullSuites.append(TLS_RSA_WITH_NULL_MD5) + nullSuites.append(TLS_RSA_WITH_NULL_SHA) # SHA-1 HMAC, protocol default PRF shaSuites = [] @@ -323,6 +326,7 @@ class CipherSuite: shaSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) shaSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) shaSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) + shaSuites.append(TLS_RSA_WITH_NULL_SHA) # SHA-256 HMAC, SHA-256 PRF sha256Suites = [] @@ -461,6 +465,7 @@ def getSrpAllSuites(settings, version=None): certSuites.append(TLS_RSA_WITH_RC4_128_SHA) certSuites.append(TLS_RSA_WITH_RC4_128_MD5) certSuites.append(TLS_RSA_WITH_NULL_MD5) + certSuites.append(TLS_RSA_WITH_NULL_SHA) @staticmethod def getCertSuites(settings, version=None): From ccd293d107ae517d617a5e51177a7ea36fc94040 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 14:27:20 +0200 Subject: [PATCH 119/574] add TLS_RSA_WITH_NULL_SHA256 --- tlslite/constants.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 743dddde..b3b7eeca 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -200,6 +200,8 @@ class CipherSuite: ietfNames[0x0001] = 'TLS_RSA_WITH_NULL_MD5' TLS_RSA_WITH_NULL_SHA = 0x0002 ietfNames[0x0002] = 'TLS_RSA_WITH_NULL_SHA' + TLS_RSA_WITH_NULL_SHA256 = 0x003B + ietfNames[0x003B] = 'TLS_RSA_WITH_NULL_SHA256' # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A @@ -307,6 +309,7 @@ class CipherSuite: nullSuites = [] nullSuites.append(TLS_RSA_WITH_NULL_MD5) nullSuites.append(TLS_RSA_WITH_NULL_SHA) + nullSuites.append(TLS_RSA_WITH_NULL_SHA256) # SHA-1 HMAC, protocol default PRF shaSuites = [] @@ -336,6 +339,7 @@ class CipherSuite: sha256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) sha256Suites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) sha256Suites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) + sha256Suites.append(TLS_RSA_WITH_NULL_SHA256) # SHA-384 HMAC, SHA-384 PRF sha384Suites = [] @@ -466,6 +470,7 @@ def getSrpAllSuites(settings, version=None): certSuites.append(TLS_RSA_WITH_RC4_128_MD5) certSuites.append(TLS_RSA_WITH_NULL_MD5) certSuites.append(TLS_RSA_WITH_NULL_SHA) + certSuites.append(TLS_RSA_WITH_NULL_SHA256) @staticmethod def getCertSuites(settings, version=None): From 223fa8f3f5b9e7856a84efb78da482314d85a55e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 14:44:47 +0200 Subject: [PATCH 120/574] add TLS_DH_ANON_WITH_AES_128_CBC_SHA256 --- tlslite/constants.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index b3b7eeca..085ea3bc 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -234,6 +234,8 @@ class CipherSuite: ietfNames[0x0034] = 'TLS_DH_ANON_WITH_AES_128_CBC_SHA' TLS_DH_ANON_WITH_AES_256_CBC_SHA = 0x003A ietfNames[0x003A] = 'TLS_DH_ANON_WITH_AES_256_CBC_SHA' + TLS_DH_ANON_WITH_AES_128_CBC_SHA256 = 0x006C + ietfNames[0x006C] = 'TLS_DH_ANON_WITH_AES_128_CBC_SHA256' # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C @@ -278,6 +280,7 @@ class CipherSuite: aes128Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA256) aes128Suites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) + aes128Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) # AES-256 CBC ciphers aes256Suites = [] @@ -340,6 +343,7 @@ class CipherSuite: sha256Suites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) sha256Suites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) sha256Suites.append(TLS_RSA_WITH_NULL_SHA256) + sha256Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) # SHA-384 HMAC, SHA-384 PRF sha384Suites = [] @@ -499,6 +503,7 @@ def getDheCertSuites(settings, version=None): anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) anonSuites.append(TLS_DH_ANON_WITH_RC4_128_MD5) anonSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) + anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) @staticmethod def getAnonSuites(settings, version=None): From e3d3203360b1ced8d1499fb77fdc6edca9ed5301 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 14:47:42 +0200 Subject: [PATCH 121/574] add TLS_DH_ANON_WITH_AES_256_CBC_SHA256 --- tlslite/constants.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 085ea3bc..07466687 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -236,6 +236,8 @@ class CipherSuite: ietfNames[0x003A] = 'TLS_DH_ANON_WITH_AES_256_CBC_SHA' TLS_DH_ANON_WITH_AES_128_CBC_SHA256 = 0x006C ietfNames[0x006C] = 'TLS_DH_ANON_WITH_AES_128_CBC_SHA256' + TLS_DH_ANON_WITH_AES_256_CBC_SHA256 = 0x006D + ietfNames[0x006D] = 'TLS_DH_ANON_WITH_AES_256_CBC_SHA256' # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C @@ -291,6 +293,7 @@ class CipherSuite: aes256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) aes256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) + aes256Suites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) # AES-128 GCM ciphers aes128GcmSuites = [] @@ -344,6 +347,7 @@ class CipherSuite: sha256Suites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) sha256Suites.append(TLS_RSA_WITH_NULL_SHA256) sha256Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) + sha256Suites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) # SHA-384 HMAC, SHA-384 PRF sha384Suites = [] @@ -504,6 +508,7 @@ def getDheCertSuites(settings, version=None): anonSuites.append(TLS_DH_ANON_WITH_RC4_128_MD5) anonSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) + anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) @staticmethod def getAnonSuites(settings, version=None): From a09b1cf22f7b7199a10bc6f6bdb4108e13307b29 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 14:51:03 +0200 Subject: [PATCH 122/574] add TLS_DH_ANON_WITH_AES_128_GCM_SHA256 --- tlslite/constants.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 07466687..c37a3494 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -238,6 +238,8 @@ class CipherSuite: ietfNames[0x006C] = 'TLS_DH_ANON_WITH_AES_128_CBC_SHA256' TLS_DH_ANON_WITH_AES_256_CBC_SHA256 = 0x006D ietfNames[0x006D] = 'TLS_DH_ANON_WITH_AES_256_CBC_SHA256' + TLS_DH_ANON_WITH_AES_128_GCM_SHA256 = 0x00A6 + ietfNames[0x00A6] = 'TLS_DH_ANON_WITH_AES_128_GCM_SHA256' # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C @@ -299,6 +301,7 @@ class CipherSuite: aes128GcmSuites = [] aes128GcmSuites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) aes128GcmSuites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) + aes128GcmSuites.append(TLS_DH_ANON_WITH_AES_128_GCM_SHA256) # AES-256-GCM ciphers (implicit SHA384, see sha384PrfSuites) aes256GcmSuites = [] @@ -509,6 +512,7 @@ def getDheCertSuites(settings, version=None): anonSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) + anonSuites.append(TLS_DH_ANON_WITH_AES_128_GCM_SHA256) @staticmethod def getAnonSuites(settings, version=None): From f8930cf70437b7e46d0612f01a51e14f2afcdb2c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 14:53:10 +0200 Subject: [PATCH 123/574] add TLS_DH_ANON_WITH_AES_256_GCM_SHA384 --- tlslite/constants.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index c37a3494..a299eb88 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -240,6 +240,8 @@ class CipherSuite: ietfNames[0x006D] = 'TLS_DH_ANON_WITH_AES_256_CBC_SHA256' TLS_DH_ANON_WITH_AES_128_GCM_SHA256 = 0x00A6 ietfNames[0x00A6] = 'TLS_DH_ANON_WITH_AES_128_GCM_SHA256' + TLS_DH_ANON_WITH_AES_256_GCM_SHA384 = 0x00A7 + ietfNames[0x00A7] = 'TLS_DH_ANON_WITH_AES_256_GCM_SHA384' # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C @@ -307,6 +309,7 @@ class CipherSuite: aes256GcmSuites = [] aes256GcmSuites.append(TLS_RSA_WITH_AES_256_GCM_SHA384) aes256GcmSuites.append(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) + aes256GcmSuites.append(TLS_DH_ANON_WITH_AES_256_GCM_SHA384) # RC4 128 stream cipher rc4Suites = [] @@ -513,6 +516,7 @@ def getDheCertSuites(settings, version=None): anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) anonSuites.append(TLS_DH_ANON_WITH_AES_128_GCM_SHA256) + anonSuites.append(TLS_DH_ANON_WITH_AES_256_GCM_SHA384) @staticmethod def getAnonSuites(settings, version=None): From 92ee55122ea35a498d0df36d7f636f852d1d09dd Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 5 Jul 2015 11:37:41 +0200 Subject: [PATCH 124/574] split stream cipher reading from CBC ciphers reading since timing of operations is critical for CBC ciphers and is of secondary importance for stream ciphers, don't process them in same method --- tlslite/recordlayer.py | 92 ++++++++++++++++++-------- unit_tests/test_tlslite_recordlayer.py | 55 +++++++++++++++ 2 files changed, 119 insertions(+), 28 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 673e4b5a..358e3a03 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -418,41 +418,73 @@ def sendRecord(self, msg): # receiving messages # + def _decryptStreamThenMAC(self, recordType, data): + """Decrypt a stream cipher and check MAC""" + if self._readState.encContext: + assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) + + data = self._readState.encContext.decrypt(data) + + if self._readState.macContext: + #Check MAC + macGood = True + macLength = self._readState.macContext.digest_size + endLength = macLength + if endLength > len(data): + macGood = False + else: + #Read MAC + startIndex = len(data) - endLength + endIndex = startIndex + macLength + checkBytes = data[startIndex : endIndex] + + #Calculate MAC + seqnumBytes = self._readState.getSeqNumBytes() + data = data[:-endLength] + mac = self._readState.macContext.copy() + macBytes = self._calculateMAC(mac, seqnumBytes, recordType, + data) + + #Compare MACs + if macBytes != checkBytes: + macGood = False + + if not macGood: + raise TLSBadRecordMAC() + + return data + + def _decryptThenMAC(self, recordType, data): """Decrypt data, check padding and MAC""" totalPaddingLength = 0 paddingGood = True if self._readState.encContext: assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) + assert self._readState.encContext.isBlockCipher - #Decrypt if it's a block cipher - if self._readState.encContext.isBlockCipher: - blockLength = self._readState.encContext.block_size - if len(data) % blockLength != 0: - raise TLSDecryptionFailed() - data = self._readState.encContext.decrypt(data) - if self.version >= (3, 2): #For TLS 1.1, remove explicit IV - data = data[self._readState.encContext.block_size : ] - - #Check padding - paddingGood = True - paddingLength = data[-1] - if (paddingLength+1) > len(data): - paddingGood = False - totalPaddingLength = 0 - else: - totalPaddingLength = paddingLength+1 - if self.version != (3, 0): - # check if all padding bytes have correct value - paddingBytes = data[-totalPaddingLength:-1] - for byte in paddingBytes: - if byte != paddingLength: - paddingGood = False - totalPaddingLength = 0 - - #Decrypt if it's a stream cipher + blockLength = self._readState.encContext.block_size + if len(data) % blockLength != 0: + raise TLSDecryptionFailed() + data = self._readState.encContext.decrypt(data) + if self.version >= (3, 2): #For TLS 1.1, remove explicit IV + data = data[self._readState.encContext.block_size : ] + + #Check padding + paddingGood = True + paddingLength = data[-1] + if (paddingLength+1) > len(data): + paddingGood = False + totalPaddingLength = 0 else: - data = self._readState.encContext.decrypt(data) + totalPaddingLength = paddingLength+1 + if self.version != (3, 0): + # check if all padding bytes have correct value + paddingBytes = data[-totalPaddingLength:-1] + for byte in paddingBytes: + if byte != paddingLength: + paddingGood = False + totalPaddingLength = 0 if self._readState.macContext: #Check MAC @@ -592,8 +624,12 @@ def recvRecord(self): data = self._decryptAndUnseal(header.type, data) elif self.encryptThenMAC: data = self._macThenDecrypt(header.type, data) - else: + elif self._readState and \ + self._readState.encContext and \ + self._readState.encContext.isBlockCipher: data = self._decryptThenMAC(header.type, data) + else: + data = self._decryptStreamThenMAC(header.type, data) yield (header, Parser(data)) diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 7ad079b2..b05f6366 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -1170,6 +1170,61 @@ def test_recvRecord_with_stream_cipher_and_tls1_0(self): self.assertEqual((3, 1), header.version) self.assertEqual(bytearray(b'test'), parser.bytes) + def test_recvRecord_with_stream_cipher_and_tls1_0_and_incorrect_data(self): + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLSv1.0 + b'\x00\x18' + # length (24 bytes) + # data from test_sendRecord_with_stream_cipher_and_tls1_0 + # last byte changed from \x1e to \x0e + b'B\xb8H\xc6\xd7\\\x01\xe27\xa9\x86\xf2\xfdm!\x1d' + + b'\xa1\xaf]Q%y5\x0e' + )) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 1) + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_RC4_128_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + with self.assertRaises(TLSBadRecordMAC): + for result in recordLayer.recvRecord(): + if result in (0, 1): + self.assertTrue(False, "Blocking read") + else: + break + + def test_recvRecord_with_stream_cipher_and_tls1_0_and_too_short_data(self): + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLSv1.0 + b'\x00\x13' + # length (19 bytes) + # data from test_sendRecord_with_stream_cipher_and_tls1_0 + b'B\xb8H\xc6\xd7\\\x01\xe27\xa9\x86\xf2\xfdm!\x1d' + + b'\xa1\xaf]' + )) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 1) + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_RC4_128_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + with self.assertRaises(TLSBadRecordMAC): + for result in recordLayer.recvRecord(): + if result in (0, 1): + self.assertTrue(False, "Blocking read") + else: + break + def test_recvRecord_with_invalid_length_payload(self): sock = MockSocket(bytearray( b'\x17' + # application data From d7b288316bca7bcdd082e6ccff5491e241305233 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 5 Jul 2015 14:42:56 +0200 Subject: [PATCH 125/574] constant time implementation of CBC mac and pad check to protect against Lucky 13 attacks, we need to check both the hash and padding in constant time, independent of the length of padding --- tlslite/utils/constanttime.py | 201 ++++++++ unit_tests/test_tlslite_utils_constanttime.py | 435 ++++++++++++++++++ 2 files changed, 636 insertions(+) create mode 100644 tlslite/utils/constanttime.py create mode 100644 unit_tests/test_tlslite_utils_constanttime.py diff --git a/tlslite/utils/constanttime.py b/tlslite/utils/constanttime.py new file mode 100644 index 00000000..c68077fb --- /dev/null +++ b/tlslite/utils/constanttime.py @@ -0,0 +1,201 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. +"""Various constant time functions for processing sensitive data""" + +from __future__ import division + +from .compat import compatHMAC +import hmac + +def ct_lt_u32(val_a, val_b): + """ + Returns 1 if val_a < val_b, 0 otherwise. Constant time. + + @type val_a: int + @type val_b: int + @param val_a: an unsigned integer representable as a 32 bit value + @param val_b: an unsigned integer representable as a 32 bit value + @rtype: int + """ + val_a &= 0xffffffff + val_b &= 0xffffffff + + return (val_a^((val_a^val_b)|(((val_a-val_b)&0xffffffff)^val_b)))>>31 + +def ct_gt_u32(val_a, val_b): + """ + Return 1 if val_a > val_b, 0 otherwise. Constant time. + + @type val_a: int + @type val_b: int + @param val_a: an unsigned integer representable as a 32 bit value + @param val_b: an unsigned integer representable as a 32 bit value + @rtype: int + """ + return ct_lt_u32(val_b, val_a) + +def ct_le_u32(val_a, val_b): + """ + Return 1 if val_a <= val_b, 0 otherwise. Constant time. + + @type val_a: int + @type val_b: int + @param val_a: an unsigned integer representable as a 32 bit value + @param val_b: an unsigned integer representable as a 32 bit value + @rtype: int + """ + return 1 ^ ct_gt_u32(val_a, val_b) + +def ct_lsb_prop_u8(val): + """Propagate LSB to all 8 bits of the returned byte. Constant time.""" + val &= 0x01 + val |= val << 1 + val |= val << 2 + val |= val << 4 + return val + +def ct_isnonzero_u32(val): + """ + Returns 1 if val is != 0, 0 otherwise. Constant time. + + @type val: int + @param val: an unsigned integer representable as a 32 bit value + @rtype: int + """ + val &= 0xffffffff + return (val|(-val&0xffffffff)) >> 31 + +def ct_neq_u32(val_a, val_b): + """ + Return 1 if val_a != val_b, 0 otherwise. Constant time. + + @type val_a: int + @type val_b: int + @param val_a: an unsigned integer representable as a 32 bit value + @param val_b: an unsigned integer representable as a 32 bit value + @rtype: int + """ + val_a &= 0xffffffff + val_b &= 0xffffffff + + return (((val_a-val_b)&0xffffffff) | ((val_b-val_a)&0xffffffff)) >> 31 + +def ct_eq_u32(val_a, val_b): + """ + Return 1 if val_a == val_b, 0 otherwise. Constant time. + + @type val_a: int + @type val_b: int + @param val_a: an unsigned integer representable as a 32 bit value + @param val_b: an unsigned integer representable as a 32 bit value + @rtype: int + """ + return 1 ^ ct_neq_u32(val_a, val_b) + +def ct_check_cbc_mac_and_pad(data, mac, seqnumBytes, contentType, version): + """ + Check CBC cipher HMAC and padding. Close to constant time. + + @type data: bytearray + @param data: data with HMAC value to test and padding + + @type mac: hashlib mac + @param mac: empty HMAC, initialised with a key + + @type seqnumBytes: bytearray + @param seqnumBytes: TLS sequence number, used as input to HMAC + + @type contentType: int + @param contentType: a single byte, used as input to HMAC + + @type version: tuple of int + @param version: a tuple of two ints, used as input to HMAC and to guide + checking of padding + + @rtype: boolean + @return: True if MAC and pad is ok, False otherwise + """ + assert version in ((3, 0), (3, 1), (3, 2), (3, 3)) + + data_len = len(data) + if mac.digest_size + 1 > data_len: # data_len is public + return False + + # 0 - OK + result = 0x00 + + # + # check padding + # + pad_length = data[data_len-1] + pad_start = data_len - pad_length - 1 + pad_start = max(0, pad_start) + + if version == (3, 0): # version is public + # in SSLv3 we can only check if pad is not longer than overall length + + # subtract 1 for the pad length byte + mask = ct_lsb_prop_u8(ct_lt_u32(data_len-1, pad_length)) + result |= mask + else: + start_pos = max(0, data_len - 256) + for i in range(start_pos, data_len): + # if pad_start < i: mask = 0xff; else: mask = 0x00 + mask = ct_lsb_prop_u8(ct_le_u32(pad_start, i)) + # if data[i] != pad_length and "inside_pad": result = False + result |= (data[i] ^ pad_length) & mask + + # + # check MAC + # + + # real place where mac starts and data ends + mac_start = pad_start - mac.digest_size + mac_start = max(0, mac_start) + + # place to start processing + start_pos = max(0, data_len - (256 + mac.digest_size)) // mac.block_size + start_pos *= mac.block_size + + # add start data + data_mac = mac.copy() + data_mac.update(compatHMAC(seqnumBytes)) + data_mac.update(compatHMAC(bytearray([contentType]))) + if version != (3, 0): # version is public + data_mac.update(compatHMAC(bytearray([version[0]]))) + data_mac.update(compatHMAC(bytearray([version[1]]))) + data_mac.update(compatHMAC(bytearray([mac_start >> 8]))) + data_mac.update(compatHMAC(bytearray([mac_start & 0xff]))) + data_mac.update(compatHMAC(data[:start_pos])) + + # don't check past the array end (already checked to be >= zero) + end_pos = data_len - 1 - mac.digest_size + + # calculate all possible + for i in range(start_pos, end_pos): # constant for given overall length + cur_mac = data_mac.copy() + cur_mac.update(compatHMAC(data[start_pos:i])) + mac_compare = bytearray(cur_mac.digest()) + # compare the hash for real only if it's the place where mac is + # supposed to be + mask = ct_lsb_prop_u8(ct_eq_u32(i, mac_start)) + for j in range(0, mac.digest_size): # digest_size is public + result |= (data[i+j] ^ mac_compare[j]) & mask + + # return python boolean + return result == 0 + +if hasattr(hmac, 'compare_digest'): + ct_compare_digest = hmac.compare_digest +else: + def ct_compare_digest(val_a, val_b): + """Compares if string like objects are equal. Constant time.""" + if len(val_a) != len(val_b): + return False + + result = 0 + for x, y in zip(val_a, val_b): + result |= x ^ y + + return result == 0 diff --git a/unit_tests/test_tlslite_utils_constanttime.py b/unit_tests/test_tlslite_utils_constanttime.py new file mode 100644 index 00000000..e30ecf29 --- /dev/null +++ b/unit_tests/test_tlslite_utils_constanttime.py @@ -0,0 +1,435 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.utils.constanttime import ct_lt_u32, ct_gt_u32, ct_le_u32, \ + ct_lsb_prop_u8, ct_isnonzero_u32, ct_neq_u32, ct_eq_u32, \ + ct_check_cbc_mac_and_pad, ct_compare_digest + +from tlslite.utils.compat import compatHMAC +from tlslite.recordlayer import RecordLayer +import hashlib +import hmac + +class TestContanttime(unittest.TestCase): + def test_ct_lt_u32(self): + for i in range(0, 256): + for j in range(0, 256): + self.assertEqual((i < j), (ct_lt_u32(i, j) == 1)) + + for i in range(2**32-256, 2**32): + for j in range(2**32-256, 2**32): + self.assertEqual((i < j), (ct_lt_u32(i, j) == 1)) + + for i in range(0, 256): + for j in range(2**32-256, 2**32): + self.assertEqual((i < j), (ct_lt_u32(i, j) == 1)) + + for i in range(2**32-256, 2**32): + for j in range(0, 256): + self.assertEqual((i < j), (ct_lt_u32(i, j) == 1)) + + def test_ct_gt_u32(self): + for i in range(0, 256): + for j in range(0, 256): + self.assertEqual((i > j), (ct_gt_u32(i, j) == 1)) + + for i in range(2**32-256, 2**32): + for j in range(2**32-256, 2**32): + self.assertEqual((i > j), (ct_gt_u32(i, j) == 1)) + + for i in range(0, 256): + for j in range(2**32-256, 2**32): + self.assertEqual((i > j), (ct_gt_u32(i, j) == 1)) + + for i in range(2**32-256, 2**32): + for j in range(0, 256): + self.assertEqual((i > j), (ct_gt_u32(i, j) == 1)) + + def test_ct_le_u32(self): + for i in range(0, 256): + for j in range(0, 256): + self.assertEqual((i <= j), (ct_le_u32(i, j) == 1)) + + for i in range(2**32-256, 2**32): + for j in range(2**32-256, 2**32): + self.assertEqual((i <= j), (ct_le_u32(i, j) == 1)) + + for i in range(0, 256): + for j in range(2**32-256, 2**32): + self.assertEqual((i <= j), (ct_le_u32(i, j) == 1)) + + for i in range(2**32-256, 2**32): + for j in range(0, 256): + self.assertEqual((i <= j), (ct_le_u32(i, j) == 1)) + + def test_ct_lsb_prop_u8(self): + for i in range(0, 256): + self.assertEqual(((i & 0x1) == 1), (ct_lsb_prop_u8(i) == 0xff)) + self.assertEqual(((i & 0x1) == 0), (ct_lsb_prop_u8(i) == 0x00)) + + def test_ct_isnonzero_u32(self): + for i in range(0, 256): + self.assertEqual((i != 0), (ct_isnonzero_u32(i) == 1)) + + def test_ct_neq_u32(self): + for i in range(0, 256): + for j in range(0, 256): + self.assertEqual((i != j), (ct_neq_u32(i, j) == 1)) + + for i in range(2**32-128, 2**32): + for j in range(2**32-128, 2**32): + self.assertEqual((i != j), (ct_neq_u32(i, j) == 1)) + + def test_ct_eq_u32(self): + for i in range(0, 256): + for j in range(0, 256): + self.assertEqual((i == j), (ct_eq_u32(i, j) == 1)) + + for i in range(2**32-128, 2**32): + for j in range(2**32-128, 2**32): + self.assertEqual((i == j), (ct_eq_u32(i, j) == 1)) + +class TestContanttimeCBCCheck(unittest.TestCase): + + @staticmethod + def data_prepare(application_data, seqnum_bytes, content_type, version, + mac, key): + r_layer = RecordLayer(None) + r_layer.version = version + + h = hmac.new(key, digestmod=mac) + + digest = r_layer._calculateMAC(h, seqnum_bytes, content_type, + application_data) + + return application_data + digest + + def test_with_empty_data_and_minimum_pad(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 1) + application_data = bytearray(0) + mac = hashlib.sha1 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\x00') + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertTrue(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_empty_data_and_maximum_pad(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 1) + application_data = bytearray(0) + mac = hashlib.sha1 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\xff'*256) + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertTrue(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_little_data_and_minimum_pad(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 1) + application_data = bytearray(b'\x01'*32) + mac = hashlib.sha1 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\x00') + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertTrue(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_little_data_and_maximum_pad(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 1) + application_data = bytearray(b'\x01'*32) + mac = hashlib.sha1 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\xff'*256) + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertTrue(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_lots_of_data_and_minimum_pad(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 1) + application_data = bytearray(b'\x01'*1024) + mac = hashlib.sha1 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\x00') + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertTrue(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_lots_of_data_and_maximum_pad(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 1) + application_data = bytearray(b'\x01'*1024) + mac = hashlib.sha1 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\xff'*256) + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertTrue(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_lots_of_data_and_small_pad(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 1) + application_data = bytearray(b'\x01'*1024) + mac = hashlib.sha1 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\x0a'*11) + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertTrue(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_too_little_data(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 1) + mac = hashlib.sha1 + + data = bytearray(mac().digest_size) + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertFalse(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_invalid_hash(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 1) + application_data = bytearray(b'\x01'*1024) + mac = hashlib.sha1 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + data[-1] ^= 0xff + + padding = bytearray(b'\xff'*256) + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertFalse(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_invalid_pad(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 1) + application_data = bytearray(b'\x01'*1024) + mac = hashlib.sha1 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\x00' + b'\xff'*255) + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertFalse(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_pad_longer_than_data(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 1) + application_data = bytearray(b'\x01') + mac = hashlib.sha1 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\xff') + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertFalse(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_pad_longer_than_data_in_SSLv3(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 0) + application_data = bytearray(b'\x01') + mac = hashlib.sha1 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray([len(application_data) + mac().digest_size + 1]) + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertFalse(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_null_pad_in_SSLv3(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 0) + application_data = bytearray(b'\x01'*10) + mac = hashlib.md5 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\x00'*10 + b'\x0a') + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertTrue(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_MD5(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 1) + application_data = bytearray(b'\x01'*10) + mac = hashlib.md5 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\x0a'*11) + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertTrue(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_SHA256(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 3) + application_data = bytearray(b'\x01'*10) + mac = hashlib.sha256 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\x0a'*11) + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertTrue(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + + def test_with_SHA384(self): + key = compatHMAC(bytearray(20)) + seqnum_bytes = bytearray(16) + content_type = 0x14 + version = (3, 3) + application_data = bytearray(b'\x01'*10) + mac = hashlib.sha384 + + data = self.data_prepare(application_data, seqnum_bytes, content_type, + version, mac, key) + + padding = bytearray(b'\x0a'*11) + data += padding + + h = hmac.new(key, digestmod=mac) + h.block_size = mac().block_size # python2 workaround + self.assertTrue(ct_check_cbc_mac_and_pad(data, h, seqnum_bytes, + content_type, version)) + +class TestCompareDigest(unittest.TestCase): + def test_with_equal_length(self): + self.assertTrue(ct_compare_digest(bytearray(10), bytearray(10))) + + self.assertTrue(ct_compare_digest(bytearray(b'\x02'*8), + bytearray(b'\x02'*8))) + + def test_different_lengths(self): + self.assertFalse(ct_compare_digest(bytearray(10), bytearray(12))) + + self.assertFalse(ct_compare_digest(bytearray(20), bytearray(12))) + + def test_different(self): + self.assertFalse(ct_compare_digest(bytearray(b'\x01'), + bytearray(b'\x03'))) + + self.assertFalse(ct_compare_digest(bytearray(b'\x01'*10 + b'\x02'), + bytearray(b'\x01'*10 + b'\x03'))) + + self.assertFalse(ct_compare_digest(bytearray(b'\x02' + b'\x01'*10), + bytearray(b'\x03' + b'\x01'*10))) From 60d4f1c6ea70308f07f4999626bb60a81758eb17 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 5 Jul 2015 15:34:19 +0200 Subject: [PATCH 126/574] fix Lucky 13, mostly because variables in python are stored in hashtables, it's not possible to make a real constant time implementation, do a best effort anyway --- tlslite/mathtls.py | 6 +++- tlslite/recordlayer.py | 64 +++++++++++++++--------------------------- 2 files changed, 27 insertions(+), 43 deletions(-) diff --git a/tlslite/mathtls.py b/tlslite/mathtls.py index 411d534d..1a9755f1 100644 --- a/tlslite/mathtls.py +++ b/tlslite/mathtls.py @@ -131,7 +131,9 @@ def makeK(N, g): return bytesToNumber(SHA1(numberToByteArray(N) + PAD(N, g))) def createHMAC(k, digestmod=hashlib.sha1): - return hmac.HMAC(k, digestmod=digestmod) + h = hmac.HMAC(k, digestmod=digestmod) + h.block_size = digestmod().block_size + return h def createMAC_SSL(k, digestmod=None): mac = MAC_SSL() @@ -142,6 +144,7 @@ def createMAC_SSL(k, digestmod=None): class MAC_SSL(object): def create(self, k, digestmod=None): self.digestmod = digestmod or hashlib.sha1 + self.block_size = self.digestmod().block_size # Repeat pad bytes 48 times for MD5; 40 times for other hash functions. self.digest_size = 16 if (self.digestmod is hashlib.md5) else 20 repeat = 40 if self.digest_size == 20 else 48 @@ -160,6 +163,7 @@ def copy(self): new.ohash = self.ohash.copy() new.digestmod = self.digestmod new.digest_size = self.digest_size + new.block_size = self.block_size return new def digest(self): diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 358e3a03..e73cf240 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -14,6 +14,7 @@ from .utils.codec import Parser, Writer from .utils.compat import compatHMAC from .utils.cryptomath import getRandomBytes +from .utils.constanttime import ct_compare_digest, ct_check_cbc_mac_and_pad from .errors import TLSRecordOverflow, TLSIllegalParameterException,\ TLSAbruptCloseError, TLSDecryptionFailed, TLSBadRecordMAC from .mathtls import createMAC_SSL, createHMAC, PRF_SSL, PRF, PRF_1_2, \ @@ -446,7 +447,7 @@ def _decryptStreamThenMAC(self, recordType, data): data) #Compare MACs - if macBytes != checkBytes: + if not ct_compare_digest(macBytes, checkBytes): macGood = False if not macGood: @@ -457,12 +458,14 @@ def _decryptStreamThenMAC(self, recordType, data): def _decryptThenMAC(self, recordType, data): """Decrypt data, check padding and MAC""" - totalPaddingLength = 0 - paddingGood = True if self._readState.encContext: assert self.version in ((3, 0), (3, 1), (3, 2), (3, 3)) assert self._readState.encContext.isBlockCipher + assert self._readState.macContext + # + # decrypt the record + # blockLength = self._readState.encContext.block_size if len(data) % blockLength != 0: raise TLSDecryptionFailed() @@ -470,48 +473,25 @@ def _decryptThenMAC(self, recordType, data): if self.version >= (3, 2): #For TLS 1.1, remove explicit IV data = data[self._readState.encContext.block_size : ] - #Check padding - paddingGood = True - paddingLength = data[-1] - if (paddingLength+1) > len(data): - paddingGood = False - totalPaddingLength = 0 - else: - totalPaddingLength = paddingLength+1 - if self.version != (3, 0): - # check if all padding bytes have correct value - paddingBytes = data[-totalPaddingLength:-1] - for byte in paddingBytes: - if byte != paddingLength: - paddingGood = False - totalPaddingLength = 0 + # + # check padding and MAC + # + seqnumBytes = self._readState.getSeqNumBytes() - if self._readState.macContext: - #Check MAC - macGood = True - macLength = self._readState.macContext.digest_size - endLength = macLength + totalPaddingLength - if endLength > len(data): - macGood = False - else: - #Read MAC - startIndex = len(data) - endLength - endIndex = startIndex + macLength - checkBytes = data[startIndex : endIndex] + if not ct_check_cbc_mac_and_pad(data, + self._readState.macContext, + seqnumBytes, + recordType, + self.version): + raise TLSBadRecordMAC() - #Calculate MAC - seqnumBytes = self._readState.getSeqNumBytes() - data = data[:-endLength] - mac = self._readState.macContext.copy() - macBytes = self._calculateMAC(mac, seqnumBytes, recordType, - data) + # + # strip padding and MAC + # - #Compare MACs - if macBytes != checkBytes: - macGood = False + endLength = data[-1] + 1 + self._readState.macContext.digest_size - if not (paddingGood and macGood): - raise TLSBadRecordMAC() + data = data[:-endLength] return data @@ -535,7 +515,7 @@ def _macThenDecrypt(self, recordType, buf): macBytes = self._calculateMAC(mac, seqnumBytes, recordType, buf) - if macBytes != checkBytes: + if not ct_compare_digest(macBytes, checkBytes): raise TLSBadRecordMAC("MAC mismatch") if self._readState.encContext: From fb127d41032f59ebf6a7e65112f1fa2f0ca71ec2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 13 Jul 2015 13:42:43 +0200 Subject: [PATCH 127/574] README update after recent merges --- README.md | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a6745b82..422e6248 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.5.0-alpha3 2015-06-20 +tlslite-ng version 0.5.0-alpha4 2015-07-13 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -501,20 +501,35 @@ may not work with all asyncore.dispatcher subclasses. tlslite-ng is beta-quality code. It hasn't received much security analysis. Use at your own risk. -tlslite-ng does NOT verify certificates by default. +tlslite-ng **CANNOT** verify certificates - you must use external means to +check if the certificate is the expected one. -tlslite-ng's pure-python ciphers are probably vulnerable to timing attacks. - -tlslite-ng **is** vulnerable to the "Lucky 13" timing attack if AES or 3DES -are used, or the weak cipher RC4 otherwise. This unhappy situation will remain -until tlslite-ng implements authenticated-encryption ciphersuites (like GCM), or -RFC 7366 and allows refusing connections which don't use them. +Because python execution environmnet uses hash tables to store variables (that +includes functions, objects and classes) it's very hard to create +implementations that are timing attack resistant. This includes both the +pure-python implementation of ciphers (i.e. AES or 3DES) and the HMAC and +padding check of ciphers working in CBC MAC-then-encrypt mode. +In other words, pure-python (tlslite-ng internal) implementations of all +ciphers, as well as all CBC mode ciphers working in MAC-then-encrypt mode +are **NOT** secure. Don't use them. Prefer AEAD ciphersuites (AES-GCM) or +encrypt-then-MAC mode for CBC ciphers. 12 History =========== 0.5.0-alpha - xx/xx/xxxx - Hubert Kario + - remove most CBC MAC-ing and padding timing side-channel leaks (should fix + CVE-2013-0169, a.k.a. Lucky13) + - add support for NULL encryption - TLS_RSA_WITH_NULL_MD5, + TLS_RSA_WITH_NULL_SHA and TLS_RSA_WITH_NULL_SHA256 ciphersuites + - add more ADH ciphers (TLS_DH_ANON_WITH_RC4_128_MD5, + TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA, TLS_DH_ANON_WITH_AES_128_CBC_SHA256, + TLS_DH_ANON_WITH_AES_256_CBC_SHA256, TLS_DH_ANON_WITH_AES_128_GCM_SHA256, + TLS_DH_ANON_WITH_AES_256_GCM_SHA384) + - implement a TLS record layer abstraction that makes it very easy to handle + TLS handshake and alert protocol messages (MessageSocket) + - fix reqCert option in tls.py server - implement AES-256-GCM ciphersuites and SHA384 PRF - implement AES-GCM cipher and AES-128-GCM ciphersuites (David Benjamin - Chromium) From e095bd423aa5d1c50bcf47ccaf516cab32303846 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 19:21:55 +0200 Subject: [PATCH 128/574] don't send empty SNI --- tlslite/tlsconnection.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 7e848dad..c4944186 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -152,9 +152,9 @@ def __init__(self, sock): # Client Handshake Functions #********************************************************* - def handshakeClientAnonymous(self, session=None, settings=None, - checker=None, serverName="", - async=False): + def handshakeClientAnonymous(self, session=None, settings=None, + checker=None, serverName=None, + async=False): """Perform an anonymous handshake in the role of client. This function performs an SSL or TLS handshake using an @@ -218,8 +218,8 @@ def handshakeClientAnonymous(self, session=None, settings=None, pass def handshakeClientSRP(self, username, password, session=None, - settings=None, checker=None, - reqTack=True, serverName="", + settings=None, checker=None, + reqTack=True, serverName=None, async=False): """Perform an SRP handshake in the role of client. @@ -301,7 +301,7 @@ def handshakeClientSRP(self, username, password, session=None, def handshakeClientCert(self, certChain=None, privateKey=None, session=None, settings=None, checker=None, - nextProtos=None, reqTack=True, serverName="", + nextProtos=None, reqTack=True, serverName=None, async=False): """Perform a certificate-based handshake in the role of client. @@ -378,10 +378,13 @@ def handshakeClientCert(self, certChain=None, privateKey=None, @raise tlslite.errors.TLSAuthenticationError: If the checker doesn't like the other party's authentication credentials. """ - handshaker = self._handshakeClientAsync(certParams=(certChain, - privateKey), session=session, settings=settings, - checker=checker, serverName=serverName, - nextProtos=nextProtos, reqTack=reqTack) + handshaker = \ + self._handshakeClientAsync(certParams=(certChain, privateKey), + session=session, settings=settings, + checker=checker, + serverName=serverName, + nextProtos=nextProtos, + reqTack=reqTack) # The handshaker is a Python Generator which executes the handshake. # It allows the handshake to be run in a "piecewise", asynchronous # fashion, returning 1 when it is waiting to able to write, 0 when @@ -396,8 +399,8 @@ def handshakeClientCert(self, certChain=None, privateKey=None, def _handshakeClientAsync(self, srpParams=(), certParams=(), anonParams=(), - session=None, settings=None, checker=None, - nextProtos=None, serverName="", reqTack=True): + session=None, settings=None, checker=None, + nextProtos=None, serverName=None, reqTack=True): handshaker = self._handshakeClientAsyncHelper(srpParams=srpParams, certParams=certParams, From c871ed00c490523d820a4efc939dfe44b16b024c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 19:49:00 +0200 Subject: [PATCH 129/574] test coverage for renegotiation_info extension --- unit_tests/test_tlslite_extensions.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 9097d21a..f5950bb5 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -111,6 +111,18 @@ def test_parse_with_NPN_ext(self): self.assertEqual(ext.protocols, [b'http/1.1']) + def test_parse_with_renego_info_server_side(self): + p = Parser(bytearray( + b'\xff\x01' + # type of extension - renegotiation_info + b'\x00\x01' + # overall length + b'\x00' # extension length + )) + + ext = TLSExtension(server=True).parse(p) + + # XXX not supported + self.assertIsInstance(ext, TLSExtension) + def test_parse_with_elliptic_curves(self): p = Parser(bytearray( b'\x00\x0a' + # type of extension From b5c690354bf9a5b95d49415797229d7169f52c23 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 27 Jun 2015 19:49:27 +0200 Subject: [PATCH 130/574] fix parsing server side server_name extension --- tlslite/extensions.py | 7 +++++-- unit_tests/test_tlslite_extensions.py | 26 +++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 0db62e19..c8187b09 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -350,8 +350,8 @@ def write(self): return w.bytes def parse(self, p): - """ Parses the on the wire extension data and returns an object that - represents it. + """ + Deserialise the extension from on-the-wire data The parser should not include the type or length of extension! @@ -362,6 +362,9 @@ def parse(self, p): @raise SyntaxError: when the internal sizes don't match the attached data """ + if p.getRemainingLength() == 0: + return self + self.server_names = [] p.startLengthCheck(2) diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index f5950bb5..4f59921f 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -111,6 +111,17 @@ def test_parse_with_NPN_ext(self): self.assertEqual(ext.protocols, [b'http/1.1']) + def test_parse_with_SNI_server_side(self): + p = Parser(bytearray( + b'\x00\x00' + # type of extension - SNI + b'\x00\x00' # overall length - 0 bytes + )) + + ext = TLSExtension(server=True).parse(p) + + self.assertIsInstance(ext, SNIExtension) + self.assertEqual(ext.server_names, None) + def test_parse_with_renego_info_server_side(self): p = Parser(bytearray( b'\xff\x01' + # type of extension - renegotiation_info @@ -414,13 +425,22 @@ def test_write_of_empty_list_of_names(self): b'\x00\x00' # length of array of names - 0 bytes ), server_name.write()) - def test_parse(self): + def tes_parse_with_invalid_data(self): server_name = SNIExtension() - p = Parser(bytearray(0)) + p = Parser(bytearray(b'\x00\x01')) with self.assertRaises(SyntaxError): - server_name = server_name.parse(p) + server_name.parse(p) + + def test_parse_of_server_side_version(self): + server_name = SNIExtension() + + p = Parser(bytearray(0)) + + server_name = server_name.parse(p) + + self.assertIsNone(server_name.server_names) def test_parse_null_length_array(self): server_name = SNIExtension() From a7ca105513853a59e979da1f589ad92b2df213bb Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 30 Jun 2015 00:22:01 +0200 Subject: [PATCH 131/574] add Supported Groups extension a.k.a supported_curves, needed to support ECDHE and ECDSA later on --- tlslite/constants.py | 51 +++++++++++++++++- tlslite/extensions.py | 71 +++++++++++++++++++++++++ unit_tests/test_tlslite_extensions.py | 76 ++++++++++++++++++++++++--- 3 files changed, 190 insertions(+), 8 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index a299eb88..79e43212 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -42,8 +42,9 @@ class ContentType: class ExtensionType: # RFC 6066 / 4366 server_name = 0 # RFC 6066 / 4366 - srp = 12 # RFC 5054 cert_type = 9 # RFC 6091 + supported_groups = 10 # RFC 4492, RFC-ietf-tls-negotiated-ff-dhe-10 + srp = 12 # RFC 5054 encrypt_then_mac = 22 # RFC 7366 tack = 0xF300 supports_npn = 13172 @@ -69,6 +70,54 @@ class SignatureAlgorithm: dsa = 2 ecdsa = 3 +class GroupName(object): + + """Name of groups supported for (EC)DH key exchange""" + + # RFC4492 + sect163k1 = 1 + sect163r1 = 2 + sect163r2 = 3 + sect193r1 = 4 + sect193r2 = 5 + sect233k1 = 6 + sect233r1 = 7 + sect239k1 = 8 + sect283k1 = 9 + sect283r1 = 10 + sect409k1 = 11 + sect409r1 = 12 + sect571k1 = 13 + sect571r1 = 14 + secp160k1 = 15 + secp160r1 = 16 + secp160r2 = 17 + secp192k1 = 18 + secp192r1 = 19 + secp224k1 = 20 + secp224r1 = 21 + secp256k1 = 22 + secp256r1 = 23 + secp384r1 = 24 + secp521r1 = 25 + allEC = list(range(1, 26)) + + # RFC7027 + brainpoolP256r1 = 26 + brainpoolP384r1 = 27 + brainpoolP512r1 = 28 + allEC.append(list(range(26, 29))) + + # RFC-ietf-tls-negotiated-ff-dhe-10 + ffdhe2048 = 256 + ffdhe3072 = 257 + ffdhe4096 = 258 + ffdhe6144 = 259 + ffdhe8192 = 260 + allFF = list(range(256, 261)) + + all = allEC + allFF + class NameType: host_name = 0 diff --git a/tlslite/extensions.py b/tlslite/extensions.py index c8187b09..f14e0465 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -912,9 +912,80 @@ def parse(self, p): return self +class SupportedGroupsExtension(TLSExtension): + + """ + Client side list of supported groups of (EC)DHE key exchage. + + See RFC4492, RFC7027 and RFC-ietf-tls-negotiated-ff-dhe-10 + + @type groups: int + @ivar groups: list of groups that the client supports + """ + + def __init__(self): + """Create instance of class""" + self.groups = None + + @property + def ext_type(self): + """ + Type of extension, in this case - 10 + + @rtype: int + """ + return ExtensionType.supported_groups + + @property + def ext_data(self): + """ + Return raw data encoding of the extension + + @rtype: bytearray + """ + if self.groups is None: + return bytearray(0) + + writer = Writer() + # encode length of two bytes per group in two bytes + writer.add(len(self.groups) * 2, 2) + for group in self.groups: + writer.add(group, 2) + return writer.bytes + + def create(self, groups): + """ + Set the supported groups in the extension + + @type groups: list of int + @param groups: list of supported groups + """ + self.groups = groups + return self + + def parse(self, parser): + """ + Deserialise extension from on-the-wire data + + @type parser: L{Parser} + @rtype: SupportedGroupsExtension + """ + if parser.getRemainingLength() == 0: + self.groups = None + return self + self.groups = [] + + parser.startLengthCheck(2) + while not parser.atLengthCheck(): + self.groups.append(parser.get(2)) + parser.stopLengthCheck() + + return self + TLSExtension._universal_extensions = { ExtensionType.server_name : SNIExtension, ExtensionType.cert_type : ClientCertTypeExtension, + ExtensionType.supported_groups : SupportedGroupsExtension, ExtensionType.srp : SRPExtension, ExtensionType.supports_npn : NPNExtension} diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 4f59921f..d204961d 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -10,9 +10,9 @@ import unittest from tlslite.extensions import TLSExtension, SNIExtension, NPNExtension,\ SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\ - TACKExtension + TACKExtension, SupportedGroupsExtension from tlslite.utils.codec import Parser -from tlslite.constants import NameType +from tlslite.constants import NameType, ExtensionType, GroupName from tlslite.errors import TLSInternalError class TestTLSExtension(unittest.TestCase): @@ -146,12 +146,11 @@ def test_parse_with_elliptic_curves(self): ext = TLSExtension().parse(p) - # XXX not supported - self.assertIsInstance(ext, TLSExtension) + self.assertIsInstance(ext, SupportedGroupsExtension) - #self.assertEqual(ext.curves, [EllipticCurves.secp256r1, - # EllipticCurves.secp384r1, - # EllipticCurves.secp521r1]) + self.assertEqual(ext.groups, [GroupName.secp256r1, + GroupName.secp384r1, + GroupName.secp521r1]) def test_parse_with_ec_point_formats(self): p = Parser(bytearray( @@ -1114,5 +1113,68 @@ def test___repr__(self): "])", repr(tack_ext)) +class TestSupportedGroups(unittest.TestCase): + def test___init__(self): + ext = SupportedGroupsExtension() + + self.assertIsNotNone(ext) + self.assertIsNone(ext.groups) + + def test_write(self): + ext = SupportedGroupsExtension() + ext.create([19, 21]) + + self.assertEqual(bytearray( + b'\x00\x0A' + # type of extension - 10 + b'\x00\x06' + # overall length of extension + b'\x00\x04' + # length of extension list array + b'\x00\x13' + # secp192r1 + b'\x00\x15' # secp224r1 + ), ext.write()) + + def test_write_empty(self): + ext = SupportedGroupsExtension() + + self.assertEqual(bytearray(b'\x00\x0A\x00\x00'), ext.write()) + + def test_parse(self): + parser = Parser(bytearray( + b'\x00\x04' + # length of extension list array + b'\x00\x13' + # secp192r1 + b'\x00\x15' # secp224r1 + )) + + ext = SupportedGroupsExtension().parse(parser) + + self.assertEqual(ext.ext_type, ExtensionType.supported_groups) + self.assertEqual(ext.groups, + [GroupName.secp192r1, GroupName.secp224r1]) + for group in ext.groups: + self.assertTrue(group in GroupName.allEC) + self.assertFalse(group in GroupName.allFF) + + def test_parse_with_empty_data(self): + parser = Parser(bytearray()) + + ext = SupportedGroupsExtension().parse(parser) + + self.assertEqual(ext.ext_type, ExtensionType.supported_groups) + self.assertIsNone(ext.groups) + + def test_parse_with_empty_array(self): + parser = Parser(bytearray(2)) + + ext = SupportedGroupsExtension().parse(parser) + + self.assertEqual([], ext.groups) + + def test_parse_with_invalid_data(self): + parser = Parser(bytearray(b'\x00\x01\x00')) + + ext = SupportedGroupsExtension() + + with self.assertRaises(SyntaxError): + ext.parse(parser) + if __name__ == '__main__': unittest.main() From 5bed5bd46b032403c247093c16119a9573849d96 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 2 Jul 2015 17:54:55 +0200 Subject: [PATCH 132/574] add Supported Point Formats Extension Add the ec_points_format extension from RFC4492 --- tlslite/constants.py | 13 +++++ tlslite/extensions.py | 69 +++++++++++++++++++++++++++ unit_tests/test_tlslite_extensions.py | 44 +++++++++++++++-- 3 files changed, 121 insertions(+), 5 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 79e43212..ac3777b5 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -44,6 +44,7 @@ class ExtensionType: # RFC 6066 / 4366 server_name = 0 # RFC 6066 / 4366 cert_type = 9 # RFC 6091 supported_groups = 10 # RFC 4492, RFC-ietf-tls-negotiated-ff-dhe-10 + ec_point_formats = 11 # RFC 4492 srp = 12 # RFC 5054 encrypt_then_mac = 22 # RFC 7366 tack = 0xF300 @@ -118,6 +119,18 @@ class GroupName(object): all = allEC + allFF +class ECPointFormat(object): + + """Names and ID's of supported EC point formats""" + + uncompressed = 0 + ansiX962_compressed_prime = 1 + ansiX962_compressed_char2 = 2 + + all = [uncompressed, + ansiX962_compressed_prime, + ansiX962_compressed_char2] + class NameType: host_name = 0 diff --git a/tlslite/extensions.py b/tlslite/extensions.py index f14e0465..d96ceedb 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -982,10 +982,79 @@ def parse(self, parser): return self +class ECPointFormatsExtension(TLSExtension): + + """ + Client side list of supported ECC point formats. + + See RFC4492. + """ + + def __init__(self): + """Create instance of class""" + self.formats = None + + @property + def ext_type(self): + """ + Type of extension, in this case - 11 + + @rtype: int + """ + return ExtensionType.ec_point_formats + + @property + def ext_data(self): + """ + Return raw encoding of the extension + + @rtype: bytearray + """ + if self.formats is None: + return bytearray(0) + + writer = Writer() + # the length is number of formats encoded in one byte + writer.add(len(self.formats), 1) + for fmt in self.formats: + writer.add(fmt, 1) + return writer.bytes + + def create(self, formats): + """ + Set the list of supported EC point formats + + @type formats: list of int + @param formats: list of supported EC point formats + """ + self.formats = formats + return self + + def parse(self, parser): + """ + Deserialise extension from on the wire data + + @type parser: L{Parser} + @rtype: ECPointFormatsExtension + """ + if parser.getRemainingLength() == 0: + self.formats = None + return self + + self.formats = [] + + parser.startLengthCheck(1) + while not parser.atLengthCheck(): + self.formats.append(parser.get(1)) + parser.stopLengthCheck() + + return self + TLSExtension._universal_extensions = { ExtensionType.server_name : SNIExtension, ExtensionType.cert_type : ClientCertTypeExtension, ExtensionType.supported_groups : SupportedGroupsExtension, + ExtensionType.ec_point_formats : ECPointFormatsExtension, ExtensionType.srp : SRPExtension, ExtensionType.supports_npn : NPNExtension} diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index d204961d..49304283 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -10,9 +10,9 @@ import unittest from tlslite.extensions import TLSExtension, SNIExtension, NPNExtension,\ SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\ - TACKExtension, SupportedGroupsExtension + TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension from tlslite.utils.codec import Parser -from tlslite.constants import NameType, ExtensionType, GroupName +from tlslite.constants import NameType, ExtensionType, GroupName, ECPointFormat from tlslite.errors import TLSInternalError class TestTLSExtension(unittest.TestCase): @@ -162,10 +162,9 @@ def test_parse_with_ec_point_formats(self): ext = TLSExtension().parse(p) - # XXX unsupported - self.assertIsInstance(ext, TLSExtension) + self.assertIsInstance(ext, ECPointFormatsExtension) - #self.assertEqual(ext.formats, [PointFormats.uncompressed]) + self.assertEqual(ext.formats, [ECPointFormat.uncompressed]) def test_parse_with_signature_algorithms(self): p = Parser(bytearray( @@ -1176,5 +1175,40 @@ def test_parse_with_invalid_data(self): with self.assertRaises(SyntaxError): ext.parse(parser) +class TestECPointFormatsExtension(unittest.TestCase): + def test___init__(self): + ext = ECPointFormatsExtension() + + self.assertIsNotNone(ext) + self.assertEqual(ext.ext_data, bytearray(0)) + self.assertEqual(ext.ext_type, 11) + + def test_write(self): + ext = ECPointFormatsExtension() + ext.create([ECPointFormat.ansiX962_compressed_prime]) + + self.assertEqual(bytearray( + b'\x00\x0b' + # type of extension + b'\x00\x02' + # overall length + b'\x01' + # length of list + b'\x01'), ext.write()) + + def test_parse(self): + parser = Parser(bytearray(b'\x01\x00')) + + ext = ECPointFormatsExtension() + self.assertIsNone(ext.formats) + ext.parse(parser) + self.assertEqual(ext.formats, [ECPointFormat.uncompressed]) + + def test_parse_with_empty_data(self): + parser = Parser(bytearray(0)) + + ext = ECPointFormatsExtension() + + ext.parse(parser) + + self.assertIsNone(ext.formats) + if __name__ == '__main__': unittest.main() From 0e29fe3a563455767c0b1fe32d85d44845e7d14b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 2 Jul 2015 18:31:41 +0200 Subject: [PATCH 133/574] add Signature Algorithms extension add signature_algorithms from RFC 5246 --- tlslite/constants.py | 1 + tlslite/extensions.py | 69 +++++++++++++++++++++++ tlslite/messages.py | 7 +-- tlslite/utils/codec.py | 29 ++++++++++ unit_tests/test_tlslite_extensions.py | 78 ++++++++++++++++++++++++-- unit_tests/test_tlslite_utils_codec.py | 32 +++++++++++ 6 files changed, 206 insertions(+), 10 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index ac3777b5..d657d156 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -46,6 +46,7 @@ class ExtensionType: # RFC 6066 / 4366 supported_groups = 10 # RFC 4492, RFC-ietf-tls-negotiated-ff-dhe-10 ec_point_formats = 11 # RFC 4492 srp = 12 # RFC 5054 + signature_algorithms = 13 # RFC 5246 encrypt_then_mac = 22 # RFC 7366 tack = 0xF300 supports_npn = 13172 diff --git a/tlslite/extensions.py b/tlslite/extensions.py index d96ceedb..8b83e6b0 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -1050,12 +1050,81 @@ def parse(self, parser): return self +class SignatureAlgorithmsExtension(TLSExtension): + + """ + Client side list of supported signature algorithms. + + Should be used by server to select certificate and signing method for + Server Key Exchange messages. In practice used only for the latter. + + See RFC5246. + """ + + def __init__(self): + """Create instance of class""" + self.sigalgs = None + + @property + def ext_type(self): + """ + Type of extension, in this case - 13 + + @rtype: int + """ + return ExtensionType.signature_algorithms + + @property + def ext_data(self): + """ + Return raw encoding of the exteion + + @rtype: bytearray + """ + if self.sigalgs is None: + return bytearray(0) + + writer = Writer() + # elements 1 byte each, overall length encoded in 2 bytes + writer.addVarTupleSeq(self.sigalgs, 1, 2) + return writer.bytes + + def create(self, sigalgs): + """ + Set the list of supported algorithm types + + @type sigalgs: list of tuples + @param sigalgs: list of pairs of a hash algorithm and signature + algorithm + """ + self.sigalgs = sigalgs + return self + + def parse(self, parser): + """ + Deserialise extension from on the wire data + + @type parser: L{Parser} + @rtype: SignatureAlgorithmsExtension + """ + if parser.getRemainingLength() == 0: + self.sigalgs = None + return self + + self.sigalgs = parser.getVarTupleList(1, 2, 2) + + if parser.getRemainingLength() != 0: + raise SyntaxError() + + return self + TLSExtension._universal_extensions = { ExtensionType.server_name : SNIExtension, ExtensionType.cert_type : ClientCertTypeExtension, ExtensionType.supported_groups : SupportedGroupsExtension, ExtensionType.ec_point_formats : ECPointFormatsExtension, ExtensionType.srp : SRPExtension, + ExtensionType.signature_algorithms : SignatureAlgorithmsExtension, ExtensionType.supports_npn : NPNExtension} TLSExtension._server_extensions = { diff --git a/tlslite/messages.py b/tlslite/messages.py index 8666ec62..293f312c 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -909,12 +909,7 @@ def write(self): w = Writer() w.addVarSeq(self.certificate_types, 1, 1) if self.version >= (3,3): - w2 = Writer() - for (hash_alg, signature) in self.supported_signature_algs: - w2.add(hash_alg, 1) - w2.add(signature, 1) - w.add(len(w2.bytes), 2) - w.bytes += w2.bytes + w.addVarTupleSeq(self.supported_signature_algs, 1, 2) caLength = 0 #determine length for ca_dn in self.certificate_authorities: diff --git a/tlslite/utils/codec.py b/tlslite/utils/codec.py index 33761cdc..03fe60fe 100644 --- a/tlslite/utils/codec.py +++ b/tlslite/utils/codec.py @@ -28,6 +28,35 @@ def addVarSeq(self, seq, length, lengthLength): for e in seq: self.add(e, length) + def addVarTupleSeq(self, seq, length, lengthLength): + """ + Add a variable length list of same-sized element tuples. + + Note that all tuples must have the same size. + + Inverse of Parser.getVarTupleList() + + @type seq: enumerable + @param seq: list of tuples + + @type length: int + @param length: length of single element in tuple + + @type lengthLength: int + @param lengthLength: length in bytes of overall length field + """ + if len(seq) == 0: + self.add(0, lengthLength) + else: + tupleSize = len(seq[0]) + tupleLength = tupleSize*length + self.add(len(seq)*tupleLength, lengthLength) + for elemTuple in seq: + if len(elemTuple) != tupleSize: + raise ValueError("Tuples of different sizes") + for elem in elemTuple: + self.add(elem, length) + class Parser(object): def __init__(self, bytes): self.bytes = bytes diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 49304283..737ec18e 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -10,9 +10,11 @@ import unittest from tlslite.extensions import TLSExtension, SNIExtension, NPNExtension,\ SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\ - TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension + TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\ + SupportedGroupsExtension, SignatureAlgorithmsExtension from tlslite.utils.codec import Parser -from tlslite.constants import NameType, ExtensionType, GroupName, ECPointFormat +from tlslite.constants import NameType, ExtensionType, GroupName,\ + ECPointFormat, HashAlgorithm, SignatureAlgorithm from tlslite.errors import TLSInternalError class TestTLSExtension(unittest.TestCase): @@ -188,8 +190,34 @@ def test_parse_with_signature_algorithms(self): ext = TLSExtension().parse(p) - # XXX unsupported - self.assertIsInstance(ext, TLSExtension) + self.assertIsInstance(ext, SignatureAlgorithmsExtension) + + self.assertEqual(ext.sigalgs, [(HashAlgorithm.sha256, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha256, + SignatureAlgorithm.dsa), + (HashAlgorithm.sha256, + SignatureAlgorithm.ecdsa), + (HashAlgorithm.sha384, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha384, + SignatureAlgorithm.ecdsa), + (HashAlgorithm.sha512, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha512, + SignatureAlgorithm.ecdsa), + (HashAlgorithm.sha224, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha224, + SignatureAlgorithm.dsa), + (HashAlgorithm.sha224, + SignatureAlgorithm.ecdsa), + (HashAlgorithm.sha1, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha1, + SignatureAlgorithm.dsa), + (HashAlgorithm.sha1, + SignatureAlgorithm.ecdsa)]) def test_equality(self): a = TLSExtension().create(0, bytearray(0)) @@ -1210,5 +1238,47 @@ def test_parse_with_empty_data(self): self.assertIsNone(ext.formats) +class TestSignatureAlgorithmsExtension(unittest.TestCase): + def test__init__(self): + ext = SignatureAlgorithmsExtension() + + self.assertIsNotNone(ext) + self.assertIsNone(ext.sigalgs) + self.assertEqual(ext.ext_type, 13) + self.assertEqual(ext.ext_data, bytearray(0)) + + def test_write(self): + ext = SignatureAlgorithmsExtension() + ext.create([(HashAlgorithm.sha1, SignatureAlgorithm.rsa), + (HashAlgorithm.sha256, SignatureAlgorithm.rsa)]) + + self.assertEqual(bytearray( + b'\x00\x0d' + # type of extension + b'\x00\x06' + # overall length of extension + b'\x00\x04' + # array length + b'\x02\x01' + # SHA1+RSA + b'\x04\x01' # SHA256+RSA + ), ext.write()) + + def test_parse_with_empty_data(self): + parser = Parser(bytearray(0)) + + ext = SignatureAlgorithmsExtension() + + ext.parse(parser) + + self.assertIsNone(ext.sigalgs) + + def test_parse_with_extra_data_at_end(self): + parser = Parser(bytearray( + b'\x00\x02' + # array length + b'\x04\x01' + # SHA256+RSA + b'\xff\xff')) # padding + + ext = SignatureAlgorithmsExtension() + + with self.assertRaises(SyntaxError): + ext.parse(parser) + if __name__ == '__main__': unittest.main() diff --git a/unit_tests/test_tlslite_utils_codec.py b/unit_tests/test_tlslite_utils_codec.py index 276ee0a6..c0215eeb 100644 --- a/unit_tests/test_tlslite_utils_codec.py +++ b/unit_tests/test_tlslite_utils_codec.py @@ -251,5 +251,37 @@ def test_bytes(self): self.assertEqual(bytearray(b'\xbe\xef\x0f'), w.bytes) + def test_addVarTupleSeq(self): + w = Writer() + w.addVarTupleSeq([(1, 2), (2, 9)], 1, 2) + + self.assertEqual(bytearray( + b'\x00\x04' + # length + b'\x01\x02' + # first tuple + b'\x02\x09' # second tuple + ), w.bytes) + + def test_addVarTupleSeq_with_single_element_tuples(self): + w = Writer() + w.addVarTupleSeq([[1], [9], [12]], 2, 3) + + self.assertEqual(bytearray( + b'\x00\x00\x06' + # length + b'\x00\x01' + # 1st element + b'\x00\x09' + # 2nd element + b'\x00\x0c'), w.bytes) + + def test_addVarTupleSeq_with_empty_array(self): + w = Writer() + w.addVarTupleSeq([], 1, 2) + + self.assertEqual(bytearray( + b'\x00\x00'), w.bytes) + + def test_addVarTupleSeq_with_invalid_sized_tuples(self): + w = Writer() + with self.assertRaises(ValueError): + w.addVarTupleSeq([(1, 2), (2, 3, 9)], 1, 2) + if __name__ == '__main__': unittest.main() From e11a15cfb7a38b9239cdecede46346b52a5584f7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 2 Jul 2015 18:37:14 +0200 Subject: [PATCH 134/574] add ID for renegotiation_info extension --- tlslite/constants.py | 1 + unit_tests/test_tlslite_extensions.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index d657d156..a9d72639 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -50,6 +50,7 @@ class ExtensionType: # RFC 6066 / 4366 encrypt_then_mac = 22 # RFC 7366 tack = 0xF300 supports_npn = 13172 + renegotiation_info = 0xff01 class HashAlgorithm: diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 737ec18e..a602bade 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -136,6 +136,9 @@ def test_parse_with_renego_info_server_side(self): # XXX not supported self.assertIsInstance(ext, TLSExtension) + self.assertEqual(ext.ext_data, bytearray(b'\x00')) + self.assertEqual(ext.ext_type, 0xff01) + def test_parse_with_elliptic_curves(self): p = Parser(bytearray( b'\x00\x0a' + # type of extension From 27ce8edf16a2c0bb2c02cad719c422af8cf8b7f0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 13 Jul 2015 14:08:25 +0200 Subject: [PATCH 135/574] fix pylint C0330 (bad-continuation) --- tlslite/extensions.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 8b83e6b0..78592d41 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -1118,7 +1118,8 @@ def parse(self, parser): return self -TLSExtension._universal_extensions = { +TLSExtension._universal_extensions = \ + { ExtensionType.server_name : SNIExtension, ExtensionType.cert_type : ClientCertTypeExtension, ExtensionType.supported_groups : SupportedGroupsExtension, @@ -1127,6 +1128,7 @@ def parse(self, parser): ExtensionType.signature_algorithms : SignatureAlgorithmsExtension, ExtensionType.supports_npn : NPNExtension} -TLSExtension._server_extensions = { +TLSExtension._server_extensions = \ + { ExtensionType.cert_type : ServerCertTypeExtension, ExtensionType.tack : TACKExtension} From a14d542ba810bcda7c0285f0f4ea66bf38749d7d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 14 Jul 2015 10:50:02 +0200 Subject: [PATCH 136/574] fix compare to None in unit tests Since None is a special object, we should check if the variable under test is None, rather if it's equal to None --- unit_tests/test_tlslite_extensions.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index a602bade..8724b020 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -122,7 +122,7 @@ def test_parse_with_SNI_server_side(self): ext = TLSExtension(server=True).parse(p) self.assertIsInstance(ext, SNIExtension) - self.assertEqual(ext.server_names, None) + self.assertIsNone(ext.server_names) def test_parse_with_renego_info_server_side(self): p = Parser(bytearray( @@ -286,7 +286,7 @@ class TestSNIExtension(unittest.TestCase): def test___init__(self): server_name = SNIExtension() - self.assertEqual(None, server_name.server_names) + self.assertIsNone(server_name.server_names) self.assertEqual(tuple(), server_name.host_names) # properties inherited from TLSExtension: self.assertEqual(0, server_name.ext_type) @@ -296,7 +296,7 @@ def test_create(self): server_name = SNIExtension() server_name = server_name.create() - self.assertEqual(None, server_name.server_names) + self.assertIsNone(server_name.server_names) self.assertEqual(tuple(), server_name.host_names) def test_create_with_hostname(self): @@ -633,7 +633,7 @@ def test___init___(self): self.assertEqual(9, cert_type.ext_type) self.assertEqual(bytearray(0), cert_type.ext_data) - self.assertEqual(None, cert_type.cert_types) + self.assertIsNone(cert_type.cert_types) def test_create(self): cert_type = ClientCertTypeExtension() @@ -641,7 +641,7 @@ def test_create(self): self.assertEqual(9, cert_type.ext_type) self.assertEqual(bytearray(0), cert_type.ext_data) - self.assertEqual(None, cert_type.cert_types) + self.assertIsNone(cert_type.cert_types) def test_create_with_empty_list(self): cert_type = ClientCertTypeExtension() @@ -707,7 +707,7 @@ def test___init__(self): self.assertEqual(9, cert_type.ext_type) self.assertEqual(bytearray(0), cert_type.ext_data) - self.assertEqual(None, cert_type.cert_type) + self.assertIsNone(cert_type.cert_type) def test_create(self): cert_type = ServerCertTypeExtension().create(0) @@ -760,7 +760,7 @@ class TestSRPExtension(unittest.TestCase): def test___init___(self): srp_extension = SRPExtension() - self.assertEqual(None, srp_extension.identity) + self.assertIsNone(srp_extension.identity) self.assertEqual(12, srp_extension.ext_type) self.assertEqual(bytearray(0), srp_extension.ext_data) @@ -768,7 +768,7 @@ def test_create(self): srp_extension = SRPExtension() srp_extension = srp_extension.create() - self.assertEqual(None, srp_extension.identity) + self.assertIsNone(srp_extension.identity) self.assertEqual(12, srp_extension.ext_type) self.assertEqual(bytearray(0), srp_extension.ext_data) @@ -836,7 +836,7 @@ class TestNPNExtension(unittest.TestCase): def test___init___(self): npn_extension = NPNExtension() - self.assertEqual(None, npn_extension.protocols) + self.assertIsNone(npn_extension.protocols) self.assertEqual(13172, npn_extension.ext_type) self.assertEqual(bytearray(0), npn_extension.ext_data) @@ -844,7 +844,7 @@ def test_create(self): npn_extension = NPNExtension() npn_extension = npn_extension.create() - self.assertEqual(None, npn_extension.protocols) + self.assertIsNone(npn_extension.protocols) self.assertEqual(13172, npn_extension.ext_type) self.assertEqual(bytearray(0), npn_extension.ext_data) From ca35d817d6c2dac2168adb8dfb75664b2e2db560 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 14:38:45 +0200 Subject: [PATCH 137/574] fix double import SupportedGroupsExtension was imported twice --- unit_tests/test_tlslite_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 8724b020..04ab12d1 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -11,7 +11,7 @@ from tlslite.extensions import TLSExtension, SNIExtension, NPNExtension,\ SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\ TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\ - SupportedGroupsExtension, SignatureAlgorithmsExtension + SignatureAlgorithmsExtension from tlslite.utils.codec import Parser from tlslite.constants import NameType, ExtensionType, GroupName,\ ECPointFormat, HashAlgorithm, SignatureAlgorithm From f867389188acd4c48589d46dc5b0491a55543424 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 16:25:45 +0200 Subject: [PATCH 138/574] after merge readme update --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 422e6248..1e3d8942 100644 --- a/README.md +++ b/README.md @@ -519,6 +519,8 @@ encrypt-then-MAC mode for CBC ciphers. =========== 0.5.0-alpha - xx/xx/xxxx - Hubert Kario + - add supported_groups, supported_point_formats, signature_algorithms and + renegotiation_info extensions - remove most CBC MAC-ing and padding timing side-channel leaks (should fix CVE-2013-0169, a.k.a. Lucky13) - add support for NULL encryption - TLS_RSA_WITH_NULL_MD5, From 55e9c001f9db58765f8c4b8f1edba005b69e069d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 16:54:18 +0200 Subject: [PATCH 139/574] rename ext_type since all fields and methods use camel case, the newly introduced fields should too public API change possible since all this code was not part of a stable release yet --- tlslite/extensions.py | 80 +++++++++++++-------------- tlslite/messages.py | 16 +++--- unit_tests/test_tlslite_extensions.py | 42 +++++++------- unit_tests/test_tlslite_messages.py | 4 +- 4 files changed, 71 insertions(+), 71 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 78592d41..9eae01ff 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -21,8 +21,8 @@ class TLSExtension(object): It is used as a base class for specific users and as a way to store extensions that are not implemented in library. - @type ext_type: int - @ivar ext_type: a 2^16-1 limited integer specifying the type of the + @type extType: int + @ivar extType: a 2^16-1 limited integer specifying the type of the extension that it contains, e.g. 0 indicates server name extension @type ext_data: bytearray @@ -62,23 +62,23 @@ def __init__(self, server=False): @param server: whatever to select ClientHello or ServerHello version for parsing """ - self.ext_type = None + self.extType = None self.ext_data = bytearray(0) self.server_type = server - def create(self, ext_type, data): + def create(self, extType, data): """ Initializes a generic TLS extension that can later be used in client hello or server hello messages - @type ext_type: int - @param ext_type: type of the extension encoded as an integer between + @type extType: int + @param extType: type of the extension encoded as an integer between M{0} and M{2^16-1} @type data: bytearray @param data: raw data representing extension on the wire @rtype: L{TLSExtension} """ - self.ext_type = ext_type + self.extType = extType self.ext_data = data return self @@ -93,10 +93,10 @@ def write(self): @raise AssertionError: when the object was not initialized """ - assert self.ext_type is not None + assert self.extType is not None w = Writer() - w.add(self.ext_type, 2) + w.add(self.extType, 2) w.add(len(self.ext_data), 2) w.addFixSeq(self.ext_data, 1) return w.bytes @@ -113,26 +113,26 @@ def parse(self, p): @rtype: L{TLSExtension} """ - ext_type = p.get(2) + extType = p.get(2) ext_length = p.get(2) # first check if we shouldn't use server side parser - if self.server_type and ext_type in self._server_extensions: - ext = self._server_extensions[ext_type]() + if self.server_type and extType in self._server_extensions: + ext = self._server_extensions[extType]() ext_parser = Parser(p.getFixBytes(ext_length)) ext = ext.parse(ext_parser) return ext # then fallback to universal/ClientHello-specific parsers - if ext_type in self._universal_extensions: - ext = self._universal_extensions[ext_type]() + if extType in self._universal_extensions: + ext = self._universal_extensions[extType]() ext_parser = Parser(p.getFixBytes(ext_length)) ext = ext.parse(ext_parser) return ext # finally, just save the extension data as there are extensions which # don't require specific handlers and indicate option by mere presence - self.ext_type = ext_type + self.extType = extType self.ext_data = p.getFixBytes(ext_length) assert len(self.ext_data) == ext_length return self @@ -143,8 +143,8 @@ def __eq__(self, that): Will return False for every object that's not an extension. """ - if hasattr(that, 'ext_type') and hasattr(that, 'ext_data'): - return self.ext_type == that.ext_type and \ + if hasattr(that, 'extType') and hasattr(that, 'ext_data'): + return self.extType == that.extType and \ self.ext_data == that.ext_data else: return False @@ -154,9 +154,9 @@ def __repr__(self): @rtype: str """ - return "TLSExtension(ext_type={0!r}, ext_data={1!r},"\ - " server_type={2!r})".format( - self.ext_type, self.ext_data, self.server_type) + return "TLSExtension(extType={0!r}, ext_data={1!r},"\ + " server_type={2!r})".format(self.extType, self.ext_data, + self.server_type) class SNIExtension(TLSExtension): """ @@ -194,8 +194,8 @@ class SNIExtension(TLSExtension): The list will be empty if the on the wire extension had and empty list while it will be None if the extension was empty. - @type ext_type: int - @ivar ext_type: numeric type of SNIExtension, i.e. 0 + @type extType: int + @ivar extType: numeric type of SNIExtension, i.e. 0 @type ext_data: bytearray @ivar ext_data: raw representation of the extension @@ -264,7 +264,7 @@ def create(self, hostname=None, host_names=None, server_names=None): return self @property - def ext_type(self): + def extType(self): """ Return the type of TLS extension, in this case - 0 @rtype: int @@ -343,7 +343,7 @@ def write(self): raw_data = self.ext_data w = Writer() - w.add(self.ext_type, 2) + w.add(self.extType, 2) w.add(len(raw_data), 2) w.bytes += raw_data @@ -381,8 +381,8 @@ class ClientCertTypeExtension(TLSExtension): This class handles the Certificate Type extension (variant sent by client) defined in RFC 6091. - @type ext_type: int - @ivar ext_type: numeric type of Certificate Type extension, i.e. 9 + @type extType: int + @ivar extType: numeric type of Certificate Type extension, i.e. 9 @type ext_data: bytearray @ivar ext_data: raw representation of the extension data @@ -409,7 +409,7 @@ def __repr__(self): .format(self.cert_types) @property - def ext_type(self): + def extType(self): """ Return the type of TLS extension, in this case - 9 @@ -471,8 +471,8 @@ class ServerCertTypeExtension(TLSExtension): This class handles the Certificate Type extension (variant sent by server) defined in RFC 6091. - @type ext_type: int - @ivar ext_type: byneruc ttoe if Certificate Type extension, i.e. 9 + @type extType: int + @ivar extType: byneruc ttoe if Certificate Type extension, i.e. 9 @type ext_data: bytearray @ivar ext_data: raw representation of the extension data @@ -498,7 +498,7 @@ def __repr__(self): return "ServerCertTypeExtension(cert_type={0!r})".format(self.cert_type) @property - def ext_type(self): + def extType(self): """ Return the type of TLS extension, in this case - 9 @@ -547,8 +547,8 @@ class SRPExtension(TLSExtension): This class handles the Secure Remote Password protocol TLS extension defined in RFC 5054. - @type ext_type: int - @ivar ext_type: numeric type of SRPExtension, i.e. 12 + @type extType: int + @ivar extType: numeric type of SRPExtension, i.e. 12 @type ext_data: bytearray @ivar ext_data: raw representation of extension data @@ -575,7 +575,7 @@ def __repr__(self): return "SRPExtension(identity={0!r})".format(self.identity) @property - def ext_type(self): + def extType(self): """ Return the type of TLS extension, in this case - 12 @@ -643,8 +643,8 @@ class NPNExtension(TLSExtension): @type protocols: list of bytearrays @ivar protocols: list of protocol names supported by the server - @type ext_type: int - @ivar ext_type: numeric type of NPNExtension, i.e. 13172 + @type extType: int + @ivar extType: numeric type of NPNExtension, i.e. 13172 @type ext_data: bytearray @ivar ext_data: raw representation of extension data @@ -668,7 +668,7 @@ def __repr__(self): return "NPNExtension(protocols={0!r})".format(self.protocols) @property - def ext_type(self): + def extType(self): """ Return the type of TLS extension, in this case - 13172 @rtype: int @@ -856,7 +856,7 @@ def __repr__(self): self.activation_flags, self.tacks) @property - def ext_type(self): + def extType(self): """ Returns the type of TLS extension, in this case - 62208 @@ -928,7 +928,7 @@ def __init__(self): self.groups = None @property - def ext_type(self): + def extType(self): """ Type of extension, in this case - 10 @@ -995,7 +995,7 @@ def __init__(self): self.formats = None @property - def ext_type(self): + def extType(self): """ Type of extension, in this case - 11 @@ -1066,7 +1066,7 @@ def __init__(self): self.sigalgs = None @property - def ext_type(self): + def extType(self): """ Type of extension, in this case - 13 diff --git a/tlslite/messages.py b/tlslite/messages.py index 293f312c..b0775bf5 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -250,7 +250,7 @@ def __repr__(self): self.ssl2, self.client_version, self.random, self.session_id, self.cipher_suites, self.compression_methods, self.extensions) - def getExtension(self, ext_type): + def getExtension(self, extType): """ Returns extension of given type if present, None otherwise @@ -261,7 +261,7 @@ def getExtension(self, ext_type): if self.extensions is None: return None - exts = [x for x in self.extensions if x.ext_type == ext_type] + exts = [ext for ext in self.extensions if ext.extType == extType] if len(exts) > 1: raise TLSInternalError( "Multiple extensions of the same type present") @@ -381,8 +381,8 @@ def tack(self, present): if self.extensions is None: return # remove all extensions of this type without changing reference - self.extensions[:] = [x for x in self.extensions if - x.ext_type != ExtensionType.tack] + self.extensions[:] = [ext for ext in self.extensions if + ext.extType != ExtensionType.tack] @property def supports_npn(self): @@ -421,8 +421,8 @@ def supports_npn(self, present): if self.extensions is None: return #remove all extension of this type without changing reference - self.extensions[:] = [x for x in self.extensions if - x.ext_type != ExtensionType.supports_npn] + self.extensions[:] = [ext for ext in self.extensions if + ext.extType != ExtensionType.supports_npn] @property def server_name(self): @@ -634,7 +634,7 @@ def __repr__(self): self.cipher_suite, self.compression_method, self._tack_ext, self.extensions) - def getExtension(self, ext_type): + def getExtension(self, extType): """Return extension of a given type, None if extension of given type is not present @@ -644,7 +644,7 @@ def getExtension(self, ext_type): if self.extensions is None: return None - exts = [x for x in self.extensions if x.ext_type == ext_type] + exts = [ext for ext in self.extensions if ext.extType == extType] if len(exts) > 1: raise TLSInternalError( "Multiple extensions of the same type present") diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 94ea7d16..6ea57bdf 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -22,14 +22,14 @@ def test___init__(self): tls_extension = TLSExtension() assert(tls_extension) - self.assertIsNone(tls_extension.ext_type) + self.assertIsNone(tls_extension.extType) self.assertEqual(bytearray(0), tls_extension.ext_data) def test_create(self): tls_extension = TLSExtension().create(1, bytearray(b'\x01\x00')) assert tls_extension - self.assertEqual(1, tls_extension.ext_type) + self.assertEqual(1, tls_extension.extType) self.assertEqual(bytearray(b'\x01\x00'), tls_extension.ext_data) def test_write(self): @@ -56,7 +56,7 @@ def test_parse(self): )) tls_extension = TLSExtension().parse(p) - self.assertEqual(66, tls_extension.ext_type) + self.assertEqual(66, tls_extension.extType) self.assertEqual(bytearray(b'\xff'), tls_extension.ext_data) def test_parse_with_length_long_by_one(self): @@ -148,7 +148,7 @@ def test_parse_with_renego_info_server_side(self): self.assertIsInstance(ext, TLSExtension) self.assertEqual(ext.ext_data, bytearray(b'\x00')) - self.assertEqual(ext.ext_type, 0xff01) + self.assertEqual(ext.extType, 0xff01) def test_parse_with_elliptic_curves(self): p = Parser(bytearray( @@ -248,7 +248,7 @@ def test_equality_with_empty_array_in_sni_extension(self): def test_equality_with_nearly_good_object(self): class TestClass(object): def __init__(self): - self.ext_type = 0 + self.extType = 0 a = TLSExtension().create(0, bytearray(b'\x00\x00')) b = TestClass() @@ -289,7 +289,7 @@ def test___repr__(self): ext = TLSExtension() ext = ext.create(0, bytearray(b'\x00\x00')) - self.assertEqual("TLSExtension(ext_type=0, "\ + self.assertEqual("TLSExtension(extType=0, "\ "ext_data=bytearray(b'\\x00\\x00'), server_type=False)", repr(ext)) @@ -300,7 +300,7 @@ def test___init__(self): self.assertIsNone(server_name.server_names) self.assertEqual(tuple(), server_name.host_names) # properties inherited from TLSExtension: - self.assertEqual(0, server_name.ext_type) + self.assertEqual(0, server_name.extType) self.assertEqual(bytearray(0), server_name.ext_data) def test_create(self): @@ -642,7 +642,7 @@ class TestClientCertTypeExtension(unittest.TestCase): def test___init___(self): cert_type = ClientCertTypeExtension() - self.assertEqual(9, cert_type.ext_type) + self.assertEqual(9, cert_type.extType) self.assertEqual(bytearray(0), cert_type.ext_data) self.assertIsNone(cert_type.cert_types) @@ -650,7 +650,7 @@ def test_create(self): cert_type = ClientCertTypeExtension() cert_type = cert_type.create() - self.assertEqual(9, cert_type.ext_type) + self.assertEqual(9, cert_type.extType) self.assertEqual(bytearray(0), cert_type.ext_data) self.assertIsNone(cert_type.cert_types) @@ -685,7 +685,7 @@ def test_parse(self): cert_type = cert_type.parse(p) - self.assertEqual(9, cert_type.ext_type) + self.assertEqual(9, cert_type.extType) self.assertEqual([], cert_type.cert_types) def test_parse_with_list(self): @@ -716,14 +716,14 @@ class TestServerCertTypeExtension(unittest.TestCase): def test___init__(self): cert_type = ServerCertTypeExtension() - self.assertEqual(9, cert_type.ext_type) + self.assertEqual(9, cert_type.extType) self.assertEqual(bytearray(0), cert_type.ext_data) self.assertIsNone(cert_type.cert_type) def test_create(self): cert_type = ServerCertTypeExtension().create(0) - self.assertEqual(9, cert_type.ext_type) + self.assertEqual(9, cert_type.extType) self.assertEqual(bytearray(b'\x00'), cert_type.ext_data) self.assertEqual(0, cert_type.cert_type) @@ -772,7 +772,7 @@ def test___init___(self): srp_extension = SRPExtension() self.assertIsNone(srp_extension.identity) - self.assertEqual(12, srp_extension.ext_type) + self.assertEqual(12, srp_extension.extType) self.assertEqual(bytearray(0), srp_extension.ext_data) def test_create(self): @@ -780,7 +780,7 @@ def test_create(self): srp_extension = srp_extension.create() self.assertIsNone(srp_extension.identity) - self.assertEqual(12, srp_extension.ext_type) + self.assertEqual(12, srp_extension.extType) self.assertEqual(bytearray(0), srp_extension.ext_data) def test_create_with_name(self): @@ -848,7 +848,7 @@ def test___init___(self): npn_extension = NPNExtension() self.assertIsNone(npn_extension.protocols) - self.assertEqual(13172, npn_extension.ext_type) + self.assertEqual(13172, npn_extension.extType) self.assertEqual(bytearray(0), npn_extension.ext_data) def test_create(self): @@ -856,7 +856,7 @@ def test_create(self): npn_extension = npn_extension.create() self.assertIsNone(npn_extension.protocols) - self.assertEqual(13172, npn_extension.ext_type) + self.assertEqual(13172, npn_extension.extType) self.assertEqual(bytearray(0), npn_extension.ext_data) def test_create_with_list_of_protocols(self): @@ -955,7 +955,7 @@ def test___init__(self): self.assertEqual([], tack_ext.tacks) self.assertEqual(0, tack_ext.activation_flags) - self.assertEqual(62208, tack_ext.ext_type) + self.assertEqual(62208, tack_ext.extType) self.assertEqual(bytearray(b'\x00\x00\x00'), tack_ext.ext_data) def test_create(self): @@ -1187,7 +1187,7 @@ def test_parse(self): ext = SupportedGroupsExtension().parse(parser) - self.assertEqual(ext.ext_type, ExtensionType.supported_groups) + self.assertEqual(ext.extType, ExtensionType.supported_groups) self.assertEqual(ext.groups, [GroupName.secp192r1, GroupName.secp224r1]) for group in ext.groups: @@ -1199,7 +1199,7 @@ def test_parse_with_empty_data(self): ext = SupportedGroupsExtension().parse(parser) - self.assertEqual(ext.ext_type, ExtensionType.supported_groups) + self.assertEqual(ext.extType, ExtensionType.supported_groups) self.assertIsNone(ext.groups) def test_parse_with_empty_array(self): @@ -1223,7 +1223,7 @@ def test___init__(self): self.assertIsNotNone(ext) self.assertEqual(ext.ext_data, bytearray(0)) - self.assertEqual(ext.ext_type, 11) + self.assertEqual(ext.extType, 11) def test_write(self): ext = ECPointFormatsExtension() @@ -1258,7 +1258,7 @@ def test__init__(self): self.assertIsNotNone(ext) self.assertIsNone(ext.sigalgs) - self.assertEqual(ext.ext_type, 13) + self.assertEqual(ext.extType, 13) self.assertEqual(ext.ext_data, bytearray(0)) def test_write(self): diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 8af9c083..85dc7db7 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -421,7 +421,7 @@ def test___str___with_extensions(self): self.assertEqual("client_hello,version(3.0),random(...),"\ "session ID(bytearray(b'')),cipher suites([]),"\ "compression methods([0]),extensions(["\ - "TLSExtension(ext_type=0, ext_data=bytearray(b'\\x00'), "\ + "TLSExtension(extType=0, ext_data=bytearray(b'\\x00'), "\ "server_type=False)])", str(client_hello)) @@ -432,7 +432,7 @@ def test___repr__(self): self.assertEqual("ClientHello(ssl2=False, client_version=(3.3), "\ "random=bytearray(b'\\x00'), session_id=bytearray(b''), "\ "cipher_suites=[], compression_methods=[0], "\ - "extensions=[TLSExtension(ext_type=0, "\ + "extensions=[TLSExtension(extType=0, "\ "ext_data=bytearray(b''), server_type=False)])", repr(client_hello)) From f47d2fe98e09e5483f724b60de6fcc1a99f36b7d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 15:02:31 +0200 Subject: [PATCH 140/574] rename ext_data since all fields and methods use camelCase, the newly introduced fields should too public API change possible since all this code was not part of a stable release yet --- tlslite/extensions.py | 64 +++++++++++++-------------- tlslite/messages.py | 2 +- unit_tests/test_tlslite_extensions.py | 54 +++++++++++----------- unit_tests/test_tlslite_messages.py | 4 +- 4 files changed, 62 insertions(+), 62 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 9eae01ff..9801831a 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -25,8 +25,8 @@ class TLSExtension(object): @ivar extType: a 2^16-1 limited integer specifying the type of the extension that it contains, e.g. 0 indicates server name extension - @type ext_data: bytearray - @ivar ext_data: a byte array containing the value of the extension as + @type extData: bytearray + @ivar extData: a byte array containing the value of the extension as to be written on the wire @type server_type: boolean @@ -63,7 +63,7 @@ def __init__(self, server=False): for parsing """ self.extType = None - self.ext_data = bytearray(0) + self.extData = bytearray(0) self.server_type = server def create(self, extType, data): @@ -79,7 +79,7 @@ def create(self, extType, data): @rtype: L{TLSExtension} """ self.extType = extType - self.ext_data = data + self.extData = data return self def write(self): @@ -97,8 +97,8 @@ def write(self): w = Writer() w.add(self.extType, 2) - w.add(len(self.ext_data), 2) - w.addFixSeq(self.ext_data, 1) + w.add(len(self.extData), 2) + w.addFixSeq(self.extData, 1) return w.bytes def parse(self, p): @@ -133,8 +133,8 @@ def parse(self, p): # finally, just save the extension data as there are extensions which # don't require specific handlers and indicate option by mere presence self.extType = extType - self.ext_data = p.getFixBytes(ext_length) - assert len(self.ext_data) == ext_length + self.extData = p.getFixBytes(ext_length) + assert len(self.extData) == ext_length return self def __eq__(self, that): @@ -143,9 +143,9 @@ def __eq__(self, that): Will return False for every object that's not an extension. """ - if hasattr(that, 'extType') and hasattr(that, 'ext_data'): + if hasattr(that, 'extType') and hasattr(that, 'extData'): return self.extType == that.extType and \ - self.ext_data == that.ext_data + self.extData == that.extData else: return False @@ -154,8 +154,8 @@ def __repr__(self): @rtype: str """ - return "TLSExtension(extType={0!r}, ext_data={1!r},"\ - " server_type={2!r})".format(self.extType, self.ext_data, + return "TLSExtension(extType={0!r}, extData={1!r},"\ + " server_type={2!r})".format(self.extType, self.extData, self.server_type) class SNIExtension(TLSExtension): @@ -197,8 +197,8 @@ class SNIExtension(TLSExtension): @type extType: int @ivar extType: numeric type of SNIExtension, i.e. 0 - @type ext_data: bytearray - @ivar ext_data: raw representation of the extension + @type extData: bytearray + @ivar extData: raw representation of the extension """ ServerName = namedtuple('ServerName', 'name_type name') @@ -312,7 +312,7 @@ def host_names(self): x.name_type != NameType.host_name] @property - def ext_data(self): + def extData(self): """ raw encoding of extension data, without type and length header @rtype: bytearray @@ -340,7 +340,7 @@ def write(self): on the wire, including the type, length and extension data """ - raw_data = self.ext_data + raw_data = self.extData w = Writer() w.add(self.extType, 2) @@ -384,8 +384,8 @@ class ClientCertTypeExtension(TLSExtension): @type extType: int @ivar extType: numeric type of Certificate Type extension, i.e. 9 - @type ext_data: bytearray - @ivar ext_data: raw representation of the extension data + @type extData: bytearray + @ivar extData: raw representation of the extension data @type cert_types: list of int @ivar cert_types: list of certificate type identifiers (each one byte long) @@ -419,7 +419,7 @@ def extType(self): return ExtensionType.cert_type @property - def ext_data(self): + def extData(self): """ Return the raw encoding of this extension @@ -474,8 +474,8 @@ class ServerCertTypeExtension(TLSExtension): @type extType: int @ivar extType: byneruc ttoe if Certificate Type extension, i.e. 9 - @type ext_data: bytearray - @ivar ext_data: raw representation of the extension data + @type extData: bytearray + @ivar extData: raw representation of the extension data @type cert_type: int @ivar cert_type: the certificate type selected by server @@ -507,7 +507,7 @@ def extType(self): return ExtensionType.cert_type @property - def ext_data(self): + def extData(self): """ Return the raw encoding of the extension data @@ -550,8 +550,8 @@ class SRPExtension(TLSExtension): @type extType: int @ivar extType: numeric type of SRPExtension, i.e. 12 - @type ext_data: bytearray - @ivar ext_data: raw representation of extension data + @type extData: bytearray + @ivar extData: raw representation of extension data @type identity: bytearray @ivar identity: UTF-8 encoding of user name @@ -585,7 +585,7 @@ def extType(self): return ExtensionType.srp @property - def ext_data(self): + def extData(self): """ Return raw data encoding of the extension @@ -646,8 +646,8 @@ class NPNExtension(TLSExtension): @type extType: int @ivar extType: numeric type of NPNExtension, i.e. 13172 - @type ext_data: bytearray - @ivar ext_data: raw representation of extension data + @type extData: bytearray + @ivar extData: raw representation of extension data """ def __init__(self): @@ -676,7 +676,7 @@ def extType(self): return ExtensionType.supports_npn @property - def ext_data(self): + def extData(self): """ Return the raw data encoding of the extension @rtype: bytearray @@ -865,7 +865,7 @@ def extType(self): return ExtensionType.tack @property - def ext_data(self): + def extData(self): """ Return the raw data encoding of the extension @@ -937,7 +937,7 @@ def extType(self): return ExtensionType.supported_groups @property - def ext_data(self): + def extData(self): """ Return raw data encoding of the extension @@ -1004,7 +1004,7 @@ def extType(self): return ExtensionType.ec_point_formats @property - def ext_data(self): + def extData(self): """ Return raw encoding of the extension @@ -1075,7 +1075,7 @@ def extType(self): return ExtensionType.signature_algorithms @property - def ext_data(self): + def extData(self): """ Return raw encoding of the exteion diff --git a/tlslite/messages.py b/tlslite/messages.py index b0775bf5..3103ff70 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -673,7 +673,7 @@ def tackExt(self): if ext is None or not tackpyLoaded: return None else: - self._tack_ext = TackExtension(ext.ext_data) + self._tack_ext = TackExtension(ext.extData) return self._tack_ext @tackExt.setter diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 6ea57bdf..aca787dc 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -23,14 +23,14 @@ def test___init__(self): assert(tls_extension) self.assertIsNone(tls_extension.extType) - self.assertEqual(bytearray(0), tls_extension.ext_data) + self.assertEqual(bytearray(0), tls_extension.extData) def test_create(self): tls_extension = TLSExtension().create(1, bytearray(b'\x01\x00')) assert tls_extension self.assertEqual(1, tls_extension.extType) - self.assertEqual(bytearray(b'\x01\x00'), tls_extension.ext_data) + self.assertEqual(bytearray(b'\x01\x00'), tls_extension.extData) def test_write(self): tls_extension = TLSExtension() @@ -57,7 +57,7 @@ def test_parse(self): tls_extension = TLSExtension().parse(p) self.assertEqual(66, tls_extension.extType) - self.assertEqual(bytearray(b'\xff'), tls_extension.ext_data) + self.assertEqual(bytearray(b'\xff'), tls_extension.extData) def test_parse_with_length_long_by_one(self): p = Parser(bytearray( @@ -147,7 +147,7 @@ def test_parse_with_renego_info_server_side(self): # XXX not supported self.assertIsInstance(ext, TLSExtension) - self.assertEqual(ext.ext_data, bytearray(b'\x00')) + self.assertEqual(ext.extData, bytearray(b'\x00')) self.assertEqual(ext.extType, 0xff01) def test_parse_with_elliptic_curves(self): @@ -290,7 +290,7 @@ def test___repr__(self): ext = ext.create(0, bytearray(b'\x00\x00')) self.assertEqual("TLSExtension(extType=0, "\ - "ext_data=bytearray(b'\\x00\\x00'), server_type=False)", + "extData=bytearray(b'\\x00\\x00'), server_type=False)", repr(ext)) class TestSNIExtension(unittest.TestCase): @@ -301,7 +301,7 @@ def test___init__(self): self.assertEqual(tuple(), server_name.host_names) # properties inherited from TLSExtension: self.assertEqual(0, server_name.extType) - self.assertEqual(bytearray(0), server_name.ext_data) + self.assertEqual(bytearray(0), server_name.extData) def test_create(self): server_name = SNIExtension() @@ -399,7 +399,7 @@ def test_write(self): b'\x00\x0b' + # length of element - 11 bytes # UTF-8 encoding of example.com b'\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d' - ), server_name.ext_data) + ), server_name.extData) self.assertEqual(bytearray( b'\x00\x00' + # type of extension - SNI (0) @@ -427,7 +427,7 @@ def test_write_with_multiple_hostnames(self): b'\x00\x0b' + # length of elemnet - 11 bytes # utf-8 encoding of example.org b'\x65\x78\x61\x6d\x70\x6c\x65\x2e\x6f\x72\x67' - ), server_name.ext_data) + ), server_name.extData) self.assertEqual(bytearray( b'\x00\x00' + # type of extension - SNI (0) @@ -457,7 +457,7 @@ def test_write_of_empty_list_of_names(self): self.assertEqual(bytearray( b'\x00\x00' # length of array - 0 bytes - ), server_name.ext_data) + ), server_name.extData) self.assertEqual(bytearray( b'\x00\x00' + # type of extension - SNI 0 @@ -643,7 +643,7 @@ def test___init___(self): cert_type = ClientCertTypeExtension() self.assertEqual(9, cert_type.extType) - self.assertEqual(bytearray(0), cert_type.ext_data) + self.assertEqual(bytearray(0), cert_type.extData) self.assertIsNone(cert_type.cert_types) def test_create(self): @@ -651,21 +651,21 @@ def test_create(self): cert_type = cert_type.create() self.assertEqual(9, cert_type.extType) - self.assertEqual(bytearray(0), cert_type.ext_data) + self.assertEqual(bytearray(0), cert_type.extData) self.assertIsNone(cert_type.cert_types) def test_create_with_empty_list(self): cert_type = ClientCertTypeExtension() cert_type = cert_type.create([]) - self.assertEqual(bytearray(b'\x00'), cert_type.ext_data) + self.assertEqual(bytearray(b'\x00'), cert_type.extData) self.assertEqual([], cert_type.cert_types) def test_create_with_list(self): cert_type = ClientCertTypeExtension() cert_type = cert_type.create([0]) - self.assertEqual(bytearray(b'\x01\x00'), cert_type.ext_data) + self.assertEqual(bytearray(b'\x01\x00'), cert_type.extData) self.assertEqual([0], cert_type.cert_types) def test_write(self): @@ -717,14 +717,14 @@ def test___init__(self): cert_type = ServerCertTypeExtension() self.assertEqual(9, cert_type.extType) - self.assertEqual(bytearray(0), cert_type.ext_data) + self.assertEqual(bytearray(0), cert_type.extData) self.assertIsNone(cert_type.cert_type) def test_create(self): cert_type = ServerCertTypeExtension().create(0) self.assertEqual(9, cert_type.extType) - self.assertEqual(bytearray(b'\x00'), cert_type.ext_data) + self.assertEqual(bytearray(b'\x00'), cert_type.extData) self.assertEqual(0, cert_type.cert_type) def test_parse(self): @@ -773,7 +773,7 @@ def test___init___(self): self.assertIsNone(srp_extension.identity) self.assertEqual(12, srp_extension.extType) - self.assertEqual(bytearray(0), srp_extension.ext_data) + self.assertEqual(bytearray(0), srp_extension.extData) def test_create(self): srp_extension = SRPExtension() @@ -781,7 +781,7 @@ def test_create(self): self.assertIsNone(srp_extension.identity) self.assertEqual(12, srp_extension.extType) - self.assertEqual(bytearray(0), srp_extension.ext_data) + self.assertEqual(bytearray(0), srp_extension.extData) def test_create_with_name(self): srp_extension = SRPExtension() @@ -790,7 +790,7 @@ def test_create_with_name(self): self.assertEqual(bytearray(b'username'), srp_extension.identity) self.assertEqual(bytearray( b'\x08' + # length of string - 8 bytes - b'username'), srp_extension.ext_data) + b'username'), srp_extension.extData) def test_create_with_too_long_name(self): srp_extension = SRPExtension() @@ -849,7 +849,7 @@ def test___init___(self): self.assertIsNone(npn_extension.protocols) self.assertEqual(13172, npn_extension.extType) - self.assertEqual(bytearray(0), npn_extension.ext_data) + self.assertEqual(bytearray(0), npn_extension.extData) def test_create(self): npn_extension = NPNExtension() @@ -857,7 +857,7 @@ def test_create(self): self.assertIsNone(npn_extension.protocols) self.assertEqual(13172, npn_extension.extType) - self.assertEqual(bytearray(0), npn_extension.ext_data) + self.assertEqual(bytearray(0), npn_extension.extData) def test_create_with_list_of_protocols(self): npn_extension = NPNExtension() @@ -875,7 +875,7 @@ def test_create_with_list_of_protocols(self): b'\x06' + # length of name of protocol # utf-8 encoding of "http/1.1" b'\x73\x70\x64\x79\x2f\x33' - ), npn_extension.ext_data) + ), npn_extension.extData) def test_write(self): npn_extension = NPNExtension().create() @@ -909,7 +909,7 @@ def test_parse(self): npn_extension = npn_extension.parse(p) - self.assertEqual(bytearray(0), npn_extension.ext_data) + self.assertEqual(bytearray(0), npn_extension.extData) self.assertEqual([], npn_extension.protocols) def test_parse_with_procotol(self): @@ -956,7 +956,7 @@ def test___init__(self): self.assertEqual([], tack_ext.tacks) self.assertEqual(0, tack_ext.activation_flags) self.assertEqual(62208, tack_ext.extType) - self.assertEqual(bytearray(b'\x00\x00\x00'), tack_ext.ext_data) + self.assertEqual(bytearray(b'\x00\x00\x00'), tack_ext.extData) def test_create(self): tack_ext = TACKExtension().create([], 1) @@ -1084,7 +1084,7 @@ def test_tack___eq___with_different_tacks(self): self.assertFalse(a == b) - def test_ext_data(self): + def test_extData(self): tack = TACKExtension.TACK().create( bytearray(b'\x01'*64), 2, @@ -1104,7 +1104,7 @@ def test_ext_data(self): b'\x05'*32 + # target_hash b'\x06'*64 + # signature b'\x01' # activation flag - ), tack_ext.ext_data) + ), tack_ext.extData) def test_parse(self): p = Parser(bytearray(3)) @@ -1222,7 +1222,7 @@ def test___init__(self): ext = ECPointFormatsExtension() self.assertIsNotNone(ext) - self.assertEqual(ext.ext_data, bytearray(0)) + self.assertEqual(ext.extData, bytearray(0)) self.assertEqual(ext.extType, 11) def test_write(self): @@ -1259,7 +1259,7 @@ def test__init__(self): self.assertIsNotNone(ext) self.assertIsNone(ext.sigalgs) self.assertEqual(ext.extType, 13) - self.assertEqual(ext.ext_data, bytearray(0)) + self.assertEqual(ext.extData, bytearray(0)) def test_write(self): ext = SignatureAlgorithmsExtension() diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 85dc7db7..fa96a00d 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -421,7 +421,7 @@ def test___str___with_extensions(self): self.assertEqual("client_hello,version(3.0),random(...),"\ "session ID(bytearray(b'')),cipher suites([]),"\ "compression methods([0]),extensions(["\ - "TLSExtension(extType=0, ext_data=bytearray(b'\\x00'), "\ + "TLSExtension(extType=0, extData=bytearray(b'\\x00'), "\ "server_type=False)])", str(client_hello)) @@ -433,7 +433,7 @@ def test___repr__(self): "random=bytearray(b'\\x00'), session_id=bytearray(b''), "\ "cipher_suites=[], compression_methods=[0], "\ "extensions=[TLSExtension(extType=0, "\ - "ext_data=bytearray(b''), server_type=False)])", + "extData=bytearray(b''), server_type=False)])", repr(client_hello)) def test_getExtension(self): From 5475c1d1496e677299b404692e1ae35b45c4a81d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 15:11:07 +0200 Subject: [PATCH 141/574] rename server_type since all fields and methods use camelCase, the newly introduced fields should too public API change possible since all this code was not part of a stable release yet --- tlslite/extensions.py | 12 ++++++------ unit_tests/test_tlslite_extensions.py | 2 +- unit_tests/test_tlslite_messages.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 9801831a..c19e963a 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -29,8 +29,8 @@ class TLSExtension(object): @ivar extData: a byte array containing the value of the extension as to be written on the wire - @type server_type: boolean - @ivar server_type: indicates that the extension was parsed with ServerHello + @type serverType: boolean + @ivar serverType: indicates that the extension was parsed with ServerHello specific parser, otherwise it used universal or ClientHello specific parser @@ -64,7 +64,7 @@ def __init__(self, server=False): """ self.extType = None self.extData = bytearray(0) - self.server_type = server + self.serverType = server def create(self, extType, data): """ @@ -117,7 +117,7 @@ def parse(self, p): ext_length = p.get(2) # first check if we shouldn't use server side parser - if self.server_type and extType in self._server_extensions: + if self.serverType and extType in self._server_extensions: ext = self._server_extensions[extType]() ext_parser = Parser(p.getFixBytes(ext_length)) ext = ext.parse(ext_parser) @@ -155,8 +155,8 @@ def __repr__(self): @rtype: str """ return "TLSExtension(extType={0!r}, extData={1!r},"\ - " server_type={2!r})".format(self.extType, self.extData, - self.server_type) + " serverType={2!r})".format(self.extType, self.extData, + self.serverType) class SNIExtension(TLSExtension): """ diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index aca787dc..173b548f 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -290,7 +290,7 @@ def test___repr__(self): ext = ext.create(0, bytearray(b'\x00\x00')) self.assertEqual("TLSExtension(extType=0, "\ - "extData=bytearray(b'\\x00\\x00'), server_type=False)", + "extData=bytearray(b'\\x00\\x00'), serverType=False)", repr(ext)) class TestSNIExtension(unittest.TestCase): diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index fa96a00d..ea7b1cf5 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -422,7 +422,7 @@ def test___str___with_extensions(self): "session ID(bytearray(b'')),cipher suites([]),"\ "compression methods([0]),extensions(["\ "TLSExtension(extType=0, extData=bytearray(b'\\x00'), "\ - "server_type=False)])", + "serverType=False)])", str(client_hello)) def test___repr__(self): @@ -433,7 +433,7 @@ def test___repr__(self): "random=bytearray(b'\\x00'), session_id=bytearray(b''), "\ "cipher_suites=[], compression_methods=[0], "\ "extensions=[TLSExtension(extType=0, "\ - "extData=bytearray(b''), server_type=False)])", + "extData=bytearray(b''), serverType=False)])", repr(client_hello)) def test_getExtension(self): From 7b6abcec9e852ab54b1182d1f89d8524584289b0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 15:11:46 +0200 Subject: [PATCH 142/574] rename server_names since all fields and methods use camelCase, the newly introduced fields should too public API change possible since all this code was not part of a stable release yet --- tlslite/extensions.py | 50 +++++++++++++-------------- unit_tests/test_tlslite_extensions.py | 38 ++++++++++---------- unit_tests/test_tlslite_messages.py | 2 +- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index c19e963a..02a48e03 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -185,8 +185,8 @@ class SNIExtension(TLSExtension): sni_extension.host_names = names - @type server_names: list of L{ServerName} - @ivar server_names: list of all names advertised in extension. + @type serverNames: list of L{ServerName} + @ivar serverNames: list of all names advertised in extension. L{ServerName} is a namedtuple with two elements, the first element (type) defines the type of the name (encoded as int) while the other (name) is a bytearray that carries the value. @@ -209,7 +209,7 @@ def __init__(self): See also: L{create} and L{parse}. """ - self.server_names = None + self.serverNames = None def __repr__(self): """ @@ -217,20 +217,20 @@ def __repr__(self): @rtype: str """ - return "SNIExtension(server_names={0!r})".format(self.server_names) + return "SNIExtension(serverNames={0!r})".format(self.serverNames) - def create(self, hostname=None, host_names=None, server_names=None): + def create(self, hostname=None, host_names=None, serverNames=None): """ Initializes an instance with provided hostname, host names or raw server names. Any of the parameters may be None, in that case the list inside the - extension won't be defined, if either host_names or server_names is + extension won't be defined, if either host_names or serverNames is an empty list, then the extension will define a list of lenght 0. If multiple parameters are specified at the same time, then the resulting list of names will be concatenated in order of hostname, - host_names and server_names last. + host_names and serverNames last. @type hostname: bytearray @param hostname: raw UTF-8 encoding of the host name @@ -238,28 +238,28 @@ def create(self, hostname=None, host_names=None, server_names=None): @type host_names: list of bytearrays @param host_names: list of raw UTF-8 encoded host names - @type server_names: list of L{ServerName} - @param server_names: pairs of name_type and name encoded as a namedtuple + @type serverNames: list of L{ServerName} + @param serverNames: pairs of name_type and name encoded as a namedtuple @rtype: L{SNIExtension} """ - if hostname is None and host_names is None and server_names is None: - self.server_names = None + if hostname is None and host_names is None and serverNames is None: + self.serverNames = None return self else: - self.server_names = [] + self.serverNames = [] if hostname: - self.server_names += [SNIExtension.ServerName(NameType.host_name,\ + self.serverNames += [SNIExtension.ServerName(NameType.host_name,\ hostname)] if host_names: - self.server_names +=\ + self.serverNames +=\ [SNIExtension.ServerName(NameType.host_name, x) for x in\ host_names] - if server_names: - self.server_names += server_names + if serverNames: + self.serverNames += serverNames return self @@ -279,10 +279,10 @@ def host_names(self): """ # because we can't simulate assignments to array elements we return # an immutable type - if self.server_names is None: + if self.serverNames is None: return tuple() else: - return tuple([x.name for x in self.server_names if \ + return tuple([x.name for x in self.serverNames if \ x.name_type == NameType.host_name]) @host_names.setter @@ -297,10 +297,10 @@ def host_names(self, host_names): @param host_names: host names to replace the old server names of type 0 """ - self.server_names = \ + self.serverNames = \ [SNIExtension.ServerName(NameType.host_name, x) for x in \ host_names] + \ - [x for x in self.server_names if \ + [x for x in self.serverNames if \ x.name_type != NameType.host_name] @host_names.deleter @@ -308,7 +308,7 @@ def host_names(self): """ Remove all host names from extension, leaves other name types unmodified """ - self.server_names = [x for x in self.server_names if \ + self.serverNames = [x for x in self.serverNames if \ x.name_type != NameType.host_name] @property @@ -317,11 +317,11 @@ def extData(self): @rtype: bytearray """ - if self.server_names is None: + if self.serverNames is None: return bytearray(0) w2 = Writer() - for server_name in self.server_names: + for server_name in self.serverNames: w2.add(server_name.name_type, 1) w2.add(len(server_name.name), 2) w2.bytes += server_name.name @@ -365,13 +365,13 @@ def parse(self, p): if p.getRemainingLength() == 0: return self - self.server_names = [] + self.serverNames = [] p.startLengthCheck(2) while not p.atLengthCheck(): sn_type = p.get(1) sn_name = p.getVarBytes(2) - self.server_names += [SNIExtension.ServerName(sn_type, sn_name)] + self.serverNames += [SNIExtension.ServerName(sn_type, sn_name)] p.stopLengthCheck() return self diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 173b548f..a247bd3b 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -133,7 +133,7 @@ def test_parse_with_SNI_server_side(self): ext = TLSExtension(server=True).parse(p) self.assertIsInstance(ext, SNIExtension) - self.assertIsNone(ext.server_names) + self.assertIsNone(ext.serverNames) def test_parse_with_renego_info_server_side(self): p = Parser(bytearray( @@ -241,7 +241,7 @@ def test_equality(self): def test_equality_with_empty_array_in_sni_extension(self): a = TLSExtension().create(0, bytearray(b'\x00\x00')) - b = SNIExtension().create(server_names=[]) + b = SNIExtension().create(serverNames=[]) self.assertTrue(a == b) @@ -297,7 +297,7 @@ class TestSNIExtension(unittest.TestCase): def test___init__(self): server_name = SNIExtension() - self.assertIsNone(server_name.server_names) + self.assertIsNone(server_name.serverNames) self.assertEqual(tuple(), server_name.host_names) # properties inherited from TLSExtension: self.assertEqual(0, server_name.extType) @@ -307,7 +307,7 @@ def test_create(self): server_name = SNIExtension() server_name = server_name.create() - self.assertIsNone(server_name.server_names) + self.assertIsNone(server_name.serverNames) self.assertEqual(tuple(), server_name.host_names) def test_create_with_hostname(self): @@ -318,7 +318,7 @@ def test_create_with_hostname(self): self.assertEqual([SNIExtension.ServerName( NameType.host_name, bytearray(b'example.com') - )], server_name.server_names) + )], server_name.serverNames) def test_create_with_host_names(self): server_name = SNIExtension() @@ -336,11 +336,11 @@ def test_create_with_host_names(self): SNIExtension.ServerName( NameType.host_name, bytearray(b'www.example.com'))], - server_name.server_names) + server_name.serverNames) - def test_create_with_server_names(self): + def test_create_with_serverNames(self): server_name = SNIExtension() - server_name = server_name.create(server_names=[ + server_name = server_name.create(serverNames=[ SNIExtension.ServerName(1, bytearray(b'example.com')), SNIExtension.ServerName(4, bytearray(b'www.example.com')), SNIExtension.ServerName(0, bytearray(b'example.net'))]) @@ -353,11 +353,11 @@ def test_create_with_server_names(self): 4, bytearray(b'www.example.com')), SNIExtension.ServerName( 0, bytearray(b'example.net'))], - server_name.server_names) + server_name.serverNames) def test_host_names(self): server_name = SNIExtension() - server_name = server_name.create(server_names=[ + server_name = server_name.create(serverNames=[ SNIExtension.ServerName(0, bytearray(b'example.net')), SNIExtension.ServerName(1, bytearray(b'example.com')), SNIExtension.ServerName(4, bytearray(b'www.example.com')) @@ -371,11 +371,11 @@ def test_host_names(self): SNIExtension.ServerName(0, bytearray(b'example.com')), SNIExtension.ServerName(1, bytearray(b'example.com')), SNIExtension.ServerName(4, bytearray(b'www.example.com'))], - server_name.server_names) + server_name.serverNames) def test_host_names_delete(self): server_name = SNIExtension() - server_name = server_name.create(server_names=[ + server_name = server_name.create(serverNames=[ SNIExtension.ServerName(0, bytearray(b'example.net')), SNIExtension.ServerName(1, bytearray(b'example.com')), SNIExtension.ServerName(4, bytearray(b'www.example.com')) @@ -387,7 +387,7 @@ def test_host_names_delete(self): self.assertEqual([ SNIExtension.ServerName(1, bytearray(b'example.com')), SNIExtension.ServerName(4, bytearray(b'www.example.com'))], - server_name.server_names) + server_name.serverNames) def test_write(self): server_name = SNIExtension() @@ -453,7 +453,7 @@ def test_write_of_empty_extension(self): def test_write_of_empty_list_of_names(self): server_name = SNIExtension() - server_name = server_name.create(server_names=[]) + server_name = server_name.create(serverNames=[]) self.assertEqual(bytearray( b'\x00\x00' # length of array - 0 bytes @@ -480,7 +480,7 @@ def test_parse_of_server_side_version(self): server_name = server_name.parse(p) - self.assertIsNone(server_name.server_names) + self.assertIsNone(server_name.serverNames) def test_parse_null_length_array(self): server_name = SNIExtension() @@ -489,7 +489,7 @@ def test_parse_null_length_array(self): server_name = server_name.parse(p) - self.assertEqual([], server_name.server_names) + self.assertEqual([], server_name.serverNames) def test_parse_with_host_name(self): server_name = SNIExtension() @@ -532,7 +532,7 @@ def test_parse_with_multiple_host_names(self): self.assertEqual([ SN(10, bytearray(b'example.org')), SN(0, bytearray(b'example.com')) - ], server_name.server_names) + ], server_name.serverNames) def test_parse_with_array_length_long_by_one(self): server_name = SNIExtension() @@ -629,11 +629,11 @@ def test_parse_with_name_length_short_by_one(self): def test___repr__(self): server_name = SNIExtension() server_name = server_name.create( - server_names=[ + serverNames=[ SNIExtension.ServerName(0, bytearray(b'example.com')), SNIExtension.ServerName(1, bytearray(b'\x04\x01'))]) - self.assertEqual("SNIExtension(server_names=["\ + self.assertEqual("SNIExtension(serverNames=["\ "ServerName(name_type=0, name=bytearray(b'example.com')), "\ "ServerName(name_type=1, name=bytearray(b'\\x04\\x01'))])", repr(server_name)) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index ea7b1cf5..b3018acc 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -561,7 +561,7 @@ def test_server_name_other_than_dns_name(self): client_hello = ClientHello().create((3, 3), bytearray(1), bytearray(0), []) - sni_ext = SNIExtension().create(server_names=[\ + sni_ext = SNIExtension().create(serverNames=[\ SNIExtension.ServerName(1, b'test')]) client_hello.extensions = [sni_ext] From dd8e29a3dbe0d5fccccf8aed13ca13d7d83b0c14 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 15:15:10 +0200 Subject: [PATCH 143/574] rename host_names since all fields and methods use camelCase, the newly introduced fields should too public API change possible since all this code was not part of a stable release yet --- tlslite/extensions.py | 46 +++++++++++++-------------- tlslite/messages.py | 8 ++--- unit_tests/test_tlslite_extensions.py | 40 +++++++++++------------ 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 02a48e03..f44e5e55 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -169,20 +169,20 @@ class SNIExtension(TLSExtension): opaque byte strings, in case of DNS host names (records of type 0) they are UTF-8 encoded domain names (without the ending dot). - @type host_names: tuple of bytearrays - @ivar host_names: tuple of hostnames (server name records of type 0) + @type hostNames: tuple of bytearrays + @ivar hostNames: tuple of hostnames (server name records of type 0) advertised in the extension. Note that it may not include all names from client hello as the client can advertise other types. Also note that while it's not possible to change the returned array in place, it is possible to assign a new set of names. IOW, this won't work:: - sni_extension.host_names[0] = bytearray(b'example.com') + sni_extension.hostNames[0] = bytearray(b'example.com') while this will work:: - names = list(sni_extension.host_names) + names = list(sni_extension.hostNames) names[0] = bytearray(b'example.com') - sni_extension.host_names = names + sni_extension.hostNames = names @type serverNames: list of L{ServerName} @@ -219,31 +219,31 @@ def __repr__(self): """ return "SNIExtension(serverNames={0!r})".format(self.serverNames) - def create(self, hostname=None, host_names=None, serverNames=None): + def create(self, hostname=None, hostNames=None, serverNames=None): """ Initializes an instance with provided hostname, host names or raw server names. Any of the parameters may be None, in that case the list inside the - extension won't be defined, if either host_names or serverNames is + extension won't be defined, if either hostNames or serverNames is an empty list, then the extension will define a list of lenght 0. If multiple parameters are specified at the same time, then the resulting list of names will be concatenated in order of hostname, - host_names and serverNames last. + hostNames and serverNames last. @type hostname: bytearray @param hostname: raw UTF-8 encoding of the host name - @type host_names: list of bytearrays - @param host_names: list of raw UTF-8 encoded host names + @type hostNames: list of bytearrays + @param hostNames: list of raw UTF-8 encoded host names @type serverNames: list of L{ServerName} @param serverNames: pairs of name_type and name encoded as a namedtuple @rtype: L{SNIExtension} """ - if hostname is None and host_names is None and serverNames is None: + if hostname is None and hostNames is None and serverNames is None: self.serverNames = None return self else: @@ -253,10 +253,10 @@ def create(self, hostname=None, host_names=None, serverNames=None): self.serverNames += [SNIExtension.ServerName(NameType.host_name,\ hostname)] - if host_names: + if hostNames: self.serverNames +=\ [SNIExtension.ServerName(NameType.host_name, x) for x in\ - host_names] + hostNames] if serverNames: self.serverNames += serverNames @@ -272,8 +272,8 @@ def extType(self): return ExtensionType.server_name @property - def host_names(self): - """ Returns a simulated list of host_names from the extension. + def hostNames(self): + """ Returns a simulated list of hostNames from the extension. @rtype: tuple of bytearrays """ @@ -285,26 +285,26 @@ def host_names(self): return tuple([x.name for x in self.serverNames if \ x.name_type == NameType.host_name]) - @host_names.setter - def host_names(self, host_names): + @hostNames.setter + def hostNames(self, hostNames): """ Removes all host names from the extension and replaces them by - names in X{host_names} parameter. + names in X{hostNames} parameter. Newly added parameters will be added at the I{beginning} of the list of extensions. - @type host_names: iterable of bytearrays - @param host_names: host names to replace the old server names of type 0 + @type hostNames: iterable of bytearrays + @param hostNames: host names to replace the old server names of type 0 """ self.serverNames = \ [SNIExtension.ServerName(NameType.host_name, x) for x in \ - host_names] + \ + hostNames] + \ [x for x in self.serverNames if \ x.name_type != NameType.host_name] - @host_names.deleter - def host_names(self): + @hostNames.deleter + def hostNames(self): """ Remove all host names from extension, leaves other name types unmodified """ diff --git a/tlslite/messages.py b/tlslite/messages.py index 3103ff70..2efa8d59 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -436,8 +436,8 @@ def server_name(self): if sni_ext is None: return bytearray(0) else: - if len(sni_ext.host_names) > 0: - return sni_ext.host_names[0] + if len(sni_ext.hostNames) > 0: + return sni_ext.hostNames[0] else: return bytearray(0) @@ -454,9 +454,9 @@ def server_name(self, hostname): sni_ext = SNIExtension().create(hostname) self.addExtension(sni_ext) else: - names = list(sni_ext.host_names) + names = list(sni_ext.hostNames) names[0] = hostname - sni_ext.host_names = names + sni_ext.hostNames = names def create(self, version, random, session_id, cipher_suites, certificate_types=None, srpUsername=None, diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index a247bd3b..fe54ae3b 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -83,7 +83,7 @@ def test_parse_with_sni_ext(self): self.assertIsInstance(tls_extension, SNIExtension) - self.assertEqual(bytearray(b'example.com'), tls_extension.host_names[0]) + self.assertEqual(bytearray(b'example.com'), tls_extension.hostNames[0]) def test_parse_with_SNI_server_side(self): p = Parser(bytearray( @@ -298,7 +298,7 @@ def test___init__(self): server_name = SNIExtension() self.assertIsNone(server_name.serverNames) - self.assertEqual(tuple(), server_name.host_names) + self.assertEqual(tuple(), server_name.hostNames) # properties inherited from TLSExtension: self.assertEqual(0, server_name.extType) self.assertEqual(bytearray(0), server_name.extData) @@ -308,27 +308,27 @@ def test_create(self): server_name = server_name.create() self.assertIsNone(server_name.serverNames) - self.assertEqual(tuple(), server_name.host_names) + self.assertEqual(tuple(), server_name.hostNames) def test_create_with_hostname(self): server_name = SNIExtension() server_name = server_name.create(bytearray(b'example.com')) - self.assertEqual((bytearray(b'example.com'),), server_name.host_names) + self.assertEqual((bytearray(b'example.com'),), server_name.hostNames) self.assertEqual([SNIExtension.ServerName( NameType.host_name, bytearray(b'example.com') )], server_name.serverNames) - def test_create_with_host_names(self): + def test_create_with_hostNames(self): server_name = SNIExtension() - server_name = server_name.create(host_names=[bytearray(b'example.com'), + server_name = server_name.create(hostNames=[bytearray(b'example.com'), bytearray(b'www.example.com')]) self.assertEqual(( bytearray(b'example.com'), bytearray(b'www.example.com') - ), server_name.host_names) + ), server_name.hostNames) self.assertEqual([ SNIExtension.ServerName( NameType.host_name, @@ -345,7 +345,7 @@ def test_create_with_serverNames(self): SNIExtension.ServerName(4, bytearray(b'www.example.com')), SNIExtension.ServerName(0, bytearray(b'example.net'))]) - self.assertEqual((bytearray(b'example.net'),), server_name.host_names) + self.assertEqual((bytearray(b'example.net'),), server_name.hostNames) self.assertEqual([ SNIExtension.ServerName( 1, bytearray(b'example.com')), @@ -355,7 +355,7 @@ def test_create_with_serverNames(self): 0, bytearray(b'example.net'))], server_name.serverNames) - def test_host_names(self): + def test_hostNames(self): server_name = SNIExtension() server_name = server_name.create(serverNames=[ SNIExtension.ServerName(0, bytearray(b'example.net')), @@ -363,17 +363,17 @@ def test_host_names(self): SNIExtension.ServerName(4, bytearray(b'www.example.com')) ]) - server_name.host_names = \ + server_name.hostNames = \ [bytearray(b'example.com')] - self.assertEqual((bytearray(b'example.com'),), server_name.host_names) + self.assertEqual((bytearray(b'example.com'),), server_name.hostNames) self.assertEqual([ SNIExtension.ServerName(0, bytearray(b'example.com')), SNIExtension.ServerName(1, bytearray(b'example.com')), SNIExtension.ServerName(4, bytearray(b'www.example.com'))], server_name.serverNames) - def test_host_names_delete(self): + def test_hostNames_delete(self): server_name = SNIExtension() server_name = server_name.create(serverNames=[ SNIExtension.ServerName(0, bytearray(b'example.net')), @@ -381,9 +381,9 @@ def test_host_names_delete(self): SNIExtension.ServerName(4, bytearray(b'www.example.com')) ]) - del server_name.host_names + del server_name.hostNames - self.assertEqual(tuple(), server_name.host_names) + self.assertEqual(tuple(), server_name.hostNames) self.assertEqual([ SNIExtension.ServerName(1, bytearray(b'example.com')), SNIExtension.ServerName(4, bytearray(b'www.example.com'))], @@ -413,7 +413,7 @@ def test_write(self): def test_write_with_multiple_hostnames(self): server_name = SNIExtension() - server_name = server_name.create(host_names=[ + server_name = server_name.create(hostNames=[ bytearray(b'example.com'), bytearray(b'example.org')]) @@ -503,11 +503,11 @@ def test_parse_with_host_name(self): server_name = server_name.parse(p) - self.assertEqual(bytearray(b'example.com'), server_name.host_names[0]) + self.assertEqual(bytearray(b'example.com'), server_name.hostNames[0]) self.assertEqual(tuple([bytearray(b'example.com')]), - server_name.host_names) + server_name.hostNames) - def test_parse_with_multiple_host_names(self): + def test_parse_with_multiple_hostNames(self): server_name = SNIExtension() p = Parser(bytearray( @@ -523,9 +523,9 @@ def test_parse_with_multiple_host_names(self): server_name = server_name.parse(p) - self.assertEqual(bytearray(b'example.com'), server_name.host_names[0]) + self.assertEqual(bytearray(b'example.com'), server_name.hostNames[0]) self.assertEqual(tuple([bytearray(b'example.com')]), - server_name.host_names) + server_name.hostNames) SN = SNIExtension.ServerName From 4efea5d9171f3a5725ad689a0ec56284759a662a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 15:18:01 +0200 Subject: [PATCH 144/574] rename _universal_extensions since all fields and methods use camelCase, the newly introduced fields should too public API change possible since all this code was not part of a stable release yet --- tlslite/extensions.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index f44e5e55..16ef16c7 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -34,8 +34,8 @@ class TLSExtension(object): specific parser, otherwise it used universal or ClientHello specific parser - @type _universal_extensions: dict - @cvar _universal_extensions: dictionary with concrete implementations of + @type _universalExtensions: dict + @cvar _universalExtensions: dictionary with concrete implementations of specific TLS extensions where key is the numeric value of the extension ID. Contains ClientHello version of extensions or universal implementations @@ -47,7 +47,7 @@ class TLSExtension(object): ServerHello versions. """ # actual definition at the end of file, after definitions of all classes - _universal_extensions = {} + _universalExtensions = {} _server_extensions = {} def __init__(self, server=False): @@ -124,8 +124,8 @@ def parse(self, p): return ext # then fallback to universal/ClientHello-specific parsers - if extType in self._universal_extensions: - ext = self._universal_extensions[extType]() + if extType in self._universalExtensions: + ext = self._universalExtensions[extType]() ext_parser = Parser(p.getFixBytes(ext_length)) ext = ext.parse(ext_parser) return ext @@ -1118,7 +1118,7 @@ def parse(self, parser): return self -TLSExtension._universal_extensions = \ +TLSExtension._universalExtensions = \ { ExtensionType.server_name : SNIExtension, ExtensionType.cert_type : ClientCertTypeExtension, From 313a6a45c504f2822d9a0cfc28b4caf60e2bb7f7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 15:19:15 +0200 Subject: [PATCH 145/574] rename _server_extensions since all fields and methods use camelCase, the newly introduced fields should too public API change possible since all this code was not part of a stable release yet --- tlslite/extensions.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 16ef16c7..3f065b00 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -40,15 +40,15 @@ class TLSExtension(object): ID. Contains ClientHello version of extensions or universal implementations - @type _server_extensions: dict - @cvar _server_extensions: dictionary with concrete implementations of + @type _serverExtensions: dict + @cvar _serverExtensions: dictionary with concrete implementations of specific TLS extensions where key is the numeric value of the extension ID. Includes only those extensions that require special handlers for ServerHello versions. """ # actual definition at the end of file, after definitions of all classes _universalExtensions = {} - _server_extensions = {} + _serverExtensions = {} def __init__(self, server=False): """ @@ -117,8 +117,8 @@ def parse(self, p): ext_length = p.get(2) # first check if we shouldn't use server side parser - if self.serverType and extType in self._server_extensions: - ext = self._server_extensions[extType]() + if self.serverType and extType in self._serverExtensions: + ext = self._serverExtensions[extType]() ext_parser = Parser(p.getFixBytes(ext_length)) ext = ext.parse(ext_parser) return ext @@ -1128,7 +1128,7 @@ def parse(self, parser): ExtensionType.signature_algorithms : SignatureAlgorithmsExtension, ExtensionType.supports_npn : NPNExtension} -TLSExtension._server_extensions = \ +TLSExtension._serverExtensions = \ { ExtensionType.cert_type : ServerCertTypeExtension, ExtensionType.tack : TACKExtension} From 181d1d18a801d7c219813739c119f51ca5a3a52a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 15:23:18 +0200 Subject: [PATCH 146/574] rename cert_types since all fields and methods use camelCase, the newly introduced fields should too public API change possible since all this code was not part of a stable release yet --- tlslite/extensions.py | 26 +++++++++++++------------- tlslite/messages.py | 4 ++-- unit_tests/test_tlslite_extensions.py | 16 ++++++++-------- unit_tests/test_tlslite_messages.py | 8 ++++---- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 3f065b00..26b6c7be 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -387,8 +387,8 @@ class ClientCertTypeExtension(TLSExtension): @type extData: bytearray @ivar extData: raw representation of the extension data - @type cert_types: list of int - @ivar cert_types: list of certificate type identifiers (each one byte long) + @type certTypes: list of int + @ivar certTypes: list of certificate type identifiers (each one byte long) """ def __init__(self): @@ -398,15 +398,15 @@ def __init__(self): See also: L{create} and L{parse} """ - self.cert_types = None + self.certTypes = None def __repr__(self): """ Return programmer-centric representation of extension @rtype: str """ - return "ClientCertTypeExtension(cert_types={0!r})"\ - .format(self.cert_types) + return "ClientCertTypeExtension(certTypes={0!r})"\ + .format(self.certTypes) @property def extType(self): @@ -426,27 +426,27 @@ def extData(self): @rtype: bytearray """ - if self.cert_types is None: + if self.certTypes is None: return bytearray(0) w = Writer() - w.add(len(self.cert_types), 1) - for c_type in self.cert_types: + w.add(len(self.certTypes), 1) + for c_type in self.certTypes: w.add(c_type, 1) return w.bytes - def create(self, cert_types=None): + def create(self, certTypes=None): """ Return instance of this extension with specified certificate types - @type cert_types: iterable list of int - @param cert_types: list of certificate types to advertise, all values + @type certTypes: iterable list of int + @param certTypes: list of certificate types to advertise, all values should be between 0 and 2^8-1 inclusive @raises ValueError: when the list includes too big or negative integers """ - self.cert_types = cert_types + self.certTypes = certTypes return self def parse(self, p): @@ -462,7 +462,7 @@ def parse(self, p): @rtype: L{ClientCertTypeExtension} """ - self.cert_types = p.getVarList(1, 1) + self.certTypes = p.getVarList(1, 1) return self diff --git a/tlslite/messages.py b/tlslite/messages.py index 2efa8d59..75f6db1d 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -295,7 +295,7 @@ def certificate_types(self): # depends on a default value of this property return [CertificateType.x509] else: - return cert_type.cert_types + return cert_type.certTypes @certificate_types.setter def certificate_types(self, val): @@ -314,7 +314,7 @@ def certificate_types(self, val): ext = ClientCertTypeExtension().create(val) self.addExtension(ext) else: - cert_type.cert_types = val + cert_type.certTypes = val @property def srp_username(self): diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index fe54ae3b..0cc0d0a3 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -283,7 +283,7 @@ def test_parse_with_client_cert_type_extension(self): self.assertIsInstance(ext, ClientCertTypeExtension) - self.assertEqual([1], ext.cert_types) + self.assertEqual([1], ext.certTypes) def test___repr__(self): ext = TLSExtension() @@ -644,7 +644,7 @@ def test___init___(self): self.assertEqual(9, cert_type.extType) self.assertEqual(bytearray(0), cert_type.extData) - self.assertIsNone(cert_type.cert_types) + self.assertIsNone(cert_type.certTypes) def test_create(self): cert_type = ClientCertTypeExtension() @@ -652,21 +652,21 @@ def test_create(self): self.assertEqual(9, cert_type.extType) self.assertEqual(bytearray(0), cert_type.extData) - self.assertIsNone(cert_type.cert_types) + self.assertIsNone(cert_type.certTypes) def test_create_with_empty_list(self): cert_type = ClientCertTypeExtension() cert_type = cert_type.create([]) self.assertEqual(bytearray(b'\x00'), cert_type.extData) - self.assertEqual([], cert_type.cert_types) + self.assertEqual([], cert_type.certTypes) def test_create_with_list(self): cert_type = ClientCertTypeExtension() cert_type = cert_type.create([0]) self.assertEqual(bytearray(b'\x01\x00'), cert_type.extData) - self.assertEqual([0], cert_type.cert_types) + self.assertEqual([0], cert_type.certTypes) def test_write(self): cert_type = ClientCertTypeExtension() @@ -686,7 +686,7 @@ def test_parse(self): cert_type = cert_type.parse(p) self.assertEqual(9, cert_type.extType) - self.assertEqual([], cert_type.cert_types) + self.assertEqual([], cert_type.certTypes) def test_parse_with_list(self): cert_type = ClientCertTypeExtension() @@ -695,7 +695,7 @@ def test_parse_with_list(self): cert_type = cert_type.parse(p) - self.assertEqual([1, 0], cert_type.cert_types) + self.assertEqual([1, 0], cert_type.certTypes) def test_parse_with_length_long_by_one(self): cert_type = ClientCertTypeExtension() @@ -709,7 +709,7 @@ def test___repr__(self): cert_type = ClientCertTypeExtension() cert_type = cert_type.create([0, 1]) - self.assertEqual("ClientCertTypeExtension(cert_types=[0, 1])", + self.assertEqual("ClientCertTypeExtension(certTypes=[0, 1])", repr(cert_type)) class TestServerCertTypeExtension(unittest.TestCase): diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index b3018acc..38a2b180 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -173,7 +173,7 @@ def test_parse_with_cert_type_extension(self): b'\x00'*2 + # cipher suites length b'\x00' + # compression methods length b'\x00\x07' + # extensions length - 7 bytes - b'\x00\x09' + # extension type - cert_types (9) + b'\x00\x09' + # extension type - certTypes (9) b'\x00\x03' + # extension length - 3 bytes b'\x02' + # length of array - 2 bytes b'\x00' + # type - x509 (0) @@ -188,8 +188,8 @@ def test_parse_with_cert_type_extension(self): self.assertEqual([], client_hello.cipher_suites) self.assertEqual([], client_hello.compression_methods) self.assertEqual([0,1], client_hello.certificate_types) - cert_types = ClientCertTypeExtension().create([0,1]) - self.assertEqual([cert_types], client_hello.extensions) + certTypes = ClientCertTypeExtension().create([0,1]) + self.assertEqual([certTypes], client_hello.extensions) def test_parse_with_SRP_extension(self): p = Parser(bytearray( @@ -475,7 +475,7 @@ def test_certificate_types(self): self.assertEqual(client_hello.certificate_types, [0, 1, 2]) ext = client_hello.getExtension(ExtensionType.cert_type) - self.assertEqual(ext.cert_types, [0, 1, 2]) + self.assertEqual(ext.certTypes, [0, 1, 2]) def test_srp_username(self): client_hello = ClientHello().create((3, 3), bytearray(1), bytearray(0), From 5dc97e4b06288cf5dbd69e7daf570d7906360c92 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 15:42:30 +0200 Subject: [PATCH 147/574] rename type_name since all fields and methods use camelCase, the newly introduced fields should too public API change possible since all this code was not part of a stable release yet --- tlslite/messages.py | 4 ++-- unit_tests/test_tlslite_messages.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 75f6db1d..b9645e63 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -64,7 +64,7 @@ def parse(self, parser): return self @property - def type_name(self): + def typeName(self): matching = [x[0] for x in ContentType.__dict__.items() if x[1] == self.type] if len(matching) == 0: @@ -75,7 +75,7 @@ def type_name(self): def __str__(self): return "SSLv3 record,version({0[0]}.{0[1]}),"\ "content type({1}),length({2})".format(self.version, - self.type_name, self.length) + self.typeName, self.length) def __repr__(self): return "RecordHeader3(type={0}, version=({1[0]}.{1[1]}), length={2})".\ diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 38a2b180..8b8bfea5 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1022,11 +1022,11 @@ def test_parse(self): self.assertEqual((3, 3), rh.version) self.assertEqual(15, rh.length) - def test_type_name(self): + def test_typeName(self): rh = RecordHeader3() rh = rh.create((3,0), ContentType.application_data, 0) - self.assertEqual("application_data", rh.type_name) + self.assertEqual("application_data", rh.typeName) def test___str__(self): rh = RecordHeader3() From 806bc7d7074391bf0367ca5e8bd3da677f677f64 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 15:56:04 +0200 Subject: [PATCH 148/574] rename level_name since all fields and methods use camelCase, the newly introduced fields should too public API change possible since all this code was not part of a stable release yet --- tlslite/messages.py | 4 ++-- unit_tests/test_tlslite_messages.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index b9645e63..7a03638b 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -144,7 +144,7 @@ def write(self): return w.bytes @property - def level_name(self): + def levelName(self): matching = [x[0] for x in AlertLevel.__dict__.items() if x[1] == self.level] if len(matching) == 0: @@ -162,7 +162,7 @@ def description_name(self): return str(matching[0]) def __str__(self): - return "Alert, level:{0}, description:{1}".format(self.level_name, + return "Alert, level:{0}, description:{1}".format(self.levelName, self.description_name) def __repr__(self): diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 8b8bfea5..d0b0b705 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1058,16 +1058,16 @@ def test___init__(self): self.assertEqual(alert.level, 0) self.assertEqual(alert.description, 0) - def test_level_name(self): + def test_levelName(self): alert = Alert().create(AlertDescription.record_overflow, AlertLevel.fatal) - self.assertEqual("fatal", alert.level_name) + self.assertEqual("fatal", alert.levelName) - def test_level_name_with_wrong_level(self): + def test_levelName_with_wrong_level(self): alert = Alert().create(AlertDescription.close_notify, 11) - self.assertEqual("unknown(11)", alert.level_name) + self.assertEqual("unknown(11)", alert.levelName) def test_description_name(self): alert = Alert().create(AlertDescription.record_overflow, From 9bb13a0c6f19bb6b9b26bad0dc5f84c348b7f131 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 15:58:08 +0200 Subject: [PATCH 149/574] rename description_name since all fields and methods use camelCase, the newly introduced fields should too public API change possible since all this code was not part of a stable release yet --- tlslite/messages.py | 4 ++-- unit_tests/test_tlslite_messages.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 7a03638b..54e0701b 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -153,7 +153,7 @@ def levelName(self): return str(matching[0]) @property - def description_name(self): + def descriptionName(self): matching = [x[0] for x in AlertDescription.__dict__.items() if x[1] == self.description] if len(matching) == 0: @@ -163,7 +163,7 @@ def description_name(self): def __str__(self): return "Alert, level:{0}, description:{1}".format(self.levelName, - self.description_name) + self.descriptionName) def __repr__(self): return "Alert(level={0}, description={1})".format(self.level, diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index d0b0b705..2f5eb326 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1069,16 +1069,16 @@ def test_levelName_with_wrong_level(self): self.assertEqual("unknown(11)", alert.levelName) - def test_description_name(self): + def test_descriptionName(self): alert = Alert().create(AlertDescription.record_overflow, AlertLevel.fatal) - self.assertEqual("record_overflow", alert.description_name) + self.assertEqual("record_overflow", alert.descriptionName) - def test_description_name_with_wrong_id(self): + def test_descriptionName_with_wrong_id(self): alert = Alert().create(1) - self.assertEqual("unknown(1)", alert.description_name) + self.assertEqual("unknown(1)", alert.descriptionName) def test___str__(self): alert = Alert().create(AlertDescription.record_overflow, From 845873bb6094cc8436f145ba2b8e11e2127ccbca Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 16:07:38 +0200 Subject: [PATCH 150/574] rename signature_algorithm since all fields and methods use camelCase, the newly introduced fields should too public API change possible since all this code was not part of a stable release yet --- tlslite/messages.py | 14 +++++++------- unit_tests/test_tlslite_messages.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 54e0701b..9c161fb5 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1231,18 +1231,18 @@ def __init__(self, version): """ HandshakeMsg.__init__(self, HandshakeType.certificate_verify) self.version = version - self.signature_algorithm = None + self.signatureAlgorithm = None self.signature = bytearray(0) - def create(self, signature, signature_algorithm=None): + def create(self, signature, signatureAlgorithm=None): """ Provide data for serialisation of message @param signature: signature carried in the message - @param signature_algorithm: signature algorithm used to make the + @param signatureAlgorithm: signature algorithm used to make the signature (TLSv1.2 only) """ - self.signature_algorithm = signature_algorithm + self.signatureAlgorithm = signatureAlgorithm self.signature = signature return self @@ -1254,7 +1254,7 @@ def parse(self, parser): """ parser.startLengthCheck(3) if self.version >= (3, 3): - self.signature_algorithm = (parser.get(1), parser.get(1)) + self.signatureAlgorithm = (parser.get(1), parser.get(1)) self.signature = parser.getVarBytes(2) parser.stopLengthCheck() return self @@ -1267,8 +1267,8 @@ def write(self): """ writer = Writer() if self.version >= (3, 3): - writer.add(self.signature_algorithm[0], 1) - writer.add(self.signature_algorithm[1], 1) + writer.add(self.signatureAlgorithm[0], 1) + writer.add(self.signatureAlgorithm[1], 1) writer.addVarSeq(self.signature, 1, 2) return self.postWrite(writer) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 2f5eb326..8a238670 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1776,8 +1776,8 @@ def test_parse_with_TLSv1_2(self): cv.parse(parser) self.assertEqual(cv.signature, bytearray(b'\xab\xcd')) - self.assertEqual(cv.signature_algorithm, (HashAlgorithm.sha1, - SignatureAlgorithm.rsa)) + self.assertEqual(cv.signatureAlgorithm, (HashAlgorithm.sha1, + SignatureAlgorithm.rsa)) def test_write_with_TLSv1_2(self): cv = CertificateVerify((3, 3)) From 6fed31d1162eac5a676dd43c5c9a229643c57715 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 17:50:48 +0200 Subject: [PATCH 151/574] change my contributions to LGPL v2 --- LICENSE | 478 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 456 insertions(+), 22 deletions(-) diff --git a/LICENSE b/LICENSE index a2f93920..d29479ce 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ TLS Lite includes code from different sources. All code is either dedicated to -the public domain by its authors, or available under a BSD-style license. In -particular: +the public domain by its authors, available under a BSD-style license or +available under GNU LGPL v2 license. In particular: - @@ -73,27 +73,461 @@ Code written by Hubert Kario is available under the following terms: Copyright (c) 2014, Hubert Kario, Red Hat Inc. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] -3. Neither the name of the copyright holder nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. + Preamble -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS From 98c302dc38fa1ca2c64b10d6c02775e992bd6a07 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 18:42:03 +0200 Subject: [PATCH 152/574] release version 0.5.0-beta2 --- setup.py | 9 ++++++--- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 745a0009..4a9cd210 100755 --- a/setup.py +++ b/setup.py @@ -6,11 +6,14 @@ from distutils.core import setup setup(name="tlslite-ng", - version="0.5.0-alpha3", + version="0.5.0-beta2", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", description="tlslite implements SSL and TLS.", - license="public domain and BSD", + license="LGPLv2", scripts=["scripts/tls.py", "scripts/tlsdb.py"], - packages=["tlslite", "tlslite.utils", "tlslite.integration"],) + packages=["tlslite", "tlslite.utils", "tlslite.integration"], + package_data={ + 'package1': ['LICENSE', 'README.md']}, + ) diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 0d05bc94..48468401 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.5.0-alpha3 +@version: 0.5.0-beta2 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index dfa29382..b31bab3d 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.5.0-alpha3" +__version__ = "0.5.0-beta2" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 857da280237402cf607a8ea53652566d2b8ce438 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 19:23:31 +0200 Subject: [PATCH 153/574] release version 0.5.0-beta3 --- README | 20 ++++++++++++++++++++ setup.py | 23 +++++++++++++++++++++-- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 README diff --git a/README b/README new file mode 100644 index 00000000..5c464635 --- /dev/null +++ b/README @@ -0,0 +1,20 @@ +tlslite-ng is a pure python implementation of SSLv3.0, TLS 1.0, TLS 1.1 and +TLS 1.2 protocols. + +It can use pycrypto, m2crypto and gmp for acceleration of cryptographic +operations but is not dependant upon them. + +Functionality implemented include: + - RC4, 3DES-CBC, AES-CBC and AES-GCM ciphers + - MD5, SHA1, SHA256 and SHA384 HMACs as well as AEAD mode of operation + - RSA and DHE_RSA key exchange + - anonymous DHE key exchange + - NULL encryption ciphersuites + - FALLBACK_SCSV + - encrypt-then-MAC mode of operation for CBC ciphersuites + - client certificates + - TACK certificate pinning + - SRP_SHA_RSA ciphersuites + +tlslite-ng aims to be a drop-in replacement for tlslite while providing more +comprehensive set of features and more secure defautls. diff --git a/setup.py b/setup.py index 4a9cd210..8e4b4833 100755 --- a/setup.py +++ b/setup.py @@ -5,15 +5,34 @@ from distutils.core import setup + + setup(name="tlslite-ng", - version="0.5.0-beta2", + version="0.5.0-beta3", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", - description="tlslite implements SSL and TLS.", + description="Pure python implementation of SSL and TLS.", license="LGPLv2", scripts=["scripts/tls.py", "scripts/tlsdb.py"], packages=["tlslite", "tlslite.utils", "tlslite.integration"], package_data={ 'package1': ['LICENSE', 'README.md']}, + obsoletes=["tlslite"], + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Topic :: Security :: Cryptography', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: System :: Networking' + ], + keywords="ssl, tls, pure-python" ) diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 48468401..8a014992 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.5.0-beta2 +@version: 0.5.0-beta3 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index b31bab3d..f1545af1 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.5.0-beta2" +__version__ = "0.5.0-beta3" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From a43e2575475afafa0e72d627f99c0f51e20361ee Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 19:27:15 +0200 Subject: [PATCH 154/574] update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e3d8942..ee59edd8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.5.0-alpha4 2015-07-13 +tlslite-ng version 0.5.0-beta3 2015-07-23 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` From b9fa15aa217b506d237f1e5fd66ceec5e854a339 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jul 2015 19:54:34 +0200 Subject: [PATCH 155/574] disable RC4 ciphers by default --- README.md | 5 +++-- tests/tlstest.py | 1 + tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- tlslite/handshakesettings.py | 4 ++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ee59edd8..5d7fdffd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.5.0-beta3 2015-07-23 +tlslite-ng version 0.5.0-beta4 2015-07-23 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -518,7 +518,8 @@ encrypt-then-MAC mode for CBC ciphers. 12 History =========== -0.5.0-alpha - xx/xx/xxxx - Hubert Kario +0.5.0-beta4 - 23/07/2015 - Hubert Kario + - removed RC4 from ciphers supported by default - add supported_groups, supported_point_formats, signature_algorithms and renegotiation_info extensions - remove most CBC MAC-ing and padding timing side-channel leaks (should fix diff --git a/tests/tlstest.py b/tests/tlstest.py index 42d7e379..8a7b969a 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -136,6 +136,7 @@ def connect(): connection = connect() settings = HandshakeSettings() settings.macNames = ["md5"] + settings.cipherNames = ["rc4"] connection.handshakeClientCert(settings=settings) testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 8a014992..932a6f88 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.5.0-beta3 +@version: 0.5.0-beta4 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index f1545af1..4c48e179 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.5.0-beta3" +__version__ = "0.5.0-beta4" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 5094f26f..a7943ac5 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -11,8 +11,8 @@ from .utils import cryptomath from .utils import cipherfactory -CIPHER_NAMES = ["aes256gcm", "aes128gcm", "rc4", "aes256", "aes128", "3des"] -ALL_CIPHER_NAMES = CIPHER_NAMES + ["null"] # require encryption by default +CIPHER_NAMES = ["aes256gcm", "aes128gcm", "aes256", "aes128", "3des"] +ALL_CIPHER_NAMES = CIPHER_NAMES + ["rc4", "null"] MAC_NAMES = ["sha", "sha256", "aead"] # Don't allow "md5" by default. ALL_MAC_NAMES = MAC_NAMES + ["md5"] KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "srp_sha", "srp_sha_rsa", "dh_anon"] From 5f788c4266c43fffa5b0e1df1e36dc560adeb74c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Jul 2015 14:44:45 +0200 Subject: [PATCH 156/574] mark as 0.5.0-beta4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8e4b4833..6e2736da 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.5.0-beta3", + version="0.5.0-beta4", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", From d6955c4a25a2823d7e37ca9681d1fc6fc7d23c5a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 20 Aug 2015 00:06:59 +0200 Subject: [PATCH 157/574] fix CVE-2015-3220 adds test cases for "no pad length byte" since the MAC-then-Encrypt code was rewritten to fix Lucky 13 it wasn't vulnerable, only the Encrypt-then-MAC remained vulnerable, this fixes this issue --- tlslite/recordlayer.py | 3 ++ unit_tests/test_tlslite_recordlayer.py | 46 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index e73cf240..e3e985b4 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -530,6 +530,9 @@ def _macThenDecrypt(self, recordType, buf): if self.version >= (3, 2): buf = buf[blockLength:] + if len(buf) == 0: + raise TLSBadRecordMAC("No data left after IV removal") + # check padding paddingLength = buf[-1] if paddingLength + 1 > len(buf): diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index b05f6366..3f28ea5b 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -1250,6 +1250,52 @@ def test_recvRecord_with_invalid_length_payload(self): with self.assertRaises(TLSDecryptionFailed): next(gen) + def test_recvRecord_with_zero_length_payload(self): + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x02' + # TLSv1.1 + b'\x00\x00' # length + )) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 2) + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + gen = recordLayer.recvRecord() + + with self.assertRaises(TLSBadRecordMAC): + next(gen) + + def test_recvRecord_with_zero_length_payload_EtM(self): + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x01' + # TLSv1.0 + b'\x00\x14' + # length (just MAC alone, no data) + b'A~\x1c\x88s\xdf\xa2sQ\xca\xdd\xb2\xd0\xdc\n\x94\x8e\xc8W\x04' + )) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 1) + recordLayer.encryptThenMAC = True + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + gen = recordLayer.recvRecord() + + with self.assertRaises(TLSBadRecordMAC): + next(gen) + def test_recvRecord_with_zero_filled_padding_in_SSLv3(self): # make sure the IV is predictible (all zero) patcher = mock.patch.object(os, From ac07b808d473564d74de4de77ec38a1b490c9be8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 22 Aug 2015 14:26:08 +0200 Subject: [PATCH 158/574] note fix for the CVE-2015-3220 in README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ef4d5397..1bdd34be 100644 --- a/README.md +++ b/README.md @@ -519,6 +519,9 @@ encrypt-then-MAC mode for CBC ciphers. 12 History =========== +0.5.0-X - xx/xx/xxxx + - fix CVE-2015-3220 - remote DoS caused by incorrect malformed packet handling + 0.5.0-beta4 - 23/07/2015 - Hubert Kario - removed RC4 from ciphers supported by default - add supported_groups, supported_point_formats, signature_algorithms and From 4b5b147cdd66e8c2302032a598a2fb4a37cded6e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 4 Sep 2015 19:39:17 +0200 Subject: [PATCH 159/574] refactor calcFinished out since it's a cryptographic calculation, it shouldn't be done inside TLSConnection. Moving it out to a helper module also allows us to put it under test coverage --- tlslite/mathtls.py | 42 +++++++++ tlslite/tlsconnection.py | 48 +++------- unit_tests/test_tlslite_mathtls.py | 135 ++++++++++++++++++++++++++++- 3 files changed, 186 insertions(+), 39 deletions(-) diff --git a/tlslite/mathtls.py b/tlslite/mathtls.py index 1a9755f1..e2957775 100644 --- a/tlslite/mathtls.py +++ b/tlslite/mathtls.py @@ -98,6 +98,48 @@ def calcMasterSecret(version, cipherSuite, premasterSecret, clientRandom, raise AssertionError() return masterSecret +def calcFinished(version, masterSecret, cipherSuite, handshakeHashes, + isClient): + """Calculate the Handshake protocol Finished value + + @param version: TLS protocol version tuple + @param masterSecret: negotiated master secret of the connection + @param cipherSuite: negotiated cipher suite of the connection, + @param handshakeHashes: running hash of the handshake messages + @param isClient: whether the calculation should be performed for message + sent by client (True) or by server (False) side of connection + """ + assert version in ((3, 0), (3, 1), (3, 2), (3, 3)) + if version == (3,0): + if isClient: + senderStr = b"\x43\x4C\x4E\x54" + else: + senderStr = b"\x53\x52\x56\x52" + + verifyData = handshakeHashes.digestSSL(masterSecret, senderStr) + return verifyData + elif version in ((3,1), (3,2)): + if isClient: + label = b"client finished" + else: + label = b"server finished" + + handshakeHash = handshakeHashes.digest() + verifyData = PRF(masterSecret, label, handshakeHash, 12) + return verifyData + else: # version == (3,3): + if isClient: + label = b"client finished" + else: + label = b"server finished" + + if cipherSuite in CipherSuite.sha384PrfSuites: + handshakeHash = handshakeHashes.digest('sha384') + verifyData = PRF_1_2_SHA384(masterSecret, label, handshakeHash, 12) + else: + handshakeHash = handshakeHashes.digest('sha256') + verifyData = PRF_1_2(masterSecret, label, handshakeHash, 12) + return verifyData def makeX(salt, username, password): if len(username)>=256: diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index c4944186..191c967b 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1959,7 +1959,11 @@ def _sendFinished(self, masterSecret, cipherSuite=None, nextProto=None): yield result #Calculate verification data - verifyData = self._calcFinished(masterSecret, cipherSuite, True) + verifyData = calcFinished(self.version, + masterSecret, + cipherSuite, + self._handshake_hash, + self._client) if self.fault == Fault.badFinished: verifyData[0] = (verifyData[0]+1)%256 @@ -2003,7 +2007,11 @@ def _getFinished(self, masterSecret, cipherSuite=None, self.next_proto = nextProto #Calculate verification data - verifyData = self._calcFinished(masterSecret, cipherSuite, False) + verifyData = calcFinished(self.version, + masterSecret, + cipherSuite, + self._handshake_hash, + not self._client) #Get and check Finished message under new state for result in self._getMsg(ContentType.handshake, @@ -2016,42 +2024,6 @@ def _getFinished(self, masterSecret, cipherSuite=None, "Finished message is incorrect"): yield result - def _calcFinished(self, masterSecret, cipherSuite, send=True): - if self.version == (3,0): - if (self._client and send) or (not self._client and not send): - senderStr = b"\x43\x4C\x4E\x54" - else: - senderStr = b"\x53\x52\x56\x52" - - verifyData = self._handshake_hash.digestSSL(masterSecret, senderStr) - return verifyData - - elif self.version in ((3,1), (3,2)): - if (self._client and send) or (not self._client and not send): - label = b"client finished" - else: - label = b"server finished" - - handshakeHashes = self._handshake_hash.digest() - verifyData = PRF(masterSecret, label, handshakeHashes, 12) - return verifyData - elif self.version == (3,3): - if (self._client and send) or (not self._client and not send): - label = b"client finished" - else: - label = b"server finished" - - if cipherSuite in CipherSuite.sha384PrfSuites: - handshakeHashes = self._handshake_hash.digest('sha384') - verifyData = PRF_1_2_SHA384(masterSecret, label, handshakeHashes, 12) - else: - handshakeHashes = self._handshake_hash.digest('sha256') - verifyData = PRF_1_2(masterSecret, label, handshakeHashes, 12) - return verifyData - else: - raise AssertionError() - - def _handshakeWrapperAsync(self, handshaker, checker): try: for result in handshaker: diff --git a/unit_tests/test_tlslite_mathtls.py b/unit_tests/test_tlslite_mathtls.py index 9068288d..d8d5eb4d 100644 --- a/unit_tests/test_tlslite_mathtls.py +++ b/unit_tests/test_tlslite_mathtls.py @@ -9,7 +9,9 @@ except ImportError: import unittest -from tlslite.mathtls import PRF_1_2, calcMasterSecret +from tlslite.mathtls import PRF_1_2, calcMasterSecret, calcFinished +from tlslite.handshakehashes import HandshakeHashes +from tlslite.constants import CipherSuite class TestCalcMasterSecret(unittest.TestCase): def test_with_empty_values(self): @@ -33,3 +35,134 @@ def test_with_realistic_values(self): ret = PRF_1_2(bytearray(48), b"key expansion", bytearray(64), 16) self.assertEqual(bytearray(b'S\xb5\xdb\xc8T }u)BxuB\xe4\xeb\xeb'), ret) + +class TestCalcFinished(unittest.TestCase): + def setUp(self): + self.hhashes = HandshakeHashes() + self.hhashes.update(bytearray(10)) + +class TestCalcFinishedInSSL3(TestCalcFinished): + def setUp(self): + super(TestCalcFinishedInSSL3, self).setUp() + + self.finished = calcFinished((3, 0), + bytearray(48), + 0, + self.hhashes, + True) + def test_client_value(self): + self.assertEqual(bytearray( + b'\x15\xa9\xd7\xf1\x8bV\xecY\xab\xee\xbaS\x9c}\xffW\xa0'+ + b'\xa8\\q\xe5x8"\xf4\xedp\xabl\x8aV\xd9G\xab\x0fz'), + self.finished) + + def test_server_value(self): + ret = calcFinished((3, 0), bytearray(48), 0, self.hhashes, False) + + self.assertEqual(bytearray( + b'\xe3^aCb\x8a\xfc\x98\xbf\xd7\x08\xddX\xdc[\xeac\x02\xdb'+ + b'\x9b\x8aN\xed\xed\xaaZ\xcb\xda"\x87K\xff\x89m\xa9/'), + ret) + + def test_if_multiple_runs_are_the_same(self): + ret2 = calcFinished((3, 0), bytearray(48), 0, self.hhashes, True) + + self.assertEqual(self.finished, ret2) + + def test_if_client_and_server_values_differ(self): + ret_srv = calcFinished((3, 0), bytearray(48), 0, self.hhashes, False) + + self.assertNotEqual(self.finished, ret_srv) + +class TestCalcFinishedInTLS1_0(TestCalcFinished): + def setUp(self): + super(TestCalcFinishedInTLS1_0, self).setUp() + + self.finished = calcFinished((3, 1), + bytearray(48), + 0, + self.hhashes, + True) + + def test_client_value(self): + self.assertEqual(12, len(self.finished)) + self.assertEqual(bytearray( + b'\xf8N\x8a\x8dx\xb8\xfe\x9e1\x0b\x8a#'), + self.finished) + + def test_server_value(self): + ret_srv = calcFinished((3, 1), bytearray(48), 0, self.hhashes, False) + + self.assertEqual(12, len(ret_srv)) + self.assertEqual(bytearray( + b'kYB\xce \x7f\xbb\xee\xe5\xe7<\x9d'), + ret_srv) + + def test_if_client_and_server_values_differ(self): + ret_srv = calcFinished((3, 1), bytearray(48), 0, self.hhashes, False) + + self.assertNotEqual(self.finished, ret_srv) + + def test_if_values_for_TLS1_0_and_TLS1_0_are_same(self): + ret = calcFinished((3, 2), bytearray(48), 0, self.hhashes, True) + + self.assertEqual(self.finished, ret) + +class TestCalcFinishedInTLS1_2WithSHA256(TestCalcFinished): + def setUp(self): + super(TestCalcFinishedInTLS1_2WithSHA256, self).setUp() + + self.finished = calcFinished((3, 3), + bytearray(48), + 0, + self.hhashes, + True) + + def test_client_value(self): + self.assertEqual(12, len(self.finished)) + self.assertEqual(bytearray( + b'\x8e\x8c~\x03lU$S\x9fz\\\xcc'), + self.finished) + + def test_server_value(self): + ret_srv = calcFinished((3, 3), bytearray(48), 0, self.hhashes, False) + + self.assertEqual(12, len(self.finished)) + self.assertEqual(bytearray( + b'\xa8\xf1\xdf8s|\xedU\\Z=U'), + ret_srv) + + def test_if_client_and_server_values_differ(self): + ret_srv = calcFinished((3, 3), bytearray(48), 0, self.hhashes, False) + + self.assertNotEqual(ret_srv, self.finished) + +class TestCalcFinishedInTLS1_2WithSHA384(TestCalcFinished): + def setUp(self): + super(TestCalcFinishedInTLS1_2WithSHA384, self).setUp() + + self.finished = calcFinished((3, 3), + bytearray(48), + CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384, + self.hhashes, + True) + + def test_client_value(self): + self.assertEqual(12, len(self.finished)) + self.assertEqual(bytearray( + b'UB\xeeq\x86\xa5\x88L \x04\x893'), + self.finished) + + def test_server_value(self): + ret_srv = calcFinished((3, 3), bytearray(48), + CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384, + self.hhashes, False) + self.assertEqual(bytearray( + b'\x02St\x13\xa8\xe6\xb6\xa2\x1c4\xff\xc5'), + ret_srv) + + def test_if_client_and_server_values_differ(self): + ret_srv = calcFinished((3, 3), bytearray(48), + CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384, + self.hhashes, False) + self.assertNotEqual(self.finished, ret_srv) From 65bb7411be58ab872b598b04e6a68acff6d0adfb Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 4 Sep 2015 20:09:20 +0200 Subject: [PATCH 160/574] simplify calcFinished there is a bit of code duplication in calcFinished, remove it --- tlslite/mathtls.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/tlslite/mathtls.py b/tlslite/mathtls.py index e2957775..aef0320e 100644 --- a/tlslite/mathtls.py +++ b/tlslite/mathtls.py @@ -117,29 +117,25 @@ def calcFinished(version, masterSecret, cipherSuite, handshakeHashes, senderStr = b"\x53\x52\x56\x52" verifyData = handshakeHashes.digestSSL(masterSecret, senderStr) - return verifyData - elif version in ((3,1), (3,2)): - if isClient: - label = b"client finished" - else: - label = b"server finished" - - handshakeHash = handshakeHashes.digest() - verifyData = PRF(masterSecret, label, handshakeHash, 12) - return verifyData - else: # version == (3,3): + else: if isClient: label = b"client finished" else: label = b"server finished" - if cipherSuite in CipherSuite.sha384PrfSuites: - handshakeHash = handshakeHashes.digest('sha384') - verifyData = PRF_1_2_SHA384(masterSecret, label, handshakeHash, 12) - else: - handshakeHash = handshakeHashes.digest('sha256') - verifyData = PRF_1_2(masterSecret, label, handshakeHash, 12) - return verifyData + if version in ((3,1), (3,2)): + handshakeHash = handshakeHashes.digest() + verifyData = PRF(masterSecret, label, handshakeHash, 12) + else: # version == (3,3): + if cipherSuite in CipherSuite.sha384PrfSuites: + handshakeHash = handshakeHashes.digest('sha384') + verifyData = PRF_1_2_SHA384(masterSecret, label, + handshakeHash, 12) + else: + handshakeHash = handshakeHashes.digest('sha256') + verifyData = PRF_1_2(masterSecret, label, handshakeHash, 12) + + return verifyData def makeX(salt, username, password): if len(username)>=256: From e0e3d7b4df3f8a7dad405cfe0820e7ee05b818eb Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 9 Sep 2015 18:37:33 +0200 Subject: [PATCH 161/574] release version 0.5.0-beta6 --- README.md | 6 ++---- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1bdd34be..8f4dae9f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.5.0-beta4 2015-07-23 +tlslite-ng version 0.5.0-beta6 2015-09-09 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -519,10 +519,8 @@ encrypt-then-MAC mode for CBC ciphers. 12 History =========== -0.5.0-X - xx/xx/xxxx +0.5.0-beta6 - 09/09/2015 - fix CVE-2015-3220 - remote DoS caused by incorrect malformed packet handling - -0.5.0-beta4 - 23/07/2015 - Hubert Kario - removed RC4 from ciphers supported by default - add supported_groups, supported_point_formats, signature_algorithms and renegotiation_info extensions diff --git a/setup.py b/setup.py index 6e2736da..e92d0da6 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.5.0-beta4", + version="0.5.0-beta6", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 932a6f88..0793a43d 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.5.0-beta4 +@version: 0.5.0-beta6 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 4c48e179..90de8076 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.5.0-beta4" +__version__ = "0.5.0-beta6" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From be9c59f043af05fa7b475f3162538664868d94ec Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 9 Sep 2015 19:22:04 +0200 Subject: [PATCH 162/574] make tls.py test server python3 compatible --- scripts/tls.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/tls.py b/scripts/tls.py index 6a7e5482..a097389e 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -23,6 +23,7 @@ from http import client as httplib from socketserver import * from http.server import * + from http.server import SimpleHTTPRequestHandler if __name__ != "__main__": raise "This must be run as a command, not used as a module!" @@ -104,9 +105,13 @@ def handleArgs(argv, argString, flagsList=[]): for opt, arg in opts: if opt == "-k": s = open(arg, "rb").read() + if sys.version_info[0] >= 3: + s = str(s, 'utf-8') privateKey = parsePEMKey(s, private=True) elif opt == "-c": s = open(arg, "rb").read() + if sys.version_info[0] >= 3: + s = str(s, 'utf-8') x509 = X509() x509.parse(s) certChain = X509CertChain([x509]) @@ -273,6 +278,7 @@ def serverCmd(argv): ############# sessionCache = SessionCache() + username = None class MyHTTPServer(ThreadingMixIn, TLSSocketServerMixIn, HTTPServer): def handshake(self, connection): From e462591134dccdf73687837e4479386c740b2db2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 10 Sep 2015 13:08:51 +0200 Subject: [PATCH 163/574] make copies of "constant" lists, so that users can .remove() on them because lists are passed as a reference in Python, we need to make copies of the "const" lists for use inside HandshakeSettings, this way users can do a settings.macNames.remove("aead") for example --- tlslite/handshakesettings.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index a7943ac5..7693f5fc 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -102,13 +102,13 @@ class HandshakeSettings(object): def __init__(self): self.minKeySize = 1023 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,1) - self.maxVersion = (3,3) + self.cipherNames = list(CIPHER_NAMES) + self.macNames = list(MAC_NAMES) + self.keyExchangeNames = list(KEY_EXCHANGE_NAMES) + self.cipherImplementations = list(CIPHER_IMPLEMENTATIONS) + self.certificateTypes = list(CERTIFICATE_TYPES) + self.minVersion = (3, 1) + self.maxVersion = (3, 3) self.useExperimentalTackExtension = False self.sendFallbackSCSV = False self.useEncryptThenMAC = True From 5e3c3da9a4a0cc56f4ac951abbec80d5bb874913 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 10 Sep 2015 13:14:14 +0200 Subject: [PATCH 164/574] don't negotiate EtM with AEAD ciphersuites (while the extension was technically negotiated, it didn't affect the on-the-wire data transfers) EtM is only applicable to CBC ciphesuites inside TLS, so don't negotiate it with stream or AEAD ciphersuites also fixes a bug where AEAD ciphers were inside SHA256 HMAC list and NULL ciphers were not recognised as stream ciphers --- tests/tlstest.py | 15 ++++++++++++--- tlslite/constants.py | 7 +++++-- tlslite/tlsconnection.py | 3 ++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index 8a7b969a..b0ef3ab8 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -117,7 +117,8 @@ def connect(): testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) assert(connection.session.serverName == address[0]) - assert(connection.encryptThenMAC == True) + assert(connection.session.cipherSuite in constants.CipherSuite.aeadSuites) + assert(connection.encryptThenMAC == False) connection.close() print("Test 1.a - good X509, SSLv3") @@ -141,6 +142,7 @@ def connect(): testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) assert(connection.session.cipherSuite == constants.CipherSuite.TLS_RSA_WITH_RC4_128_MD5) + assert(connection.encryptThenMAC == False) connection.close() if tackpyLoaded: @@ -471,6 +473,7 @@ def connect(): synchro.recv(1) connection = connect() settings = HandshakeSettings() + settings.macNames.remove("aead") assert(settings.useEncryptThenMAC) connection.handshakeClientCert(serverName=address[0], settings=settings) testConnClient(connection) @@ -483,6 +486,7 @@ def connect(): synchro.recv(1) connection = connect() settings = HandshakeSettings() + settings.macNames.remove("aead") settings.useEncryptThenMAC = False connection.handshakeClientCert(serverName=address[0], settings=settings) testConnClient(connection) @@ -494,7 +498,9 @@ def connect(): print("Test 26.c - resumption with EtM") synchro.recv(1) connection = connect() - connection.handshakeClientCert(serverName=address[0]) + settings = HandshakeSettings() + settings.macNames.remove("aead") + connection.handshakeClientCert(serverName=address[0], settings=settings) testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) assert(connection.session.serverName == address[0]) @@ -517,7 +523,9 @@ def connect(): print("Test 26.d - resumption with no EtM in 2nd handshake") synchro.recv(1) connection = connect() - connection.handshakeClientCert(serverName=address[0]) + settings = HandshakeSettings() + settings.macNames.remove("aead") + connection.handshakeClientCert(serverName=address[0], settings=settings) testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) assert(connection.session.serverName == address[0]) @@ -530,6 +538,7 @@ def connect(): synchro.recv(1) settings = HandshakeSettings() settings.useEncryptThenMAC = False + settings.macNames.remove("aead") connection = connect() try: connection.handshakeClientCert(serverName=address[0], session=session, diff --git a/tlslite/constants.py b/tlslite/constants.py index a9d72639..f0522790 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -413,8 +413,6 @@ class CipherSuite: sha256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) sha256Suites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) sha256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) - sha256Suites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) - sha256Suites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) sha256Suites.append(TLS_RSA_WITH_NULL_SHA256) sha256Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) sha256Suites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) @@ -422,6 +420,11 @@ class CipherSuite: # SHA-384 HMAC, SHA-384 PRF sha384Suites = [] + # stream cipher construction + streamSuites = [] + streamSuites.extend(rc4Suites) + streamSuites.extend(nullSuites) + # AEAD integrity, any PRF aeadSuites = [] aeadSuites.extend(aes128GcmSuites) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 191c967b..2b0fc742 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1418,7 +1418,8 @@ def _handshakeServerAsyncHelper(self, verifierDB, # Prepare other extensions if requested if settings.useEncryptThenMAC and \ clientHello.getExtension(ExtensionType.encrypt_then_mac) and \ - cipherSuite not in CipherSuite.rc4Suites: + cipherSuite not in CipherSuite.streamSuites and \ + cipherSuite not in CipherSuite.aeadSuites: extensions = [TLSExtension().create(ExtensionType.encrypt_then_mac, bytearray(0))] self._recordLayer.encryptThenMAC = True From e7ebde6ba6eda5e39e295ed5492d59e51f8a8a93 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 8 Oct 2015 16:18:26 +0200 Subject: [PATCH 165/574] fix python3.2 builds because coverage 4.0 is officially incompatible with Python 3.2, we must install older version of it when running on Python 3.2 --- .travis.yml | 1 + build-requirements.txt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2e12fc76..15bb5057 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,6 +73,7 @@ install: - if [[ $M2CRYPTO == 'true' ]]; then travis_retry pip install M2Crypto; fi - if [[ $PYCRYPTO == 'true' ]]; then travis_retry pip install pycrypto; fi - if [[ $GMPY == 'true' ]]; then travis_retry pip install gmpy; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then travis_retry pip install 'coverage<4'; else travis_retry pip install coverage; fi - travis_retry pip install -r build-requirements.txt script: diff --git a/build-requirements.txt b/build-requirements.txt index 0bf32528..3e51d838 100644 --- a/build-requirements.txt +++ b/build-requirements.txt @@ -1,4 +1,3 @@ pylint diff_cover -coverage coveralls From 4b77118968f9a52ce87967c449e533a3833d8853 Mon Sep 17 00:00:00 2001 From: Theron Lewis Date: Wed, 23 Sep 2015 14:31:28 -0700 Subject: [PATCH 166/574] Fixed generator functions (next) to work with python 3. --- tlslite/integration/asyncstatemachine.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tlslite/integration/asyncstatemachine.py b/tlslite/integration/asyncstatemachine.py index 50a6f4a3..73fafed9 100644 --- a/tlslite/integration/asyncstatemachine.py +++ b/tlslite/integration/asyncstatemachine.py @@ -157,7 +157,7 @@ def inWriteEvent(self): def _doHandshakeOp(self): try: - self.result = self.handshaker.next() + self.result = next(self.handshaker) except StopIteration: self.handshaker = None self.result = None @@ -165,14 +165,14 @@ def _doHandshakeOp(self): def _doCloseOp(self): try: - self.result = self.closer.next() + self.result = next(self.closer) except StopIteration: self.closer = None self.result = None self.outCloseEvent() def _doReadOp(self): - self.result = self.reader.next() + self.result = next(self.reader) if not self.result in (0,1): readBuffer = self.result self.reader = None @@ -181,7 +181,7 @@ def _doReadOp(self): def _doWriteOp(self): try: - self.result = self.writer.next() + self.result = next(self.writer) except StopIteration: self.writer = None self.result = None From 0a073990c4953575843e7a01daf58b6d6a70ecf7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 8 Oct 2015 15:58:40 +0200 Subject: [PATCH 167/574] define fields inside __init__ of AsyncStateMachine --- tlslite/integration/asyncstatemachine.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tlslite/integration/asyncstatemachine.py b/tlslite/integration/asyncstatemachine.py index 73fafed9..262bc25e 100644 --- a/tlslite/integration/asyncstatemachine.py +++ b/tlslite/integration/asyncstatemachine.py @@ -22,6 +22,11 @@ class AsyncStateMachine: """ def __init__(self): + self.result = None + self.handshaker = None + self.closer = None + self.reader = None + self.writer = None self._clear() def _clear(self): From 741f9f8b62ae21cc8ea462a0e0d12ba4e57716e7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 10 Oct 2015 14:19:32 +0200 Subject: [PATCH 168/574] Release version 0.5.0 --- README.md | 5 +++-- setup.py | 4 ++-- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8f4dae9f..d860ee17 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.5.0-beta6 2015-09-09 +tlslite-ng version 0.5.0 2015-10-10 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -519,7 +519,8 @@ encrypt-then-MAC mode for CBC ciphers. 12 History =========== -0.5.0-beta6 - 09/09/2015 +0.5.0 - 10/10/2015 + - fix generators in AsyncStateMachine to work on Python3 (Theron Lewis) - fix CVE-2015-3220 - remote DoS caused by incorrect malformed packet handling - removed RC4 from ciphers supported by default - add supported_groups, supported_point_formats, signature_algorithms and diff --git a/setup.py b/setup.py index e92d0da6..d0a2a4ae 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.5.0-beta6", + version="0.5.0", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", @@ -22,7 +22,7 @@ classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', + 'License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 0793a43d..d3eb2dd8 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.5.0-beta6 +@version: 0.5.0 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 90de8076..08d3cfb9 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.5.0-beta6" +__version__ = "0.5.0" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 2598e50d763a8282e1d50383c1014008a636218c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 17 Oct 2015 14:11:53 +0200 Subject: [PATCH 169/574] mark KeyExchange class as not part of stable API yet --- tlslite/tlsconnection.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 2b0fc742..548c71d9 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -28,6 +28,13 @@ from .utils.rsakey import RSAKey class KeyExchange(object): + + """ + Common API for calculating Premaster secret + + NOT stable, will get moved from this file + """ + def __init__(self, cipherSuite, clientHello, serverHello, privateKey): """ Initializes the KeyExchange. privateKey is the signing private key. @@ -53,6 +60,13 @@ def processClientKeyExchange(clientKeyExchange): raise NotImplementedError() class RSAKeyExchange(KeyExchange): + + """ + Handling of RSA key exchange + + NOT stable API, do NOT use + """ + def makeServerKeyExchange(self): return None @@ -76,6 +90,13 @@ def processClientKeyExchange(self, clientKeyExchange): return premasterSecret class DHE_RSAKeyExchange(KeyExchange): + + """ + Handling of ephemeral Diffe-Hellman Key exchange + + NOT stable API, do NOT use + """ + # 2048-bit MODP Group (RFC 3526, Section 3) dh_g, dh_p = goodGroupParameters[2] From 5a8f54238ec1df069283e5941a5fdac37eaad680 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 17 Oct 2015 14:29:24 +0200 Subject: [PATCH 170/574] little fixups in KeyExchange --- tlslite/tlsconnection.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 548c71d9..c369b465 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1,4 +1,4 @@ -# Authors: +# Authors: # Trevor Perrin # Google - added reqCAs parameter # Google (adapted by Sam Rushing and Marcelo Fernandez) - NPN support @@ -79,7 +79,7 @@ def processClientKeyExchange(self, clientKeyExchange): randomPreMasterSecret = getRandomBytes(48) if not premasterSecret: premasterSecret = randomPreMasterSecret - elif len(premasterSecret)!=48: + elif len(premasterSecret) != 48: premasterSecret = randomPreMasterSecret else: versionCheck = (premasterSecret[0], premasterSecret[1]) @@ -97,6 +97,11 @@ class DHE_RSAKeyExchange(KeyExchange): NOT stable API, do NOT use """ + def __init__(self, cipherSuite, clientHello, serverHello, privateKey): + super(DHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello, privateKey) + self.dh_Xs = None + # 2048-bit MODP Group (RFC 3526, Section 3) dh_g, dh_p = goodGroupParameters[2] @@ -114,7 +119,7 @@ def makeServerKeyExchange(self): serverKeyExchange.createDH(self.dh_p, self.dh_g, dh_Ys) hashBytes = serverKeyExchange.hash(self.clientHello.random, self.serverHello.random) - if version >= (3,3): + if version >= (3, 3): # TODO: Signature algorithm negotiation not supported. hashBytes = RSAKey.addPKCS1SHA1Prefix(hashBytes) serverKeyExchange.signAlg = SignatureAlgorithm.rsa From 06bad1272fc7f977534cc9ab0302f20f46cf1ce5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 17 Oct 2015 15:31:46 +0200 Subject: [PATCH 171/574] actually count the tests in tlstest.py since the addition of new features requires adding tests in the middle of the test suite, renumbering of tests is common and it's a waste of time --- tests/tlstest.py | 392 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 288 insertions(+), 104 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index b0ef3ab8..c6154a8a 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -99,18 +99,20 @@ def connect(): c = TLSConnection(sock) return c - test = 0 + test_no = 0 badFault = False - print("Test 0 - anonymous handshake") + print("Test {0} - anonymous handshake".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientAnonymous() testConnClient(connection) connection.close() - - print("Test 1 - good X509 (plus SNI)") + + test_no += 1 + + print("Test {0} - good X509 (plus SNI)".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientCert(serverName=address[0]) @@ -121,7 +123,9 @@ def connect(): assert(connection.encryptThenMAC == False) connection.close() - print("Test 1.a - good X509, SSLv3") + test_no += 1 + + print("Test {0} - good X509, SSLv3".format(test_no)) synchro.recv(1) connection = connect() settings = HandshakeSettings() @@ -130,9 +134,11 @@ def connect(): connection.handshakeClientCert(settings=settings) testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) - connection.close() + connection.close() - print("Test 1.b - good X509, RC4-MD5") + test_no += 1 + + print("Test {0} - good X509, RC4-MD5".format(test_no)) synchro.recv(1) connection = connect() settings = HandshakeSettings() @@ -146,20 +152,25 @@ def connect(): connection.close() if tackpyLoaded: - + settings = HandshakeSettings() settings.useExperimentalTackExtension = True - print("Test 2.a - good X.509, TACK") + test_no += 1 + + print("Test {0} - good X.509, TACK".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientCert(settings=settings) assert(connection.session.tackExt.tacks[0].getTackId() == "5lcbe.eyweo.yxuan.rw6xd.jtoz7") assert(connection.session.tackExt.activation_flags == 1) testConnClient(connection) - connection.close() + connection.close() - print("Test 2.b - good X.509, TACK unrelated to cert chain") + test_no += 1 + + print("Test {0} - good X.509, TACK unrelated to cert chain".\ + format(test_no)) synchro.recv(1) connection = connect() try: @@ -169,15 +180,30 @@ def connect(): if alert.description != AlertDescription.illegal_parameter: raise connection.close() + else: + test_no += 1 + + print("Test {0} - good X.509, TACK...skipped (no tackpy)".\ + format(test_no)) + + test_no += 1 - print("Test 3 - good SRP") + print("Test {0} - good X.509, TACK unrelated to cert chain...skipped" + " (no tackpy)".\ + format(test_no)) + + test_no += 1 + + print("Test {0} - good SRP".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientSRP("test", "password") testConnClient(connection) connection.close() - print("Test 4 - SRP faults") + test_no += 1 + + print("Test {0} - SRP faults".format(test_no)) for fault in Fault.clientSrpFaults + Fault.genericFaults: synchro.recv(1) connection = connect() @@ -189,7 +215,9 @@ def connect(): print(" BAD FAULT %s: %s" % (Fault.faultNames[fault], str(e))) badFault = True - print("Test 6 - good SRP: with X.509 certificate, TLSv1.0") + test_no += 1 + + print("Test {0} - good SRP: with X.509 certificate, TLSv1.0".format(test_no)) settings = HandshakeSettings() settings.minVersion = (3,1) settings.maxVersion = (3,1) @@ -200,7 +228,9 @@ def connect(): testConnClient(connection) connection.close() - print("Test 7 - X.509 with SRP faults") + test_no += 1 + + print("Test {0} - X.509 with SRP faults".format(test_no)) for fault in Fault.clientSrpFaults + Fault.genericFaults: synchro.recv(1) connection = connect() @@ -212,7 +242,9 @@ def connect(): print(" BAD FAULT %s: %s" % (Fault.faultNames[fault], str(e))) badFault = True - print("Test 11 - X.509 faults") + test_no += 1 + + print("Test {0} - X.509 faults".format(test_no)) for fault in Fault.clientNoAuthFaults + Fault.genericFaults: synchro.recv(1) connection = connect() @@ -224,7 +256,9 @@ def connect(): print(" BAD FAULT %s: %s" % (Fault.faultNames[fault], str(e))) badFault = True - print("Test 14 - good mutual X509") + test_no += 1 + + print("Test {0} - good mutual X509".format(test_no)) x509Cert = X509().parse(open(os.path.join(dir, "clientX509Cert.pem")).read()) x509Chain = X509CertChain([x509Cert]) s = open(os.path.join(dir, "clientX509Key.pem")).read() @@ -237,7 +271,9 @@ def connect(): assert(isinstance(connection.session.serverCertChain, X509CertChain)) connection.close() - print("Test 14.a - good mutual X509, TLSv1.1") + test_no += 1 + + print("Test {0} - good mutual X509, TLSv1.1".format(test_no)) synchro.recv(1) connection = connect() settings = HandshakeSettings() @@ -248,7 +284,9 @@ def connect(): assert(isinstance(connection.session.serverCertChain, X509CertChain)) connection.close() - print("Test 14.b - good mutual X509, SSLv3") + test_no += 1 + + print("Test {0} - good mutual X509, SSLv3".format(test_no)) synchro.recv(1) connection = connect() settings = HandshakeSettings() @@ -259,7 +297,9 @@ def connect(): assert(isinstance(connection.session.serverCertChain, X509CertChain)) connection.close() - print("Test 15 - mutual X.509 faults") + test_no += 1 + + print("Test {0} - mutual X.509 faults".format(test_no)) for fault in Fault.clientCertFaults + Fault.genericFaults: synchro.recv(1) connection = connect() @@ -271,7 +311,10 @@ def connect(): print(" BAD FAULT %s: %s" % (Fault.faultNames[fault], str(e))) badFault = True - print("Test 18 - good SRP, prepare to resume... (plus SNI)") + test_no += 1 + + print("Test {0} - good SRP, prepare to resume... (plus SNI)".\ + format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientSRP("test", "password", serverName=address[0]) @@ -279,7 +322,9 @@ def connect(): connection.close() session = connection.session - print("Test 19 - resumption (plus SNI)") + test_no += 1 + + print("Test {0} - resumption (plus SNI)".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientSRP("test", "garbage", serverName=address[0], @@ -287,7 +332,9 @@ def connect(): testConnClient(connection) #Don't close! -- see below - print("Test 20 - invalidated resumption (plus SNI)") + test_no += 1 + + print("Test {0} - invalidated resumption (plus SNI)".format(test_no)) synchro.recv(1) connection.sock.close() #Close the socket without a close_notify! synchro.recv(1) @@ -300,8 +347,10 @@ def connect(): if alert.description != AlertDescription.bad_record_mac: raise connection.close() - - print("Test 21 - HTTPS test X.509") + + test_no += 1 + + print("Test {0} - HTTPS test X.509".format(test_no)) address = address[0], address[1]+1 if hasattr(socket, "timeout"): timeoutEx = socket.timeout @@ -338,11 +387,15 @@ def connect(): implementations.append("pycrypto") implementations.append("python") - print("Test 22 - different ciphers, TLSv1.0") + test_no += 1 + + print("Test {0} - different ciphers, TLSv1.0".format(test_no)) for implementation in implementations: for cipher in ["aes128", "aes256", "rc4"]: - print("Test 22:", end=' ') + test_no += 1 + + print("Test {0}:".format(test_no), end=' ') synchro.recv(1) connection = connect() @@ -356,7 +409,9 @@ def connect(): print("%s %s" % (connection.getCipherName(), connection.getCipherImplementation())) connection.close() - print("Test 23 - throughput test") + test_no += 1 + + print("Test {0} - throughput test".format(test_no)) for implementation in implementations: for cipher in ["aes128gcm", "aes256gcm", "aes128", "aes256", "3des", "rc4"]: @@ -368,7 +423,9 @@ def connect(): "python"): continue - print("Test 23:", end=' ') + test_no += 1 + + print("Test {0}:".format(test_no), end=' ') synchro.recv(1) connection = connect() @@ -389,8 +446,10 @@ def connect(): assert(h == b"hello"*10000) connection.close() - - print("Test 24.a - Next-Protocol Client Negotiation") + + test_no += 1 + + print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientCert(nextProtos=[b"http/1.1"]) @@ -398,31 +457,39 @@ def connect(): assert(connection.next_proto == b'http/1.1') connection.close() - print("Test 24.b - Next-Protocol Client Negotiation") + test_no += 1 + + print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientCert(nextProtos=[b"spdy/2", b"http/1.1"]) #print(" Next-Protocol Negotiated: %s" % connection.next_proto) assert(connection.next_proto == b'spdy/2') connection.close() - - print("Test 24.c - Next-Protocol Client Negotiation") + + test_no += 1 + + print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientCert(nextProtos=[b"spdy/2", b"http/1.1"]) #print(" Next-Protocol Negotiated: %s" % connection.next_proto) assert(connection.next_proto == b'spdy/2') connection.close() - - print("Test 24.d - Next-Protocol Client Negotiation") + + test_no += 1 + + print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientCert(nextProtos=[b"spdy/3", b"spdy/2", b"http/1.1"]) #print(" Next-Protocol Negotiated: %s" % connection.next_proto) assert(connection.next_proto == b'spdy/2') connection.close() - - print("Test 24.e - Next-Protocol Client Negotiation") + + test_no += 1 + + print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientCert(nextProtos=[b"spdy/3", b"spdy/2", b"http/1.1"]) @@ -430,7 +497,9 @@ def connect(): assert(connection.next_proto == b'spdy/3') connection.close() - print("Test 24.f - Next-Protocol Client Negotiation") + test_no += 1 + + print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientCert(nextProtos=[b"http/1.1"]) @@ -438,7 +507,9 @@ def connect(): assert(connection.next_proto == b'http/1.1') connection.close() - print("Test 24.g - Next-Protocol Client Negotiation") + test_no += 1 + + print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientCert(nextProtos=[b"spdy/2", b"http/1.1"]) @@ -446,7 +517,9 @@ def connect(): assert(connection.next_proto == b'spdy/2') connection.close() - print("Test 25.a - FALLBACK_SCSV") + test_no += 1 + + print("Test {0} - FALLBACK_SCSV".format(test_no)) synchro.recv(1) connection = connect() settings = HandshakeSettings() @@ -455,7 +528,9 @@ def connect(): testConnClient(connection) connection.close() - print("Test 25.b - FALLBACK_SCSV") + test_no += 1 + + print("Test {0} - FALLBACK_SCSV".format(test_no)) synchro.recv(1) connection = connect() settings = HandshakeSettings() @@ -469,7 +544,9 @@ def connect(): raise connection.close() - print("Test 26.a - no EtM server side") + test_no += 1 + + print("Test {0} - no EtM server side".format(test_no)) synchro.recv(1) connection = connect() settings = HandshakeSettings() @@ -482,7 +559,9 @@ def connect(): assert(not connection.encryptThenMAC) connection.close() - print("Test 26.b - no EtM client side") + test_no += 1 + + print("Test {0} - no EtM client side".format(test_no)) synchro.recv(1) connection = connect() settings = HandshakeSettings() @@ -495,7 +574,9 @@ def connect(): assert(not connection.encryptThenMAC) connection.close() - print("Test 26.c - resumption with EtM") + test_no += 1 + + print("Test {0} - resumption with EtM".format(test_no)) synchro.recv(1) connection = connect() settings = HandshakeSettings() @@ -520,7 +601,9 @@ def connect(): assert(connection.encryptThenMAC) connection.close() - print("Test 26.d - resumption with no EtM in 2nd handshake") + test_no += 1 + + print("Test {0} - resumption with no EtM in 2nd handshake".format(test_no)) synchro.recv(1) connection = connect() settings = HandshakeSettings() @@ -549,7 +632,9 @@ def connect(): raise AssertionError("No exception raised") connection.close() - print('Test 27 - good standard XMLRPC https client') + test_no += 1 + + print('Test {0} - good standard XMLRPC https client'.format(test_no)) address = address[0], address[1]+1 synchro.recv(1) try: @@ -566,7 +651,9 @@ def connect(): synchro.recv(1) assert server.pow(2,4) == 16 - print('Test 28 - good tlslite XMLRPC client') + test_no += 1 + + print('Test {0} - good tlslite XMLRPC client'.format(test_no)) transport = XMLRPCTransport(ignoreAbruptClose=True) server = xmlrpclib.Server('https://%s:%s' % address, transport) synchro.recv(1) @@ -574,29 +661,39 @@ def connect(): synchro.recv(1) assert server.pow(2,4) == 16 - print('Test 29 - good XMLRPC ignored protocol') + test_no += 1 + + print('Test {0} - good XMLRPC ignored protocol'.format(test_no)) server = xmlrpclib.Server('http://%s:%s' % address, transport) synchro.recv(1) assert server.add(1,2) == 3 synchro.recv(1) assert server.pow(2,4) == 16 - print("Test 30 - Internet servers test") + test_no += 1 + + print("Test {0} - Internet servers test".format(test_no)) try: i = IMAP4_TLS("cyrus.andrew.cmu.edu") i.login("anonymous", "anonymous@anonymous.net") i.logout() - print("Test 31: IMAP4 good") + + test_no += 1 + + print("Test {0}: IMAP4 good".format(test_no)) p = POP3_TLS("pop.gmail.com") p.quit() - print("Test 32: POP3 good") + + test_no += 1 + + print("Test {0}: POP3 good".format(test_no)) except socket.error as e: print("Non-critical error: socket error trying to reach internet server: ", e) synchro.close() if not badFault: - print("Test succeeded") + print("Test succeeded, {0} good".format(test_no)) else: print("Test failed") @@ -645,14 +742,18 @@ def connect(): s = open(os.path.join(dir, "serverX509Key.pem")).read() x509Key = parsePEMKey(s, private=True) - print("Test 0 - Anonymous server handshake") + test_no = 0 + + print("Test {0} - Anonymous server handshake".format(test_no)) synchro.send(b'R') connection = connect() connection.handshakeServer(anon=True) testConnServer(connection) - connection.close() - - print("Test 1 - good X.509") + connection.close() + + test_no += 1 + + print("Test {0} - good X.509".format(test_no)) synchro.send(b'R') connection = connect() connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) @@ -660,7 +761,9 @@ def connect(): testConnServer(connection) connection.close() - print("Test 1.a - good X.509, SSL v3") + test_no += 1 + + print("Test {0} - good X.509, SSL v3".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -668,9 +771,11 @@ def connect(): settings.maxVersion = (3,0) connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, settings=settings) testConnServer(connection) - connection.close() + connection.close() - print("Test 1.b - good X.509, RC4-MD5") + test_no += 1 + + print("Test {0} - good X.509, RC4-MD5".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -678,24 +783,29 @@ def connect(): settings.cipherNames = ["rc4"] connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, settings=settings) testConnServer(connection) - connection.close() - + connection.close() + if tackpyLoaded: tack = Tack.createFromPem(open("./TACK1.pem", "rU").read()) tackUnrelated = Tack.createFromPem(open("./TACKunrelated.pem", "rU").read()) - + settings = HandshakeSettings() settings.useExperimentalTackExtension = True - print("Test 2.a - good X.509, TACK") + test_no += 1 + + print("Test {0} - good X.509, TACK".format(test_no)) synchro.send(b'R') connection = connect() connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, tacks=[tack], activationFlags=1, settings=settings) - testConnServer(connection) - connection.close() + testConnServer(connection) + connection.close() + + test_no += 1 - print("Test 2.b - good X.509, TACK unrelated to cert chain") + print("Test {0} - good X.509, TACK unrelated to cert chain".\ + format(test_no)) synchro.send(b'R') connection = connect() try: @@ -704,9 +814,21 @@ def connect(): assert(False) except TLSRemoteAlert as alert: if alert.description != AlertDescription.illegal_parameter: - raise - - print("Test 3 - good SRP") + raise + else: + test_no += 1 + + print("Test {0} - good X.509, TACK...skipped (no tackpy)".\ + format(test_no)) + + test_no += 1 + + print("Test {0} - good X.509, TACK unrelated to cert chain" + "...skipped (no tackpy)".format(test_no)) + + test_no += 1 + + print("Test {0} - good SRP".format(test_no)) verifierDB = VerifierDB() verifierDB.create() entry = VerifierDB.makeVerifier("test", "password", 1536) @@ -718,7 +840,9 @@ def connect(): testConnServer(connection) connection.close() - print("Test 4 - SRP faults") + test_no += 1 + + print("Test {0} - SRP faults".format(test_no)) for fault in Fault.clientSrpFaults + Fault.genericFaults: synchro.send(b'R') connection = connect() @@ -726,7 +850,9 @@ def connect(): connection.handshakeServer(verifierDB=verifierDB) connection.close() - print("Test 6 - good SRP: with X.509 cert") + test_no += 1 + + print("Test {0} - good SRP: with X.509 cert".format(test_no)) synchro.send(b'R') connection = connect() connection.handshakeServer(verifierDB=verifierDB, \ @@ -734,7 +860,9 @@ def connect(): testConnServer(connection) connection.close() - print("Test 7 - X.509 with SRP faults") + test_no += 1 + + print("Test {0} - X.509 with SRP faults".format(test_no)) for fault in Fault.clientSrpFaults + Fault.genericFaults: synchro.send(b'R') connection = connect() @@ -743,7 +871,9 @@ def connect(): certChain=x509Chain, privateKey=x509Key) connection.close() - print("Test 11 - X.509 faults") + test_no += 1 + + print("Test {0} - X.509 faults".format(test_no)) for fault in Fault.clientNoAuthFaults + Fault.genericFaults: synchro.send(b'R') connection = connect() @@ -751,7 +881,9 @@ def connect(): connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) connection.close() - print("Test 14 - good mutual X.509") + test_no += 1 + + print("Test {0} - good mutual X.509".format(test_no)) synchro.send(b'R') connection = connect() connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, reqCert=True) @@ -759,7 +891,9 @@ def connect(): assert(isinstance(connection.session.clientCertChain, X509CertChain)) connection.close() - print("Test 14a - good mutual X.509, TLSv1.1") + test_no += 1 + + print("Test {0} - good mutual X.509, TLSv1.1".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -770,7 +904,9 @@ def connect(): assert(isinstance(connection.session.clientCertChain, X509CertChain)) connection.close() - print("Test 14b - good mutual X.509, SSLv3") + test_no += 1 + + print("Test {0} - good mutual X.509, SSLv3".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -781,7 +917,9 @@ def connect(): assert(isinstance(connection.session.clientCertChain, X509CertChain)) connection.close() - print("Test 15 - mutual X.509 faults") + test_no += 1 + + print("Test {0} - mutual X.509 faults".format(test_no)) for fault in Fault.clientCertFaults + Fault.genericFaults: synchro.send(b'R') connection = connect() @@ -789,7 +927,9 @@ def connect(): connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, reqCert=True) connection.close() - print("Test 18 - good SRP, prepare to resume") + test_no += 1 + + print("Test {0} - good SRP, prepare to resume".format(test_no)) synchro.send(b'R') sessionCache = SessionCache() connection = connect() @@ -798,7 +938,9 @@ def connect(): testConnServer(connection) connection.close() - print("Test 19 - resumption") + test_no += 1 + + print("Test {0} - resumption".format(test_no)) synchro.send(b'R') connection = connect() connection.handshakeServer(verifierDB=verifierDB, sessionCache=sessionCache) @@ -806,7 +948,9 @@ def connect(): testConnServer(connection) #Don't close! -- see next test - print("Test 20 - invalidated resumption") + test_no += 1 + + print("Test {0} - invalidated resumption".format(test_no)) synchro.send(b'R') try: connection.read(min=1, max=1) @@ -822,7 +966,9 @@ def connect(): raise connection.close() - print("Test 21 - HTTPS test X.509") + test_no += 1 + + print("Test {0} - HTTPS test X.509".format(test_no)) #Close the current listening socket lsock.close() @@ -860,11 +1006,15 @@ def server_bind(self): implementations.append("pycrypto") implementations.append("python") - print("Test 22 - different ciphers") + test_no += 1 + + print("Test {0} - different ciphers".format(test_no)) for implementation in ["python"] * len(implementations): for cipher in ["aes128", "aes256", "rc4"]: - print("Test 22:", end=' ') + test_no += 1 + + print("Test {0}:".format(test_no), end=' ') synchro.send(b'R') connection = connect() @@ -878,7 +1028,9 @@ def server_bind(self): testConnServer(connection) connection.close() - print("Test 23 - throughput test") + test_no += 1 + + print("Test {0} - throughput test".format(test_no)) for implementation in implementations: for cipher in ["aes128gcm", "aes256gcm", "aes128", "aes256", "3des", "rc4"]: @@ -890,7 +1042,9 @@ def server_bind(self): "python"): continue - print("Test 23:", end=' ') + test_no += 1 + + print("Test {0}:".format(test_no), end=' ') synchro.send(b'R') connection = connect() @@ -906,7 +1060,9 @@ def server_bind(self): connection.write(h) connection.close() - print("Test 24.a - Next-Protocol Server Negotiation") + test_no += 1 + + print("Test {0} - Next-Protocol Server Negotiation".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -915,7 +1071,9 @@ def server_bind(self): testConnServer(connection) connection.close() - print("Test 24.b - Next-Protocol Server Negotiation") + test_no += 1 + + print("Test {0} - Next-Protocol Server Negotiation".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -923,8 +1081,10 @@ def server_bind(self): settings=settings, nextProtos=[b"spdy/2", b"http/1.1"]) testConnServer(connection) connection.close() - - print("Test 24.c - Next-Protocol Server Negotiation") + + test_no += 1 + + print("Test {0} - Next-Protocol Server Negotiation".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -933,7 +1093,9 @@ def server_bind(self): testConnServer(connection) connection.close() - print("Test 24.d - Next-Protocol Server Negotiation") + test_no += 1 + + print("Test {0} - Next-Protocol Server Negotiation".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -941,8 +1103,10 @@ def server_bind(self): settings=settings, nextProtos=[b"spdy/2", b"http/1.1"]) testConnServer(connection) connection.close() - - print("Test 24.e - Next-Protocol Server Negotiation") + + test_no += 1 + + print("Test {0} - Next-Protocol Server Negotiation".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -950,8 +1114,10 @@ def server_bind(self): settings=settings, nextProtos=[b"http/1.1", b"spdy/2", b"spdy/3"]) testConnServer(connection) connection.close() - - print("Test 24.f - Next-Protocol Server Negotiation") + + test_no += 1 + + print("Test {0} - Next-Protocol Server Negotiation".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -959,8 +1125,10 @@ def server_bind(self): settings=settings, nextProtos=[b"spdy/3", b"spdy/2"]) testConnServer(connection) connection.close() - - print("Test 24.g - Next-Protocol Server Negotiation") + + test_no += 1 + + print("Test {0} - Next-Protocol Server Negotiation".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -969,14 +1137,18 @@ def server_bind(self): testConnServer(connection) connection.close() - print("Test 25.a - FALLBACK_SCSV") + test_no += 1 + + print("Test {0} - FALLBACK_SCSV".format(test_no)) synchro.send(b'R') connection = connect() connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) testConnServer(connection) connection.close() - print("Test 25.b - FALLBACK_SCSV") + test_no += 1 + + print("Test {0} - FALLBACK_SCSV".format(test_no)) synchro.send(b'R') connection = connect() try: @@ -987,7 +1159,9 @@ def server_bind(self): raise connection.close() - print("Test 26.a - no EtM server side") + test_no += 1 + + print("Test {0} - no EtM server side".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -997,14 +1171,18 @@ def server_bind(self): testConnServer(connection) connection.close() - print("Test 26.b - no EtM client side") + test_no += 1 + + print("Test {0} - no EtM client side".format(test_no)) synchro.send(b'R') connection = connect() connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) testConnServer(connection) connection.close() - print("Test 26.c - resumption with EtM") + test_no += 1 + + print("Test {0} - resumption with EtM".format(test_no)) synchro.send(b'R') sessionCache = SessionCache() connection = connect() @@ -1021,7 +1199,9 @@ def server_bind(self): testConnServer(connection) connection.close() - print("Test 26.d - resumption with no EtM in 2nd handshake") + test_no += 1 + + print("Test {0} - resumption with no EtM in 2nd handshake".format(test_no)) synchro.send(b'R') sessionCache = SessionCache() connection = connect() @@ -1042,7 +1222,11 @@ def server_bind(self): raise AssertionError("no exception raised") connection.close() - print("Tests 27-29 - XMLRPXC server") + test_no += 1 + + print("Tests {0}-{1} - XMLRPXC server".format(test_no, test_no + 2)) + test_no += 2 + address = address[0], address[1]+1 class Server(TLSXMLRPCServer): From 662ec43a5a4fb0863a84eaf5833544502bf37fb5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 17 Oct 2015 16:59:31 +0200 Subject: [PATCH 172/574] workaround M2Crypto bug number 65 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 15bb5057..a9922577 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,7 +70,8 @@ before_install: install: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then travis_retry pip install unittest2; fi - if [[ $TACKPY == 'true' ]]; then travis_retry pip install tackpy; fi - - if [[ $M2CRYPTO == 'true' ]]; then travis_retry pip install M2Crypto; fi + # 'm2crypto==0.22.3' is a workaround for https://gitlab.com/m2crypto/m2crypto/issues/65 + - if [[ $M2CRYPTO == 'true' ]]; then travis_retry pip install 'm2crypto==0.22.3'; fi - if [[ $PYCRYPTO == 'true' ]]; then travis_retry pip install pycrypto; fi - if [[ $GMPY == 'true' ]]; then travis_retry pip install gmpy; fi - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then travis_retry pip install 'coverage<4'; else travis_retry pip install coverage; fi From 5133390b21c9f9685613ea01511df40d16c42ec8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 18 Oct 2015 20:36:07 +0200 Subject: [PATCH 173/574] using rc of m2crypto workaround the sslv2 issue that made using current m2crypto impossible on ubuntu --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a9922577..be7398ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,8 +70,7 @@ before_install: install: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then travis_retry pip install unittest2; fi - if [[ $TACKPY == 'true' ]]; then travis_retry pip install tackpy; fi - # 'm2crypto==0.22.3' is a workaround for https://gitlab.com/m2crypto/m2crypto/issues/65 - - if [[ $M2CRYPTO == 'true' ]]; then travis_retry pip install 'm2crypto==0.22.3'; fi + - if [[ $M2CRYPTO == 'true' ]]; then travis_retry pip install --pre m2crypto; fi - if [[ $PYCRYPTO == 'true' ]]; then travis_retry pip install pycrypto; fi - if [[ $GMPY == 'true' ]]; then travis_retry pip install gmpy; fi - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then travis_retry pip install 'coverage<4'; else travis_retry pip install coverage; fi From e91584d91f2f847644031e6b37f5a646150b0d1a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 3 Nov 2015 18:09:20 +0100 Subject: [PATCH 174/574] Implement the ChaCha cipher --- tlslite/utils/chacha.py | 127 +++++++++ unit_tests/test_tlslite_utils_chacha.py | 349 ++++++++++++++++++++++++ 2 files changed, 476 insertions(+) create mode 100644 tlslite/utils/chacha.py create mode 100644 unit_tests/test_tlslite_utils_chacha.py diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py new file mode 100644 index 00000000..c5a7eb3b --- /dev/null +++ b/tlslite/utils/chacha.py @@ -0,0 +1,127 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. +"""Pure Python implementation of ChaCha cipher + +Implementation that follows RFC 7539 closely. +""" + +from __future__ import division +from .compat import compat26Str +import copy +import struct + +class ChaCha(object): + + """Pure python implementation of ChaCha cipher""" + + constants = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574] + + @staticmethod + def rotl32(v, c): + """Rotate left a 32 bit integer v by c bits""" + return ((v << c) & 0xffffffff) | (v >> (32 - c)) + + @staticmethod + def quarter_round(x, a, b, c, d): + """Perform a ChaCha quarter round""" + x[a] = (x[a] + x[b]) & 0xffffffff + x[d] = x[d] ^ x[a] + x[d] = ChaCha.rotl32(x[d], 16) + + x[c] = (x[c] + x[d]) & 0xffffffff + x[b] = x[b] ^ x[c] + x[b] = ChaCha.rotl32(x[b], 12) + + x[a] = (x[a] + x[b]) & 0xffffffff + x[d] = x[d] ^ x[a] + x[d] = ChaCha.rotl32(x[d], 8) + + x[c] = (x[c] + x[d]) & 0xffffffff + x[b] = x[b] ^ x[c] + x[b] = ChaCha.rotl32(x[b], 7) + + @staticmethod + def double_round(x): + """Perform two rounds of ChaCha cipher""" + ChaCha.quarter_round(x, 0, 4, 8, 12) + ChaCha.quarter_round(x, 1, 5, 9, 13) + ChaCha.quarter_round(x, 2, 6, 10, 14) + ChaCha.quarter_round(x, 3, 7, 11, 15) + ChaCha.quarter_round(x, 0, 5, 10, 15) + ChaCha.quarter_round(x, 1, 6, 11, 12) + ChaCha.quarter_round(x, 2, 7, 8, 13) + ChaCha.quarter_round(x, 3, 4, 9, 14) + + @staticmethod + def chacha_block(key, counter, nonce, rounds): + """Generate a state of a single block""" + state = [] + state.extend(ChaCha.constants) + state.extend(key) + state.append(counter) + state.extend(nonce) + + working_state = copy.copy(state) + for i in range(0, rounds // 2): + ChaCha.double_round(working_state) + + for i, _ in enumerate(working_state): + state[i] = (state[i] + working_state[i]) & 0xffffffff + + return state + + @staticmethod + def word_to_bytearray(state): + """Convert state to little endian bytestream""" + ret = bytearray() + for i in state: + ret += struct.pack('> 24) | \ + ((data & 0x00ff0000) >> 8) | \ + ((data & 0x0000ff00) << 8) | \ + ((data & 0x000000ff) << 24) + + def test___init__(self): + chacha = ChaCha(key=bytearray(256//8), nonce=bytearray(96//8)) + self.assertIsNotNone(chacha) + + def test___init___with_wrong_key_size(self): + with self.assertRaises(ValueError): + ChaCha(key=bytearray(16), nonce=bytearray(96//8)) + + def test___init___with_wrong_nonce_size(self): + with self.assertRaises(ValueError): + ChaCha(key=bytearray(32), nonce=bytearray(16)) + + def test_quarter_round(self): + #RFC 7539 in text test vector + x = [0x11111111, 0x01020304, 0x9b8d6f43, 0x01234567] + + ChaCha.quarter_round(x, 0, 1, 2, 3) + + self.assertEqual(x, + [0xea2a92f4, 0xcb1cf8ce, 0x4581472e, 0x5881c4bb]) + + def test_quarter_round_on_state(self): + #RFC 7539 in text test vector + x = [0x879531e0, 0xc5ecf37d, 0x516461b1, 0xc9a62f8a, + 0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0x2a5f714c, + 0x53372767, 0xb00a5631, 0x974c541a, 0x359e9963, + 0x5c971061, 0x3d631689, 0x2098d9d6, 0x91dbd320] + + ChaCha.quarter_round(x, 2, 7, 8, 13) + + self.assertEqual(x, + [0x879531e0, 0xc5ecf37d, 0xbdb886dc, 0xc9a62f8a, + 0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0xcfacafd2, + 0xe46bea80, 0xb00a5631, 0x974c541a, 0x359e9963, + 0x5c971061, 0xccc07c79, 0x2098d9d6, 0x91dbd320]) + + def test_betole32(self): + number = self.betole32(bytesToNumber(bytearray(b'\x00\x01\x02\x03'))) + + self.assertEqual(number, 0x03020100) + + def test_chacha_block(self): + # RFC 7539 in text test vector + key = [0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, + 0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f] + for i, item in enumerate(key): + key[i] = self.betole32(item) + nonce = [0x00000009, 0x0000004a, 0x00000000] + for i, item in enumerate(nonce): + nonce[i] = self.betole32(item) + counter = 1 + + x = ChaCha.chacha_block(key, counter, nonce, rounds=20) + + self.assertEqual(x, + [0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3, + 0xc7f4d1c7, 0x0368c033, 0x9aaa2204, 0x4e6cd4c3, + 0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9, + 0xd19c12b5, 0xb94e16de, 0xe883d0cb, 0x4e3c50a2]) + + self.assertEqual(ChaCha.word_to_bytearray(x), bytearray( + b'\x10\xf1\xe7\xe4\xd1\x3b\x59\x15\x50\x0f\xdd\x1f\xa3\x20\x71\xc4' + b'\xc7\xd1\xf4\xc7\x33\xc0\x68\x03\x04\x22\xaa\x9a\xc3\xd4\x6c\x4e' + b'\xd2\x82\x64\x46\x07\x9f\xaa\x09\x14\xc2\xd7\x05\xd9\x8b\x02\xa2' + b'\xb5\x12\x9c\xd1\xde\x16\x4e\xb9\xcb\xd0\x83\xe8\xa2\x50\x3c\x4e' + )) + + def test_chacha_encrypt(self): + # RFC 7539 in text test vector + key = bytearray(range(0x00, 0x20)) + nonce = bytearray(b'\x00'*7 + b'\x4a' + b'\x00'*4) + chacha = ChaCha(key, nonce) + self.assertIsNotNone(chacha) + chacha.counter = 1 + + plaintext = bytearray(b'Ladies and Gentlemen of the class of \'99: ' + b'If I could offer you only one tip for the ' + b'future, sunscreen would be it.') + + self.assertEqual(len(plaintext), 64+50) + + #import pdb; pdb.set_trace() + ciphertext = chacha.encrypt(plaintext) + + self.assertEqual(ciphertext, bytearray( + b'\x6e\x2e\x35\x9a\x25\x68\xf9\x80\x41\xba\x07\x28\xdd\x0d\x69\x81' + b'\xe9\x7e\x7a\xec\x1d\x43\x60\xc2\x0a\x27\xaf\xcc\xfd\x9f\xae\x0b' + b'\xf9\x1b\x65\xc5\x52\x47\x33\xab\x8f\x59\x3d\xab\xcd\x62\xb3\x57' + b'\x16\x39\xd6\x24\xe6\x51\x52\xab\x8f\x53\x0c\x35\x9f\x08\x61\xd8' + b'\x07\xca\x0d\xbf\x50\x0d\x6a\x61\x56\xa3\x8e\x08\x8a\x22\xb6\x5e' + b'\x52\xbc\x51\x4d\x16\xcc\xf8\x06\x81\x8c\xe9\x1a\xb7\x79\x37\x36' + b'\x5a\xf9\x0b\xbf\x74\xa3\x5b\xe6\xb4\x0b\x8e\xed\xf2\x78\x5e\x42' + b'\x87\x4d' + )) + + crib = chacha.decrypt(ciphertext) + + self.assertEqual(crib, plaintext) + + def test_chacha_block_vector1(self): + # RFC 7539 Appendix A.1 test vector #1 + key = [0]*8 + nonce = [0]*3 + counter = 0 + + x = ChaCha.chacha_block(key, counter, nonce, rounds=20) + + self.assertEqual(x, + [0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653, + 0xb819d2bd, 0x1aed8da0, 0xccef36a8, 0xc70d778b, + 0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8, + 0xf4b8436a, 0x1ca11815, 0x69b687c3, 0x8665eeb2]) + + ciphertext = ChaCha.word_to_bytearray(x) + + self.assertEqual(ciphertext, bytearray( + b'\x76\xb8\xe0\xad\xa0\xf1\x3d\x90\x40\x5d\x6a\xe5\x53\x86\xbd\x28' + b'\xbd\xd2\x19\xb8\xa0\x8d\xed\x1a\xa8\x36\xef\xcc\x8b\x77\x0d\xc7' + b'\xda\x41\x59\x7c\x51\x57\x48\x8d\x77\x24\xe0\x3f\xb8\xd8\x4a\x37' + b'\x6a\x43\xb8\xf4\x15\x18\xa1\x1c\xc3\x87\xb6\x69\xb2\xee\x65\x86' + )) + + def test_chacha_block_vector2(self): + # RFC 7539 Appendix A.1 test vector #2 + key = [0]*8 + nonce = [0]*3 + counter = 1 + + x = ChaCha.chacha_block(key, counter, nonce, rounds=20) + + self.assertEqual(x, + [0xbee7079f, 0x7a385155, 0x7c97ba98, 0x0d082d73, + 0xa0290fcb, 0x6965e348, 0x3e53c612, 0xed7aee32, + 0x7621b729, 0x434ee69c, 0xb03371d5, 0xd539d874, + 0x281fed31, 0x45fb0a51, 0x1f0ae1ac, 0x6f4d794b]) + + ciphertext = ChaCha.word_to_bytearray(x) + + self.assertEqual(ciphertext, bytearray( + b'\x9f\x07\xe7\xbe\x55\x51\x38\x7a\x98\xba\x97\x7c\x73\x2d\x08\x0d' + b'\xcb\x0f\x29\xa0\x48\xe3\x65\x69\x12\xc6\x53\x3e\x32\xee\x7a\xed' + b'\x29\xb7\x21\x76\x9c\xe6\x4e\x43\xd5\x71\x33\xb0\x74\xd8\x39\xd5' + b'\x31\xed\x1f\x28\x51\x0a\xfb\x45\xac\xe1\x0a\x1f\x4b\x79\x4d\x6f' + )) + + def test_chacha_block_vector3(self): + # RFC 7539 Appendix A.1 test vector #3 + key = bytearray(b'\x00'*31 + b'\x01') + nonce = bytearray(12) + counter = 1 + + chacha = ChaCha(key, nonce, counter=counter) + + x = ChaCha.chacha_block(chacha.key, + chacha.counter, + chacha.nonce, + rounds=20) + + self.assertEqual(x, + [0x2452eb3a, 0x9249f8ec, 0x8d829d9b, 0xddd4ceb1, + 0xe8252083, 0x60818b01, 0xf38422b8, 0x5aaa49c9, + 0xbb00ca8e, 0xda3ba7b4, 0xc4b592d1, 0xfdf2732f, + 0x4436274e, 0x2561b3c8, 0xebdd4aa6, 0xa0136c00]) + + ciphertext = ChaCha.word_to_bytearray(x) + + self.assertEqual(ciphertext, bytearray( + b'\x3a\xeb\x52\x24\xec\xf8\x49\x92\x9b\x9d\x82\x8d\xb1\xce\xd4\xdd' + b'\x83\x20\x25\xe8\x01\x8b\x81\x60\xb8\x22\x84\xf3\xc9\x49\xaa\x5a' + b'\x8e\xca\x00\xbb\xb4\xa7\x3b\xda\xd1\x92\xb5\xc4\x2f\x73\xf2\xfd' + b'\x4e\x27\x36\x44\xc8\xb3\x61\x25\xa6\x4a\xdd\xeb\x00\x6c\x13\xa0' + )) + + def test_chacha_block_vector4(self): + # RFC 7539 Appendix A.1 test vector #4 + key = bytearray(b'\x00' + b'\xff' + b'\x00'*30) + nonce = bytearray(12) + counter = 2 + + chacha = ChaCha(key, nonce, counter=counter) + + x = ChaCha.chacha_block(chacha.key, + chacha.counter, + chacha.nonce, + rounds=20) + + self.assertEqual(x, + [0xfb4dd572, 0x4bc42ef1, 0xdf922636, 0x327f1394, + 0xa78dea8f, 0x5e269039, 0xa1bebbc1, 0xcaf09aae, + 0xa25ab213, 0x48a6b46c, 0x1b9d9bcb, 0x092c5be6, + 0x546ca624, 0x1bec45d5, 0x87f47473, 0x96f0992e]) + + ciphertext = ChaCha.word_to_bytearray(x) + + self.assertEqual(ciphertext, bytearray( + b'\x72\xd5\x4d\xfb\xf1\x2e\xc4\x4b\x36\x26\x92\xdf\x94\x13\x7f\x32' + b'\x8f\xea\x8d\xa7\x39\x90\x26\x5e\xc1\xbb\xbe\xa1\xae\x9a\xf0\xca' + b'\x13\xb2\x5a\xa2\x6c\xb4\xa6\x48\xcb\x9b\x9d\x1b\xe6\x5b\x2c\x09' + b'\x24\xa6\x6c\x54\xd5\x45\xec\x1b\x73\x74\xf4\x87\x2e\x99\xf0\x96' + )) + + def test_chacha_block_vector5(self): + # RFC 7539 Appendix A.1 test vector #5 + key = bytearray(32) + nonce = bytearray(b'\x00'*11 + b'\x02') + counter = 0 + + chacha = ChaCha(key, nonce, counter=counter) + + x = ChaCha.chacha_block(chacha.key, + chacha.counter, + chacha.nonce, + rounds=20) + + self.assertEqual(x, + [0x374dc6c2, 0x3736d58c, 0xb904e24a, 0xcd3f93ef, + 0x88228b1a, 0x96a4dfb3, 0x5b76ab72, 0xc727ee54, + 0x0e0e978a, 0xf3145c95, 0x1b748ea8, 0xf786c297, + 0x99c28f5f, 0x628314e8, 0x398a19fa, 0x6ded1b53]) + + ciphertext = ChaCha.word_to_bytearray(x) + + self.assertEqual(ciphertext, bytearray( + b'\xc2\xc6\x4d\x37\x8c\xd5\x36\x37\x4a\xe2\x04\xb9\xef\x93\x3f\xcd' + b'\x1a\x8b\x22\x88\xb3\xdf\xa4\x96\x72\xab\x76\x5b\x54\xee\x27\xc7' + b'\x8a\x97\x0e\x0e\x95\x5c\x14\xf3\xa8\x8e\x74\x1b\x97\xc2\x86\xf7' + b'\x5f\x8f\xc2\x99\xe8\x14\x83\x62\xfa\x19\x8a\x39\x53\x1b\xed\x6d' + )) + + def test_chacha_encryption_vector1(self): + # RFC 7539 Appendix A.2 test vector #1 + key = bytearray(32) + nonce = bytearray(12) + counter = 0 + + chacha = ChaCha(key, nonce, counter) + + ciphertext = chacha.encrypt(bytearray(64)) + + self.assertEqual(ciphertext, bytearray( + b'\x76\xb8\xe0\xad\xa0\xf1\x3d\x90\x40\x5d\x6a\xe5\x53\x86\xbd\x28' + b'\xbd\xd2\x19\xb8\xa0\x8d\xed\x1a\xa8\x36\xef\xcc\x8b\x77\x0d\xc7' + b'\xda\x41\x59\x7c\x51\x57\x48\x8d\x77\x24\xe0\x3f\xb8\xd8\x4a\x37' + b'\x6a\x43\xb8\xf4\x15\x18\xa1\x1c\xc3\x87\xb6\x69\xb2\xee\x65\x86' + )) + + def test_chacha_encryption_vector2(self): + # RFC 7539 Appendix A.2 test vector #2 + key = bytearray(31) + bytearray(b'\x01') + nonce = bytearray(11) + bytearray(b'\x02') + counter = 1 + + chacha = ChaCha(key, nonce, counter) + + plaintext = bytearray(b'Any submission to the IETF intended by the ' + b'Contributor for publication as all or part of ' + b'an IETF Internet-Draft or RFC and any statement' + b' made within the context of an IETF activity is' + b' considered an "IETF Contribution". Such ' + b'statements include oral statements in IETF ' + b'sessions, as well as written and electronic ' + b'communications made at any time or place, which' + b' are addressed to') + + ciphertext = chacha.encrypt(plaintext) + + self.assertEqual(ciphertext, bytearray( + b'\xa3\xfb\xf0\x7d\xf3\xfa\x2f\xde\x4f\x37\x6c\xa2\x3e\x82\x73\x70' + b'\x41\x60\x5d\x9f\x4f\x4f\x57\xbd\x8c\xff\x2c\x1d\x4b\x79\x55\xec' + b'\x2a\x97\x94\x8b\xd3\x72\x29\x15\xc8\xf3\xd3\x37\xf7\xd3\x70\x05' + b'\x0e\x9e\x96\xd6\x47\xb7\xc3\x9f\x56\xe0\x31\xca\x5e\xb6\x25\x0d' + b'\x40\x42\xe0\x27\x85\xec\xec\xfa\x4b\x4b\xb5\xe8\xea\xd0\x44\x0e' + b'\x20\xb6\xe8\xdb\x09\xd8\x81\xa7\xc6\x13\x2f\x42\x0e\x52\x79\x50' + b'\x42\xbd\xfa\x77\x73\xd8\xa9\x05\x14\x47\xb3\x29\x1c\xe1\x41\x1c' + b'\x68\x04\x65\x55\x2a\xa6\xc4\x05\xb7\x76\x4d\x5e\x87\xbe\xa8\x5a' + b'\xd0\x0f\x84\x49\xed\x8f\x72\xd0\xd6\x62\xab\x05\x26\x91\xca\x66' + b'\x42\x4b\xc8\x6d\x2d\xf8\x0e\xa4\x1f\x43\xab\xf9\x37\xd3\x25\x9d' + b'\xc4\xb2\xd0\xdf\xb4\x8a\x6c\x91\x39\xdd\xd7\xf7\x69\x66\xe9\x28' + b'\xe6\x35\x55\x3b\xa7\x6c\x5c\x87\x9d\x7b\x35\xd4\x9e\xb2\xe6\x2b' + b'\x08\x71\xcd\xac\x63\x89\x39\xe2\x5e\x8a\x1e\x0e\xf9\xd5\x28\x0f' + b'\xa8\xca\x32\x8b\x35\x1c\x3c\x76\x59\x89\xcb\xcf\x3d\xaa\x8b\x6c' + b'\xcc\x3a\xaf\x9f\x39\x79\xc9\x2b\x37\x20\xfc\x88\xdc\x95\xed\x84' + b'\xa1\xbe\x05\x9c\x64\x99\xb9\xfd\xa2\x36\xe7\xe8\x18\xb0\x4b\x0b' + b'\xc3\x9c\x1e\x87\x6b\x19\x3b\xfe\x55\x69\x75\x3f\x88\x12\x8c\xc0' + b'\x8a\xaa\x9b\x63\xd1\xa1\x6f\x80\xef\x25\x54\xd7\x18\x9c\x41\x1f' + b'\x58\x69\xca\x52\xc5\xb8\x3f\xa3\x6f\xf2\x16\xb9\xc1\xd3\x00\x62' + b'\xbe\xbc\xfd\x2d\xc5\xbc\xe0\x91\x19\x34\xfd\xa7\x9a\x86\xf6\xe6' + b'\x98\xce\xd7\x59\xc3\xff\x9b\x64\x77\x33\x8f\x3d\xa4\xf9\xcd\x85' + b'\x14\xea\x99\x82\xcc\xaf\xb3\x41\xb2\x38\x4d\xd9\x02\xf3\xd1\xab' + b'\x7a\xc6\x1d\xd2\x9c\x6f\x21\xba\x5b\x86\x2f\x37\x30\xe3\x7c\xfd' + b'\xc4\xfd\x80\x6c\x22\xf2\x21' + )) + + def test_chacha_encryption_vector3(self): + # RFC 7539 Appendix A.2 test vector #3 + key = bytearray( + b'\x1c\x92\x40\xa5\xeb\x55\xd3\x8a\xf3\x33\x88\x86\x04\xf6\xb5\xf0' + b'\x47\x39\x17\xc1\x40\x2b\x80\x09\x9d\xca\x5c\xbc\x20\x70\x75\xc0' + ) + nonce = bytearray(11) + bytearray(b'\x02') + counter = 42 + + chacha = ChaCha(key, nonce, counter) + + plaintext = bytearray( + b'\x27\x54\x77\x61\x73\x20\x62\x72\x69\x6c\x6c\x69\x67\x2c\x20\x61' + b'\x6e\x64\x20\x74\x68\x65\x20\x73\x6c\x69\x74\x68\x79\x20\x74\x6f' + b'\x76\x65\x73\x0a\x44\x69\x64\x20\x67\x79\x72\x65\x20\x61\x6e\x64' + b'\x20\x67\x69\x6d\x62\x6c\x65\x20\x69\x6e\x20\x74\x68\x65\x20\x77' + b'\x61\x62\x65\x3a\x0a\x41\x6c\x6c\x20\x6d\x69\x6d\x73\x79\x20\x77' + b'\x65\x72\x65\x20\x74\x68\x65\x20\x62\x6f\x72\x6f\x67\x6f\x76\x65' + b'\x73\x2c\x0a\x41\x6e\x64\x20\x74\x68\x65\x20\x6d\x6f\x6d\x65\x20' + b'\x72\x61\x74\x68\x73\x20\x6f\x75\x74\x67\x72\x61\x62\x65\x2e' + ) + + ciphertext = chacha.encrypt(plaintext) + + self.assertEqual(ciphertext, bytearray( + b'\x62\xe6\x34\x7f\x95\xed\x87\xa4\x5f\xfa\xe7\x42\x6f\x27\xa1\xdf' + b'\x5f\xb6\x91\x10\x04\x4c\x0d\x73\x11\x8e\xff\xa9\x5b\x01\xe5\xcf' + b'\x16\x6d\x3d\xf2\xd7\x21\xca\xf9\xb2\x1e\x5f\xb1\x4c\x61\x68\x71' + b'\xfd\x84\xc5\x4f\x9d\x65\xb2\x83\x19\x6c\x7f\xe4\xf6\x05\x53\xeb' + b'\xf3\x9c\x64\x02\xc4\x22\x34\xe3\x2a\x35\x6b\x3e\x76\x43\x12\xa6' + b'\x1a\x55\x32\x05\x57\x16\xea\xd6\x96\x25\x68\xf8\x7d\x3f\x3f\x77' + b'\x04\xc6\xa8\xd1\xbc\xd1\xbf\x4d\x50\xd6\x15\x4b\x6d\xa7\x31\xb1' + b'\x87\xb5\x8d\xfd\x72\x8a\xfa\x36\x75\x7a\x79\x7a\xc1\x88\xd1' + )) From 9f0f5856f86a81956d731a5ad7374a76572b9eea Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 3 Nov 2015 18:31:06 +0100 Subject: [PATCH 175/574] implement Poly1305 algorithm --- tlslite/utils/poly1305.py | 53 ++++++ unit_tests/test_tlslite_utils_poly1305.py | 210 ++++++++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 tlslite/utils/poly1305.py create mode 100644 unit_tests/test_tlslite_utils_poly1305.py diff --git a/tlslite/utils/poly1305.py b/tlslite/utils/poly1305.py new file mode 100644 index 00000000..83e3233f --- /dev/null +++ b/tlslite/utils/poly1305.py @@ -0,0 +1,53 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. +"""Implementation of Poly1305 authenticator for RFC 7539""" + +class Poly1305(object): + + """Poly1305 authenticator""" + + P = 0x3fffffffffffffffffffffffffffffffb # 2^130-5 + + @staticmethod + def le_bytes_to_num(data): + """Convert a number from little endian byte format""" + ret = 0 + for i in range(len(data) - 1, -1, -1): + ret <<= 8 + ret += data[i] + return ret + + @staticmethod + def num_to_16_le_bytes(num): + """Convert number to 16 bytes in little endian format""" + ret = [0]*16 + for i, _ in enumerate(ret): + ret[i] = num & 0xff + num >>= 8 + return bytearray(ret) + + @staticmethod + def divceil(divident, divisor): + """Integer division with rounding up""" + quot, r = divmod(divident, divisor) + return quot + int(bool(r)) + + def __init__(self, key): + """Set the authenticator key""" + if len(key) != 32: + raise ValueError("Key must be 256 bit long") + self.acc = 0 + self.r = self.le_bytes_to_num(key[0:16]) + self.r &= 0x0ffffffc0ffffffc0ffffffc0fffffff + self.s = self.le_bytes_to_num(key[16:32]) + + def create_tag(self, data): + """Calculate authentication tag for data""" + for i in range(0, self.divceil(len(data), 16)): + n = self.le_bytes_to_num(data[i*16:(i+1)*16] + b'\x01') + self.acc += n + self.acc = (self.r * self.acc) % self.P + self.acc += self.s + return self.num_to_16_le_bytes(self.acc) + diff --git a/unit_tests/test_tlslite_utils_poly1305.py b/unit_tests/test_tlslite_utils_poly1305.py new file mode 100644 index 00000000..825826ad --- /dev/null +++ b/unit_tests/test_tlslite_utils_poly1305.py @@ -0,0 +1,210 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +from __future__ import division +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.utils.poly1305 import Poly1305 + +class TestPoly1305(unittest.TestCase): + def test___init__(self): + poly = Poly1305(bytearray(256//8)) + + self.assertIsNotNone(poly) + + def test___init___with_wrong_key_size(self): + with self.assertRaises(ValueError): + Poly1305(bytearray(128//8)) + + def test_le_bytes_to_num_32(self): + self.assertEqual(0x01020304, + Poly1305.le_bytes_to_num( + bytearray(b'\x04\x03\x02\x01'))) + + def test_le_bytes_to_num_40(self): + self.assertEqual(0x0001020304, + Poly1305.le_bytes_to_num( + bytearray(b'\x04\x03\x02\x01\x00'))) + + def test_le_bytes_to_num_64(self): + self.assertEqual(0x0102030405060708, + Poly1305.le_bytes_to_num( + bytearray(b'\x08\x07\x06\x05\x04\x03\x02\x01'))) + + def test_le_bytes_to_num_72(self): + self.assertEqual(0x0a0102030405060708, + Poly1305.le_bytes_to_num( + bytearray(b'\x08\x07\x06\x05\x04\x03\x02\x01\x0a'))) + + def test_num_to_16_le_bytes(self): + self.assertEqual(bytearray( + b'\x04\x03\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + ), Poly1305.num_to_16_le_bytes(0x01020304)) + + def test_generate_tag(self): + poly = Poly1305(bytearray( + b'\x85\xd6\xbe\x78\x57\x55\x6d\x33\x7f\x44\x52\xfe\x42\xd5\x06\xa8' + b'\x01\x03\x80\x8a\xfb\x0d\xb2\xfd\x4a\xbf\xf6\xaf\x41\x49\xf5\x1b' + )) + + message = bytearray(b'Cryptographic Forum Research Group') + + tag = poly.create_tag(message) + + self.assertEqual(tag, bytearray( + b'\xa8\x06\x1d\xc1\x30\x51\x36\xc6\xc2\x2b\x8b\xaf\x0c\x01\x27\xa9' + )) + + def test_vector1(self): + #RFC 7539 Appendix A.3 vector #1 + poly = Poly1305(bytearray(32)) + + message = bytearray(64) + + tag = poly.create_tag(message) + + self.assertEqual(tag, bytearray(16)) + + ietf_text = bytearray( + b'Any submission to the IETF intended by the Contributor for publi' + b'cation as all or part of an IETF Internet-Draft or RFC and any s' + b'tatement made within the context of an IETF activity is consider' + b'ed an "IETF Contribution". Such statements include oral statemen' + b'ts in IETF sessions, as well as written and electronic communica' + b'tions made at any time or place, which are addressed to') + + def test_vector2(self): + #RFC 7539 Appendix A.3 vector #2 + poly = Poly1305(bytearray(16) + bytearray( + b'\x36\xe5\xf6\xb5\xc5\xe0\x60\x70\xf0\xef\xca\x96\x22\x7a\x86\x3e' + )) + + tag = poly.create_tag(self.ietf_text) + + self.assertEqual(tag, bytearray( + b'\x36\xe5\xf6\xb5\xc5\xe0\x60\x70\xf0\xef\xca\x96\x22\x7a\x86\x3e' + )) + + def test_vector3(self): + #RFC 7539 Appendix A.3 vector #3 + poly = Poly1305(bytearray( + b'\x36\xe5\xf6\xb5\xc5\xe0\x60\x70\xf0\xef\xca\x96\x22\x7a\x86\x3e' + ) + bytearray(16)) + + tag = poly.create_tag(self.ietf_text) + + self.assertEqual(tag, bytearray( + b'\xf3\x47\x7e\x7c\xd9\x54\x17\xaf\x89\xa6\xb8\x79\x4c\x31\x0c\xf0' + )) + + def test_vector4(self): + #RFC 7539 Appendix A.3 vector #4 + poly = Poly1305(bytearray( + b'\x1c\x92\x40\xa5\xeb\x55\xd3\x8a\xf3\x33\x88\x86\x04\xf6\xb5\xf0' + b'\x47\x39\x17\xc1\x40\x2b\x80\x09\x9d\xca\x5c\xbc\x20\x70\x75\xc0' + )) + + message = bytearray( + b'\x27\x54\x77\x61\x73\x20\x62\x72\x69\x6c\x6c\x69\x67\x2c\x20\x61' + b'\x6e\x64\x20\x74\x68\x65\x20\x73\x6c\x69\x74\x68\x79\x20\x74\x6f' + b'\x76\x65\x73\x0a\x44\x69\x64\x20\x67\x79\x72\x65\x20\x61\x6e\x64' + b'\x20\x67\x69\x6d\x62\x6c\x65\x20\x69\x6e\x20\x74\x68\x65\x20\x77' + b'\x61\x62\x65\x3a\x0a\x41\x6c\x6c\x20\x6d\x69\x6d\x73\x79\x20\x77' + b'\x65\x72\x65\x20\x74\x68\x65\x20\x62\x6f\x72\x6f\x67\x6f\x76\x65' + b'\x73\x2c\x0a\x41\x6e\x64\x20\x74\x68\x65\x20\x6d\x6f\x6d\x65\x20' + b'\x72\x61\x74\x68\x73\x20\x6f\x75\x74\x67\x72\x61\x62\x65\x2e') + + self.assertEqual(len(message), 112+15) + + tag = poly.create_tag(message) + + self.assertEqual(tag, bytearray( + b'\x45\x41\x66\x9a\x7e\xaa\xee\x61\xe7\x08\xdc\x7c\xbc\xc5\xeb\x62' + )) + + def test_vector5(self): + #RFC 7539 Appendix A.3 vector #5 + poly = Poly1305(bytearray(b'\x02' + b'\x00'*31)) + + message = bytearray(b'\xff'*16) + + tag = poly.create_tag(message) + + self.assertEqual(tag, bytearray(b'\x03' + b'\x00'*15)) + + def test_vector6(self): + #RFC 7539 Appendix A.3 vector #6 + poly = Poly1305(bytearray(b'\x02' + b'\x00'*15 + b'\xff'*16)) + + message = bytearray(b'\x02' + b'\x00'*15) + + tag = poly.create_tag(message) + + self.assertEqual(tag, bytearray(b'\x03' + b'\x00'*15)) + + def test_vector7(self): + #RFC 7539 Appendix A.3 vector #7 + poly = Poly1305(bytearray(b'\x01' + b'\x00'*31)) + + message = bytearray(b'\xff'*16 + b'\xf0' + b'\xff'*15 + b'\x11' + + b'\x00'*15) + + tag = poly.create_tag(message) + + self.assertEqual(tag, bytearray(b'\x05' + b'\x00'*15)) + + def test_vector8(self): + #RFC 7539 Appendix A.3 vector #8 + poly = Poly1305(bytearray(b'\x01' + b'\x00'*31)) + + message = bytearray(b'\xff'*16 + b'\xfb' + b'\xfe'*15 + b'\x01'*16) + + tag = poly.create_tag(message) + + self.assertEqual(tag, bytearray(b'\x00'*16)) + + def test_vector9(self): + #RFC 7539 Appendix A.3 vector #9 + poly = Poly1305(bytearray(b'\x02' + b'\x00'*31)) + + message = bytearray(b'\xfd' + b'\xff'*15) + + tag = poly.create_tag(message) + + self.assertEqual(tag, bytearray(b'\xfa' + b'\xff'*15)) + + def test_vector10(self): + #RFC 7539 Appendix A.3 vector #10 + poly = Poly1305(bytearray(b'\x01' + b'\x00'*7 + b'\x04' + b'\x00'*23)) + + message = bytearray( + b'\xE3\x35\x94\xD7\x50\x5E\x43\xB9\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x33\x94\xD7\x50\x5E\x43\x79\xCD\x01\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + ) + + tag = poly.create_tag(message) + + self.assertEqual(tag, bytearray(b'\x14' + b'\x00'*7 + + b'\x55' + b'\x00'*7)) + + def test_vector11(self): + #RFC 7539 Appendix A.3 vector #11 + poly = Poly1305(bytearray(b'\x01' + b'\x00'*7 + b'\x04' + b'\x00'*23)) + + message = bytearray( + b'\xE3\x35\x94\xD7\x50\x5E\x43\xB9\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x33\x94\xD7\x50\x5E\x43\x79\xCD\x01\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + ) + + tag = poly.create_tag(message) + + self.assertEqual(tag, bytearray(b'\x13' + b'\x00'*15)) From 1d94c49ef5a26e450c6ef654ed6f23f988388643 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 20 Sep 2015 18:23:46 +0200 Subject: [PATCH 176/574] implement ChaCha20/Poly1305 AEAD cipher --- tlslite/utils/chacha20_poly1305.py | 93 +++++++++++++ .../test_tlslite_utils_chacha20_poly1305.py | 131 ++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 tlslite/utils/chacha20_poly1305.py create mode 100644 unit_tests/test_tlslite_utils_chacha20_poly1305.py diff --git a/tlslite/utils/chacha20_poly1305.py b/tlslite/utils/chacha20_poly1305.py new file mode 100644 index 00000000..b1ab35f5 --- /dev/null +++ b/tlslite/utils/chacha20_poly1305.py @@ -0,0 +1,93 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. +"""Pure Python implementation of ChaCha20/Poly1305 AEAD cipher + +Implementation that follows RFC 7539 and draft-ietf-tls-chacha20-poly1305-00 +""" + +from __future__ import division +from .chacha import ChaCha +from .poly1305 import Poly1305 +import struct + +class CHACHA20_POLY1305(object): + + """Pure python implementation of ChaCha20/Poly1305 AEAD cipher""" + + def __init__(self, key, implementation): + """Set the initial state for the ChaCha20 AEAD""" + if len(key) != 32: + raise ValueError("Key must be 256 bit long") + if implementation != "python": + raise ValueError("Implementations other then python unsupported") + + self.isBlockCipher = False + self.isAEAD = True + self.nonceLength = 12 + self.tagLength = 16 + self.implementation = implementation + self.name = "chacha20-poly1305" + self.key = key + + @staticmethod + def poly1305_key_gen(key, nonce): + """Generate the key for the Poly1305 authenticator""" + poly = ChaCha(key, nonce) + return poly.encrypt(bytearray(32)) + + @staticmethod + def pad16(data): + """Return padding for the Associated Authenticated Data""" + if len(data) % 16 == 0: + return bytearray(0) + else: + return bytearray(16-(len(data)%16)) + + def seal(self, nonce, plaintext, data): + """ + Encrypts and authenticates plaintext using nonce and data. Returns the + ciphertext, consisting of the encrypted plaintext and tag concatenated. + """ + if len(nonce) != 12: + raise ValueError("Nonce must be 96 bit large") + + otk = self.poly1305_key_gen(self.key, nonce) + + ciphertext = ChaCha(self.key, nonce, counter=1).encrypt(plaintext) + + mac_data = data + self.pad16(data) + mac_data += ciphertext + self.pad16(ciphertext) + mac_data += struct.pack(' Date: Tue, 3 Nov 2015 18:50:42 +0100 Subject: [PATCH 177/574] implement TLS ciphersuite from draft-ietf-tls-chacha20-poly1305-00 --- tests/tlstest.py | 12 ++++++-- tlslite/constants.py | 14 ++++++++++ tlslite/handshakesettings.py | 5 +++- tlslite/recordlayer.py | 34 +++++++++++++---------- tlslite/utils/cipherfactory.py | 18 ++++++++++++ tlslite/utils/python_chacha20_poly1305.py | 11 ++++++++ 6 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 tlslite/utils/python_chacha20_poly1305.py diff --git a/tests/tlstest.py b/tests/tlstest.py index c6154a8a..d57fb8cd 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -414,7 +414,8 @@ def connect(): print("Test {0} - throughput test".format(test_no)) for implementation in implementations: for cipher in ["aes128gcm", "aes256gcm", "aes128", "aes256", "3des", - "rc4"]: + "rc4", "chacha20-poly1305"]: + # skip tests with implementations that don't support them if cipher == "3des" and implementation not in ("openssl", "pycrypto"): continue @@ -422,6 +423,9 @@ def connect(): implementation not in ("pycrypto", "python"): continue + if cipher in ("chacha20-poly1305", ) and \ + implementation not in ("python", ): + continue test_no += 1 @@ -1033,7 +1037,8 @@ def server_bind(self): print("Test {0} - throughput test".format(test_no)) for implementation in implementations: for cipher in ["aes128gcm", "aes256gcm", "aes128", "aes256", "3des", - "rc4"]: + "rc4", "chacha20-poly1305"]: + # skip tests with implementations that don't support them if cipher == "3des" and implementation not in ("openssl", "pycrypto"): continue @@ -1041,6 +1046,9 @@ def server_bind(self): implementation not in ("pycrypto", "python"): continue + if cipher in ("chacha20-poly1305", ) and \ + implementation not in ("python", ): + continue test_no += 1 diff --git a/tlslite/constants.py b/tlslite/constants.py index f0522790..5ee6791d 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -329,6 +329,11 @@ class CipherSuite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F ietfNames[0x009F] = 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384' + # draft-ietf-tls-chacha20-poly1305-00 + # ChaCha20/Poly1305 based Cipher Suites for TLS1.2 + TLS_DHE_RSA_WITH_CHACHA20_POLY1305 = 0xcca3 + ietfNames[0xcca3] = 'TLS_DHE_RSA_WITH_CHACHA20_POLY1305' + # # Define cipher suite families below # @@ -375,6 +380,9 @@ class CipherSuite: aes256GcmSuites.append(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) aes256GcmSuites.append(TLS_DH_ANON_WITH_AES_256_GCM_SHA384) + chacha20Suites = [] + chacha20Suites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305) + # RC4 128 stream cipher rc4Suites = [] rc4Suites.append(TLS_DH_ANON_WITH_RC4_128_MD5) @@ -429,6 +437,7 @@ class CipherSuite: aeadSuites = [] aeadSuites.extend(aes128GcmSuites) aeadSuites.extend(aes256GcmSuites) + aeadSuites.extend(chacha20Suites) # TLS1.2 with SHA384 PRF sha384PrfSuites = [] @@ -482,6 +491,8 @@ def _filterSuites(suites, settings, version=None): macSuites += CipherSuite.aeadSuites cipherSuites = [] + if "chacha20-poly1305" in cipherNames and version >= (3, 3): + cipherSuites += CipherSuite.chacha20Suites if "aes128gcm" in cipherNames and version >= (3, 3): cipherSuites += CipherSuite.aes128GcmSuites if "aes256gcm" in cipherNames and version >= (3, 3): @@ -559,6 +570,7 @@ def getCertSuites(settings, version=None): # FFDHE key exchange, RSA authentication dheCertSuites = [] + dheCertSuites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) @@ -608,6 +620,8 @@ def canonicalCipherName(ciphersuite): return "3des" elif ciphersuite in CipherSuite.nullSuites: return "null" + elif ciphersuite in CipherSuite.chacha20Suites: + return "chacha20-poly1305" else: return None diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 7693f5fc..cd21ddfc 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -11,7 +11,10 @@ from .utils import cryptomath from .utils import cipherfactory -CIPHER_NAMES = ["aes256gcm", "aes128gcm", "aes256", "aes128", "3des"] +CIPHER_NAMES = ["chacha20-poly1305", + "aes256gcm", "aes128gcm", + "aes256", "aes128", + "3des"] ALL_CIPHER_NAMES = CIPHER_NAMES + ["rc4", "null"] MAC_NAMES = ["sha", "sha256", "aead"] # Don't allow "md5" by default. ALL_MAC_NAMES = MAC_NAMES + ["md5"] diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index e3e985b4..e8f29a09 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -10,7 +10,7 @@ from .constants import ContentType, CipherSuite from .messages import RecordHeader3, RecordHeader2, Message from .utils.cipherfactory import createAESGCM, createAES, createRC4, \ - createTripleDES + createTripleDES, createCHACHA20 from .utils.codec import Parser, Writer from .utils.compat import compatHMAC from .utils.cryptomath import getRandomBytes @@ -378,13 +378,14 @@ def _encryptThenSeal(self, buf, contentType): #The nonce is always the fixed nonce and the sequence number. nonce = self._writeState.fixedNonce + seqNumBytes + assert len(nonce) == self._writeState.encContext.nonceLength buf = self._writeState.encContext.seal(nonce, buf, authData) - #The only AEAD supported, AES-GCM, has an explicit variable - #nonce. - buf = seqNumBytes + buf + #AES-GCM, has an explicit variable nonce. + if "aes" in self._writeState.encContext.name: + buf = seqNumBytes + buf return buf @@ -556,21 +557,22 @@ def _macThenDecrypt(self, recordType, buf): def _decryptAndUnseal(self, recordType, buf): """Decrypt AEAD encrypted data""" - #The only AEAD supported, AES-GCM, has an explicit variable - #nonce. - explicitNonceLength = 8 - if explicitNonceLength > len(buf): - #Publicly invalid. - raise TLSBadRecordMAC("Truncated nonce") - nonce = self._readState.fixedNonce + buf[:explicitNonceLength] - buf = buf[8:] + seqnumBytes = self._readState.getSeqNumBytes() + #AES-GCM, has an explicit variable nonce. + if "aes" in self._readState.encContext.name: + explicitNonceLength = 8 + if explicitNonceLength > len(buf): + #Publicly invalid. + raise TLSBadRecordMAC("Truncated nonce") + nonce = self._readState.fixedNonce + buf[:explicitNonceLength] + buf = buf[8:] + else: + nonce = self._readState.fixedNonce + seqnumBytes if self._readState.encContext.tagLength > len(buf): #Publicly invalid. raise TLSBadRecordMAC("Truncated tag") - #Assemble the authenticated data. - seqnumBytes = self._readState.getSeqNumBytes() plaintextLen = len(buf) - self._readState.encContext.tagLength authData = seqnumBytes + bytearray([recordType, self.version[0], self.version[1], @@ -651,6 +653,10 @@ def _getCipherSettings(cipherSuite): keyLength = 16 ivLength = 4 createCipherFunc = createAESGCM + elif cipherSuite in CipherSuite.chacha20Suites: + keyLength = 32 + ivLength = 4 + createCipherFunc = createCHACHA20 elif cipherSuite in CipherSuite.aes128Suites: keyLength = 16 ivLength = 16 diff --git a/tlslite/utils/cipherfactory.py b/tlslite/utils/cipherfactory.py index 6de3ceb6..dd487ed4 100644 --- a/tlslite/utils/cipherfactory.py +++ b/tlslite/utils/cipherfactory.py @@ -7,6 +7,7 @@ from tlslite.utils import python_aes from tlslite.utils import python_aesgcm +from tlslite.utils import python_chacha20_poly1305 from tlslite.utils import python_rc4 from tlslite.utils import cryptomath @@ -73,6 +74,23 @@ def createAESGCM(key, implList=None): return python_aesgcm.new(key) raise NotImplementedError() +def createCHACHA20(key, implList=None): + """Create a new CHACHA20_POLY1305 object. + + @type key: bytearray + @param key: a 32 byte array to serve as key + + @rtype: L{tlslite.utils.CHACHA20_POLY1305} + @return: A ChaCha20/Poly1305 object + """ + if implList is None: + implList = ["python"] + + for impl in implList: + if impl == "python": + return python_chacha20_poly1305.new(key) + raise NotImplementedError() + def createRC4(key, IV, implList=None): """Create a new RC4 object. diff --git a/tlslite/utils/python_chacha20_poly1305.py b/tlslite/utils/python_chacha20_poly1305.py new file mode 100644 index 00000000..30a74e6c --- /dev/null +++ b/tlslite/utils/python_chacha20_poly1305.py @@ -0,0 +1,11 @@ +# Author: Hubert Kario (c) 2015 +# +# See the LICENSE file for legal information regarding use of this file. + +"""Pure-Python ChaCha20/Poly1305 implementation.""" + +from .chacha20_poly1305 import CHACHA20_POLY1305 + +def new(key): + """Return an AEAD cipher implementation""" + return CHACHA20_POLY1305(key, "python") From c5a57a9f42c93b229336b0ed537d41a61c8f4174 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 4 Nov 2015 14:59:07 +0100 Subject: [PATCH 178/574] fix SRP-RSA, for real this time the signatures in TLSv1.2 follow the full PKCS#1 v1.5 spec, that means that the encrypted value need to include the prefix with ASN.1 encoding of the hash type tested for interoperability with GnuTLS both in TLS1.2 and TLS1.1, with and without certificates --- tlslite/tlsconnection.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index c369b465..1c07ccd0 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -852,6 +852,8 @@ def _clientSRPKeyExchange(self, settings, cipherSuite, certificateType, if cipherSuite in CipherSuite.srpCertSuites: #Hash ServerKeyExchange/ServerSRPParams hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) + if self.version == (3, 3): + hashBytes = RSAKey.addPKCS1SHA1Prefix(hashBytes) #Extract signature bytes from ServerKeyExchange sigBytes = serverKeyExchange.signature @@ -1730,6 +1732,8 @@ def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, if cipherSuite in CipherSuite.srpCertSuites: hashBytes = serverKeyExchange.hash(clientHello.random, serverHello.random) + if self.version == (3, 3): + hashBytes = RSAKey.addPKCS1SHA1Prefix(hashBytes) serverKeyExchange.signature = privateKey.sign(hashBytes) if self.version == (3, 3): # TODO signing algorithm not negotiatied From 6068b82d83b37dfd484d091655af2ddd55c4c30a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 5 Nov 2015 14:23:22 +0100 Subject: [PATCH 179/574] Update version to 0.5.1 --- README.md | 12 ++++++++++-- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d860ee17..f304738f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.5.0 2015-10-10 +tlslite-ng version 0.5.1 2015-11-05 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -55,6 +55,8 @@ Original code in TLS Lite has either been dedicated to the public domain by its authors, or placed under a BSD-style license. See the LICENSE file for details. +Currently it is distributed under Gnu LGPLv2 license. + Thanks to Edward Loper for Epydoc, which generated the API docs. 3 Installation @@ -63,7 +65,7 @@ Thanks to Edward Loper for Epydoc, which generated the API docs. Requirements: * Python 2.6 or higher is required. - * Python 3 is supported. + * Python 3.2 or higher is supported. Options: @@ -519,6 +521,12 @@ encrypt-then-MAC mode for CBC ciphers. 12 History =========== +0.5.1 - 2015-11-05 + - fix SRP_SHA_RSA ciphersuites in TLSv1.2 (for real this time) + - minor enchancements in test scripts + - NOTE: KeyExchange class is not part of stable API yet (it will be moved to + different module later)! + 0.5.0 - 10/10/2015 - fix generators in AsyncStateMachine to work on Python3 (Theron Lewis) - fix CVE-2015-3220 - remote DoS caused by incorrect malformed packet handling diff --git a/setup.py b/setup.py index d0a2a4ae..99147634 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.5.0", + version="0.5.1", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index d3eb2dd8..0e4bedc1 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.5.0 +@version: 0.5.1 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 08d3cfb9..7e779ef3 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.5.0" +__version__ = "0.5.1" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 957474cc1a884d7966807323bc298f33db9b557a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 5 Nov 2015 14:29:47 +0100 Subject: [PATCH 180/574] make support for Python 3.5 official --- .travis.yml | 7 +++++++ setup.py | 1 + 2 files changed, 8 insertions(+) diff --git a/.travis.yml b/.travis.yml index be7398ea..e63c449e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ python: - 3.2 - 3.3 - 3.4 + - 3.5 env: - TACKPY=true @@ -38,14 +39,20 @@ matrix: env: PYCRYPTO=true - python: 3.4 env: PYCRYPTO=true + - python: 3.5 + env: PYCRYPTO=true - python: 2.7 env: GMPY=true - python: 3.4 env: GMPY=true + - python: 3.5 + env: GMPY=true - python: 2.7 env: M2CRYPTO=true PYCRYPTO=true GMPY=true - python: 3.4 env: PYCRYPTO=true GMPY=true + - python: 3.5 + env: PYCRYPTO=true GMPY=true before_install: - | diff --git a/setup.py b/setup.py index 99147634..8f52de9f 100755 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Topic :: Security :: Cryptography', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: System :: Networking' From d5df4ada913b24a00f4e9fe58a79f72608366498 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 4 Nov 2015 17:36:34 +0100 Subject: [PATCH 181/574] test coverage for numBits and numBytes --- .gitignore | 1 + .travis.yml | 2 +- unit_tests/test_tlslite_utils_cryptomath.py | 40 ++++++++++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d33736e2..ffa11409 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .pydevproject .settings .coverage +.hypothesis coverage.xml pylint_report.txt build/ diff --git a/.travis.yml b/.travis.yml index be7398ea..44803b62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,7 +73,7 @@ install: - if [[ $M2CRYPTO == 'true' ]]; then travis_retry pip install --pre m2crypto; fi - if [[ $PYCRYPTO == 'true' ]]; then travis_retry pip install pycrypto; fi - if [[ $GMPY == 'true' ]]; then travis_retry pip install gmpy; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then travis_retry pip install 'coverage<4'; else travis_retry pip install coverage; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then travis_retry pip install 'coverage<4' 'hypothesis<1.10'; else travis_retry pip install coverage hypothesis; fi - travis_retry pip install -r build-requirements.txt script: diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index 6c96f375..9b3e54ad 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -8,8 +8,11 @@ import unittest2 as unittest except ImportError: import unittest +from hypothesis import given, example +from hypothesis.strategies import integers +import math -from tlslite.utils.cryptomath import isPrime +from tlslite.utils.cryptomath import isPrime, numBits, numBytes class TestIsPrime(unittest.TestCase): def test_with_small_primes(self): @@ -60,3 +63,38 @@ def test_with_big_composites(self): # NextPrime[NextPrime[2^512]]*NextPrime[2^512] self.assertFalse(isPrime(179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639477074095512480796227391561801824887394139579933613278628104952355769470429079061808809522886423955917442317693387325171135071792698344550223571732405562649211)) +class TestNumBits(unittest.TestCase): + + @staticmethod + def num_bits(number): + if number == 0: + return 0 + return len(bin(number).lstrip('-0b')) + + @staticmethod + def num_bytes(number): + if number == 0: + return 0 + return (TestNumBits.num_bits(number) + 7) // 8 + + @given(integers(min_value=0, max_value=1<<16384)) + @example(0) + @example(255) + @example(256) + @example((1<<1024)-1) + @example((1<<521)-1) + @example(1<<8192) + @example((1<<8192)-1) + def test_numBits(self, number): + self.assertEqual(numBits(number), self.num_bits(number)) + + @given(integers(min_value=0, max_value=1<<16384)) + @example(0) + @example(255) + @example(256) + @example((1<<1024)-1) + @example((1<<521)-1) + @example(1<<8192) + @example((1<<8192)-1) + def test_numBytes(self, number): + self.assertEqual(numBytes(number), self.num_bytes(number)) From 6b2b5db8d027ec04e2c0b57ebb4def94a35fe61b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 4 Nov 2015 17:50:04 +0100 Subject: [PATCH 182/574] document and cleanup numBits and numBytes --- tlslite/utils/cryptomath.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index 0332ce42..4a086dd7 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -13,6 +13,7 @@ import math import base64 import binascii +import sys from .compat import * @@ -149,22 +150,22 @@ def numberToMPI(n): # ************************************************************************** def numBits(n): + """Return number of bits necessary to represent the integer in binary""" if n==0: return 0 - s = "%x" % n - return ((len(s)-1)*4) + \ - {'0':0, '1':1, '2':2, '3':2, - '4':3, '5':3, '6':3, '7':3, - '8':4, '9':4, 'a':4, 'b':4, - 'c':4, 'd':4, 'e':4, 'f':4, - }[s[0]] - return int(math.floor(math.log(n, 2))+1) + if sys.version_info < (2, 7): + # bit_length() was introduced in 2.7, and it is an order of magnitude + # faster than the below code + return len(bin(n))-2 + else: + return n.bit_length() def numBytes(n): + """Return number of bytes necessary to represent the integer in bytes""" if n==0: return 0 bits = numBits(n) - return int(math.ceil(bits / 8.0)) + return (bits + 7) // 8 # ************************************************************************** # Big Number Math From 7688d405fceb410670557049c9e3c97dc300b87c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 10 Nov 2015 19:13:27 +0100 Subject: [PATCH 183/574] expose calculateMAC function --- tlslite/recordlayer.py | 12 ++++++------ unit_tests/test_tlslite_utils_constanttime.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index e3e985b4..7a803aaa 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -307,7 +307,7 @@ def _addPadding(self, data): data += paddingBytes return data - def _calculateMAC(self, mac, seqnumBytes, contentType, data): + def calculateMAC(self, mac, seqnumBytes, contentType, data): """Calculate the SSL/TLS version of a MAC""" mac.update(compatHMAC(seqnumBytes)) mac.update(compatHMAC(bytearray([contentType]))) @@ -325,7 +325,7 @@ def _macThenEncrypt(self, data, contentType): if self._writeState.macContext: seqnumBytes = self._writeState.getSeqNumBytes() mac = self._writeState.macContext.copy() - macBytes = self._calculateMAC(mac, seqnumBytes, contentType, data) + macBytes = self.calculateMAC(mac, seqnumBytes, contentType, data) data += macBytes #Encrypt for Block or Stream Cipher @@ -361,7 +361,7 @@ def _encryptThenMAC(self, buf, contentType): mac = self._writeState.macContext.copy() # append MAC - macBytes = self._calculateMAC(mac, seqnumBytes, contentType, buf) + macBytes = self.calculateMAC(mac, seqnumBytes, contentType, buf) buf += macBytes return buf @@ -443,8 +443,8 @@ def _decryptStreamThenMAC(self, recordType, data): seqnumBytes = self._readState.getSeqNumBytes() data = data[:-endLength] mac = self._readState.macContext.copy() - macBytes = self._calculateMAC(mac, seqnumBytes, recordType, - data) + macBytes = self.calculateMAC(mac, seqnumBytes, recordType, + data) #Compare MACs if not ct_compare_digest(macBytes, checkBytes): @@ -513,7 +513,7 @@ def _macThenDecrypt(self, recordType, buf): seqnumBytes = self._readState.getSeqNumBytes() mac = self._readState.macContext.copy() - macBytes = self._calculateMAC(mac, seqnumBytes, recordType, buf) + macBytes = self.calculateMAC(mac, seqnumBytes, recordType, buf) if not ct_compare_digest(macBytes, checkBytes): raise TLSBadRecordMAC("MAC mismatch") diff --git a/unit_tests/test_tlslite_utils_constanttime.py b/unit_tests/test_tlslite_utils_constanttime.py index e30ecf29..d719b25f 100644 --- a/unit_tests/test_tlslite_utils_constanttime.py +++ b/unit_tests/test_tlslite_utils_constanttime.py @@ -107,8 +107,8 @@ def data_prepare(application_data, seqnum_bytes, content_type, version, h = hmac.new(key, digestmod=mac) - digest = r_layer._calculateMAC(h, seqnum_bytes, content_type, - application_data) + digest = r_layer.calculateMAC(h, seqnum_bytes, content_type, + application_data) return application_data + digest From 2d53d57e15e74ccd4a4f591b3abf6d4d3fb12b35 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 10 Nov 2015 19:16:09 +0100 Subject: [PATCH 184/574] expose addPadding method --- tlslite/recordlayer.py | 6 +++--- unit_tests/test_tlslite_recordlayer.py | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 7a803aaa..69f09057 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -297,7 +297,7 @@ def isCBCMode(self): # sending messages # - def _addPadding(self, data): + def addPadding(self, data): """Add padding to data so that it is multiple of block size""" currentLength = len(data) blockLength = self._writeState.encContext.block_size @@ -337,7 +337,7 @@ def _macThenEncrypt(self, data, contentType): if self.version >= (3, 2): data = self.fixedIVBlock + data - data = self._addPadding(data) + data = self.addPadding(data) #Encrypt data = self._writeState.encContext.encrypt(data) @@ -351,7 +351,7 @@ def _encryptThenMAC(self, buf, contentType): if self.version >= (3, 2): buf = self.fixedIVBlock + buf - buf = self._addPadding(buf) + buf = self.addPadding(buf) buf = self._writeState.encContext.encrypt(buf) diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 3f28ea5b..667e4c5a 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -1327,7 +1327,7 @@ def broken_padding(data): bytearray([paddingLength]) data += paddingBytes return data - sendingRecordLayer._addPadding = broken_padding + sendingRecordLayer.addPadding = broken_padding msg = ApplicationData().create(bytearray(b'test')) @@ -1405,7 +1405,7 @@ def broken_padding(data): bytearray([255]) data += paddingBytes return data - sendingRecordLayer._addPadding = broken_padding + sendingRecordLayer.addPadding = broken_padding msg = ApplicationData().create(bytearray(b'test')) @@ -1477,7 +1477,7 @@ def broken_padding(data): bytearray([paddingLength]) data += paddingBytes return data - sendingRecordLayer._addPadding = broken_padding + sendingRecordLayer.addPadding = broken_padding msg = ApplicationData().create(bytearray(b'test')) @@ -1548,7 +1548,7 @@ def broken_padding(data): bytearray([paddingLength]) data += paddingBytes return data - sendingRecordLayer._addPadding = broken_padding + sendingRecordLayer.addPadding = broken_padding msg = ApplicationData().create(bytearray(b'test')) @@ -1619,7 +1619,7 @@ def broken_padding(data): bytearray([paddingLength]) data += paddingBytes return data - sendingRecordLayer._addPadding = broken_padding + sendingRecordLayer.addPadding = broken_padding msg = ApplicationData().create(bytearray(b'test')) @@ -1890,7 +1890,7 @@ def broken_padding(data): bytearray([255]) data += paddingBytes return data - sendingRecordLayer._addPadding = broken_padding + sendingRecordLayer.addPadding = broken_padding msg = ApplicationData().create(bytearray(b'test')) @@ -2021,7 +2021,7 @@ def broken_padding(data): bytearray([paddingLength]) data += paddingBytes return data - sendingRecordLayer._addPadding = broken_padding + sendingRecordLayer.addPadding = broken_padding msg = ApplicationData().create(bytearray(b'test')) From 021bef0edaf8704cae10abed6e2376f44acea7a1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 11 Nov 2015 12:48:16 +0100 Subject: [PATCH 185/574] mark 0.6.0-alpha1 --- .gitignore | 1 + README.md | 7 ++++++- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index d33736e2..ffa11409 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .pydevproject .settings .coverage +.hypothesis coverage.xml pylint_report.txt build/ diff --git a/README.md b/README.md index f304738f..d5dbbb84 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.5.1 2015-11-05 +tlslite-ng version 0.6.0-alpha1 2015-11-11 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -521,6 +521,11 @@ encrypt-then-MAC mode for CBC ciphers. 12 History =========== +0.6.0 - WIP + - add support for ChaCha20 and Poly1305 + - add TLS_DHE_RSA_WITH_CHACHA20_POLY1305 ciphersuite + - expose padding and MAC-ing functions in RecordLayer + 0.5.1 - 2015-11-05 - fix SRP_SHA_RSA ciphersuites in TLSv1.2 (for real this time) - minor enchancements in test scripts diff --git a/setup.py b/setup.py index 99147634..3276acdc 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.5.1", + version="0.6.0-alpha1", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 0e4bedc1..aef24efa 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.5.1 +@version: 0.6.0-alpha1 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 7e779ef3..825274a4 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.5.1" +__version__ = "0.6.0-alpha1" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 837c77388fcef7d86d09e1c88f314ca1f513aec4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 12 Nov 2015 14:56:11 +0100 Subject: [PATCH 186/574] expose block size used by cipher Stops the unit tests depending on private fields, useful for overriding the padding method in general --- tlslite/recordlayer.py | 7 +++- unit_tests/test_tlslite_recordlayer.py | 45 ++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index fe81f7ae..8a535ab6 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -244,6 +244,11 @@ def __init__(self, sock): self.encryptThenMAC = False + @property + def blockSize(self): + """Return the size of block used by current symmetric cipher (R/O)""" + return self._writeState.encContext.block_size + @property def version(self): """Return the TLS version used by record layer""" @@ -300,7 +305,7 @@ def isCBCMode(self): def addPadding(self, data): """Add padding to data so that it is multiple of block size""" currentLength = len(data) - blockLength = self._writeState.encContext.block_size + blockLength = self.blockSize paddingLength = blockLength - 1 - (currentLength % blockLength) paddingBytes = bytearray([paddingLength] * (paddingLength+1)) diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 667e4c5a..d378fec4 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -368,6 +368,37 @@ def test_getCipherName(self): self.assertEqual('aes128', recordLayer.getCipherName()) self.assertTrue(recordLayer.isCBCMode()) + def test_blockSize(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 3) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + self.assertEqual(16, recordLayer.blockSize) + + @unittest.skipUnless(cryptomath.m2cryptoLoaded, "requires M2Crypto") + def test_blockSize_with_3DES(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 3) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + self.assertEqual(8, recordLayer.blockSize) + def test_getCipherImplementation(self): sock = MockSocket(bytearray(0)) @@ -1320,7 +1351,7 @@ def test_recvRecord_with_zero_filled_padding_in_SSLv3(self): # change the padding method to return simple version of padding (SSLv3) def broken_padding(data): currentLength = len(data) - blockLength = sendingRecordLayer._writeState.encContext.block_size + blockLength = sendingRecordLayer.blockSize paddingLength = blockLength - 1 - (currentLength % blockLength) paddingBytes = bytearray([0] * (paddingLength)) + \ @@ -1397,7 +1428,7 @@ def test_recvRecord_with_invalid_last_byte_in_padding(self): # change the padding method to return invalid padding def broken_padding(data): currentLength = len(data) - blockLength = sendingRecordLayer._writeState.encContext.block_size + blockLength = sendingRecordLayer.blockSize paddingLength = blockLength - 1 - (currentLength % blockLength) # make the value of last byte longer than all data @@ -1468,7 +1499,7 @@ def test_recvRecord_with_invalid_middle_byte_in_padding(self): # change the padding method to return invalid padding def broken_padding(data): currentLength = len(data) - blockLength = sendingRecordLayer._writeState.encContext.block_size + blockLength = sendingRecordLayer.blockSize paddingLength = blockLength - 1 - (currentLength % blockLength) # make the value of last byte longer than all data @@ -1541,7 +1572,7 @@ def test_recvRecord_with_truncated_MAC(self): def broken_padding(data): data = data[:18] currentLength = len(data) - blockLength = sendingRecordLayer._writeState.encContext.block_size + blockLength = sendingRecordLayer.blockSize paddingLength = blockLength - 1 - (currentLength % blockLength) paddingBytes = bytearray([paddingLength] * (paddingLength)) + \ @@ -1612,7 +1643,7 @@ def test_recvRecord_with_invalid_MAC(self): def broken_padding(data): data[-1] ^= 255 currentLength = len(data) - blockLength = sendingRecordLayer._writeState.encContext.block_size + blockLength = sendingRecordLayer.blockSize paddingLength = blockLength - 1 - (currentLength % blockLength) paddingBytes = bytearray([paddingLength] * (paddingLength)) + \ @@ -1882,7 +1913,7 @@ def test_recvRecord_with_encryptThenMAC_and_bad_last_padding_byte(self): # change the padding method to return invalid padding def broken_padding(data): currentLength = len(data) - blockLength = sendingRecordLayer._writeState.encContext.block_size + blockLength = sendingRecordLayer.blockSize paddingLength = blockLength - 1 - (currentLength % blockLength) # make the value of last byte longer than all data @@ -2012,7 +2043,7 @@ def test_recvRecord_with_encryptThenMAC_and_bad_padding_byte(self): # change the padding method to return invalid padding def broken_padding(data): currentLength = len(data) - blockLength = sendingRecordLayer._writeState.encContext.block_size + blockLength = sendingRecordLayer.blockSize paddingLength = blockLength - 1 - (currentLength % blockLength) # make the value of second to last byte invalid (0) From 1923d638579a331f7e0115d16928645e32534143 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 12 Nov 2015 15:43:25 +0100 Subject: [PATCH 187/574] release 0.6.0-alpha1 --- README.md | 4 ++-- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d5dbbb84..2d50b050 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.6.0-alpha1 2015-11-11 +tlslite-ng version 0.6.0-alpha2 2015-11-12 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -524,7 +524,7 @@ encrypt-then-MAC mode for CBC ciphers. 0.6.0 - WIP - add support for ChaCha20 and Poly1305 - add TLS_DHE_RSA_WITH_CHACHA20_POLY1305 ciphersuite - - expose padding and MAC-ing functions in RecordLayer + - expose padding and MAC-ing functions and blockSize property in RecordLayer 0.5.1 - 2015-11-05 - fix SRP_SHA_RSA ciphersuites in TLSv1.2 (for real this time) diff --git a/setup.py b/setup.py index 3276acdc..31bff4b7 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.6.0-alpha1", + version="0.6.0-alpha2", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index aef24efa..f078a5ea 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.6.0-alpha1 +@version: 0.6.0-alpha2 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 825274a4..e9680898 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.6.0-alpha1" +__version__ = "0.6.0-alpha2" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 579e700a1d24b095ef7b4bdb9ca3642bb9e8f703 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 12 Nov 2015 16:45:15 +0100 Subject: [PATCH 188/574] add test if generated padding is minimal --- unit_tests/test_tlslite_recordlayer.py | 65 ++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index d378fec4..e2ba440e 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -923,6 +923,71 @@ def test_sendRecord_with_encrypting_set_up_ssl3(self): b'\xff\x842\xc7\xa2\x0byd\xab\x1a\xfd\xaf\x05\xd6\xba\x89' )) + def test_if_padding_is_minimal_in_ssl3_low_end(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 0) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test of pad')) + + self.assertIsNotNone(app_data) + self.assertEqual(len(app_data.write()), 11) + + for result in recordLayer.sendRecord(app_data): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x00' + # SSL3 + b'\x00\x20' # length - 32 bytes (2 blocks) + )) # (11 bytes of data + 20 bytes of MAC + # + 1 byte of padding length) + self.assertEqual(len(sock.sent[0][5:]), 32) + + def test_if_padding_is_minimal_in_ssl3_high_end(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 0) + + recordLayer.calcPendingStates(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test of padd')) + + self.assertIsNotNone(app_data) + self.assertEqual(len(app_data.write()), 12) + + for result in recordLayer.sendRecord(app_data): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x00' + # SSL3 + b'\x00\x30' # length - 48 bytes (3 blocks) + )) # (12 bytes of data + 20 bytes of MAC + # + 15 bytes of padding + # + 1 byte of padding length) + self.assertEqual(len(sock.sent[0][5:]), 48) + def test_sendRecord_with_wrong_SSL_version(self): sock = MockSocket(bytearray(0)) From e6f5af8725a88dc993672066a28abda894aa0066 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 4 Nov 2015 19:21:17 +0100 Subject: [PATCH 189/574] easy reverse lookups for HashAlgorithm and SignatureAlgorithm --- tlslite/constants.py | 33 ++++++++++++++++++++++++---- unit_tests/test_tlslite_constants.py | 24 +++++++++++++++++++- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 5ee6791d..483608f9 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -10,6 +10,33 @@ """Constants used in various places.""" +class TLSEnum(object): + """Base class for different enums of TLS IDs""" + + @classmethod + def toRepr(cls, value, blacklist=None): + """ + Convert numeric type to string representation + + name if found, None otherwise + """ + if blacklist is None: + blacklist = [] + return next((key for key, val in cls.__dict__.items() \ + if key not in ('__weakref__', '__dict__', '__doc__', + '__module__') and \ + key not in blacklist and \ + val == value), None) + + @classmethod + def toStr(cls, value, blacklist=None): + """Convert numeric type to human-readable string if possible""" + ret = cls.toRepr(value, blacklist) + if ret is not None: + return ret + else: + return '{0}'.format(value) + class CertificateType: x509 = 0 openpgp = 1 @@ -52,8 +79,7 @@ class ExtensionType: # RFC 6066 / 4366 supports_npn = 13172 renegotiation_info = 0xff01 -class HashAlgorithm: - +class HashAlgorithm(TLSEnum): """Hash algorithm IDs used in TLSv1.2""" none = 0 @@ -64,8 +90,7 @@ class HashAlgorithm: sha384 = 5 sha512 = 6 -class SignatureAlgorithm: - +class SignatureAlgorithm(TLSEnum): """Signing algorithms used in TLSv1.2""" anonymous = 0 diff --git a/unit_tests/test_tlslite_constants.py b/unit_tests/test_tlslite_constants.py index b48599cf..bd05dc16 100644 --- a/unit_tests/test_tlslite_constants.py +++ b/unit_tests/test_tlslite_constants.py @@ -9,7 +9,29 @@ except ImportError: import unittest -from tlslite.constants import CipherSuite +from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm + +class TestHashAlgorithm(unittest.TestCase): + + def test_toRepr(self): + self.assertEqual(HashAlgorithm.toRepr(5), 'sha384') + + def test_toRepr_with_invalid_id(self): + self.assertIsNone(HashAlgorithm.toRepr(None)) + + def test_toRepr_with_unknown_id(self): + self.assertIsNone(HashAlgorithm.toRepr(200)) + + def test_toStr_with_unknown_id(self): + self.assertEqual(HashAlgorithm.toStr(200), '200') + + def test_toStr(self): + self.assertEqual(HashAlgorithm.toStr(6), 'sha512') + +class TestSignatureAlgorithm(unittest.TestCase): + + def test_toRepr(self): + self.assertEqual(SignatureAlgorithm.toRepr(1), 'rsa') class TestCipherSuite(unittest.TestCase): From f4177285f1d8f48d8a574c7f2070286a94afe48c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 5 Nov 2015 14:53:57 +0100 Subject: [PATCH 190/574] reverse lookups for ContentType --- tlslite/constants.py | 14 ++++++++++++-- unit_tests/test_tlslite_constants.py | 12 +++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 483608f9..26d6f9ba 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -60,12 +60,22 @@ class HandshakeType: finished = 20 next_protocol = 67 -class ContentType: +class ContentType(TLSEnum): + """TLS record layer content types of payloads""" + change_cipher_spec = 20 alert = 21 handshake = 22 application_data = 23 - all = (20,21,22,23) + all = (20, 21, 22, 23) + + @classmethod + def toRepr(cls, value, blacklist=None): + """Convert numeric type to name representation""" + if blacklist is None: + blacklist = [] + blacklist.append('all') + return super(ContentType, cls).toRepr(value, blacklist) class ExtensionType: # RFC 6066 / 4366 server_name = 0 # RFC 6066 / 4366 diff --git a/unit_tests/test_tlslite_constants.py b/unit_tests/test_tlslite_constants.py index bd05dc16..b7eb08fd 100644 --- a/unit_tests/test_tlslite_constants.py +++ b/unit_tests/test_tlslite_constants.py @@ -9,7 +9,8 @@ except ImportError: import unittest -from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm +from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ + ContentType class TestHashAlgorithm(unittest.TestCase): @@ -33,6 +34,15 @@ class TestSignatureAlgorithm(unittest.TestCase): def test_toRepr(self): self.assertEqual(SignatureAlgorithm.toRepr(1), 'rsa') +class TestContentType(unittest.TestCase): + + def test_toRepr_with_invalid_value(self): + self.assertIsNone(ContentType.toRepr((20, 21, 22, 23))) + + def test_toStr_with_invalid_value(self): + self.assertEqual(ContentType.toStr((20, 21, 22, 23)), + '(20, 21, 22, 23)') + class TestCipherSuite(unittest.TestCase): def test___init__(self): From 5e6427a57fb5317c713d82de648dc91d89b5a915 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 20 Nov 2015 14:42:55 +0100 Subject: [PATCH 191/574] reverse lookups for AlertLevel and AlertDescription --- tlslite/constants.py | 6 ++++-- unit_tests/test_tlslite_constants.py | 10 +++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 26d6f9ba..8f7b025d 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -171,11 +171,13 @@ class ECPointFormat(object): class NameType: host_name = 0 -class AlertLevel: +class AlertLevel(TLSEnum): + """Enumeration of TLS Alert protocol levels""" + warning = 1 fatal = 2 -class AlertDescription: +class AlertDescription(TLSEnum): """ @cvar bad_record_mac: A TLS record failed to decrypt properly. diff --git a/unit_tests/test_tlslite_constants.py b/unit_tests/test_tlslite_constants.py index b7eb08fd..87e18895 100644 --- a/unit_tests/test_tlslite_constants.py +++ b/unit_tests/test_tlslite_constants.py @@ -10,7 +10,7 @@ import unittest from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ - ContentType + ContentType, AlertDescription, AlertLevel class TestHashAlgorithm(unittest.TestCase): @@ -43,6 +43,14 @@ def test_toStr_with_invalid_value(self): self.assertEqual(ContentType.toStr((20, 21, 22, 23)), '(20, 21, 22, 23)') +class TestAlertDescription(unittest.TestCase): + def test_toRepr(self): + self.assertEqual(AlertDescription.toStr(40), 'handshake_failure') + +class TestAlertLevel(unittest.TestCase): + def test_toRepr(self): + self.assertEqual(AlertLevel.toStr(1), 'warning') + class TestCipherSuite(unittest.TestCase): def test___init__(self): From 2a9d17d63db6abe3e653c2ae4648585d2350240c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 20 Nov 2015 14:49:47 +0100 Subject: [PATCH 192/574] reverse lookups for HandshakeType --- tlslite/constants.py | 6 ++++-- unit_tests/test_tlslite_constants.py | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 8f7b025d..745f13b7 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -46,8 +46,10 @@ class ClientCertificateType: dss_sign = 2 rsa_fixed_dh = 3 dss_fixed_dh = 4 - -class HandshakeType: + +class HandshakeType(TLSEnum): + """Message types in TLS Handshake protocol""" + hello_request = 0 client_hello = 1 server_hello = 2 diff --git a/unit_tests/test_tlslite_constants.py b/unit_tests/test_tlslite_constants.py index 87e18895..2df856d4 100644 --- a/unit_tests/test_tlslite_constants.py +++ b/unit_tests/test_tlslite_constants.py @@ -10,7 +10,7 @@ import unittest from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ - ContentType, AlertDescription, AlertLevel + ContentType, AlertDescription, AlertLevel, HandshakeType class TestHashAlgorithm(unittest.TestCase): @@ -51,6 +51,10 @@ class TestAlertLevel(unittest.TestCase): def test_toRepr(self): self.assertEqual(AlertLevel.toStr(1), 'warning') +class TestHandshakeType(unittest.TestCase): + def test_toRepr(self): + self.assertEqual(HandshakeType.toStr(1), 'client_hello') + class TestCipherSuite(unittest.TestCase): def test___init__(self): From f2b320a13d50e4b4fd520c0fa930b68d1e155a48 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 20 Nov 2015 15:02:39 +0100 Subject: [PATCH 193/574] configuration for pep257 tool both when running standalone and when running as part of prospector tool We ignore D203 because it conflicts with the newer D211, the former requires 1 whiteline before class doc text while the latter requires no whitelines before class doc text --- .landscape.yaml | 4 ++++ .pep257 | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 .pep257 diff --git a/.landscape.yaml b/.landscape.yaml index 7bddad22..9379218d 100644 --- a/.landscape.yaml +++ b/.landscape.yaml @@ -10,3 +10,7 @@ ignore-paths: - tests - unit_tests - scripts + +pep257: + disable: + - D203 diff --git a/.pep257 b/.pep257 new file mode 100644 index 00000000..9b05d2ce --- /dev/null +++ b/.pep257 @@ -0,0 +1,3 @@ +[pep257] + +add-ignore = D203 From f80fde915b7b6d8ea6876c4688a3f784b0434484 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 23 Nov 2015 16:06:54 +0100 Subject: [PATCH 194/574] update after recent merges --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 2d50b050..f4741942 100644 --- a/README.md +++ b/README.md @@ -522,6 +522,9 @@ encrypt-then-MAC mode for CBC ciphers. =========== 0.6.0 - WIP + - mark library as compatible with Python 3.5 (it was previously, but now + it is verified with Continous Integration) + - small cleanups and more documentation - add support for ChaCha20 and Poly1305 - add TLS_DHE_RSA_WITH_CHACHA20_POLY1305 ciphersuite - expose padding and MAC-ing functions and blockSize property in RecordLayer From c9d22963fec4ff2470f554458d233826882863d7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 25 Nov 2015 16:40:56 +0100 Subject: [PATCH 195/574] add quantifiedcode badge and excludes --- .checkignore | 3 +++ README.md | 1 + 2 files changed, 4 insertions(+) create mode 100644 .checkignore diff --git a/.checkignore b/.checkignore new file mode 100644 index 00000000..7932505f --- /dev/null +++ b/.checkignore @@ -0,0 +1,3 @@ +scripts/** +tests/** +unit_tests/** diff --git a/README.md b/README.md index f4741942..1e3c84ae 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ https://github.com/tomato42/tlslite-ng/ [![Coverage Status](https://coveralls.io/repos/tomato42/tlslite-ng/badge.svg?branch=master)](https://coveralls.io/r/tomato42/tlslite-ng?branch=master) [![Code Health](https://landscape.io/github/tomato42/tlslite-ng/master/landscape.svg?style=flat)](https://landscape.io/github/tomato42/tlslite-ng/master) [![Code Climate](https://codeclimate.com/github/tomato42/tlslite-ng/badges/gpa.svg)](https://codeclimate.com/github/tomato42/tlslite-ng) +[![Code Issues](https://www.quantifiedcode.com/api/v1/project/59e6019ef3c84ad7ba30c49ec46d990f/badge.svg)](https://www.quantifiedcode.com/app/project/59e6019ef3c84ad7ba30c49ec46d990f) Table of Contents From 2c32fe5794f3ca65296b5be1b3b52284aafd80e7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 18 Oct 2015 13:36:36 +0200 Subject: [PATCH 196/574] test coverage for numberToByteArray --- unit_tests/test_tlslite_utils_cryptomath.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index 9b3e54ad..9187513b 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -12,7 +12,7 @@ from hypothesis.strategies import integers import math -from tlslite.utils.cryptomath import isPrime, numBits, numBytes +from tlslite.utils.cryptomath import isPrime, numBits, numBytes, numberToByteArray class TestIsPrime(unittest.TestCase): def test_with_small_primes(self): @@ -63,6 +63,23 @@ def test_with_big_composites(self): # NextPrime[NextPrime[2^512]]*NextPrime[2^512] self.assertFalse(isPrime(179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639477074095512480796227391561801824887394139579933613278628104952355769470429079061808809522886423955917442317693387325171135071792698344550223571732405562649211)) +class TestNumberToBytesFunctions(unittest.TestCase): + def test_numberToByteArray(self): + self.assertEqual(numberToByteArray(0x00000000000001), + bytearray(b'\x01')) + + def test_numberToByteArray_with_MSB_number(self): + self.assertEqual(numberToByteArray(0xff), + bytearray(b'\xff')) + + def test_numberToByteArray_with_length(self): + self.assertEqual(numberToByteArray(0xff, 2), + bytearray(b'\x00\xff')) + + def test_numberToByteArray_with_not_enough_length(self): + self.assertEqual(numberToByteArray(0x0a0b0c, 2), + bytearray(b'\x0b\x0c')) + class TestNumBits(unittest.TestCase): @staticmethod From 3dc3cba6dd050a191d20dad1334ba4a1104f5ace Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 5 Nov 2015 18:18:31 +0100 Subject: [PATCH 197/574] fix definition of KeyExchange methods --- tlslite/tlsconnection.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 1c07ccd0..a7ea4cb3 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -44,16 +44,20 @@ def __init__(self, cipherSuite, clientHello, serverHello, privateKey): self.serverHello = serverHello self.privateKey = privateKey - def makeServerKeyExchange(): + def makeServerKeyExchange(self): """ + Create a ServerKeyExchange object + 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): + def processClientKeyExchange(self, clientKeyExchange): """ + Process ClientKeyExchange and return premaster secret + Processes the client's ClientKeyExchange message and returns the premaster secret. Raises TLSLocalAlert on error. """ From 3a9aed128e36d35bd4a1dec7a07505736fedc58e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 17 Oct 2015 20:19:51 +0200 Subject: [PATCH 198/574] SHA2 algorithm identifiers in PKCS#1 v1.5 signatures TLSv1.2 allows us to select different hash algorithms, but we need to encode them differently --- tlslite/utils/rsakey.py | 38 ++++++++++++++++++++++--- unit_tests/test_tlslite_utils_rsakey.py | 7 +++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/tlslite/utils/rsakey.py b/tlslite/utils/rsakey.py index fb022cc6..a6b5ef8e 100644 --- a/tlslite/utils/rsakey.py +++ b/tlslite/utils/rsakey.py @@ -232,14 +232,44 @@ def addPKCS1SHA1Prefix(bytes, withNULL=True): # always implemented. Anyways, verification code should probably # accept both. if not withNULL: - prefixBytes = bytearray(\ - [0x30,0x1f,0x30,0x07,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x04,0x14]) + prefixBytes = bytearray([0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, + 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14]) else: - prefixBytes = bytearray(\ - [0x30,0x21,0x30,0x09,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x05,0x00,0x04,0x14]) + prefixBytes = RSAKey._pkcs1Prefixes['sha1'] prefixedBytes = prefixBytes + bytes return prefixedBytes + _pkcs1Prefixes = {'md5' : bytearray([0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x02, 0x05, 0x05, 0x00, 0x04, 0x10]), + 'sha1' : bytearray([0x30, 0x21, 0x30, 0x09, 0x06, 0x05, + 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, + 0x00, 0x04, 0x14]), + 'sha224' : bytearray([0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, + 0x1c]), + 'sha256' : bytearray([0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, + 0x20]), + 'sha384' : bytearray([0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, + 0x30]), + 'sha512' : bytearray([0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, + 0x40])} + + @staticmethod + def addPKCS1Prefix(data, hashName): + """Add the PKCS#1 v1.5 algorithm identifier prefix to hash bytes""" + hashName = hashName.lower() + assert hashName in RSAKey._pkcs1Prefixes + prefixBytes = RSAKey._pkcs1Prefixes[hashName] + return prefixBytes + data + def _addPKCS1Padding(self, bytes, blockType): padLength = (numBytes(self.n) - (len(bytes)+3)) if blockType == 1: #Signature padding diff --git a/unit_tests/test_tlslite_utils_rsakey.py b/unit_tests/test_tlslite_utils_rsakey.py index a185c269..3711d89a 100644 --- a/unit_tests/test_tlslite_utils_rsakey.py +++ b/unit_tests/test_tlslite_utils_rsakey.py @@ -116,3 +116,10 @@ def test_addPKCS1SHA1Prefix_without_NULL(self): self.assertEqual(RSAKey.addPKCS1SHA1Prefix(data, False), bytearray( b'0\x1f0\x07\x06\x05+\x0e\x03\x02\x1a\x04\x14' + b' sha-1 hash of data ')) + + def test_addPKCS1Prefix(self): + data = bytearray(b' sha-1 hash of data ') + + self.assertEqual(RSAKey.addPKCS1Prefix(data, 'sha1'), bytearray( + b'0!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14' + + b' sha-1 hash of data ')) From 4c2967854fa89542cb61dac47d49f0e0df837d43 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 5 Nov 2015 18:23:11 +0100 Subject: [PATCH 199/574] add rsaSigHashes to HandshakeSettings --- tlslite/handshakesettings.py | 19 +++++++++++++++++++ unit_tests/test_tlslite_handshakesettings.py | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index cd21ddfc..af93f87f 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -21,6 +21,8 @@ KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "srp_sha", "srp_sha_rsa", "dh_anon"] CIPHER_IMPLEMENTATIONS = ["openssl", "pycrypto", "python"] CERTIFICATE_TYPES = ["x509"] +RSA_SIGNATURE_HASHES = ["sha512", "sha384", "sha256", "sha224", "sha1"] +ALL_RSA_SIGNATURE_HASHES = RSA_SIGNATURE_HASHES + ["md5"] class HandshakeSettings(object): """This class encapsulates various parameters that can be used with @@ -101,6 +103,16 @@ class HandshakeSettings(object): @type sendFallbackSCSV: bool @ivar sendFallbackSCSV: Whether to, as a client, send FALLBACK_SCSV. + + @type rsaSigHashes: list + @ivar rsaSigHashes: List of hashes supported (and advertised as such) for + TLS 1.2 signatures over Server Key Exchange or Certificate Verify with + RSA signature algorithm. + + The list is sorted from most wanted to least wanted algorithm. + + The allowed hashes are: "md5", "sha1", "sha224", "sha256", + "sha384" and "sha512". The default list does not include md5. """ def __init__(self): self.minKeySize = 1023 @@ -115,6 +127,7 @@ def __init__(self): self.useExperimentalTackExtension = False self.sendFallbackSCSV = False self.useEncryptThenMAC = True + self.rsaSigHashes = list(RSA_SIGNATURE_HASHES) def validate(self): """ @@ -137,6 +150,7 @@ def validate(self): other.maxVersion = self.maxVersion other.sendFallbackSCSV = self.sendFallbackSCSV other.useEncryptThenMAC = self.useEncryptThenMAC + other.rsaSigHashes = self.rsaSigHashes if not cipherfactory.tripleDESPresent: other.cipherNames = [e for e in self.cipherNames if e != "3des"] @@ -197,6 +211,11 @@ def validate(self): if other.useEncryptThenMAC not in (True, False): raise ValueError("useEncryptThenMAC can only be True or False") + for val in other.rsaSigHashes: + if val not in ALL_RSA_SIGNATURE_HASHES: + raise ValueError("Unknown RSA signature hash: '{0}'".\ + format(val)) + return other def getCertificateTypes(self): diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index c63339b2..253148fe 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -183,3 +183,9 @@ def test_invalid_KEX(self): with self.assertRaises(ValueError): hs.validate() + + def test_invalid_signature_algorithm(self): + hs = HandshakeSettings() + hs.rsaSigHashes += ['md2'] + with self.assertRaises(ValueError): + hs.validate() From 50f4d93af16a690644ad0b4fa1c1b3a3dc9cbcc5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 18 Oct 2015 15:08:47 +0200 Subject: [PATCH 200/574] Support for remaining handshake hash types For CertificateVerify message to be signed with other hash types, we need to keep all types that we support. --- tlslite/handshakehashes.py | 10 ++++++++++ unit_tests/test_tlslite_handshakehashes.py | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/tlslite/handshakehashes.py b/tlslite/handshakehashes.py index 6718e2a2..3c03d560 100644 --- a/tlslite/handshakehashes.py +++ b/tlslite/handshakehashes.py @@ -20,8 +20,10 @@ def __init__(self): """Create instance""" self._handshakeMD5 = hashlib.md5() self._handshakeSHA = hashlib.sha1() + self._handshakeSHA224 = hashlib.sha224() self._handshakeSHA256 = hashlib.sha256() self._handshakeSHA384 = hashlib.sha384() + self._handshakeSHA512 = hashlib.sha512() def update(self, data): """ @@ -33,8 +35,10 @@ def update(self, data): text = compat26Str(data) self._handshakeMD5.update(text) self._handshakeSHA.update(text) + self._handshakeSHA224.update(text) self._handshakeSHA256.update(text) self._handshakeSHA384.update(text) + self._handshakeSHA512.update(text) def digest(self, digest=None): """ @@ -51,10 +55,14 @@ def digest(self, digest=None): return self._handshakeMD5.digest() elif digest == 'sha1': return self._handshakeSHA.digest() + elif digest == 'sha224': + return self._handshakeSHA224.digest() elif digest == 'sha256': return self._handshakeSHA256.digest() elif digest == 'sha384': return self._handshakeSHA384.digest() + elif digest == 'sha512': + return self._handshakeSHA512.digest() else: raise ValueError("Unknown digest name") @@ -99,6 +107,8 @@ def copy(self): other = HandshakeHashes() other._handshakeMD5 = self._handshakeMD5.copy() other._handshakeSHA = self._handshakeSHA.copy() + other._handshakeSHA224 = self._handshakeSHA224.copy() other._handshakeSHA256 = self._handshakeSHA256.copy() other._handshakeSHA384 = self._handshakeSHA384.copy() + other._handshakeSHA512 = self._handshakeSHA512.copy() return other diff --git a/unit_tests/test_tlslite_handshakehashes.py b/unit_tests/test_tlslite_handshakehashes.py index 4892d0a9..1f211382 100644 --- a/unit_tests/test_tlslite_handshakehashes.py +++ b/unit_tests/test_tlslite_handshakehashes.py @@ -71,6 +71,24 @@ def test_digest_sha256(self): b"A\xe4d\x9b\x93L\xa4\x95\x99\x1bxR\xb8U", hh.digest('sha256')) + def test_digest_sha224(self): + hh = HandshakeHashes() + + self.assertEqual(( + b'\xd1J\x02\x8c*:+\xc9Ga\x02\xbb(\x824\xc4\x15\xa2\xb0' + b'\x1f\x82\x8e\xa6*\xc5\xb3\xe4/'), + hh.digest('sha224')) + + def test_digest_sha512(self): + hh = HandshakeHashes() + + self.assertEqual(( + b'\xcf\x83\xe15~\xef\xb8\xbd\xf1T(P\xd6m\x80\x07\xd6 ' + b'\xe4\x05\x0bW\x15\xdc\x83\xf4\xa9!\xd3l\xe9\xceG\xd0' + b'\xd1<]\x85\xf2\xb0\xff\x83\x18\xd2\x87~\xec/c\xb91' + b'\xbdGAz\x81\xa582z\xf9\'\xda>'), + hh.digest('sha512')) + def test_digest_with_partial_writes(self): hh = HandshakeHashes() hh.update(b'text') From 8cbc01a81dab97f4b53b57b5644c5a61d8003423 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 5 Nov 2015 16:56:57 +0100 Subject: [PATCH 201/574] support for SHA-2 hashes --- tlslite/utils/cryptomath.py | 12 +++- unit_tests/test_tlslite_utils_cryptomath.py | 71 ++++++++++++++++++++- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index 4a086dd7..b30edb52 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -70,10 +70,18 @@ def getRandomBytes(howMany): import hashlib def MD5(b): - return bytearray(hashlib.md5(compat26Str(b)).digest()) + """Return a MD5 digest of data""" + return secureHash(b, 'md5') def SHA1(b): - return bytearray(hashlib.sha1(compat26Str(b)).digest()) + """Return a SHA1 digest of data""" + return secureHash(b, 'sha1') + +def secureHash(data, algorithm): + """Return a digest of `data` using `algorithm`""" + hashInstance = hashlib.new(algorithm) + hashInstance.update(compat26Str(data)) + return bytearray(hashInstance.digest()) def HMAC_MD5(k, b): k = compatHMAC(k) diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index 9187513b..936d497e 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -12,7 +12,8 @@ from hypothesis.strategies import integers import math -from tlslite.utils.cryptomath import isPrime, numBits, numBytes, numberToByteArray +from tlslite.utils.cryptomath import isPrime, numBits, numBytes, \ + numberToByteArray, MD5, SHA1, secureHash class TestIsPrime(unittest.TestCase): def test_with_small_primes(self): @@ -115,3 +116,71 @@ def test_numBits(self, number): @example((1<<8192)-1) def test_numBytes(self, number): self.assertEqual(numBytes(number), self.num_bytes(number)) + +class TestHashMethods(unittest.TestCase): + def test_MD5(self): + self.assertEqual(MD5(b"message digest"), + bytearray(b'\xf9\x6b\x69\x7d\x7c\xb7\x93\x8d' + b'\x52\x5a\x2f\x31\xaa\xf1\x61\xd0')) + + def test_SHA1(self): + self.assertEqual(SHA1(b'abc'), + bytearray(b'\xA9\x99\x3E\x36' + b'\x47\x06\x81\x6A' + b'\xBA\x3E\x25\x71' + b'\x78\x50\xC2\x6C' + b'\x9C\xD0\xD8\x9D')) + def test_SHA224(self): + self.assertEqual(secureHash(b'abc', 'sha224'), + bytearray(b'\x23\x09\x7D\x22' + b'\x34\x05\xD8\x22' + b'\x86\x42\xA4\x77' + b'\xBD\xA2\x55\xB3' + b'\x2A\xAD\xBC\xE4' + b'\xBD\xA0\xB3\xF7' + b'\xE3\x6C\x9D\xA7')) + + def test_SHA256(self): + self.assertEqual(secureHash(b'abc', 'sha256'), + bytearray(b'\xBA\x78\x16\xBF' + b'\x8F\x01\xCF\xEA' + b'\x41\x41\x40\xDE' + b'\x5D\xAE\x22\x23' + b'\xB0\x03\x61\xA3' + b'\x96\x17\x7A\x9C' + b'\xB4\x10\xFF\x61' + b'\xF2\x00\x15\xAD')) + + def test_SHA384(self): + self.assertEqual(secureHash(b'abc', 'sha384'), + bytearray(b'\xCB\x00\x75\x3F' + b'\x45\xA3\x5E\x8B' + b'\xB5\xA0\x3D\x69' + b'\x9A\xC6\x50\x07' + b'\x27\x2C\x32\xAB' + b'\x0E\xDE\xD1\x63' + b'\x1A\x8B\x60\x5A' + b'\x43\xFF\x5B\xED' + b'\x80\x86\x07\x2B' + b'\xA1\xE7\xCC\x23' + b'\x58\xBA\xEC\xA1' + b'\x34\xC8\x25\xA7')) + + def test_SHA512(self): + self.assertEqual(secureHash(b'abc', 'sha512'), + bytearray(b'\xDD\xAF\x35\xA1' + b'\x93\x61\x7A\xBA' + b'\xCC\x41\x73\x49' + b'\xAE\x20\x41\x31' + b'\x12\xE6\xFA\x4E' + b'\x89\xA9\x7E\xA2' + b'\x0A\x9E\xEE\xE6' + b'\x4B\x55\xD3\x9A' + b'\x21\x92\x99\x2A' + b'\x27\x4F\xC1\xA8' + b'\x36\xBA\x3C\x23' + b'\xA3\xFE\xEB\xBD' + b'\x45\x4D\x44\x23' + b'\x64\x3C\xE8\x0E' + b'\x2A\x9A\xC9\x4F' + b'\xA5\x4C\xA4\x9F')) From c3f9ff094808e886e743764142bdd88818dbe645 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 14:17:23 +0100 Subject: [PATCH 202/574] make DHE key exchange use generic methods for sigs --- tlslite/handshakesettings.py | 2 + tlslite/messages.py | 9 +- tlslite/tlsconnection.py | 308 +++++++---- unit_tests/test_tlslite_handshakesettings.py | 12 + unit_tests/test_tlslite_messages.py | 13 +- unit_tests/test_tlslite_tlsconnection.py | 523 +++++++++++++++++-- 6 files changed, 732 insertions(+), 135 deletions(-) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index af93f87f..00538d10 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -215,6 +215,8 @@ def validate(self): if val not in ALL_RSA_SIGNATURE_HASHES: raise ValueError("Unknown RSA signature hash: '{0}'".\ format(val)) + if len(other.rsaSigHashes) == 0 and other.maxVersion >= (3, 3): + raise ValueError("TLS 1.2 requires signature algorithms to be set") return other diff --git a/tlslite/messages.py b/tlslite/messages.py index 9c161fb5..5c38980b 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1074,14 +1074,17 @@ def write(self): def hash(self, clientRandom, serverRandom): """ - Calculate hash of paramters to sign + Calculate hash of parameters to sign @rtype: bytearray """ bytesToHash = clientRandom + serverRandom + self.writeParams() if self.version >= (3, 3): - # TODO: Signature algorithm negotiation not supported. - return SHA1(bytesToHash) + hashAlg = HashAlgorithm.toRepr(self.hashAlg) + if hashAlg is None: + raise AssertionError("Unknown hash algorithm: {0}".\ + format(self.hashAlg)) + return secureHash(bytesToHash, hashAlg) return MD5(bytesToHash) + SHA1(bytesToHash) class ServerHelloDone(HandshakeMsg): diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index a7ea4cb3..72abf7b4 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -44,7 +44,7 @@ def __init__(self, cipherSuite, clientHello, serverHello, privateKey): self.serverHello = serverHello self.privateKey = privateKey - def makeServerKeyExchange(self): + def makeServerKeyExchange(self, sigHash=None): """ Create a ServerKeyExchange object @@ -63,6 +63,110 @@ def processClientKeyExchange(self, clientKeyExchange): """ raise NotImplementedError() + def signServerKeyExchange(self, serverKeyExchange, sigHash=None): + """ + Sign a server key best matching supported algorithms + + @type sigHash: str + @param sigHash: name of the hash used for signing + """ + if self.serverHello.server_version >= (3, 3): + serverKeyExchange.signAlg = SignatureAlgorithm.rsa + serverKeyExchange.hashAlg = getattr(HashAlgorithm, sigHash) + hashBytes = serverKeyExchange.hash(self.clientHello.random, + self.serverHello.random) + + if self.serverHello.server_version >= (3, 3): + hashBytes = RSAKey.addPKCS1Prefix(hashBytes, sigHash) + + serverKeyExchange.signature = self.privateKey.sign(hashBytes) + + @staticmethod + def verifyServerKeyExchange(serverKeyExchange, publicKey, clientRandom, + serverRandom, validSigAlgs): + """Verify signature on the Server Key Exchange message + + the only acceptable signature algorithms are specified by validSigAlgs + """ + if serverKeyExchange.version >= (3, 3): + if (serverKeyExchange.hashAlg, serverKeyExchange.signAlg) not in \ + validSigAlgs: + raise TLSIllegalParameterException("Server selected " + "invalid signature " + "algorithm") + assert serverKeyExchange.signAlg == SignatureAlgorithm.rsa + hashName = HashAlgorithm.toRepr(serverKeyExchange.hashAlg) + if hashName is None: + raise TLSIllegalParameterException("Unknown signature " + "algorithm") + hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) + + if serverKeyExchange.version == (3, 3): + hashBytes = RSAKey.addPKCS1Prefix(hashBytes, hashName) + + sigBytes = serverKeyExchange.signature + if len(sigBytes) == 0: + raise TLSIllegalParameterException("Empty signature") + + if not publicKey.verify(sigBytes, hashBytes): + raise TLSDecryptionFailed("Server Key Exchange signature " + "invalid") + + @staticmethod + def calcVerifyBytes(version, handshakeHashes, signatureAlg, + premasterSecret, clientRandom, serverRandom): + """Calculate signed bytes for Certificate Verify""" + if version == (3, 0): + masterSecret = calcMasterSecret(version, + 0, + premasterSecret, + clientRandom, + serverRandom) + verifyBytes = handshakeHashes.digestSSL(masterSecret, b"") + elif version in ((3, 1), (3, 2)): + verifyBytes = handshakeHashes.digest() + elif version == (3, 3): + hashName = HashAlgorithm.toRepr(signatureAlg[0]) + verifyBytes = handshakeHashes.digest(hashName) + verifyBytes = RSAKey.addPKCS1Prefix(verifyBytes, hashName) + return verifyBytes + + @staticmethod + def makeCertificateVerify(version, handshakeHashes, validSigAlgs, + privateKey, certificateRequest, premasterSecret, + clientRandom, serverRandom): + """Create a Certificate Verify message + + @param version: protocol version in use + @param handshakeHashes: the running hash of all handshake messages + @param validSigAlgs: acceptable signature algorithms for client side, + applicable only to TLSv1.2 (or later) + @param certificateRequest: the server provided Certificate Request + message + @param premasterSecret: the premaster secret, needed only for SSLv3 + @param clientRandom: client provided random value, needed only for SSLv3 + @param serverRandom: server provided random value, needed only for SSLv3 + """ + signatureAlgorithm = None + # in TLS 1.2 we must decide which algorithm to use for signing + if version == (3, 3): + serverSigAlgs = certificateRequest.supported_signature_algs + signatureAlgorithm = next((sigAlg for sigAlg in validSigAlgs \ + if sigAlg in serverSigAlgs), None) + # if none acceptable, do a last resort: + if signatureAlgorithm is None: + signatureAlgorithm = validSigAlgs[0] + verifyBytes = KeyExchange.calcVerifyBytes(version, handshakeHashes, + signatureAlgorithm, + premasterSecret, + clientRandom, + serverRandom) + signedBytes = privateKey.sign(verifyBytes) + certificateVerify = CertificateVerify(version) + certificateVerify.create(signedBytes, signatureAlgorithm) + + return certificateVerify + class RSAKeyExchange(KeyExchange): """ @@ -71,10 +175,12 @@ class RSAKeyExchange(KeyExchange): NOT stable API, do NOT use """ - def makeServerKeyExchange(self): + def makeServerKeyExchange(self, sigHash=None): + """Don't create a server key exchange for RSA key exchange""" return None def processClientKeyExchange(self, clientKeyExchange): + """Decrypt client key exchange, return premaster secret""" premasterSecret = self.privateKey.decrypt(\ clientKeyExchange.encryptedPreMasterSecret) @@ -94,7 +200,6 @@ def processClientKeyExchange(self, clientKeyExchange): return premasterSecret class DHE_RSAKeyExchange(KeyExchange): - """ Handling of ephemeral Diffe-Hellman Key exchange @@ -112,7 +217,8 @@ def __init__(self, cipherSuite, clientHello, serverHello, privateKey): # RFC 3526, Section 8. strength = 160 - def makeServerKeyExchange(self): + def makeServerKeyExchange(self, sigHash=None): + """Prepare server side of key exchange with selected parameters""" # 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)) @@ -121,17 +227,11 @@ def makeServerKeyExchange(self): version = self.serverHello.server_version serverKeyExchange = ServerKeyExchange(self.cipherSuite, version) serverKeyExchange.createDH(self.dh_p, self.dh_g, dh_Ys) - hashBytes = serverKeyExchange.hash(self.clientHello.random, - self.serverHello.random) - if version >= (3, 3): - # TODO: Signature algorithm negotiation not supported. - hashBytes = RSAKey.addPKCS1SHA1Prefix(hashBytes) - serverKeyExchange.signAlg = SignatureAlgorithm.rsa - serverKeyExchange.hashAlg = HashAlgorithm.sha1 - serverKeyExchange.signature = self.privateKey.sign(hashBytes) + self.signServerKeyExchange(serverKeyExchange, sigHash) return serverKeyExchange def processClientKeyExchange(self, clientKeyExchange): + """Use client provided parameters to establish premaster secret""" dh_Yc = clientKeyExchange.dh_Yc # First half of RFC 2631, Section 2.1.5. Validate the client's public @@ -647,13 +747,23 @@ def _clientSendClientHello(self, settings, session, srpUsername, #Initialize acceptable certificate types certificateTypes = settings.getCertificateTypes() + extensions = [] + #Initialize TLS extensions if settings.useEncryptThenMAC: - extensions = [TLSExtension().create(ExtensionType.encrypt_then_mac, - bytearray(0))] - else: + extensions.append(TLSExtension().\ + create(ExtensionType.encrypt_then_mac, + bytearray(0))) + # In TLS1.2 advertise support for additional signature types + if settings.maxVersion >= (3, 3): + sigList = self._sigHashesToList(settings) + assert len(sigList) > 0 + extensions.append(SignatureAlgorithmsExtension().\ + create(sigList)) + #don't send empty list of extensions + if len(extensions) == 0: extensions = None - + #Either send ClientHello (with a resumable session)... if session and session.sessionID: #If it's resumable, then its @@ -1053,9 +1163,10 @@ def _clientDHEKeyExchange(self, settings, cipherSuite, serverKeyExchange = result for result in self._getMsg(ContentType.handshake, - (HandshakeType.certificate_request, - HandshakeType.server_hello_done)): - if result in (0,1): yield result + (HandshakeType.certificate_request, + HandshakeType.server_hello_done)): + if result in (0, 1): + yield result else: break certificateRequest = None @@ -1072,26 +1183,7 @@ def _clientDHEKeyExchange(self, settings, cipherSuite, #Check the server's signature, if the server chose an DHE_RSA suite serverCertChain = None if cipherSuite in CipherSuite.dheCertSuites: - #Check if the signature algorithm is sane in TLSv1.2 - if self.version == (3, 3): - if serverKeyExchange.hashAlg != HashAlgorithm.sha1 or \ - serverKeyExchange.signAlg != SignatureAlgorithm.rsa: - for result in self._sendError(\ - AlertDescription.illegal_parameter, - "Server selected not advertised " - "signature algorithm"): - yield result - - hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) - - sigBytes = serverKeyExchange.signature - if len(sigBytes) == 0: - for result in self._sendError(\ - AlertDescription.illegal_parameter, - "Server sent DHE ServerKeyExchange message " - "without a signature"): - yield result - + # get the certificate for result in self._clientGetKeyFromChain(serverCertificate, settings, tackExt): @@ -1101,14 +1193,20 @@ def _clientDHEKeyExchange(self, settings, cipherSuite, break publicKey, serverCertChain, tackExt = result - # actually a check for hashAlg == sha1 and signAlg == rsa - if self.version == (3, 3): - hashBytes = publicKey.addPKCS1SHA1Prefix(hashBytes) - - if not publicKey.verify(sigBytes, hashBytes): - for result in self._sendError( - AlertDescription.decrypt_error, - "signature failed to verify"): + # then verify the signature on SKE + validSigAlgs = self._sigHashesToList(settings) + try: + KeyExchange.verifyServerKeyExchange(serverKeyExchange, + publicKey, + clientRandom, + serverRandom, + validSigAlgs) + except TLSIllegalParameterException: + for result in self._sendError(AlertDescription.\ + illegal_parameter): + yield result + except TLSDecryptionFailed: + for result in self._sendError(AlertDescription.decrypt_error): yield result # TODO: make it changeable @@ -1124,9 +1222,9 @@ def _clientDHEKeyExchange(self, settings, cipherSuite, # if a peer doesn't advertise support for any algorithm in TLSv1.2, # support for SHA1+RSA can be assumed if self.version == (3, 3) and \ - (HashAlgorithm.sha1, SignatureAlgorithm.rsa) \ - not in certificateRequest.supported_signature_algs and\ - len(certificateRequest.supported_signature_algs) > 0: + len([sig for sig in \ + certificateRequest.supported_signature_algs\ + if sig[1] == SignatureAlgorithm.rsa]) == 0: for result in self._sendError(\ AlertDescription.handshake_failure, "Server doesn't accept any sigalgs we support: " + @@ -1175,26 +1273,16 @@ def _clientDHEKeyExchange(self, settings, cipherSuite, #if client auth was requested and we have a private key, send a #CertificateVerify if certificateRequest and privateKey: - signatureAlgorithm = None - if self.version == (3,0): - masterSecret = calcMasterSecret(self.version, - cipherSuite, - premasterSecret, - clientRandom, - serverRandom) - verifyBytes = self._handshake_hash.digestSSL(masterSecret, b"") - elif self.version in ((3,1), (3,2)): - verifyBytes = self._handshake_hash.digest() - else: # self.version == (3,3): - # TODO: Signature algorithm negotiation not supported. - signatureAlgorithm = (HashAlgorithm.sha1, SignatureAlgorithm.rsa) - verifyBytes = self._handshake_hash.digest('sha1') - verifyBytes = RSAKey.addPKCS1SHA1Prefix(verifyBytes) - if self.fault == Fault.badVerifyMessage: - verifyBytes[0] = ((verifyBytes[0]+1) % 256) - signedBytes = privateKey.sign(verifyBytes) - certificateVerify = CertificateVerify(self.version) - certificateVerify.create(signedBytes, signatureAlgorithm) + validSigAlgs = self._sigHashesToList(settings) + certificateVerify = KeyExchange.makeCertificateVerify(\ + self.version, + self._handshake_hash, + validSigAlgs, + privateKey, + certificateRequest, + premasterSecret, + clientRandom, + serverRandom) for result in self._sendMsg(certificateVerify): yield result @@ -1734,15 +1822,15 @@ def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, serverKeyExchange = ServerKeyExchange(cipherSuite, self.version) serverKeyExchange.createSRP(N, g, s, B) if cipherSuite in CipherSuite.srpCertSuites: + if self.version == (3, 3): + # TODO signing algorithm not negotiatied + serverKeyExchange.signAlg = SignatureAlgorithm.rsa + serverKeyExchange.hashAlg = HashAlgorithm.sha1 hashBytes = serverKeyExchange.hash(clientHello.random, serverHello.random) if self.version == (3, 3): hashBytes = RSAKey.addPKCS1SHA1Prefix(hashBytes) serverKeyExchange.signature = privateKey.sign(hashBytes) - if self.version == (3, 3): - # TODO signing algorithm not negotiatied - serverKeyExchange.signAlg = SignatureAlgorithm.rsa - serverKeyExchange.hashAlg = HashAlgorithm.sha1 #Send ServerHello[, Certificate], ServerKeyExchange, #ServerHelloDone @@ -1794,18 +1882,18 @@ def _serverCertKeyExchange(self, clientHello, serverHello, msgs.append(serverHello) msgs.append(Certificate(CertificateType.x509).create(serverCertChain)) - serverKeyExchange = keyExchange.makeServerKeyExchange() + sigHashAlg = self._pickServerKeyExchangeSig(settings, clientHello) + serverKeyExchange = keyExchange.makeServerKeyExchange(sigHashAlg) if serverKeyExchange is not None: msgs.append(serverKeyExchange) if reqCert: certificateRequest = CertificateRequest(self.version) if not reqCAs: reqCAs = [] - # TODO add support for more HashAlgorithms + validSigAlgs = self._sigHashesToList(settings) certificateRequest.create([ClientCertificateType.rsa_sign], reqCAs, - [(HashAlgorithm.sha1, - SignatureAlgorithm.rsa)]) + validSigAlgs) msgs.append(certificateRequest) msgs.append(ServerHelloDone()) for result in self._sendMsgs(msgs): @@ -1867,23 +1955,29 @@ def _serverCertKeyExchange(self, clientHello, serverHello, #Get and check CertificateVerify, if relevant if clientCertChain: - if self.version == (3,0): - masterSecret = calcMasterSecret(self.version, - cipherSuite, - premasterSecret, - clientHello.random, - serverHello.random) - verifyBytes = self._handshake_hash.digestSSL(masterSecret, b"") - elif self.version in ((3,1), (3,2)): - verifyBytes = self._handshake_hash.digest() - elif self.version == (3,3): - verifyBytes = self._handshake_hash.digest('sha1') - verifyBytes = RSAKey.addPKCS1SHA1Prefix(verifyBytes) + handshakeHash = self._handshake_hash.copy() for result in self._getMsg(ContentType.handshake, - HandshakeType.certificate_verify): - if result in (0,1): yield result + HandshakeType.certificate_verify): + if result in (0, 1): + yield result else: break certificateVerify = result + signatureAlgorithm = None + if self.version == (3, 3): + validSigAlgs = self._sigHashesToList(settings) + if certificateVerify.signatureAlgorithm not in validSigAlgs: + for result in self._sendError(\ + AlertDescription.decryption_failed, + "Invalid signature on Certificate Verify"): + yield result + signatureAlgorithm = certificateVerify.signatureAlgorithm + + verifyBytes = KeyExchange.calcVerifyBytes(self.version, + handshakeHash, + signatureAlgorithm, + premasterSecret, + clientHello.random, + serverHello.random) publicKey = clientCertChain.getEndEntityPublicKey() if len(publicKey) < settings.minKeySize: for result in self._sendError(\ @@ -2084,3 +2178,33 @@ def _handshakeWrapperAsync(self, handshaker, checker): except: self._shutdown(False) raise + + @staticmethod + def _pickServerKeyExchangeSig(settings, clientHello): + """Pick a hash that matches most closely the supported ones""" + hashAndAlgsExt = clientHello.getExtension(\ + ExtensionType.signature_algorithms) + + if hashAndAlgsExt is None or hashAndAlgsExt.sigalgs is None: + # RFC 5246 states that if there are no hashes advertised, + # sha1 should be picked + return "sha1" + + rsaHashes = [alg[0] for alg in hashAndAlgsExt.sigalgs + if alg[1] == SignatureAlgorithm.rsa] + for hashName in settings.rsaSigHashes: + hashID = getattr(HashAlgorithm, hashName) + if hashID in rsaHashes: + return hashName + + # if none match, default to sha1 + return "sha1" + + @staticmethod + def _sigHashesToList(settings): + """Convert list of valid signature hashes to array of tuples""" + sigAlgs = [] + for hashName in settings.rsaSigHashes: + sigAlgs.append((getattr(HashAlgorithm, hashName), + SignatureAlgorithm.rsa)) + return sigAlgs diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index 253148fe..35d70a0b 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -189,3 +189,15 @@ def test_invalid_signature_algorithm(self): hs.rsaSigHashes += ['md2'] with self.assertRaises(ValueError): hs.validate() + + def test_no_signature_hashes_set_with_TLS1_2(self): + hs = HandshakeSettings() + hs.rsaSigHashes = [] + with self.assertRaises(ValueError): + hs.validate() + + def test_no_signature_hashes_set_with_TLS1_1(self): + hs = HandshakeSettings() + hs.rsaSigHashes = [] + hs.maxVersion = (3, 2) + self.assertIsNotNone(hs.validate()) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 8a238670..ae0dc0dd 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1607,7 +1607,7 @@ def test_hash_with_invalid_ciphersuite(self): with self.assertRaises(AssertionError): ske.hash(bytearray(32), bytearray(32)) - def test_hash_with_default_hash_algorithm_for_TLS_v1_2(self): + def test_hash_with_sha1_hash_algorithm_for_TLS_v1_2(self): ske = ServerKeyExchange( CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, (3, 3)) @@ -1616,6 +1616,7 @@ def test_hash_with_default_hash_algorithm_for_TLS_v1_2(self): srp_g=2, srp_s=bytearray(3), srp_B=4) + ske.hashAlg = HashAlgorithm.sha1 hash1 = ske.hash(bytearray(32), bytearray(32)) @@ -1623,6 +1624,16 @@ def test_hash_with_default_hash_algorithm_for_TLS_v1_2(self): b'\x8e?YW\xcd\xad\xc6\x83\x91\x1d.fe,\x17y=\xc4T\x89' )) + def test_hash_with_invalid_hash_algorithm(self): + ske = ServerKeyExchange( + CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + (3, 3)) + + ske.createDH(1, 2, 3) + ske.hashAlg = 300 + + with self.assertRaises(AssertionError): + ske.hash(bytearray(32), bytearray(32)) class TestCertificateRequest(unittest.TestCase): def test___init__(self): diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index b119fc68..26d96da8 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -12,51 +12,496 @@ from tlslite.handshakesettings import HandshakeSettings from tlslite.recordlayer import RecordLayer from tlslite.messages import ServerHello, Certificate, ServerHelloDone, \ - ClientHello -from tlslite.constants import CipherSuite, CertificateType, AlertDescription -from tlslite.tlsconnection import TLSConnection -from tlslite.errors import TLSLocalAlert + ClientHello, ServerKeyExchange, CertificateRequest, ClientKeyExchange +from tlslite.constants import CipherSuite, CertificateType, AlertDescription, \ + HashAlgorithm, SignatureAlgorithm +from tlslite.tlsconnection import TLSConnection, KeyExchange, RSAKeyExchange, \ + DHE_RSAKeyExchange +from tlslite.errors import TLSLocalAlert, TLSIllegalParameterException, \ + TLSDecryptionFailed from tlslite.x509 import X509 from tlslite.x509certchain import X509CertChain from tlslite.utils.keyfactory import parsePEMKey +from tlslite.utils.codec import Parser +from tlslite.utils.cryptomath import bytesToNumber, getRandomBytes, powMod, \ + numberToByteArray +from tlslite.handshakehashes import HandshakeHashes from unit_tests.mocksock import MockSocket +srv_raw_key = str( + "-----BEGIN RSA PRIVATE KEY-----\n"\ + "MIICXQIBAAKBgQDRCQR5qRLJX8sy1N4BF1G1fml1vNW5S6o4h3PeWDtg7JEn+jIt\n"\ + "M/NZekrGv/+3gU9C9ixImJU6U+Tz3kU27qw0X+4lDJAZ8VZgqQTp/MWJ9Dqz2Syy\n"\ + "yQWUvUNUj90P9mfuyDO5rY/VLIskdBNOzUy0xvXvT99fYQE+QPP7aRgo3QIDAQAB\n"\ + "AoGAVSLbE8HsyN+fHwDbuo4I1Wa7BRz33xQWLBfe9TvyUzOGm0WnkgmKn3LTacdh\n"\ + "GxgrdBZXSun6PVtV8I0im5DxyVaNdi33sp+PIkZU386f1VUqcnYnmgsnsUQEBJQu\n"\ + "fUZmgNM+bfR+Rfli4Mew8lQ0sorZ+d2/5fsM0g80Qhi5M3ECQQDvXeCyrcy0u/HZ\n"\ + "FNjIloyXaAIvavZ6Lc6gfznCSfHc5YwplOY7dIWp8FRRJcyXkA370l5dJ0EXj5Gx\n"\ + "udV9QQ43AkEA34+RxjRk4DT7Zo+tbM/Fkoi7jh1/0hFkU5NDHweJeH/mJseiHtsH\n"\ + "KOcPGtEGBBqT2KNPWVz4Fj19LiUmmjWXiwJBAIBs49O5/+ywMdAAqVblv0S0nweF\n"\ + "4fwne4cM+5ZMSiH0XsEojGY13EkTEon/N8fRmE8VzV85YmkbtFWgmPR85P0CQQCs\n"\ + "elWbN10EZZv3+q1wH7RsYzVgZX3yEhz3JcxJKkVzRCnKjYaUi6MweWN76vvbOq4K\n"\ + "G6Tiawm0Duh/K4ZmvyYVAkBppE5RRQqXiv1KF9bArcAJHvLm0vnHPpf1yIQr5bW6\n"\ + "njBuL4qcxlaKJVGRXT7yFtj2fj0gv3914jY2suWqp8XJ\n"\ + "-----END RSA PRIVATE KEY-----\n"\ + ) + +srv_raw_certificate = str( + "-----BEGIN CERTIFICATE-----\n"\ + "MIIB9jCCAV+gAwIBAgIJAMyn9DpsTG55MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\n"\ + "BAMMCWxvY2FsaG9zdDAeFw0xNTAxMjExNDQzMDFaFw0xNTAyMjAxNDQzMDFaMBQx\n"\ + "EjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n"\ + "0QkEeakSyV/LMtTeARdRtX5pdbzVuUuqOIdz3lg7YOyRJ/oyLTPzWXpKxr//t4FP\n"\ + "QvYsSJiVOlPk895FNu6sNF/uJQyQGfFWYKkE6fzFifQ6s9kssskFlL1DVI/dD/Zn\n"\ + "7sgzua2P1SyLJHQTTs1MtMb170/fX2EBPkDz+2kYKN0CAwEAAaNQME4wHQYDVR0O\n"\ + "BBYEFJtvXbRmxRFXYVMOPH/29pXCpGmLMB8GA1UdIwQYMBaAFJtvXbRmxRFXYVMO\n"\ + "PH/29pXCpGmLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAkOgC7LP/\n"\ + "Rd6uJXY28HlD2K+/hMh1C3SRT855ggiCMiwstTHACGgNM+AZNqt6k8nSfXc6k1gw\n"\ + "5a7SGjzkWzMaZC3ChBeCzt/vIAGlMyXeqTRhjTCdc/ygRv3NPrhUKKsxUYyXRk5v\n"\ + "g/g6MwxzXfQP3IyFu3a9Jia/P89Z1rQCNRY=\n"\ + "-----END CERTIFICATE-----\n"\ + ) + + +class TestKeyExchange(unittest.TestCase): + + expected_sha1_SKE = bytearray( + b"\x0c\x00\x00\x8d\x00\x01\x05\x00\x01\x02\x00\x01\x03" + b"\x02\x01" + b"\x00\x80" + b"\xb4\xe0t\xa3\x13\x8e\xc8z\'|>\x8c\x1d\x9e\x00\x8c\x1c\x18\xd7" + b"#a\xe5\x15JH\xd5\xde\x1f\x12\xcej\x02k,4\x00V5\x04\xb3}\x92\xfc" + b"\xbd@\x0c\x03\x06\x02J\xb8*\xafR2\x10\xd6\x9a\xa9\n\x8e\xe8\xb3" + b"Y\xaf\tm\x0cZ\xbdzL\xdf:/\x91^c~\xfc\xf4_\xf3\xfdv\x00\xc1d\x97" + b"\x95\xf4A\xd1\x9e&J@\xect\xc2\xe7\xff\xfc\xdf/d\xbd\x1c\xbc\xa1" + b"f\x14\x92\x06c\xb853\xaf\xf27\xda\xd1\xf9\x97\xea\xec\x90") + + expected_tls1_1_SKE = bytearray( + b'\x0c\x00\x00\x8b\x00\x01\x05\x00\x01\x02\x00\x01\x03' + b'\x00\x80' + b'=\xdf\xfaW+\x81!Q\xc7\xbf\x11\xeeQ\x88\xb2[\xe6n\xd1\x1f\x86\xa8' + b'\xe5\xac\\\xae0\x0fg:tA\x1b*1?$\xd6;XQ\xac\xfdw\x85\xae\xdaOd' + b'\xc8\xb0X_\xae\x80\x87\x11\xb1\x08\x1c3!\xb5\xe6\xcf\x11\xbcV' + b'\x8f\n\x7f\xe7\xfa\x9a\xed!\xf0\xccF\xdf\x9c<\xe7)=\x9d\xde\x0f' + b'\n3\x9d5\x14\x05\x06nA\xa0\x19\xd5\xaa\x9d\xd1\x16\xb3\xb9\xae' + b'\xd1\xe4\xc04\xc1h\xc3\xf5/\xb2\xf6P\r\x1b"\xe9\xc9\x84&\xe1Z') + + def test___init__(self): + keyExchange = KeyExchange(0, None, None, None) + + self.assertIsNotNone(keyExchange) + + def test_signServerKeyExchange_with_sha1_in_TLS1_2(self): + srv_private_key = parsePEMKey(srv_raw_key, private=True) + client_hello = ClientHello() + cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + server_hello = ServerHello().create((3, 3), + bytearray(32), + bytearray(0), + cipher_suite) + keyExchange = KeyExchange(cipher_suite, + client_hello, + server_hello, + srv_private_key) + + server_key_exchange = ServerKeyExchange(cipher_suite, (3, 3))\ + .createDH(5, 2, 3) + + keyExchange.signServerKeyExchange(server_key_exchange, 'sha1') + + self.assertEqual(server_key_exchange.write(), self.expected_sha1_SKE) + + def test_signServerKeyExchange_in_TLS1_1(self): + srv_private_key = parsePEMKey(srv_raw_key, private=True) + client_hello = ClientHello() + cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + server_hello = ServerHello().create((3, 2), + bytearray(32), + bytearray(0), + cipher_suite) + keyExchange = KeyExchange(cipher_suite, + client_hello, + server_hello, + srv_private_key) + server_key_exchange = ServerKeyExchange(cipher_suite, (3, 2))\ + .createDH(5, 2, 3) + + keyExchange.signServerKeyExchange(server_key_exchange) + + self.assertEqual(server_key_exchange.write(), self.expected_tls1_1_SKE) + +class TestKeyExchangeVerifyServerKeyExchange(TestKeyExchange): + def setUp(self): + self.srv_cert_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = self.srv_cert_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + self.server_key_exchange = ServerKeyExchange(self.cipher_suite, (3, 3))\ + .parse(Parser(self.expected_sha1_SKE[1:])) + self.ske_tls1_1 = ServerKeyExchange(self.cipher_suite, (3, 2))\ + .parse(Parser(self.expected_tls1_1_SKE[1:])) + + self.client_hello = ClientHello() + + def test_verifyServerKeyExchange(self): + KeyExchange.verifyServerKeyExchange(self.server_key_exchange, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + def test_verifyServerKeyExchange_with_invalid_hash(self): + with self.assertRaises(TLSIllegalParameterException): + KeyExchange.verifyServerKeyExchange(self.server_key_exchange, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + [(HashAlgorithm.sha256, + SignatureAlgorithm.rsa)]) + + def test_verifyServerKeyExchange_with_unknown_hash(self): + self.server_key_exchange.hashAlg = 244 + with self.assertRaises(TLSIllegalParameterException): + KeyExchange.verifyServerKeyExchange(self.server_key_exchange, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + [(244, + SignatureAlgorithm.rsa)]) + + def test_verifyServerKeyExchange_with_empty_signature(self): + self.server_key_exchange.signature = bytearray(0) + + with self.assertRaises(TLSIllegalParameterException): + KeyExchange.verifyServerKeyExchange(self.server_key_exchange, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + def test_verifyServerKeyExchange_with_damaged_signature(self): + self.server_key_exchange.signature[-1] ^= 0x01 + + with self.assertRaises(TLSDecryptionFailed): + KeyExchange.verifyServerKeyExchange(self.server_key_exchange, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + def test_verifyServerKeyExchange_in_TLS1_1(self): + KeyExchange.verifyServerKeyExchange(self.ske_tls1_1, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + None) + +class TestCalcVerifyBytes(unittest.TestCase): + def setUp(self): + self.handshake_hashes = HandshakeHashes() + self.handshake_hashes.update(bytearray(b'\xab'*32)) + + def test_with_TLS1_2(self): + vrfy = KeyExchange.calcVerifyBytes((3, 3), + self.handshake_hashes, + (HashAlgorithm.sha1, + SignatureAlgorithm.rsa), + None, + None, + None) + self.assertEqual(vrfy, bytearray( + # PKCS#1 prefix + b'0!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14' + # SHA1 hash + b'L3\x81\xad\x1b\xc2\x14\xc0\x8e\xba\xe4\xb8\xa2\x9d(6V1\xfb\xb0')) + + def test_with_TLS1_1(self): + vrfy = KeyExchange.calcVerifyBytes((3, 2), + self.handshake_hashes, + None, None, None, None) + + self.assertEqual(vrfy, bytearray( + # MD5 hash + b'\xe9\x9f\xb4\xd24\xe9\xf41S\xe6?\xa5\xfe\xad\x16\x14' + # SHA1 hash + b'L3\x81\xad\x1b\xc2\x14\xc0\x8e\xba\xe4\xb8\xa2\x9d(6V1\xfb\xb0' + )) + + def test_with_SSL3(self): + vrfy = KeyExchange.calcVerifyBytes((3, 0), + self.handshake_hashes, + None, + bytearray(b'\x04'*48), + bytearray(b'\xaa'*32), + bytearray(b'\xbb'*32)) + + self.assertEqual(vrfy, bytearray( + b'r_\x06\xd2(\\}v\x87\xfc\xf5\xa2h\xd6S\xd8' + b'\xed=\x9b\xe3\xd9_%qe\xa3k\xf5\x85\x0e?\x9fr\xfaML' + )) + +class TestMakeCertificateVerify(unittest.TestCase): + def setUp(self): + cert_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.clnt_pub_key = cert_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + self.clnt_private_key = parsePEMKey(srv_raw_key, private=True) + self.handshake_hashes = HandshakeHashes() + self.handshake_hashes.update(bytearray(b'\xab'*32)) + + def test_with_TLS1_2(self): + certificate_request = CertificateRequest((3, 3)) + certificate_request.create([CertificateType.x509], + [], + [(HashAlgorithm.sha256, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + certVerify = KeyExchange.makeCertificateVerify((3, 3), + self.handshake_hashes, + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha512, + SignatureAlgorithm.rsa)], + self.clnt_private_key, + certificate_request, + None, None, None) + + self.assertIsNotNone(certVerify) + self.assertEqual(certVerify.version, (3, 3)) + self.assertEqual(certVerify.signatureAlgorithm, (HashAlgorithm.sha1, + SignatureAlgorithm.rsa)) + self.assertEqual(certVerify.signature, bytearray( + b'.\x03\xa2\xf0\xa0\xfb\xbeUs\xdb\x9b\xea\xcc(\xa6:l\x84\x8e\x13' + b'\xa1\xaa\xdb1P\xe9\x06\x876\xbe+\xe92\x89\xaa\xa5EU\x07\x9a\xde' + b'\xd37\xafGCR\xdam\xa2v\xde\xceeFI\x80:ZtL\x96\xafZ\xe2\xe2\xce/' + b'\x9f\x82\xfe\xdb*\x94\xa8\xbd\xd9Hl\xdc\xc8\xbf\x9b=o\xda\x06' + b'\xfa\x9e\xbfB+05\xc6\xda\xdf\x05\xf2m[\x18\x11\xaf\x184\x12\x9d' + b'\xb4:\x9b\xc1U\x1c\xba\xa3\x05\xceOn\x0fY\xcaK*\x0b\x04\xa5' + )) + + def test_with_TLS1_2_and_no_overlap(self): + certificate_request = CertificateRequest((3, 3)) + certificate_request.create([CertificateType.x509], + [], + [(HashAlgorithm.sha256, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha224, + SignatureAlgorithm.rsa)]) + + certVerify = KeyExchange.makeCertificateVerify((3, 3), + self.handshake_hashes, + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha512, + SignatureAlgorithm.rsa)], + self.clnt_private_key, + certificate_request, + None, None, None) + + self.assertIsNotNone(certVerify) + self.assertEqual(certVerify.version, (3, 3)) + # when there's no overlap, we select the most wanted from our side + self.assertEqual(certVerify.signatureAlgorithm, (HashAlgorithm.sha1, + SignatureAlgorithm.rsa)) + self.assertEqual(certVerify.signature, bytearray( + b'.\x03\xa2\xf0\xa0\xfb\xbeUs\xdb\x9b\xea\xcc(\xa6:l\x84\x8e\x13' + b'\xa1\xaa\xdb1P\xe9\x06\x876\xbe+\xe92\x89\xaa\xa5EU\x07\x9a\xde' + b'\xd37\xafGCR\xdam\xa2v\xde\xceeFI\x80:ZtL\x96\xafZ\xe2\xe2\xce/' + b'\x9f\x82\xfe\xdb*\x94\xa8\xbd\xd9Hl\xdc\xc8\xbf\x9b=o\xda\x06' + b'\xfa\x9e\xbfB+05\xc6\xda\xdf\x05\xf2m[\x18\x11\xaf\x184\x12\x9d' + b'\xb4:\x9b\xc1U\x1c\xba\xa3\x05\xceOn\x0fY\xcaK*\x0b\x04\xa5' + )) + + def test_with_TLS1_1(self): + certificate_request = CertificateRequest((3, 2)) + certificate_request.create([CertificateType.x509], + [], + None) + + certVerify = KeyExchange.makeCertificateVerify((3, 2), + self.handshake_hashes, + None, + self.clnt_private_key, + certificate_request, + None, None, None) + + self.assertIsNotNone(certVerify) + self.assertEqual(certVerify.version, (3, 2)) + self.assertIsNone(certVerify.signatureAlgorithm) + self.assertEqual(certVerify.signature, bytearray( + b'=X\x14\xf3\r6\x0b\x84\xde&J\x15\xa02M\xc8\xf1?\xa0\x10U\x1e\x0b' + b'\x95^\xa19\x14\xf5\xf1$\xe3U[\xb4/\xe7AY(\xee]\xff\x97H\xb8\xa9' + b'\x8b\x96n\xa6\xf5\x0f\xffd\r\x08/Hs6`wi8\xc4\x02\xa4}a\xcbS\x99' + b'\x01;\x0e\x88oj\x9a\x02\x98Y\xb5\x00$f@>\xd8\x0cS\x95\xa8\x9e' + b'\x14uU\\Z\xd0.\xe7\x01_y\x1d\xea\xad\x1b\xf8c\xa6\xc9@\xc6\x90' + b'\x19~&\xd9\xaa\xc2\t,s\xde\xb1' + )) + +class TestRSAKeyExchange(unittest.TestCase): + def setUp(self): + self.srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = srv_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA + self.client_hello = ClientHello().create((3, 3), + bytearray(32), + bytearray(0), + []) + self.server_hello = ServerHello().create((3, 2), + bytearray(32), + bytearray(0), + self.cipher_suite) + + self.keyExchange = RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key) + + def test_RSA_key_exchange(self): + + self.assertIsNone(self.keyExchange.makeServerKeyExchange()) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 3 + clientKeyExchange = ClientKeyExchange(self.cipher_suite, + (3, 2)) + clientKeyExchange.createRSA(self.srv_pub_key.encrypt(premaster_secret)) + + dec_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 3 + self.assertEqual(dec_premaster, premaster_secret) + + def test_RSA_with_invalid_encryption(self): + + self.assertIsNone(self.keyExchange.makeServerKeyExchange()) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 3 + clientKeyExchange = ClientKeyExchange(self.cipher_suite, + (3, 2)) + enc_premaster = self.srv_pub_key.encrypt(premaster_secret) + enc_premaster[-1] ^= 0x01 + clientKeyExchange.createRSA(enc_premaster) + + dec_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 3 + self.assertNotEqual(dec_premaster, premaster_secret) + + def test_RSA_with_wrong_size_premaster(self): + + self.assertIsNone(self.keyExchange.makeServerKeyExchange()) + + premaster_secret = bytearray(b'\xf0'*47) + premaster_secret[0] = 3 + premaster_secret[1] = 3 + clientKeyExchange = ClientKeyExchange(self.cipher_suite, + (3, 2)) + enc_premaster = self.srv_pub_key.encrypt(premaster_secret) + clientKeyExchange.createRSA(enc_premaster) + + dec_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + premaster_secret = bytearray(b'\xf0'*47) + premaster_secret[0] = 3 + premaster_secret[1] = 3 + self.assertNotEqual(dec_premaster, premaster_secret) + + def test_RSA_with_wrong_version_in_IE(self): + # Internet Explorer sends the version from Server Hello not Client Hello + + self.assertIsNone(self.keyExchange.makeServerKeyExchange()) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 2 + clientKeyExchange = ClientKeyExchange(self.cipher_suite, + (3, 2)) + enc_premaster = self.srv_pub_key.encrypt(premaster_secret) + clientKeyExchange.createRSA(enc_premaster) + + dec_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 2 + self.assertEqual(dec_premaster, premaster_secret) + + def test_RSA_with_wrong_version(self): + + self.assertIsNone(self.keyExchange.makeServerKeyExchange()) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 1 + clientKeyExchange = ClientKeyExchange(self.cipher_suite, + (3, 2)) + clientKeyExchange.createRSA(self.srv_pub_key.encrypt(premaster_secret)) + + dec_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 1 + self.assertNotEqual(dec_premaster, premaster_secret) + +class TestDHE_RSAKeyExchange(unittest.TestCase): + def setUp(self): + self.srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = srv_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + self.client_hello = ClientHello().create((3, 3), + bytearray(32), + bytearray(0), + []) + self.server_hello = ServerHello().create((3, 3), + bytearray(32), + bytearray(0), + self.cipher_suite) + + self.keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key) + + def test_DHE_RSA_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + cln_X = bytesToNumber(getRandomBytes(32)) + cln_Yc = powMod(srv_key_ex.dh_g, + cln_X, + srv_key_ex.dh_p) + cln_secret = numberToByteArray(powMod(srv_key_ex.dh_Ys, + cln_X, + srv_key_ex.dh_p)) + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)) + cln_key_ex.createDH(cln_Yc) + + srv_secret = self.keyExchange.processClientKeyExchange(cln_key_ex) + + self.assertEqual(cln_secret, srv_secret) + class TestTLSConnection(unittest.TestCase): - srv_raw_certificate = str( - "-----BEGIN CERTIFICATE-----\n"\ - "MIIB9jCCAV+gAwIBAgIJAMyn9DpsTG55MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\n"\ - "BAMMCWxvY2FsaG9zdDAeFw0xNTAxMjExNDQzMDFaFw0xNTAyMjAxNDQzMDFaMBQx\n"\ - "EjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n"\ - "0QkEeakSyV/LMtTeARdRtX5pdbzVuUuqOIdz3lg7YOyRJ/oyLTPzWXpKxr//t4FP\n"\ - "QvYsSJiVOlPk895FNu6sNF/uJQyQGfFWYKkE6fzFifQ6s9kssskFlL1DVI/dD/Zn\n"\ - "7sgzua2P1SyLJHQTTs1MtMb170/fX2EBPkDz+2kYKN0CAwEAAaNQME4wHQYDVR0O\n"\ - "BBYEFJtvXbRmxRFXYVMOPH/29pXCpGmLMB8GA1UdIwQYMBaAFJtvXbRmxRFXYVMO\n"\ - "PH/29pXCpGmLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAkOgC7LP/\n"\ - "Rd6uJXY28HlD2K+/hMh1C3SRT855ggiCMiwstTHACGgNM+AZNqt6k8nSfXc6k1gw\n"\ - "5a7SGjzkWzMaZC3ChBeCzt/vIAGlMyXeqTRhjTCdc/ygRv3NPrhUKKsxUYyXRk5v\n"\ - "g/g6MwxzXfQP3IyFu3a9Jia/P89Z1rQCNRY=\n"\ - "-----END CERTIFICATE-----\n"\ - ) - - srv_raw_key = str( - "-----BEGIN RSA PRIVATE KEY-----\n"\ - "MIICXQIBAAKBgQDRCQR5qRLJX8sy1N4BF1G1fml1vNW5S6o4h3PeWDtg7JEn+jIt\n"\ - "M/NZekrGv/+3gU9C9ixImJU6U+Tz3kU27qw0X+4lDJAZ8VZgqQTp/MWJ9Dqz2Syy\n"\ - "yQWUvUNUj90P9mfuyDO5rY/VLIskdBNOzUy0xvXvT99fYQE+QPP7aRgo3QIDAQAB\n"\ - "AoGAVSLbE8HsyN+fHwDbuo4I1Wa7BRz33xQWLBfe9TvyUzOGm0WnkgmKn3LTacdh\n"\ - "GxgrdBZXSun6PVtV8I0im5DxyVaNdi33sp+PIkZU386f1VUqcnYnmgsnsUQEBJQu\n"\ - "fUZmgNM+bfR+Rfli4Mew8lQ0sorZ+d2/5fsM0g80Qhi5M3ECQQDvXeCyrcy0u/HZ\n"\ - "FNjIloyXaAIvavZ6Lc6gfznCSfHc5YwplOY7dIWp8FRRJcyXkA370l5dJ0EXj5Gx\n"\ - "udV9QQ43AkEA34+RxjRk4DT7Zo+tbM/Fkoi7jh1/0hFkU5NDHweJeH/mJseiHtsH\n"\ - "KOcPGtEGBBqT2KNPWVz4Fj19LiUmmjWXiwJBAIBs49O5/+ywMdAAqVblv0S0nweF\n"\ - "4fwne4cM+5ZMSiH0XsEojGY13EkTEon/N8fRmE8VzV85YmkbtFWgmPR85P0CQQCs\n"\ - "elWbN10EZZv3+q1wH7RsYzVgZX3yEhz3JcxJKkVzRCnKjYaUi6MweWN76vvbOq4K\n"\ - "G6Tiawm0Duh/K4ZmvyYVAkBppE5RRQqXiv1KF9bArcAJHvLm0vnHPpf1yIQr5bW6\n"\ - "njBuL4qcxlaKJVGRXT7yFtj2fj0gv3914jY2suWqp8XJ\n"\ - "-----END RSA PRIVATE KEY-----\n"\ - ) def test_client_with_server_responing_with_SHA256_on_TLSv1_1(self): # socket to generate the faux response @@ -118,8 +563,8 @@ def test_server_with_client_proposing_SHA256_on_TLSv1_1(self): conn = TLSConnection(sock) - srv_private_key = parsePEMKey(self.srv_raw_key, private=True) - srv_cert_chain = X509CertChain([X509().parse(self.srv_raw_certificate)]) + srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_cert_chain = X509CertChain([X509().parse(srv_raw_certificate)]) with self.assertRaises(TLSLocalAlert) as err: conn.handshakeServer(certChain=srv_cert_chain, privateKey=srv_private_key) From ceec3b337b5a9a6e7b52014490a8776beb39e894 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 14:43:42 +0100 Subject: [PATCH 203/574] move client RSA key exchange to KeyExchange --- tlslite/tlsconnection.py | 65 +++++++++++++++++------- unit_tests/test_tlslite_tlsconnection.py | 38 ++++++++++++++ 2 files changed, 85 insertions(+), 18 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 72abf7b4..717b598a 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -54,6 +54,15 @@ def makeServerKeyExchange(self, sigHash=None): """ raise NotImplementedError() + def makeClientKeyExchange(self): + """ + Create a ClientKeyExchange object + + Returns a ClientKeyExchange for the second flight from client in the + handshake. + """ + raise NotImplementedError() + def processClientKeyExchange(self, clientKeyExchange): """ Process ClientKeyExchange and return premaster secret @@ -63,6 +72,11 @@ def processClientKeyExchange(self, clientKeyExchange): """ raise NotImplementedError() + def processServerKeyExchange(self, srvPublicKey, + serverKeyExchange): + """Process the server KEX and return premaster secret""" + raise NotImplementedError() + def signServerKeyExchange(self, serverKeyExchange, sigHash=None): """ Sign a server key best matching supported algorithms @@ -168,13 +182,17 @@ def makeCertificateVerify(version, handshakeHashes, validSigAlgs, return certificateVerify class RSAKeyExchange(KeyExchange): - """ Handling of RSA key exchange NOT stable API, do NOT use """ + def __init__(self, cipherSuite, clientHello, serverHello, privateKey): + super(RSAKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello, privateKey) + self.encPremasterSecret = None + def makeServerKeyExchange(self, sigHash=None): """Don't create a server key exchange for RSA key exchange""" return None @@ -199,6 +217,25 @@ def processClientKeyExchange(self, clientKeyExchange): premasterSecret = randomPreMasterSecret return premasterSecret + def processServerKeyExchange(self, srvPublicKey, + serverKeyExchange): + """Generate premaster secret for server""" + del serverKeyExchange # not present in RSA key exchange + premasterSecret = getRandomBytes(48) + premasterSecret[0] = self.clientHello.client_version[0] + premasterSecret[1] = self.clientHello.client_version[1] + + self.encPremasterSecret = srvPublicKey.encrypt(premasterSecret) + return premasterSecret + + def makeClientKeyExchange(self): + clientKeyExchange = ClientKeyExchange(self.cipherSuite, + self.serverHello.server_version) + clientKeyExchange.createRSA(self.encPremasterSecret) + return clientKeyExchange + +# the DHE_RSA part comes from IETF ciphersuite names, we want to keep it +#pylint: disable = invalid-name class DHE_RSAKeyExchange(KeyExchange): """ Handling of ephemeral Diffe-Hellman Key exchange @@ -209,6 +246,7 @@ class DHE_RSAKeyExchange(KeyExchange): def __init__(self, cipherSuite, clientHello, serverHello, privateKey): super(DHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, serverHello, privateKey) +#pylint: enable = invalid-name self.dh_Xs = None # 2048-bit MODP Group (RFC 3526, Section 3) @@ -692,11 +730,14 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, #and also produces a CertificateVerify message that signs the #ClientKeyExchange. else: + keyExchange = RSAKeyExchange(cipherSuite, clientHello, + serverHello, None) for result in self._clientRSAKeyExchange(settings, cipherSuite, clientCertChain, privateKey, serverHello.certificate_type, clientHello.random, serverHello.random, - serverHello.tackExt): + serverHello.tackExt, + keyExchange): if result in (0,1): yield result else: break (premasterSecret, serverCertChain, clientCertChain, @@ -1027,7 +1068,7 @@ def _clientRSAKeyExchange(self, settings, cipherSuite, clientCertChain, privateKey, certificateType, clientRandom, serverRandom, - tackExt): + tackExt, keyExchange): #Get Certificate[, CertificateRequest], ServerHelloDone for result in self._getMsg(ContentType.handshake, @@ -1064,18 +1105,8 @@ def _clientRSAKeyExchange(self, settings, cipherSuite, else: break publicKey, serverCertChain, tackExt = result - #Calculate premaster secret - premasterSecret = getRandomBytes(48) - premasterSecret[0] = settings.maxVersion[0] - premasterSecret[1] = settings.maxVersion[1] - - if self.fault == Fault.badPremasterPadding: - premasterSecret[0] = 5 - if self.fault == Fault.shortPremasterSecret: - premasterSecret = premasterSecret[:-1] - - #Encrypt premaster secret to server's public key - encryptedPreMasterSecret = publicKey.encrypt(premasterSecret) + premasterSecret = keyExchange.processServerKeyExchange(publicKey, + None) #If client authentication was requested, send Certificate #message, either with certificates or empty @@ -1106,9 +1137,7 @@ def _clientRSAKeyExchange(self, settings, cipherSuite, clientCertChain = None #Send ClientKeyExchange - clientKeyExchange = ClientKeyExchange(cipherSuite, - self.version) - clientKeyExchange.createRSA(encryptedPreMasterSecret) + clientKeyExchange = keyExchange.makeClientKeyExchange() for result in self._sendMsg(clientKeyExchange): yield result diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index 26d96da8..d8b7275a 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -92,6 +92,26 @@ def test___init__(self): self.assertIsNotNone(keyExchange) + def test_makeServerKeyExchange(self): + keyExchange = KeyExchange(0, None, None, None) + with self.assertRaises(NotImplementedError): + keyExchange.makeServerKeyExchange() + + def test_makeClientKeyExchange(self): + keyExchange = KeyExchange(0, None, None, None) + with self.assertRaises(NotImplementedError): + keyExchange.makeClientKeyExchange() + + def test_processClientKeyExchange(self): + keyExchange = KeyExchange(0, None, None, None) + with self.assertRaises(NotImplementedError): + keyExchange.processClientKeyExchange(None) + + def test_processServerKeyExchange(self): + keyExchange = KeyExchange(0, None, None, None) + with self.assertRaises(NotImplementedError): + keyExchange.processServerKeyExchange(None, None) + def test_signServerKeyExchange_with_sha1_in_TLS1_2(self): srv_private_key = parsePEMKey(srv_raw_key, private=True) client_hello = ClientHello() @@ -381,6 +401,24 @@ def test_RSA_key_exchange(self): premaster_secret[1] = 3 self.assertEqual(dec_premaster, premaster_secret) + def test_RSA_key_exchange_with_client(self): + self.assertIsNone(self.keyExchange.makeServerKeyExchange()) + + client_keyExchange = RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None) + + client_premaster = client_keyExchange.processServerKeyExchange(\ + self.srv_pub_key, + None) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) + def test_RSA_with_invalid_encryption(self): self.assertIsNone(self.keyExchange.makeServerKeyExchange()) From 097269e8c038630a39f333954a37714fc55b9920 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 14:55:02 +0100 Subject: [PATCH 204/574] move client DHE key exchange to KeyExchange --- tlslite/errors.py | 5 ++ tlslite/tlsconnection.py | 62 ++++++++++++++++-------- unit_tests/test_tlslite_tlsconnection.py | 32 +++++++++++- 3 files changed, 77 insertions(+), 22 deletions(-) diff --git a/tlslite/errors.py b/tlslite/errors.py index 78bba126..26f31d2d 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -219,3 +219,8 @@ class TLSBadRecordMAC(TLSProtocolException): """Bad MAC (or padding in case of mac-then-encrypt)""" pass + +class TLSInsufficientSecurity(TLSProtocolException): + """Parameters selected by user are too weak""" + + pass diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 717b598a..2e787918 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -248,6 +248,7 @@ def __init__(self, cipherSuite, clientHello, serverHello, privateKey): serverHello, privateKey) #pylint: enable = invalid-name self.dh_Xs = None + self.dh_Yc = None # 2048-bit MODP Group (RFC 3526, Section 3) dh_g, dh_p = goodGroupParameters[2] @@ -281,6 +282,28 @@ def processClientKeyExchange(self, clientKeyExchange): S = powMod(dh_Yc, self.dh_Xs, self.dh_p) return numberToByteArray(S) + def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): + """Process the server key exchange, return premaster secret""" + del srvPublicKey + dh_p = serverKeyExchange.dh_p + # TODO make the minimum changeable + if dh_p < 2**1023: + raise TLSInsufficientSecurity("DH prime too small") + dh_g = serverKeyExchange.dh_g + dh_Xc = bytesToNumber(getRandomBytes(32)) + dh_Ys = serverKeyExchange.dh_Ys + self.dh_Yc = powMod(dh_g, dh_Xc, dh_p) + + S = powMod(dh_Ys, dh_Xc, dh_p) + return numberToByteArray(S) + + def makeClientKeyExchange(self): + """Create client key share for the key exchange""" + clientKeyExchange = ClientKeyExchange(self.cipherSuite, + self.serverHello.server_version) + clientKeyExchange.createDH(self.dh_Yc) + return clientKeyExchange + class TLSConnection(TLSRecordLayer): """ This class wraps a socket and provides TLS handshaking and data @@ -713,11 +736,14 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, #If the server selected an anonymous ciphersuite, the client #finishes reading the post-ServerHello messages. elif cipherSuite in CipherSuite.dhAllSuites: + keyExchange = DHE_RSAKeyExchange(cipherSuite, clientHello, + serverHello, None) for result in self._clientDHEKeyExchange(settings, cipherSuite, clientCertChain, privateKey, serverHello.certificate_type, serverHello.tackExt, - clientHello.random, serverHello.random): + clientHello.random, serverHello.random, + keyExchange): if result in (0,1): yield result else: break (premasterSecret, serverCertChain, clientCertChain, @@ -1171,7 +1197,8 @@ def _clientRSAKeyExchange(self, settings, cipherSuite, def _clientDHEKeyExchange(self, settings, cipherSuite, clientCertChain, privateKey, certificateType, - tackExt, clientRandom, serverRandom): + tackExt, clientRandom, serverRandom, + keyExchange): #TODO: check if received messages match cipher suite # (abort if CertfificateRequest and ADH) @@ -1238,13 +1265,6 @@ def _clientDHEKeyExchange(self, settings, cipherSuite, for result in self._sendError(AlertDescription.decrypt_error): yield result - # TODO: make it changeable - if 2**1023 > serverKeyExchange.dh_p: - for result in self._sendError( - AlertDescription.insufficient_security, - "Server sent a DHE key exchange with very small prime"): - yield result - #Send Certificate if we were asked for it if certificateRequest: @@ -1283,22 +1303,22 @@ def _clientDHEKeyExchange(self, settings, cipherSuite, privateKey = None clientCertChain = None - #calculate Yc - dh_p = serverKeyExchange.dh_p - dh_g = serverKeyExchange.dh_g - dh_Xc = bytesToNumber(getRandomBytes(32)) - dh_Ys = serverKeyExchange.dh_Ys - dh_Yc = powMod(dh_g, dh_Xc, dh_p) + try: + ske = serverKeyExchange + premasterSecret = keyExchange.processServerKeyExchange(None, + ske) + except TLSInsufficientSecurity: + for result in self._sendError( + AlertDescription.insufficient_security, + "Server sent a DHE key exchange with very small prime"): + yield result + + clientKeyExchange = keyExchange.makeClientKeyExchange() #Send ClientKeyExchange - for result in self._sendMsg(\ - ClientKeyExchange(cipherSuite, self.version).createDH(dh_Yc)): + for result in self._sendMsg(clientKeyExchange): yield result - #Calculate premaster secret - S = powMod(dh_Ys, dh_Xc, dh_p) - premasterSecret = numberToByteArray(S) - #if client auth was requested and we have a private key, send a #CertificateVerify if certificateRequest and privateKey: diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index d8b7275a..2688bec5 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -18,7 +18,7 @@ from tlslite.tlsconnection import TLSConnection, KeyExchange, RSAKeyExchange, \ DHE_RSAKeyExchange from tlslite.errors import TLSLocalAlert, TLSIllegalParameterException, \ - TLSDecryptionFailed + TLSDecryptionFailed, TLSInsufficientSecurity from tlslite.x509 import X509 from tlslite.x509certchain import X509CertChain from tlslite.utils.keyfactory import parsePEMKey @@ -538,6 +538,36 @@ def test_DHE_RSA_key_exchange(self): self.assertEqual(cln_secret, srv_secret) + def test_DHE_RSA_key_exchange_with_client(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None) + client_premaster = client_keyExchange.processServerKeyExchange(\ + None, + srv_key_ex) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) + + def test_DHE_RSA_key_exchange_with_small_prime(self): + client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None) + + srv_key_ex = ServerKeyExchange(self.cipher_suite, + self.server_hello.server_version) + srv_key_ex.createDH(2**768, 2, 2**512-1) + + with self.assertRaises(TLSInsufficientSecurity): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) + class TestTLSConnection(unittest.TestCase): From c3163778f914334dc73d22f085e9dfba795193bf Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 15:32:33 +0100 Subject: [PATCH 205/574] make SRP use the KeyExchange class --- tlslite/errors.py | 6 + tlslite/tlsconnection.py | 224 +++++++++++++---------- unit_tests/test_tlslite_tlsconnection.py | 118 +++++++++++- 3 files changed, 245 insertions(+), 103 deletions(-) diff --git a/tlslite/errors.py b/tlslite/errors.py index 26f31d2d..473c43ac 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -224,3 +224,9 @@ class TLSInsufficientSecurity(TLSProtocolException): """Parameters selected by user are too weak""" pass + +class TLSUnknownPSKIdentity(TLSProtocolException): + """The PSK or SRP identity is unknown""" + + pass + diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 2e787918..a38e24ed 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -304,10 +304,59 @@ def makeClientKeyExchange(self): clientKeyExchange.createDH(self.dh_Yc) return clientKeyExchange +class SRPKeyExchange(KeyExchange): + """Helper class for conducting SRP key exchange""" + + def __init__(self, cipherSuite, clientHello, serverHello, privateKey, + verifierDB): + """Link Key Exchange options with verifierDB for SRP""" + super(SRPKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello, privateKey) + self.N = None + self.v = None + self.b = None + self.B = None + self.verifierDB = verifierDB + + def makeServerKeyExchange(self, sigHash=None): + """Create SRP version of Server Key Exchange""" + srpUsername = self.clientHello.srp_username.decode("utf-8") + #Get parameters from username + try: + entry = self.verifierDB[srpUsername] + except KeyError: + raise TLSUnknownPSKIdentity("Unknown identity") + (self.N, g, s, self.v) = entry + + #Calculate server's ephemeral DH values (b, B) + self.b = bytesToNumber(getRandomBytes(32)) + k = makeK(self.N, g) + self.B = (powMod(g, self.b, self.N) + (k * self.v)) % self.N + + #Create ServerKeyExchange, signing it if necessary + serverKeyExchange = ServerKeyExchange(self.cipherSuite, + self.serverHello.server_version) + serverKeyExchange.createSRP(self.N, g, s, self.B) + if self.cipherSuite in CipherSuite.srpCertSuites: + self.signServerKeyExchange(serverKeyExchange, sigHash) + return serverKeyExchange + + def processClientKeyExchange(self, clientKeyExchange): + """Calculate premaster secret from Client Key Exchange and sent SKE""" + A = clientKeyExchange.srp_A + if A % self.N == 0: + raise TLSIllegalParameterException("Invalid SRP A value") + + #Calculate u + u = makeU(self.N, A, self.B) + + #Calculate premaster secret + S = powMod((A * powMod(self.v, u, self.N)) % self.N, self.b, self.N) + return numberToByteArray(S) + class TLSConnection(TLSRecordLayer): """ - This class wraps a socket and provides TLS handshaking and data - transfer. + This class wraps a socket and provides TLS handshaking and data transfer. To use this class, create a new instance, passing a connected socket into the constructor. Then call some handshake function. @@ -724,13 +773,16 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, #reading the post-ServerHello messages, then derives a #premasterSecret and sends a corresponding ClientKeyExchange. if cipherSuite in CipherSuite.srpAllSuites: - for result in self._clientSRPKeyExchange(\ - settings, cipherSuite, serverHello.certificate_type, - srpUsername, password, - clientHello.random, serverHello.random, - serverHello.tackExt): - if result in (0,1): yield result - else: break + for result in self._clientSRPKeyExchange(settings, cipherSuite, + serverHello.\ + certificate_type, + srpUsername, password, + clientHello.random, + serverHello.random, + serverHello.tackExt): + if result in (0, 1): + yield result + else: break (premasterSecret, serverCertChain, tackExt) = result #If the server selected an anonymous ciphersuite, the client @@ -739,12 +791,16 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, keyExchange = DHE_RSAKeyExchange(cipherSuite, clientHello, serverHello, None) for result in self._clientDHEKeyExchange(settings, cipherSuite, - clientCertChain, privateKey, - serverHello.certificate_type, - serverHello.tackExt, - clientHello.random, serverHello.random, - keyExchange): - if result in (0,1): yield result + clientCertChain, + privateKey, + serverHello.\ + certificate_type, + serverHello.tackExt, + clientHello.random, + serverHello.random, + keyExchange): + if result in (0, 1): + yield result else: break (premasterSecret, serverCertChain, clientCertChain, tackExt) = result @@ -1031,34 +1087,29 @@ def _clientSRPKeyExchange(self, settings, cipherSuite, certificateType, #SRP+RSA suite serverCertChain = None if cipherSuite in CipherSuite.srpCertSuites: - #Hash ServerKeyExchange/ServerSRPParams - hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) - if self.version == (3, 3): - hashBytes = RSAKey.addPKCS1SHA1Prefix(hashBytes) - - #Extract signature bytes from ServerKeyExchange - sigBytes = serverKeyExchange.signature - if len(sigBytes) == 0: - for result in self._sendError(\ - AlertDescription.illegal_parameter, - "Server sent an SRP ServerKeyExchange "\ - "message without a signature"): - yield result - # Get server's public key from the Certificate message # Also validate the chain against the ServerHello's TACKext (if any) - # If none, and a TACK cert is present, return its TACKext + # If none, and a TACK cert is present, return its TACKext for result in self._clientGetKeyFromChain(serverCertificate, - settings, tackExt): + settings, tackExt): if result in (0,1): yield result else: break publicKey, serverCertChain, tackExt = result - #Verify signature - if not publicKey.verify(sigBytes, hashBytes): - for result in self._sendError(\ - AlertDescription.decrypt_error, - "Signature failed to verify"): + # Check signature on ServerKeyExchange message + validSigAlgs = self._sigHashesToList(settings) + try: + KeyExchange.verifyServerKeyExchange(serverKeyExchange, + publicKey, + clientRandom, + serverRandom, + validSigAlgs) + except TLSIllegalParameterException: + for result in self._sendError(AlertDescription.\ + illegal_parameter): + yield result + except TLSDecryptionFailed: + for result in self._sendError(AlertDescription.decrypt_error): yield result #Calculate client's ephemeral DH values (a, A) @@ -1170,28 +1221,19 @@ def _clientRSAKeyExchange(self, settings, cipherSuite, #If client authentication was requested and we have a #private key, send CertificateVerify if certificateRequest and privateKey: - signatureAlgorithm = None - if self.version == (3,0): - masterSecret = calcMasterSecret(self.version, - cipherSuite, - premasterSecret, - clientRandom, - serverRandom) - verifyBytes = self._handshake_hash.digestSSL(masterSecret, b"") - elif self.version in ((3,1), (3,2)): - verifyBytes = self._handshake_hash.digest() - elif self.version == (3,3): - # TODO: Signature algorithm negotiation not supported. - signatureAlgorithm = (HashAlgorithm.sha1, SignatureAlgorithm.rsa) - verifyBytes = self._handshake_hash.digest('sha1') - verifyBytes = RSAKey.addPKCS1SHA1Prefix(verifyBytes) - if self.fault == Fault.badVerifyMessage: - verifyBytes[0] = ((verifyBytes[0]+1) % 256) - signedBytes = privateKey.sign(verifyBytes) - certificateVerify = CertificateVerify(self.version) - certificateVerify.create(signedBytes, signatureAlgorithm) + validSigAlgs = self._sigHashesToList(settings) + certificateVerify = KeyExchange.makeCertificateVerify(\ + self.version, + self._handshake_hash, + validSigAlgs, + privateKey, + certificateRequest, + premasterSecret, + clientRandom, + serverRandom) for result in self._sendMsg(certificateVerify): yield result + yield (premasterSecret, serverCertChain, clientCertChain, tackExt) def _clientDHEKeyExchange(self, settings, cipherSuite, @@ -1603,10 +1645,12 @@ def _handshakeServerAsyncHelper(self, verifierDB, # Perform the SRP key exchange clientCertChain = None if cipherSuite in CipherSuite.srpAllSuites: - for result in self._serverSRPKeyExchange(clientHello, serverHello, - verifierDB, cipherSuite, - privateKey, certChain): - if result in (0,1): yield result + for result in self._serverSRPKeyExchange(clientHello, serverHello, + verifierDB, cipherSuite, + privateKey, certChain, + settings): + if result in (0, 1): + yield result else: break premasterSecret = result @@ -1834,7 +1878,7 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, AlertDescription.unknown_psk_identity, "Client sent a hello, but without the SRP username"): yield result - + #If an RSA suite is chosen, check for certificate type intersection if cipherSuite in CipherSuite.certAllSuites and CertificateType.x509 \ not in clientHello.certificate_types: @@ -1848,38 +1892,25 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, # the client's session_id was not found in cache: yield (clientHello, cipherSuite) - def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, - cipherSuite, privateKey, serverCertChain): + def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, + cipherSuite, privateKey, serverCertChain, + settings): + """Perform the server side of SRP key exchange""" + keyExchange = SRPKeyExchange(cipherSuite, + clientHello, + serverHello, + privateKey, + verifierDB) - srpUsername = clientHello.srp_username.decode("utf-8") - self.allegedSrpUsername = srpUsername - #Get parameters from username + sigHash = self._pickServerKeyExchangeSig(settings, clientHello) + + #Create ServerKeyExchange, signing it if necessary try: - entry = verifierDB[srpUsername] - except KeyError: + serverKeyExchange = keyExchange.makeServerKeyExchange(sigHash) + except TLSUnknownPSKIdentity: for result in self._sendError(\ AlertDescription.unknown_psk_identity): yield result - (N, g, s, v) = entry - - #Calculate server's ephemeral DH values (b, B) - b = bytesToNumber(getRandomBytes(32)) - k = makeK(N, g) - B = (powMod(g, b, N) + (k*v)) % N - - #Create ServerKeyExchange, signing it if necessary - serverKeyExchange = ServerKeyExchange(cipherSuite, self.version) - serverKeyExchange.createSRP(N, g, s, B) - if cipherSuite in CipherSuite.srpCertSuites: - if self.version == (3, 3): - # TODO signing algorithm not negotiatied - serverKeyExchange.signAlg = SignatureAlgorithm.rsa - serverKeyExchange.hashAlg = HashAlgorithm.sha1 - hashBytes = serverKeyExchange.hash(clientHello.random, - serverHello.random) - if self.version == (3, 3): - hashBytes = RSAKey.addPKCS1SHA1Prefix(hashBytes) - serverKeyExchange.signature = privateKey.sign(hashBytes) #Send ServerHello[, Certificate], ServerKeyExchange, #ServerHelloDone @@ -1900,24 +1931,15 @@ def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, cipherSuite): if result in (0,1): yield result else: break - clientKeyExchange = result - A = clientKeyExchange.srp_A - if A % N == 0: + try: + premasterSecret = keyExchange.processClientKeyExchange(result) + except TLSIllegalParameterException: for result in self._sendError(AlertDescription.illegal_parameter, - "Suspicious A value"): + "Suspicious A value"): yield result - assert(False) # Just to ensure we don't fall through somehow - #Calculate u - u = makeU(N, A, B) - - #Calculate premaster secret - S = powMod((A * powMod(v,u,N)) % N, b, N) - premasterSecret = numberToByteArray(S) - yield premasterSecret - def _serverCertKeyExchange(self, clientHello, serverHello, serverCertChain, keyExchange, reqCert, reqCAs, cipherSuite, diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index 2688bec5..16ff8c5b 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -16,16 +16,18 @@ from tlslite.constants import CipherSuite, CertificateType, AlertDescription, \ HashAlgorithm, SignatureAlgorithm from tlslite.tlsconnection import TLSConnection, KeyExchange, RSAKeyExchange, \ - DHE_RSAKeyExchange + DHE_RSAKeyExchange, SRPKeyExchange from tlslite.errors import TLSLocalAlert, TLSIllegalParameterException, \ - TLSDecryptionFailed, TLSInsufficientSecurity + TLSDecryptionFailed, TLSInsufficientSecurity, TLSUnknownPSKIdentity from tlslite.x509 import X509 from tlslite.x509certchain import X509CertChain from tlslite.utils.keyfactory import parsePEMKey from tlslite.utils.codec import Parser from tlslite.utils.cryptomath import bytesToNumber, getRandomBytes, powMod, \ numberToByteArray +from tlslite.mathtls import makeX, makeU, makeK from tlslite.handshakehashes import HandshakeHashes +from tlslite import VerifierDB from unit_tests.mocksock import MockSocket @@ -568,6 +570,118 @@ def test_DHE_RSA_key_exchange_with_small_prime(self): with self.assertRaises(TLSInsufficientSecurity): client_keyExchange.processServerKeyExchange(None, srv_key_ex) +class TestSRPKeyExchange(unittest.TestCase): + def setUp(self): + self.srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = srv_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA + self.client_hello = ClientHello().create((3, 3), + bytearray(32), + bytearray(0), + [], + srpUsername='user') + self.server_hello = ServerHello().create((3, 3), + bytearray(32), + bytearray(0), + self.cipher_suite) + + verifierDB = VerifierDB() + verifierDB.create() + entry = verifierDB.makeVerifier('user', 'password', 2048) + verifierDB['user'] = entry + + self.keyExchange = SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key, + verifierDB) + + def test_SRP_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha256') + + KeyExchange.verifyServerKeyExchange(srv_key_ex, + self.srv_pub_key, + self.client_hello.random, + self.server_hello.random, + [(HashAlgorithm.sha256, + SignatureAlgorithm.rsa)]) + + a = bytesToNumber(getRandomBytes(32)) + A = powMod(srv_key_ex.srp_g, + a, + srv_key_ex.srp_N) + x = makeX(srv_key_ex.srp_s, bytearray(b'user'), bytearray(b'password')) + v = powMod(srv_key_ex.srp_g, + x, + srv_key_ex.srp_N) + u = makeU(srv_key_ex.srp_N, + A, + srv_key_ex.srp_B) + + k = makeK(srv_key_ex.srp_N, + srv_key_ex.srp_g) + S = powMod((srv_key_ex.srp_B - (k*v)) % srv_key_ex.srp_N, + a+(u*x), + srv_key_ex.srp_N) + + cln_premaster = numberToByteArray(S) + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)).createSRP(A) + + srv_premaster = self.keyExchange.processClientKeyExchange(cln_key_ex) + + self.assertEqual(cln_premaster, srv_premaster) + + def test_SRP_key_exchange_without_signature(self): + self.cipher_suite = CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA + self.keyExchange.cipherSuite = self.cipher_suite + self.server_hello.cipher_suite = self.cipher_suite + + srv_key_ex = self.keyExchange.makeServerKeyExchange() + + a = bytesToNumber(getRandomBytes(32)) + A = powMod(srv_key_ex.srp_g, + a, + srv_key_ex.srp_N) + x = makeX(srv_key_ex.srp_s, bytearray(b'user'), bytearray(b'password')) + v = powMod(srv_key_ex.srp_g, + x, + srv_key_ex.srp_N) + u = makeU(srv_key_ex.srp_N, + A, + srv_key_ex.srp_B) + + k = makeK(srv_key_ex.srp_N, + srv_key_ex.srp_g) + S = powMod((srv_key_ex.srp_B - (k*v)) % srv_key_ex.srp_N, + a+(u*x), + srv_key_ex.srp_N) + + cln_premaster = numberToByteArray(S) + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)).createSRP(A) + + srv_premaster = self.keyExchange.processClientKeyExchange(cln_key_ex) + + self.assertEqual(cln_premaster, srv_premaster) + + def test_SRP_with_invalid_name(self): + self.client_hello.srp_username = bytearray(b'test') + + with self.assertRaises(TLSUnknownPSKIdentity): + self.keyExchange.makeServerKeyExchange('sha1') + + def test_SRP_with_invalid_client_key_share(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + A = srv_key_ex.srp_N + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)).createSRP(A) + + with self.assertRaises(TLSIllegalParameterException): + self.keyExchange.processClientKeyExchange(cln_key_ex) + class TestTLSConnection(unittest.TestCase): From 10290cd5b05271711fdff8e6aa6cb48b4aafdcd4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 15:55:51 +0100 Subject: [PATCH 206/574] move client SRP key exchange to KeyExchange --- tlslite/tlsconnection.py | 128 +++++++++++++---------- unit_tests/test_tlslite_tlsconnection.py | 81 ++++++++++++++ 2 files changed, 152 insertions(+), 57 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index a38e24ed..ebc4f141 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -308,7 +308,7 @@ class SRPKeyExchange(KeyExchange): """Helper class for conducting SRP key exchange""" def __init__(self, cipherSuite, clientHello, serverHello, privateKey, - verifierDB): + verifierDB, srpUsername=None, password=None, settings=None): """Link Key Exchange options with verifierDB for SRP""" super(SRPKeyExchange, self).__init__(cipherSuite, clientHello, serverHello, privateKey) @@ -317,6 +317,10 @@ def __init__(self, cipherSuite, clientHello, serverHello, privateKey, self.b = None self.B = None self.verifierDB = verifierDB + self.A = None + self.srpUsername = srpUsername + self.password = password + self.settings = settings def makeServerKeyExchange(self, sigHash=None): """Create SRP version of Server Key Exchange""" @@ -354,6 +358,49 @@ def processClientKeyExchange(self, clientKeyExchange): S = powMod((A * powMod(self.v, u, self.N)) % self.N, self.b, self.N) return numberToByteArray(S) + def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): + """Calculate premaster secret from ServerKeyExchange""" + del srvPublicKey # irrelevant for SRP + N = serverKeyExchange.srp_N + g = serverKeyExchange.srp_g + s = serverKeyExchange.srp_s + B = serverKeyExchange.srp_B + + if (g, N) not in goodGroupParameters: + raise TLSInsufficientSecurity("Unknown group parameters") + if numBits(N) < self.settings.minKeySize: + raise TLSInsufficientSecurity("N value is too small: {0}".\ + format(numBits(N))) + if numBits(N) > self.settings.maxKeySize: + raise TLSInsufficientSecurity("N value is too large: {0}".\ + format(numBits(N))) + if B % N == 0: + raise TLSIllegalParameterException("Suspicious B value") + + #Client ephemeral value + a = bytesToNumber(getRandomBytes(32)) + self.A = powMod(g, a, N) + + #Calculate client's static DH values (x, v) + x = makeX(s, bytearray(self.srpUsername, "utf-8"), + bytearray(self.password, "utf-8")) + v = powMod(g, x, N) + + #Calculate u + u = makeU(N, self.A, B) + + #Calculate premaster secret + k = makeK(N, g) + S = powMod((B - (k*v)) % N, a+(u*x), N) + return numberToByteArray(S) + + def makeClientKeyExchange(self): + """Create ClientKeyExchange""" + clientKeyExchange = ClientKeyExchange(self.cipherSuite, + self.serverHello.server_version) + clientKeyExchange.createSRP(self.A) + return clientKeyExchange + class TLSConnection(TLSRecordLayer): """ This class wraps a socket and provides TLS handshaking and data transfer. @@ -773,13 +820,18 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, #reading the post-ServerHello messages, then derives a #premasterSecret and sends a corresponding ClientKeyExchange. if cipherSuite in CipherSuite.srpAllSuites: + keyExchange = SRPKeyExchange(cipherSuite, clientHello, + serverHello, None, None, + srpUsername=srpUsername, + password=password, + settings=settings) for result in self._clientSRPKeyExchange(settings, cipherSuite, serverHello.\ certificate_type, - srpUsername, password, clientHello.random, serverHello.random, - serverHello.tackExt): + serverHello.tackExt, + keyExchange): if result in (0, 1): yield result else: break @@ -1028,9 +1080,8 @@ def _clientResume(self, session, serverHello, clientRandom, self.session = session yield "resumed_and_finished" - def _clientSRPKeyExchange(self, settings, cipherSuite, certificateType, - srpUsername, password, - clientRandom, serverRandom, tackExt): + def _clientSRPKeyExchange(self, settings, cipherSuite, certificateType, + clientRandom, serverRandom, tackExt, keyExchange): #If the server chose an SRP+RSA suite... if cipherSuite in CipherSuite.srpCertSuites: @@ -1054,34 +1105,6 @@ def _clientSRPKeyExchange(self, settings, cipherSuite, certificateType, if result in (0,1): yield result else: break serverHelloDone = result - - #Calculate SRP premaster secret - #Get and check the server's group parameters and B value - N = serverKeyExchange.srp_N - g = serverKeyExchange.srp_g - s = serverKeyExchange.srp_s - B = serverKeyExchange.srp_B - - if (g,N) not in goodGroupParameters: - for result in self._sendError(\ - AlertDescription.insufficient_security, - "Unknown group parameters"): - yield result - if numBits(N) < settings.minKeySize: - for result in self._sendError(\ - AlertDescription.insufficient_security, - "N value is too small: %d" % numBits(N)): - yield result - if numBits(N) > settings.maxKeySize: - for result in self._sendError(\ - AlertDescription.insufficient_security, - "N value is too large: %d" % numBits(N)): - yield result - if B % N == 0: - for result in self._sendError(\ - AlertDescription.illegal_parameter, - "Suspicious B value"): - yield result #Check the server's signature, if server chose an #SRP+RSA suite @@ -1112,31 +1135,22 @@ def _clientSRPKeyExchange(self, settings, cipherSuite, certificateType, for result in self._sendError(AlertDescription.decrypt_error): yield result - #Calculate client's ephemeral DH values (a, A) - a = bytesToNumber(getRandomBytes(32)) - A = powMod(g, a, N) - - #Calculate client's static DH values (x, v) - x = makeX(s, bytearray(srpUsername, "utf-8"), - bytearray(password, "utf-8")) - v = powMod(g, x, N) - - #Calculate u - u = makeU(N, A, B) - - #Calculate premaster secret - k = makeK(N, g) - S = powMod((B - (k*v)) % N, a+(u*x), N) - - if self.fault == Fault.badA: - A = N - S = 0 - - premasterSecret = numberToByteArray(S) + try: + ske = serverKeyExchange + premasterSecret = keyExchange.processServerKeyExchange(None, + ske) + except TLSInsufficientSecurity as e: + for result in self._sendError(\ + AlertDescription.insufficient_security, e): + yield result + except TLSIllegalParameterException as e: + for result in self._sendError(\ + AlertDescription.illegal_parameter, e): + yield result #Send ClientKeyExchange - for result in self._sendMsg(\ - ClientKeyExchange(cipherSuite, self.version).createSRP(A)): + clientKeyExchange = keyExchange.makeClientKeyExchange() + for result in self._sendMsg(clientKeyExchange): yield result yield (premasterSecret, serverCertChain, tackExt) diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index 16ff8c5b..62baeb75 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -682,6 +682,87 @@ def test_SRP_with_invalid_client_key_share(self): with self.assertRaises(TLSIllegalParameterException): self.keyExchange.processClientKeyExchange(cln_key_ex) + def test_SRP_key_exchange_with_client(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, None, + srpUsername='user', + password='password', + settings=HandshakeSettings()) + + client_premaster = client_keyExchange.processServerKeyExchange(\ + None, + srv_key_ex) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) + + def test_client_SRP_key_exchange_with_unknown_params(self): + keyExchange = ServerKeyExchange(self.cipher_suite, + self.server_hello.server_version) + keyExchange.createSRP(1, 2, 3, 4) + + client_keyExchange = SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, None, + srpUsername='user', + password='password') + with self.assertRaises(TLSInsufficientSecurity): + client_keyExchange.processServerKeyExchange(None, keyExchange) + + def test_client_SRP_key_exchange_with_too_small_params(self): + keyExchange = self.keyExchange.makeServerKeyExchange('sha1') + + settings = HandshakeSettings() + settings.minKeySize = 3072 + client_keyExchange = SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, None, + srpUsername='user', + password='password', + settings=settings) + with self.assertRaises(TLSInsufficientSecurity): + client_keyExchange.processServerKeyExchange(None, keyExchange) + + def test_client_SRP_key_exchange_with_too_big_params(self): + keyExchange = self.keyExchange.makeServerKeyExchange('sha1') + + settings = HandshakeSettings() + settings.minKeySize = 512 + settings.maxKeySize = 1024 + client_keyExchange = SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, None, + srpUsername='user', + password='password', + settings=settings) + with self.assertRaises(TLSInsufficientSecurity): + client_keyExchange.processServerKeyExchange(None, keyExchange) + + def test_client_SRP_key_exchange_with_invalid_params(self): + keyExchange = self.keyExchange.makeServerKeyExchange('sha1') + keyExchange.srp_B = keyExchange.srp_N + + settings = HandshakeSettings() + client_keyExchange = SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, None, + srpUsername='user', + password='password', + settings=settings) + with self.assertRaises(TLSIllegalParameterException): + client_keyExchange.processServerKeyExchange(None, keyExchange) + class TestTLSConnection(unittest.TestCase): From ebb8035ce3992d16b8fcf2a60a0fac81453c7eb9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 16:06:31 +0100 Subject: [PATCH 207/574] make all client key exchanges use common code --- tlslite/tlsconnection.py | 330 ++++++++++----------------------------- 1 file changed, 79 insertions(+), 251 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index ebc4f141..b221c0d9 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -825,38 +825,13 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, srpUsername=srpUsername, password=password, settings=settings) - for result in self._clientSRPKeyExchange(settings, cipherSuite, - serverHello.\ - certificate_type, - clientHello.random, - serverHello.random, - serverHello.tackExt, - keyExchange): - if result in (0, 1): - yield result - else: break - (premasterSecret, serverCertChain, tackExt) = result #If the server selected an anonymous ciphersuite, the client #finishes reading the post-ServerHello messages. elif cipherSuite in CipherSuite.dhAllSuites: keyExchange = DHE_RSAKeyExchange(cipherSuite, clientHello, serverHello, None) - for result in self._clientDHEKeyExchange(settings, cipherSuite, - clientCertChain, - privateKey, - serverHello.\ - certificate_type, - serverHello.tackExt, - clientHello.random, - serverHello.random, - keyExchange): - if result in (0, 1): - yield result - else: break - (premasterSecret, serverCertChain, clientCertChain, - tackExt) = result - + #If the server selected a certificate-based RSA ciphersuite, #the client finishes reading the post-ServerHello messages. If #a CertificateRequest message was sent, the client responds with @@ -866,17 +841,21 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, else: keyExchange = RSAKeyExchange(cipherSuite, clientHello, serverHello, None) - for result in self._clientRSAKeyExchange(settings, cipherSuite, - clientCertChain, privateKey, - serverHello.certificate_type, - clientHello.random, serverHello.random, - serverHello.tackExt, - keyExchange): - if result in (0,1): yield result - else: break - (premasterSecret, serverCertChain, clientCertChain, - tackExt) = result - + + for result in self._clientKeyExchange(settings, cipherSuite, + clientCertChain, + privateKey, + serverHello.certificate_type, + serverHello.tackExt, + clientHello.random, + serverHello.random, + keyExchange): + if result in (0, 1): + yield result + else: break + (premasterSecret, serverCertChain, clientCertChain, + tackExt) = result + #After having previously sent a ClientKeyExchange, the client now #initiates an exchange of Finished messages. for result in self._clientFinished(premasterSecret, @@ -1078,201 +1057,36 @@ def _clientResume(self, session, serverHello, clientRandom, #Set the session for this connection self.session = session - yield "resumed_and_finished" - - def _clientSRPKeyExchange(self, settings, cipherSuite, certificateType, - clientRandom, serverRandom, tackExt, keyExchange): - - #If the server chose an SRP+RSA suite... - if cipherSuite in CipherSuite.srpCertSuites: - #Get Certificate, ServerKeyExchange, ServerHelloDone + yield "resumed_and_finished" + + def _clientKeyExchange(self, settings, cipherSuite, + clientCertChain, privateKey, + certificateType, + tackExt, clientRandom, serverRandom, + keyExchange): + """Perform the client side of key exchange""" + # if server chose cipher suite with authentication, get the certificate + if cipherSuite in CipherSuite.certAllSuites: for result in self._getMsg(ContentType.handshake, - HandshakeType.certificate, certificateType): - if result in (0,1): yield result + HandshakeType.certificate, + certificateType): + if result in (0, 1): + yield result else: break serverCertificate = result else: serverCertificate = None - - for result in self._getMsg(ContentType.handshake, - HandshakeType.server_key_exchange, cipherSuite): - if result in (0,1): yield result - else: break - serverKeyExchange = result - - for result in self._getMsg(ContentType.handshake, - HandshakeType.server_hello_done): - if result in (0,1): yield result - else: break - serverHelloDone = result - - #Check the server's signature, if server chose an - #SRP+RSA suite - serverCertChain = None - if cipherSuite in CipherSuite.srpCertSuites: - # Get server's public key from the Certificate message - # Also validate the chain against the ServerHello's TACKext (if any) - # If none, and a TACK cert is present, return its TACKext - for result in self._clientGetKeyFromChain(serverCertificate, - settings, tackExt): - if result in (0,1): yield result - else: break - publicKey, serverCertChain, tackExt = result - - # Check signature on ServerKeyExchange message - validSigAlgs = self._sigHashesToList(settings) - try: - KeyExchange.verifyServerKeyExchange(serverKeyExchange, - publicKey, - clientRandom, - serverRandom, - validSigAlgs) - except TLSIllegalParameterException: - for result in self._sendError(AlertDescription.\ - illegal_parameter): - yield result - except TLSDecryptionFailed: - for result in self._sendError(AlertDescription.decrypt_error): - yield result - - try: - ske = serverKeyExchange - premasterSecret = keyExchange.processServerKeyExchange(None, - ske) - except TLSInsufficientSecurity as e: - for result in self._sendError(\ - AlertDescription.insufficient_security, e): - yield result - except TLSIllegalParameterException as e: - for result in self._sendError(\ - AlertDescription.illegal_parameter, e): - yield result - - #Send ClientKeyExchange - clientKeyExchange = keyExchange.makeClientKeyExchange() - for result in self._sendMsg(clientKeyExchange): - yield result - yield (premasterSecret, serverCertChain, tackExt) - - - def _clientRSAKeyExchange(self, settings, cipherSuite, - clientCertChain, privateKey, - certificateType, - clientRandom, serverRandom, - tackExt, keyExchange): - - #Get Certificate[, CertificateRequest], ServerHelloDone - for result in self._getMsg(ContentType.handshake, - HandshakeType.certificate, certificateType): - if result in (0,1): yield result - else: break - serverCertificate = result - - # Get CertificateRequest or ServerHelloDone - for result in self._getMsg(ContentType.handshake, - (HandshakeType.server_hello_done, - HandshakeType.certificate_request)): - if result in (0,1): yield result - else: break - msg = result - certificateRequest = None - if isinstance(msg, CertificateRequest): - certificateRequest = msg - # We got CertificateRequest, so this must be ServerHelloDone + # if server chose RSA key exchange, we need to skip SKE message + if cipherSuite not in CipherSuite.certSuites: for result in self._getMsg(ContentType.handshake, - HandshakeType.server_hello_done): - if result in (0,1): yield result - else: break - serverHelloDone = result - elif isinstance(msg, ServerHelloDone): - serverHelloDone = msg - - # Get server's public key from the Certificate message - # Also validate the chain against the ServerHello's TACKext (if any) - # If none, and a TACK cert is present, return its TACKext - for result in self._clientGetKeyFromChain(serverCertificate, - settings, tackExt): - if result in (0,1): yield result - else: break - publicKey, serverCertChain, tackExt = result - - premasterSecret = keyExchange.processServerKeyExchange(publicKey, - None) - - #If client authentication was requested, send Certificate - #message, either with certificates or empty - if certificateRequest: - clientCertificate = Certificate(certificateType) - - if clientCertChain: - #Check to make sure we have the same type of - #certificates the server requested - wrongType = False - if certificateType == CertificateType.x509: - if not isinstance(clientCertChain, X509CertChain): - wrongType = True - if wrongType: - for result in self._sendError(\ - AlertDescription.handshake_failure, - "Client certificate is of wrong type"): - yield result - - clientCertificate.create(clientCertChain) - for result in self._sendMsg(clientCertificate): - yield result - else: - #The server didn't request client auth, so we - #zeroize these so the clientCertChain won't be - #stored in the session. - privateKey = None - clientCertChain = None - - #Send ClientKeyExchange - clientKeyExchange = keyExchange.makeClientKeyExchange() - for result in self._sendMsg(clientKeyExchange): - yield result - - #If client authentication was requested and we have a - #private key, send CertificateVerify - if certificateRequest and privateKey: - validSigAlgs = self._sigHashesToList(settings) - certificateVerify = KeyExchange.makeCertificateVerify(\ - self.version, - self._handshake_hash, - validSigAlgs, - privateKey, - certificateRequest, - premasterSecret, - clientRandom, - serverRandom) - for result in self._sendMsg(certificateVerify): - yield result - - yield (premasterSecret, serverCertChain, clientCertChain, tackExt) - - def _clientDHEKeyExchange(self, settings, cipherSuite, - clientCertChain, privateKey, - certificateType, - tackExt, clientRandom, serverRandom, - keyExchange): - #TODO: check if received messages match cipher suite - # (abort if CertfificateRequest and ADH) - - # if server chose DHE_RSA cipher get the certificate - if cipherSuite in CipherSuite.dheCertSuites: - for result in self._getMsg(ContentType.handshake, - HandshakeType.certificate, - certificateType): + HandshakeType.server_key_exchange, + cipherSuite): if result in (0, 1): yield result else: break - serverCertificate = result - # get rest of handshake messages - for result in self._getMsg(ContentType.handshake, - HandshakeType.server_key_exchange, cipherSuite): - if result in (0,1): yield result - else: break - serverKeyExchange = result + serverKeyExchange = result + else: + serverKeyExchange = None for result in self._getMsg(ContentType.handshake, (HandshakeType.certificate_request, @@ -1284,52 +1098,63 @@ def _clientDHEKeyExchange(self, settings, cipherSuite, certificateRequest = None if isinstance(result, CertificateRequest): certificateRequest = result + + #abort if Certificate Request with inappropriate ciphersuite + if cipherSuite not in CipherSuite.certAllSuites \ + or cipherSuite in CipherSuite.srpAllSuites: + for result in self._sendError(\ + AlertDescription.unexpected_message, + "Certificate Request with incompatible cipher suite"): + yield result + # we got CertificateRequest so now we'll get ServerHelloDone for result in self._getMsg(ContentType.handshake, - HandshakeType.server_hello_done): + HandshakeType.server_hello_done): if result in (0, 1): yield result else: break serverHelloDone = result - #Check the server's signature, if the server chose an DHE_RSA suite serverCertChain = None - if cipherSuite in CipherSuite.dheCertSuites: + publicKey = None + if cipherSuite in CipherSuite.certAllSuites: # get the certificate for result in self._clientGetKeyFromChain(serverCertificate, settings, tackExt): if result in (0, 1): yield result - else: - break + else: break publicKey, serverCertChain, tackExt = result - # then verify the signature on SKE - validSigAlgs = self._sigHashesToList(settings) - try: - KeyExchange.verifyServerKeyExchange(serverKeyExchange, - publicKey, - clientRandom, - serverRandom, - validSigAlgs) - except TLSIllegalParameterException: - for result in self._sendError(AlertDescription.\ - illegal_parameter): - yield result - except TLSDecryptionFailed: - for result in self._sendError(AlertDescription.decrypt_error): - yield result + #Check the server's signature, if the server chose an authenticated + # PFS-enabled ciphersuite + if serverKeyExchange: + validSigAlgs = self._sigHashesToList(settings) + try: + KeyExchange.verifyServerKeyExchange(serverKeyExchange, + publicKey, + clientRandom, + serverRandom, + validSigAlgs) + except TLSIllegalParameterException: + for result in self._sendError(AlertDescription.\ + illegal_parameter): + yield result + except TLSDecryptionFailed: + for result in self._sendError(\ + AlertDescription.decrypt_error): + yield result #Send Certificate if we were asked for it if certificateRequest: # if a peer doesn't advertise support for any algorithm in TLSv1.2, # support for SHA1+RSA can be assumed - if self.version == (3, 3) and \ - len([sig for sig in \ + if self.version == (3, 3)\ + and not [sig for sig in \ certificateRequest.supported_signature_algs\ - if sig[1] == SignatureAlgorithm.rsa]) == 0: + if sig[1] == SignatureAlgorithm.rsa]: for result in self._sendError(\ AlertDescription.handshake_failure, "Server doesn't accept any sigalgs we support: " + @@ -1361,12 +1186,15 @@ def _clientDHEKeyExchange(self, settings, cipherSuite, try: ske = serverKeyExchange - premasterSecret = keyExchange.processServerKeyExchange(None, + premasterSecret = keyExchange.processServerKeyExchange(publicKey, ske) - except TLSInsufficientSecurity: - for result in self._sendError( - AlertDescription.insufficient_security, - "Server sent a DHE key exchange with very small prime"): + except TLSInsufficientSecurity as e: + for result in self._sendError(\ + AlertDescription.insufficient_security, e): + yield result + except TLSIllegalParameterException as e: + for result in self._sendError(\ + AlertDescription.illegal_parameter, e): yield result clientKeyExchange = keyExchange.makeClientKeyExchange() From 59afa6707a13d80b4f8d3eb2a6e6cc4cbefdc0c2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 16:42:01 +0100 Subject: [PATCH 208/574] implement ECDHE key exchange --- .travis.yml | 1 + requirements.txt | 1 + scripts/tls.py | 12 +- setup.py | 1 + tlslite/constants.py | 86 ++++++++- tlslite/handshakesettings.py | 19 +- tlslite/messages.py | 48 ++++- tlslite/tlsconnection.py | 149 +++++++++++++++- tlslite/utils/compat.py | 8 + tlslite/utils/ecc.py | 60 +++++++ unit_tests/test_tlslite_constants.py | 7 +- unit_tests/test_tlslite_handshakesettings.py | 6 + unit_tests/test_tlslite_messages.py | 77 +++++++- unit_tests/test_tlslite_tlsconnection.py | 104 ++++++++++- unit_tests/test_tlslite_utils_ecc.py | 174 +++++++++++++++++++ 15 files changed, 735 insertions(+), 18 deletions(-) create mode 100644 requirements.txt create mode 100644 tlslite/utils/ecc.py create mode 100644 unit_tests/test_tlslite_utils_ecc.py diff --git a/.travis.yml b/.travis.yml index 9636c9c0..4e1e9fc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -81,6 +81,7 @@ install: - if [[ $PYCRYPTO == 'true' ]]; then travis_retry pip install pycrypto; fi - if [[ $GMPY == 'true' ]]; then travis_retry pip install gmpy; fi - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then travis_retry pip install 'coverage<4' 'hypothesis<1.10'; else travis_retry pip install coverage hypothesis; fi + - travis_retry pip install -r requirements.txt - travis_retry pip install -r build-requirements.txt script: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..aa5efdb5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +ecdsa diff --git a/scripts/tls.py b/scripts/tls.py index a097389e..94da40a1 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -29,7 +29,8 @@ raise "This must be run as a command, not used as a module!" from tlslite.api import * -from tlslite.constants import CipherSuite +from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ + GroupName from tlslite import __version__ try: @@ -182,6 +183,15 @@ def printGoodConnection(connection, seconds): if connection.session.serverCertChain: print(" Server X.509 SHA1 fingerprint: %s" % connection.session.serverCertChain.getFingerprint()) + if connection.version >= (3, 3) and connection.serverSigAlg is not None: + print(" Key exchange signature: {1}+{0}".format(\ + HashAlgorithm.toStr(connection.serverSigAlg[0]), + SignatureAlgorithm.toStr(connection.serverSigAlg[1]))) + if connection.ecdhCurve is not None: + print(" Group used for key exchange: {0}".format(\ + GroupName.toStr(connection.ecdhCurve))) + if connection.dhGroupSize is not None: + print(" DH group size: {0} bits".format(connection.dhGroupSize)) if connection.session.serverName: print(" SNI: %s" % connection.session.serverName) if connection.session.tackExt: diff --git a/setup.py b/setup.py index 99743afc..1557b1bb 100755 --- a/setup.py +++ b/setup.py @@ -18,6 +18,7 @@ packages=["tlslite", "tlslite.utils", "tlslite.integration"], package_data={ 'package1': ['LICENSE', 'README.md']}, + install_requires=['ecdsa'], obsoletes=["tlslite"], classifiers=[ 'Development Status :: 4 - Beta', diff --git a/tlslite/constants.py b/tlslite/constants.py index 745f13b7..c26ed528 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -110,8 +110,7 @@ class SignatureAlgorithm(TLSEnum): dsa = 2 ecdsa = 3 -class GroupName(object): - +class GroupName(TLSEnum): """Name of groups supported for (EC)DH key exchange""" # RFC4492 @@ -158,6 +157,14 @@ class GroupName(object): all = allEC + allFF + @classmethod + def toRepr(cls, value, blacklist=None): + """Convert numeric type to name representation""" + if blacklist is None: + blacklist = [] + blacklist += ['all', 'allEC', 'allFF'] + return super(GroupName, cls).toRepr(value, blacklist) + class ECPointFormat(object): """Names and ID's of supported EC point formats""" @@ -170,6 +177,13 @@ class ECPointFormat(object): ansiX962_compressed_prime, ansiX962_compressed_char2] +class ECCurveType(TLSEnum): + """Types of ECC curves supported in TLS from RFC4492""" + + explicit_prime = 1 + explicit_char2 = 2 + named_curve = 3 + class NameType: host_name = 0 @@ -270,6 +284,9 @@ class CipherSuite: ietfNames = {} +# the ciphesuite names come from IETF, we want to keep them +#pylint: disable = invalid-name + # Weird pseudo-ciphersuite from RFC 5746 # Signals that "secure renegotiation" is supported # We actually don't do any renegotiation, but this @@ -368,11 +385,31 @@ class CipherSuite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F ietfNames[0x009F] = 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384' + # RFC 4492 - ECC Cipher Suites for TLS + TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010 + ietfNames[0xC010] = 'TLS_ECDHE_RSA_WITH_NULL_SHA' + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 + ietfNames[0xC013] = 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA' + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 + ietfNames[0xC014] = 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA' + # draft-ietf-tls-chacha20-poly1305-00 # ChaCha20/Poly1305 based Cipher Suites for TLS1.2 TLS_DHE_RSA_WITH_CHACHA20_POLY1305 = 0xcca3 ietfNames[0xcca3] = 'TLS_DHE_RSA_WITH_CHACHA20_POLY1305' + + # RFC 5289 - ECC Ciphers with SHA-256/SHA284 HMAC and AES-GCM + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 + ietfNames[0xC027] = 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256' + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 + ietfNames[0xC028] = 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384' + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F + ietfNames[0xC02F] = 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 + ietfNames[0xC030] = 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384' + +#pylint: enable = invalid-name # # Define cipher suite families below # @@ -395,6 +432,8 @@ class CipherSuite: aes128Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA256) aes128Suites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) aes128Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) + aes128Suites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) + aes128Suites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) # AES-256 CBC ciphers aes256Suites = [] @@ -406,18 +445,22 @@ class CipherSuite: aes256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) aes256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) aes256Suites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) + aes256Suites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) + aes256Suites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) # AES-128 GCM ciphers aes128GcmSuites = [] aes128GcmSuites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) aes128GcmSuites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) aes128GcmSuites.append(TLS_DH_ANON_WITH_AES_128_GCM_SHA256) + aes128GcmSuites.append(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) # AES-256-GCM ciphers (implicit SHA384, see sha384PrfSuites) aes256GcmSuites = [] aes256GcmSuites.append(TLS_RSA_WITH_AES_256_GCM_SHA384) aes256GcmSuites.append(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) aes256GcmSuites.append(TLS_DH_ANON_WITH_AES_256_GCM_SHA384) + aes256GcmSuites.append(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) chacha20Suites = [] chacha20Suites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305) @@ -433,6 +476,7 @@ class CipherSuite: nullSuites.append(TLS_RSA_WITH_NULL_MD5) nullSuites.append(TLS_RSA_WITH_NULL_SHA) nullSuites.append(TLS_RSA_WITH_NULL_SHA256) + nullSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) # SHA-1 HMAC, protocol default PRF shaSuites = [] @@ -453,6 +497,9 @@ class CipherSuite: shaSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) shaSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) shaSuites.append(TLS_RSA_WITH_NULL_SHA) + shaSuites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) + shaSuites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) + shaSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) # SHA-256 HMAC, SHA-256 PRF sha256Suites = [] @@ -463,9 +510,11 @@ class CipherSuite: sha256Suites.append(TLS_RSA_WITH_NULL_SHA256) sha256Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) sha256Suites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) + sha256Suites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) # SHA-384 HMAC, SHA-384 PRF sha384Suites = [] + sha384Suites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) # stream cipher construction streamSuites = [] @@ -552,6 +601,8 @@ def _filterSuites(suites, settings, version=None): keyExchangeSuites += CipherSuite.certSuites if "dhe_rsa" in keyExchangeNames: keyExchangeSuites += CipherSuite.dheCertSuites + if "ecdhe_rsa" in keyExchangeNames: + keyExchangeSuites += CipherSuite.ecdheCertSuites if "srp_sha" in keyExchangeNames: keyExchangeSuites += CipherSuite.srpSuites if "srp_sha_rsa" in keyExchangeNames: @@ -620,10 +671,28 @@ def getCertSuites(settings, version=None): @staticmethod def getDheCertSuites(settings, version=None): - return CipherSuite._filterSuites(CipherSuite.dheCertSuites, settings, version) + """Provide authenticated DHE ciphersuites matching settings""" + return CipherSuite._filterSuites(CipherSuite.dheCertSuites, + settings, version) + + # ECDHE key exchange, RSA authentication + ecdheCertSuites = [] + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) + + @staticmethod + def getEcdheCertSuites(settings, version=None): + """Provide authenticated ECDHE ciphersuites matching settings""" + return CipherSuite._filterSuites(CipherSuite.ecdheCertSuites, settings, + version) # RSA authentication - certAllSuites = srpCertSuites + certSuites + dheCertSuites + certAllSuites = srpCertSuites + certSuites + dheCertSuites + ecdheCertSuites # anon FFDHE key exchange anonSuites = [] @@ -638,10 +707,17 @@ def getDheCertSuites(settings, version=None): @staticmethod def getAnonSuites(settings, version=None): - return CipherSuite._filterSuites(CipherSuite.anonSuites, settings, version) + """Provide anonymous DH ciphersuites matching settings""" + return CipherSuite._filterSuites(CipherSuite.anonSuites, + settings, version) dhAllSuites = dheCertSuites + anonSuites + # anon ECDHE key exchange + ecdhAnonSuites = [] + + ecdhAllSuites = ecdheCertSuites + ecdhAnonSuites + @staticmethod def canonicalCipherName(ciphersuite): """Return the canonical name of the cipher whose number is provided.""" diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 00538d10..c202602c 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -10,6 +10,7 @@ from .constants import CertificateType from .utils import cryptomath from .utils import cipherfactory +from .utils.compat import ecdsaAllCurves CIPHER_NAMES = ["chacha20-poly1305", "aes256gcm", "aes128gcm", @@ -18,11 +19,18 @@ ALL_CIPHER_NAMES = CIPHER_NAMES + ["rc4", "null"] MAC_NAMES = ["sha", "sha256", "aead"] # Don't allow "md5" by default. ALL_MAC_NAMES = MAC_NAMES + ["md5"] -KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "srp_sha", "srp_sha_rsa", "dh_anon"] +KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "ecdhe_rsa", "srp_sha", "srp_sha_rsa", + "dh_anon"] CIPHER_IMPLEMENTATIONS = ["openssl", "pycrypto", "python"] CERTIFICATE_TYPES = ["x509"] RSA_SIGNATURE_HASHES = ["sha512", "sha384", "sha256", "sha224", "sha1"] ALL_RSA_SIGNATURE_HASHES = RSA_SIGNATURE_HASHES + ["md5"] +# while secp521r1 is the most secure, it's also much slower than the others +# so place it as the last one +CURVE_NAMES = ["secp384r1", "secp256r1", "secp521r1"] +ALL_CURVE_NAMES = CURVE_NAMES + ["secp256k1"] +if ecdsaAllCurves: + ALL_CURVE_NAMES += ["secp224r1", "secp192r1"] class HandshakeSettings(object): """This class encapsulates various parameters that can be used with @@ -113,6 +121,9 @@ class HandshakeSettings(object): The allowed hashes are: "md5", "sha1", "sha224", "sha256", "sha384" and "sha512". The default list does not include md5. + + @type eccCurves: list + @ivar eccCurves: List of named curves that are to be supported """ def __init__(self): self.minKeySize = 1023 @@ -128,6 +139,7 @@ def __init__(self): self.sendFallbackSCSV = False self.useEncryptThenMAC = True self.rsaSigHashes = list(RSA_SIGNATURE_HASHES) + self.eccCurves = list(CURVE_NAMES) def validate(self): """ @@ -151,6 +163,7 @@ def validate(self): other.sendFallbackSCSV = self.sendFallbackSCSV other.useEncryptThenMAC = self.useEncryptThenMAC other.rsaSigHashes = self.rsaSigHashes + other.eccCurves = self.eccCurves if not cipherfactory.tripleDESPresent: other.cipherNames = [e for e in self.cipherNames if e != "3des"] @@ -218,6 +231,10 @@ def validate(self): if len(other.rsaSigHashes) == 0 and other.maxVersion >= (3, 3): raise ValueError("TLS 1.2 requires signature algorithms to be set") + for val in other.eccCurves: + if val not in ALL_CURVE_NAMES: + raise ValueError("Unknown ECC Curve name: {0}".format(val)) + return other def getCertificateTypes(self): diff --git a/tlslite/messages.py b/tlslite/messages.py index 5c38980b..1bcdc43b 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -941,6 +941,12 @@ class ServerKeyExchange(HandshakeMsg): @cvar dh_g: FFDHE protocol generator @type dh_Ys: int @cvar dh_Ys: FFDH protocol server key share + @type curve_type: int + @cvar curve_type: Type of curve used (explicit, named, etc.) + @type named_curve: int + @cvar named_curve: TLS ID of named curve + @type ecdh_Ys: bytearray + @cvar ecdh_Ys: ECDH protocol encoded point key share @type signature: bytearray @cvar signature: signature performed over the parameters by server @type hashAlg: int @@ -967,6 +973,10 @@ def __init__(self, cipherSuite, version): self.dh_p = 0 self.dh_g = 0 self.dh_Ys = 0 + # EC settings + self.curve_type = None + self.named_curve = None + self.ecdh_Ys = bytearray(0) # signature for certificate authenticated ciphersuites self.signature = bytearray(0) # signature hash algorithm and signing algorithm for TLSv1.2 @@ -1009,6 +1019,12 @@ def createDH(self, dh_p, dh_g, dh_Ys): self.dh_Ys = dh_Ys return self + def createECDH(self, curve_type, named_curve=None, point=None): + """Set ECDH protocol parameters""" + self.curve_type = curve_type + self.named_curve = named_curve + self.ecdh_Ys = point + def parse(self, parser): """Deserialise message from L{Parser} @@ -1025,6 +1041,12 @@ def parse(self, parser): self.dh_p = bytesToNumber(parser.getVarBytes(2)) self.dh_g = bytesToNumber(parser.getVarBytes(2)) self.dh_Ys = bytesToNumber(parser.getVarBytes(2)) + elif self.cipherSuite in CipherSuite.ecdhAllSuites: + self.curve_type = parser.get(1) + # only named curves supported + assert self.curve_type == 3 + self.named_curve = parser.get(2) + self.ecdh_Ys = parser.getVarBytes(1) else: raise AssertionError() @@ -1052,6 +1074,11 @@ def writeParams(self): writer.addVarSeq(numberToByteArray(self.dh_p), 1, 2) writer.addVarSeq(numberToByteArray(self.dh_g), 1, 2) writer.addVarSeq(numberToByteArray(self.dh_Ys), 1, 2) + elif self.cipherSuite in CipherSuite.ecdhAllSuites: + writer.add(self.curve_type, 1) + assert self.curve_type == 3 + writer.add(self.named_curve, 2) + writer.addVarSeq(self.ecdh_Ys, 1, 1) else: assert(False) return writer.bytes @@ -1116,6 +1143,8 @@ class ClientKeyExchange(HandshakeMsg): @ivar srp_A: SRP protocol client answer value @type dh_Yc: int @ivar dh_Yc: client Finite Field Diffie-Hellman protocol key share + @type ecdh_Yc: bytearray + @ivar ecdh_Yc: encoded curve coordinates @type encryptedPreMasterSecret: bytearray @ivar encryptedPreMasterSecret: client selected PremMaster secret encrypted with server public key (from certificate) @@ -1135,6 +1164,7 @@ def __init__(self, cipherSuite, version=None): self.version = version self.srp_A = 0 self.dh_Yc = 0 + self.ecdh_Yc = bytearray(0) self.encryptedPreMasterSecret = bytearray(0) def createSRP(self, srp_A): @@ -1173,7 +1203,19 @@ def createDH(self, dh_Yc): """ self.dh_Yc = dh_Yc return self - + + def createECDH(self, ecdh_Yc): + """ + Set the client ECDH key share + + returns self + + @type ecdh_Yc: bytearray + @rtype: L{ClientKeyExchange} + """ + self.ecdh_Yc = ecdh_Yc + return self + def parse(self, parser): """ Deserialise the message from L{Parser} @@ -1196,6 +1238,8 @@ def parse(self, parser): raise AssertionError() elif self.cipherSuite in CipherSuite.dhAllSuites: self.dh_Yc = bytesToNumber(parser.getVarBytes(2)) + elif self.cipherSuite in CipherSuite.ecdhAllSuites: + self.ecdh_Yc = parser.getVarBytes(1) else: raise AssertionError() parser.stopLengthCheck() @@ -1219,6 +1263,8 @@ def write(self): raise AssertionError() elif self.cipherSuite in CipherSuite.dhAllSuites: w.addVarSeq(numberToByteArray(self.dh_Yc), 1, 2) + elif self.cipherSuite in CipherSuite.ecdhAllSuites: + w.addVarSeq(self.ecdh_Yc, 1, 1) else: raise AssertionError() return self.postWrite(w) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index b221c0d9..4f64e08c 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -26,6 +26,9 @@ from .handshakesettings import HandshakeSettings from .utils.tackwrapper import * from .utils.rsakey import RSAKey +from .utils.ecc import decodeX962Point, encodeX962Point, getCurveByName, \ + getPointByteSize +import ecdsa class KeyExchange(object): @@ -304,6 +307,87 @@ def makeClientKeyExchange(self): clientKeyExchange.createDH(self.dh_Yc) return clientKeyExchange +# The ECDHE_RSA part comes from the IETF names of ciphersuites, so we want to +# keep it +#pylint: disable = invalid-name +class ECDHE_RSAKeyExchange(KeyExchange): + """Helper class for conducting ECDHE key exchange""" + + def __init__(self, cipherSuite, clientHello, serverHello, privateKey, + acceptedCurves): + super(ECDHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello, privateKey) +#pylint: enable = invalid-name + self.ecdhXs = None + self.acceptedCurves = acceptedCurves + self.group_id = None + self.ecdhYc = None + + def makeServerKeyExchange(self, sigHash=None): + """Create ECDHE version of Server Key Exchange""" + #Get client supported groups + client_curves = self.clientHello.getExtension(\ + ExtensionType.supported_groups) + if client_curves is None or client_curves.groups is None or \ + len(client_curves.groups) == 0: + raise TLSInternalError("Can't do ECDHE with no client curves") + client_curves = client_curves.groups + + #Pick first client preferred group we support + self.group_id = next((x for x in client_curves \ + if x in self.acceptedCurves), + None) + if self.group_id is None: + raise TLSInsufficientSecurity("No mutual groups") + generator = getCurveByName(GroupName.toRepr(self.group_id)).generator + self.ecdhXs = ecdsa.util.randrange(generator.order()) + + ecdhYs = encodeX962Point(generator * self.ecdhXs) + + version = self.serverHello.server_version + serverKeyExchange = ServerKeyExchange(self.cipherSuite, version) + serverKeyExchange.createECDH(ECCurveType.named_curve, + named_curve=self.group_id, + point=ecdhYs) + self.signServerKeyExchange(serverKeyExchange, sigHash) + return serverKeyExchange + + def processClientKeyExchange(self, clientKeyExchange): + """Calculate premaster secret from previously generated SKE and CKE""" + curveName = GroupName.toRepr(self.group_id) + ecdhYc = decodeX962Point(clientKeyExchange.ecdh_Yc, + getCurveByName(curveName)) + + sharedSecret = ecdhYc * self.ecdhXs + + return numberToByteArray(sharedSecret.x(), getPointByteSize(ecdhYc)) + + def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): + """Process the server key exchange, return premaster secret""" + del srvPublicKey + + if serverKeyExchange.curve_type != ECCurveType.named_curve \ + or serverKeyExchange.named_curve not in self.acceptedCurves: + raise TLSIllegalParameterException("Server picked curve we " + "didn't advertise") + + curveName = GroupName.toStr(serverKeyExchange.named_curve) + curve = getCurveByName(curveName) + generator = curve.generator + + ecdhXc = ecdsa.util.randrange(generator.order()) + ecdhYs = decodeX962Point(serverKeyExchange.ecdh_Ys, curve) + self.ecdhYc = encodeX962Point(generator * ecdhXc) + S = ecdhYs * ecdhXc + return numberToByteArray(S.x(), getPointByteSize(S)) + + def makeClientKeyExchange(self): + """Make client key exchange for ECDHE""" + clientKeyExchange = ClientKeyExchange(self.cipherSuite, + self.serverHello.server_version) + clientKeyExchange.createECDH(self.ecdhYc) + return clientKeyExchange + class SRPKeyExchange(KeyExchange): """Helper class for conducting SRP key exchange""" @@ -434,6 +518,9 @@ def __init__(self, sock): @type sock: L{socket.socket} """ TLSRecordLayer.__init__(self, sock) + self.serverSigAlg = None + self.ecdhCurve = None + self.dhGroupSize = None #********************************************************* # Client Handshake Functions @@ -832,6 +919,12 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, keyExchange = DHE_RSAKeyExchange(cipherSuite, clientHello, serverHello, None) + elif cipherSuite in CipherSuite.ecdhAllSuites: + acceptedCurves = self._curveNamesToList(settings) + keyExchange = ECDHE_RSAKeyExchange(cipherSuite, clientHello, + serverHello, None, + acceptedCurves) + #If the server selected a certificate-based RSA ciphersuite, #the client finishes reading the post-ServerHello messages. If #a CertificateRequest message was sent, the client responds with @@ -866,7 +959,7 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, if result in (0,1): yield result else: break masterSecret = result - + # Create the session object which is used for resumptions self.session = Session() self.session.create(masterSecret, serverHello.session_id, cipherSuite, @@ -885,12 +978,13 @@ def _clientSendClientHello(self, settings, session, srpUsername, if srpParams: cipherSuites += CipherSuite.getSrpAllSuites(settings) elif certParams: + cipherSuites += CipherSuite.getEcdheCertSuites(settings) cipherSuites += CipherSuite.getDheCertSuites(settings) cipherSuites += CipherSuite.getCertSuites(settings) elif anonParams: cipherSuites += CipherSuite.getAnonSuites(settings) else: - assert(False) + assert False #Add any SCSVs. These are not real cipher suites, but signaling #values which reuse the cipher suite field in the ClientHello. @@ -908,6 +1002,13 @@ def _clientSendClientHello(self, settings, session, srpUsername, extensions.append(TLSExtension().\ create(ExtensionType.encrypt_then_mac, bytearray(0))) + #Send the ECC extensions only if we advertise ECC ciphers + if next((cipher for cipher in cipherSuites \ + if cipher in CipherSuite.ecdhAllSuites), None) is not None: + extensions.append(SupportedGroupsExtension().\ + create(self._curveNamesToList(settings))) + extensions.append(ECPointFormatsExtension().\ + create([ECPointFormat.uncompressed])) # In TLS1.2 advertise support for additional signature types if settings.maxVersion >= (3, 3): sigList = self._sigHashesToList(settings) @@ -1146,6 +1247,19 @@ def _clientKeyExchange(self, settings, cipherSuite, AlertDescription.decrypt_error): yield result + if serverKeyExchange: + # store key exchange metadata for user applications + if self.version >= (3, 3) \ + and cipherSuite in CipherSuite.certAllSuites \ + and cipherSuite not in CipherSuite.certSuites: + self.serverSigAlg = (serverKeyExchange.hashAlg, + serverKeyExchange.signAlg) + + if cipherSuite in CipherSuite.dhAllSuites: + self.dhGroupSize = numBits(serverKeyExchange.dh_p) + if cipherSuite in CipherSuite.ecdhAllSuites: + self.ecdhCurve = serverKeyExchange.named_curve + #Send Certificate if we were asked for it if certificateRequest: @@ -1498,7 +1612,8 @@ def _handshakeServerAsyncHelper(self, verifierDB, # Perform a certificate-based key exchange elif (cipherSuite in CipherSuite.certSuites or - cipherSuite in CipherSuite.dheCertSuites): + cipherSuite in CipherSuite.dheCertSuites or + cipherSuite in CipherSuite.ecdheCertSuites): if cipherSuite in CipherSuite.certSuites: keyExchange = RSAKeyExchange(cipherSuite, clientHello, @@ -1509,6 +1624,13 @@ def _handshakeServerAsyncHelper(self, verifierDB, clientHello, serverHello, privateKey) + elif cipherSuite in CipherSuite.ecdheCertSuites: + acceptedCurves = self._curveNamesToList(settings) + keyExchange = ECDHE_RSAKeyExchange(cipherSuite, + clientHello, + serverHello, + privateKey, + acceptedCurves) else: assert(False) for result in self._serverCertKeyExchange(clientHello, serverHello, @@ -1601,8 +1723,19 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, AlertDescription.inappropriate_fallback): yield result + #Check if there's intersection between supported curves by client and + #server + client_groups = clientHello.getExtension(ExtensionType.supported_groups) + group_intersect = [] + if client_groups is not None: + client_groups = client_groups.groups + if client_groups is None: + client_groups = [] + server_groups = self._curveNamesToList(settings) + group_intersect = [x for x in client_groups if x in server_groups] + #Now that the version is known, limit to only the ciphers available to - #that version. + #that version and client capabilities. cipherSuites = [] if verifierDB: if certChain: @@ -1610,6 +1743,9 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, CipherSuite.getSrpCertSuites(settings, self.version) cipherSuites += CipherSuite.getSrpSuites(settings, self.version) elif certChain: + if len(group_intersect) > 0: + cipherSuites += CipherSuite.getEcdheCertSuites(settings, + self.version) cipherSuites += CipherSuite.getDheCertSuites(settings, self.version) cipherSuites += CipherSuite.getCertSuites(settings, self.version) elif anon: @@ -2121,3 +2257,8 @@ def _sigHashesToList(settings): sigAlgs.append((getattr(HashAlgorithm, hashName), SignatureAlgorithm.rsa)) return sigAlgs + + @staticmethod + def _curveNamesToList(settings): + """Convert list of acceptable curves to array identifiers""" + return [getattr(GroupName, val) for val in settings.eccCurves] diff --git a/tlslite/utils/compat.py b/tlslite/utils/compat.py index 95f7498d..2e17350a 100644 --- a/tlslite/utils/compat.py +++ b/tlslite/utils/compat.py @@ -7,6 +7,7 @@ import os import math import binascii +import ecdsa if sys.version_info >= (3,0): @@ -91,3 +92,10 @@ def formatExceptionTrace(e): newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) return newStr +try: + # Fedora and Red Hat Enterprise Linux versions have small curves removed + getattr(ecdsa, 'NIST192p') +except AttributeError: + ecdsaAllCurves = False +else: + ecdsaAllCurves = True diff --git a/tlslite/utils/ecc.py b/tlslite/utils/ecc.py new file mode 100644 index 00000000..87d875a4 --- /dev/null +++ b/tlslite/utils/ecc.py @@ -0,0 +1,60 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. +"""Methods for dealing with ECC points""" + +from .codec import Parser, Writer +from .cryptomath import bytesToNumber, numberToByteArray, numBytes +from .compat import ecdsaAllCurves +import ecdsa + +def decodeX962Point(data, curve=ecdsa.NIST256p): + """Decode a point from a X9.62 encoding""" + parser = Parser(data) + encFormat = parser.get(1) + assert encFormat == 4 + bytelength = getPointByteSize(curve) + xCoord = bytesToNumber(parser.getFixBytes(bytelength)) + yCoord = bytesToNumber(parser.getFixBytes(bytelength)) + return ecdsa.ellipticcurve.Point(curve.curve, xCoord, yCoord) + +def encodeX962Point(point): + """Encode a point in X9.62 format""" + bytelength = numBytes(point.curve().p()) + writer = Writer() + writer.add(4, 1) + writer.bytes += numberToByteArray(point.x(), bytelength) + writer.bytes += numberToByteArray(point.y(), bytelength) + return writer.bytes + +def getCurveByName(curveName): + """Return curve identified by curveName""" + curveMap = {'secp256r1':ecdsa.NIST256p, + 'secp384r1':ecdsa.NIST384p, + 'secp521r1':ecdsa.NIST521p, + 'secp256k1':ecdsa.SECP256k1} + if ecdsaAllCurves: + curveMap['secp224r1'] = ecdsa.NIST224p + curveMap['secp192r1'] = ecdsa.NIST192p + + if curveName in curveMap: + return curveMap[curveName] + else: + raise ValueError("Curve of name '{0}' unknown".format(curveName)) + +def getPointByteSize(point): + """Convert the point or curve bit size to bytes""" + curveMap = {ecdsa.NIST256p.curve: 256//8, + ecdsa.NIST384p.curve: 384//8, + ecdsa.NIST521p.curve: (521+7)//8, + ecdsa.SECP256k1.curve: 256//8} + if ecdsaAllCurves: + curveMap[ecdsa.NIST224p.curve] = 224//8 + curveMap[ecdsa.NIST192p.curve] = 192//8 + + if hasattr(point, 'curve'): + if callable(point.curve): + return curveMap[point.curve()] + else: + return curveMap[point.curve] + raise ValueError("Parameter must be a curve or point on curve") diff --git a/unit_tests/test_tlslite_constants.py b/unit_tests/test_tlslite_constants.py index 2df856d4..a115eca2 100644 --- a/unit_tests/test_tlslite_constants.py +++ b/unit_tests/test_tlslite_constants.py @@ -10,7 +10,7 @@ import unittest from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ - ContentType, AlertDescription, AlertLevel, HandshakeType + ContentType, AlertDescription, AlertLevel, HandshakeType, GroupName class TestHashAlgorithm(unittest.TestCase): @@ -43,6 +43,11 @@ def test_toStr_with_invalid_value(self): self.assertEqual(ContentType.toStr((20, 21, 22, 23)), '(20, 21, 22, 23)') +class TestGroupName(unittest.TestCase): + + def test_toRepr(self): + self.assertEqual(GroupName.toRepr(256), 'ffdhe2048') + class TestAlertDescription(unittest.TestCase): def test_toRepr(self): self.assertEqual(AlertDescription.toStr(40), 'handshake_failure') diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index 35d70a0b..5e7003aa 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -201,3 +201,9 @@ def test_no_signature_hashes_set_with_TLS1_1(self): hs.rsaSigHashes = [] hs.maxVersion = (3, 2) self.assertIsNotNone(hs.validate()) + + def test_invalid_curve_name(self): + hs = HandshakeSettings() + hs.eccCurves = ['P-256'] + with self.assertRaises(ValueError): + hs.validate() diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index ae0dc0dd..24f3e195 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -13,7 +13,7 @@ from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ - HashAlgorithm, SignatureAlgorithm + HashAlgorithm, SignatureAlgorithm, ECCurveType, GroupName from tlslite.extensions import SNIExtension, ClientCertTypeExtension, \ SRPExtension, TLSExtension from tlslite.errors import TLSInternalError @@ -1187,6 +1187,20 @@ def test_createDH(self): b'\x00\x09' + b'\x01' + b'\x00'*7 + b'\x03')) + def test_createECDH(self): + cke = ClientKeyExchange(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) + + cke.createECDH(bytearray(b'\x04\xff\xab')) + + bts = cke.write() + + self.assertEqual(bts, bytearray( + b'\x10' # type - CKE + b'\x00\x00\x04' # overall length + b'\x03' # length of point encoding + b'\x04\xff\xab' # point encoding + )) + def test_createRSA_with_unset_protocol(self): cke = ClientKeyExchange(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA) @@ -1272,6 +1286,19 @@ def test_parse_with_DH(self): self.assertEqual(2**56, cke.dh_Yc) + def test_parse_with_ECDH(self): + cke = ClientKeyExchange(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) + + parser = Parser(bytearray( + b'\x00\x00\x04' + b'\x03' + b'\x04\xff\xcd' + )) + + cke.parse(parser) + + self.assertEqual(cke.ecdh_Yc, bytearray(b'\x04\xff\xcd')) + def test_parse_with_unknown_cipher(self): cke = ClientKeyExchange(0) @@ -1451,6 +1478,31 @@ def test_createDH(self): b'\x10' # Ys value )) + def test_createECDH(self): + ske = ServerKeyExchange(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + (3, 3)) + + point = bytearray(b'\x04\x0a\x0a\x0b\x0b') + ske.createECDH(ECCurveType.named_curve, + named_curve=GroupName.secp256r1, + point=point) + ske.hashAlg = HashAlgorithm.sha1 + ske.signAlg = SignatureAlgorithm.rsa + ske.signature = bytearray(b'\xff'*16) + + self.assertEqual(ske.write(), bytearray( + b'\x0c' # message type - SKE + b'\x00\x00\x1d' # overall length + b'\x03' # named_curve + b'\x00\x17' # secp256r1 + b'\x05' # length of point encoding + b'\x04\x0a\x0a\x0b\x0b' # point + b'\x02\x01' # RSA+SHA1 + b'\x00\x10' # length of signature + # signature: + b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' + )) + def test_parse_with_unknown_cipher(self): ske = ServerKeyExchange(0, (3, 1)) @@ -1584,6 +1636,29 @@ def test_parser_with_DH(self): self.assertEqual(ske.dh_g, 2) self.assertEqual(ske.dh_Ys, 16) + def test_parser_with_ECDH(self): + ske = ServerKeyExchange(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + (3, 3)) + + parser = Parser(bytearray( + b'\x00\x00\x1d' # overall length + b'\x03' # named_curve + b'\x00\x17' # secp256r1 + b'\x05' # length of point encoding + b'\x04\x0a\x0a\x0b\x0b' # point + b'\x02\x01' # RSA+SHA1 + b'\x00\x10' # length of signature + # signature: + b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' + )) + + ske.parse(parser) + + self.assertEqual(ske.curve_type, ECCurveType.named_curve) + self.assertEqual(ske.named_curve, GroupName.secp256r1) + self.assertEqual(ske.ecdh_Ys, bytearray(b'\x04\x0a\x0a\x0b\x0b')) + self.assertEqual(ske.signature, bytearray(b'\xff'*16)) + def test_hash(self): ske = ServerKeyExchange( CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index 62baeb75..a28327e1 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -14,11 +14,12 @@ from tlslite.messages import ServerHello, Certificate, ServerHelloDone, \ ClientHello, ServerKeyExchange, CertificateRequest, ClientKeyExchange from tlslite.constants import CipherSuite, CertificateType, AlertDescription, \ - HashAlgorithm, SignatureAlgorithm + HashAlgorithm, SignatureAlgorithm, GroupName from tlslite.tlsconnection import TLSConnection, KeyExchange, RSAKeyExchange, \ - DHE_RSAKeyExchange, SRPKeyExchange + DHE_RSAKeyExchange, SRPKeyExchange, ECDHE_RSAKeyExchange from tlslite.errors import TLSLocalAlert, TLSIllegalParameterException, \ - TLSDecryptionFailed, TLSInsufficientSecurity, TLSUnknownPSKIdentity + TLSDecryptionFailed, TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ + TLSInternalError from tlslite.x509 import X509 from tlslite.x509certchain import X509CertChain from tlslite.utils.keyfactory import parsePEMKey @@ -28,6 +29,10 @@ from tlslite.mathtls import makeX, makeU, makeK from tlslite.handshakehashes import HandshakeHashes from tlslite import VerifierDB +from tlslite.extensions import SupportedGroupsExtension, SNIExtension +from tlslite.utils.ecc import getCurveByName, decodeX962Point, encodeX962Point,\ + getPointByteSize +import ecdsa from unit_tests.mocksock import MockSocket @@ -763,8 +768,99 @@ def test_client_SRP_key_exchange_with_invalid_params(self): with self.assertRaises(TLSIllegalParameterException): client_keyExchange.processServerKeyExchange(None, keyExchange) -class TestTLSConnection(unittest.TestCase): +class TestECDHE_RSAKeyExchange(unittest.TestCase): + def setUp(self): + self.srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = srv_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + ext = [SupportedGroupsExtension().create([GroupName.secp256r1])] + self.client_hello = ClientHello().create((3, 3), + bytearray(32), + bytearray(0), + [], + extensions=ext) + self.server_hello = ServerHello().create((3, 3), + bytearray(32), + bytearray(0), + self.cipher_suite) + + self.keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key, + [GroupName.secp256r1]) + + def test_ECDHE_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + KeyExchange.verifyServerKeyExchange(srv_key_ex, + self.srv_pub_key, + self.client_hello.random, + self.server_hello.random, + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + curveName = GroupName.toStr(srv_key_ex.named_curve) + curve = getCurveByName(curveName) + generator = curve.generator + cln_Xc = ecdsa.util.randrange(generator.order()) + cln_Ys = decodeX962Point(srv_key_ex.ecdh_Ys, curve) + cln_Yc = encodeX962Point(generator * cln_Xc) + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)) + cln_key_ex.createECDH(cln_Yc) + cln_S = cln_Ys * cln_Xc + cln_premaster = numberToByteArray(cln_S.x(), + getPointByteSize(cln_S)) + + srv_premaster = self.keyExchange.processClientKeyExchange(cln_key_ex) + + self.assertEqual(cln_premaster, srv_premaster) + + def test_ECDHE_key_exchange_with_missing_curves(self): + self.client_hello.extensions = [SNIExtension().create(bytearray(b"a"))] + with self.assertRaises(TLSInternalError): + self.keyExchange.makeServerKeyExchange('sha1') + + def test_ECDHE_key_exchange_with_no_mutual_curves(self): + ext = SupportedGroupsExtension().create([GroupName.secp160r1]) + self.client_hello.extensions = [ext] + with self.assertRaises(TLSInsufficientSecurity): + self.keyExchange.makeServerKeyExchange('sha1') + + def test_client_ECDHE_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.secp256r1]) + client_premaster = client_keyExchange.processServerKeyExchange(\ + None, + srv_key_ex) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) + + def test_client_ECDHE_key_exchange_with_invalid_server_curve(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + srv_key_ex.named_curve = GroupName.secp384r1 + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.secp256r1]) + with self.assertRaises(TLSIllegalParameterException): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) + +class TestTLSConnection(unittest.TestCase): def test_client_with_server_responing_with_SHA256_on_TLSv1_1(self): # socket to generate the faux response diff --git a/unit_tests/test_tlslite_utils_ecc.py b/unit_tests/test_tlslite_utils_ecc.py new file mode 100644 index 00000000..4c0acb50 --- /dev/null +++ b/unit_tests/test_tlslite_utils_ecc.py @@ -0,0 +1,174 @@ + +# Copyright (c) 2014, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.utils.ecc import decodeX962Point, encodeX962Point, getCurveByName,\ + getPointByteSize +import ecdsa + +class TestEncoder(unittest.TestCase): + def test_encode_P_256_point(self): + point = ecdsa.NIST256p.generator * 200 + + self.assertEqual(encodeX962Point(point), + bytearray(b'\x04' + # x coordinate + b'\x3a\x53\x5b\xd0\xbe\x46\x6f\xf3\xd8\x56' + b'\xa0\x77\xaa\xd9\x50\x4f\x16\xaa\x5d\x52' + b'\x28\xfc\xd7\xc2\x77\x48\x85\xee\x21\x3f' + b'\x3b\x34' + # y coordinate + b'\x66\xab\xa8\x18\x5b\x33\x41\xe0\xc2\xe3' + b'\xd1\xb3\xae\x69\xe4\x7d\x0f\x01\xd4\xbb' + b'\xd7\x06\xd9\x57\x8b\x0b\x65\xd6\xd3\xde' + b'\x1e\xfe' + )) + + def test_encode_P_256_point_with_zero_first_byte_on_x(self): + point = ecdsa.NIST256p.generator * 379 + + self.assertEqual(encodeX962Point(point), + bytearray(b'\x04' + b'\x00\x55\x43\x89\x4a\xf3\xd0\x0e\xd7\xd7' + b'\x40\xab\xdb\xd7\x5c\x96\xb0\x68\x77\xb7' + b'\x87\xdb\x5f\x70\xee\xa7\x8b\x90\xa8\xd7' + b'\xc0\x0a' + b'\xbb\x4c\x85\xa3\xd8\xea\x29\xef\xaa\xfa' + b'\x24\x40\x69\x12\xdd\x84\xd5\xb1\x4d\xc3' + b'\x2b\xf6\x56\xef\x6c\x6b\xd5\x8a\x5d\x94' + b'\x3f\x92' + )) + + def test_encode_P_256_point_with_zero_first_byte_on_y(self): + point = ecdsa.NIST256p.generator * 43 + + self.assertEqual(encodeX962Point(point), + bytearray(b'\x04' + b'\x98\x6a\xe2\x50\x6f\x1f\xf1\x04\xd0\x42' + b'\x30\x86\x1d\x8f\x4b\x49\x8f\x4b\xc4\xc6' + b'\xd0\x09\xb3\x0f\x75\x44\xdc\x12\x9b\x82' + b'\xd2\x8d' + b'\x00\x3c\xcc\xc0\xa6\x46\x0e\x0a\xe3\x28' + b'\xa4\xd9\x7d\x3c\x7b\x61\xd8\x6f\xc6\x28' + b'\x9c\x18\x9f\x25\x25\x11\x0c\x44\x1b\xb0' + b'\x7e\x97' + )) + + def test_encode_P_256_point_with_two_zero_first_bytes_on_x(self): + point = ecdsa.NIST256p.generator * 40393 + + self.assertEqual(encodeX962Point(point), + bytearray(b'\x04' + b'\x00\x00\x3f\x5f\x17\x8a\xa0\x70\x6c\x42' + b'\x31\xeb\x6e\x54\x95\xaa\x16\x42\xc5\xb8' + b'\xa9\x94\x12\x7c\x89\x46\x5f\x22\x99\x4a' + b'\x42\xf9' + b'\xc2\x48\xb3\x37\x59\x9f\x0c\x2f\x29\x77' + b'\x2e\x25\x6f\x1d\x55\x49\xc8\x9b\xa9\xe5' + b'\x73\x13\x82\xcd\x1e\x3c\xc0\x9d\x10\xd0' + b'\x0b\x55')) + + def test_encode_P_521_point(self): + point = ecdsa.NIST521p.generator * 200 + + self.assertEqual(encodeX962Point(point), + bytearray(b'\x04' + b'\x00\x3e\x2a\x2f\x9f\xd5\x9f\xc3\x8d\xfb' + b'\xde\x77\x26\xa0\xbf\xc6\x48\x2a\x6b\x2a' + b'\x86\xf6\x29\xb8\x34\xa0\x6c\x3d\x66\xcd' + b'\x79\x8d\x9f\x86\x2e\x89\x31\xf7\x10\xc7' + b'\xce\x89\x15\x9f\x35\x8b\x4a\x5c\x5b\xb3' + b'\xd2\xcc\x9e\x1b\x6e\x94\x36\x23\x6d\x7d' + b'\x6a\x5e\x00\xbc\x2b\xbe' + b'\x01\x56\x7a\x41\xcb\x48\x8d\xca\xd8\xe6' + b'\x3a\x3f\x95\xb0\x8a\xf6\x99\x2a\x69\x6a' + b'\x37\xdf\xc6\xa1\x93\xff\xbc\x3f\x91\xa2' + b'\x96\xf3\x3c\x66\x15\x57\x3c\x1c\x06\x7f' + b'\x0a\x06\x4d\x18\xbd\x0c\x81\x4e\xf7\x2a' + b'\x8f\x76\xf8\x7f\x9b\x7d\xff\xb2\xf4\x26' + b'\x36\x43\x43\x86\x11\x89')) + +class TestDecoder(unittest.TestCase): + def test_decode_P_256_point(self): + point = ecdsa.NIST256p.generator * 379 + data = bytearray(b'\x04' + b'\x00\x55\x43\x89\x4a\xf3\xd0\x0e\xd7\xd7' + b'\x40\xab\xdb\xd7\x5c\x96\xb0\x68\x77\xb7' + b'\x87\xdb\x5f\x70\xee\xa7\x8b\x90\xa8\xd7' + b'\xc0\x0a' + b'\xbb\x4c\x85\xa3\xd8\xea\x29\xef\xaa\xfa' + b'\x24\x40\x69\x12\xdd\x84\xd5\xb1\x4d\xc3' + b'\x2b\xf6\x56\xef\x6c\x6b\xd5\x8a\x5d\x94' + b'\x3f\x92' + ) + + decoded_point = decodeX962Point(data, ecdsa.NIST256p) + + self.assertEqual(point, decoded_point) + + def test_decode_P_521_point(self): + + data = bytearray(b'\x04' + b'\x01\x7d\x8a\x5d\x11\x03\x4a\xaf\x01\x26' + b'\x5f\x2d\xd6\x2d\x76\xeb\xd8\xbe\x4e\xfb' + b'\x3b\x4b\xd2\x05\x5a\xed\x4c\x6d\x20\xc7' + b'\xf3\xd7\x08\xab\x21\x9e\x34\xfd\x14\x56' + b'\x3d\x47\xd0\x02\x65\x15\xc2\xdd\x2d\x60' + b'\x66\xf9\x15\x64\x55\x7a\xae\x56\xa6\x7a' + b'\x28\x51\x65\x26\x5c\xcc' + b'\x01\xd4\x19\x56\xfa\x14\x6a\xdb\x83\x1c' + b'\xb6\x1a\xc4\x4b\x40\xb1\xcb\xcc\x9e\x4f' + b'\x57\x2c\xb2\x72\x70\xb9\xef\x38\x15\xae' + b'\x87\x1f\x85\x40\x94\xda\x69\xed\x97\xeb' + b'\xdc\x72\x25\x25\x61\x76\xb2\xde\xed\xa2' + b'\xb0\x5c\xca\xc4\x83\x8f\xfb\x54\xae\xe0' + b'\x07\x45\x0b\xbf\x7c\xfc') + + point = decodeX962Point(data, ecdsa.NIST521p) + self.assertIsNotNone(point) + + self.assertEqual(encodeX962Point(point), data) + + def test_decode_with_missing_data(self): + data = bytearray(b'\x04' + b'\x00\x55\x43\x89\x4a\xf3\xd0\x0e\xd7\xd7' + b'\x40\xab\xdb\xd7\x5c\x96\xb0\x68\x77\xb7' + b'\x87\xdb\x5f\x70\xee\xa7\x8b\x90\xa8\xd7' + b'\xc0\x0a' + b'\xbb\x4c\x85\xa3\xd8\xea\x29\xef\xaa\xfa' + b'\x24\x40\x69\x12\xdd\x84\xd5\xb1\x4d\xc3' + b'\x2b\xf6\x56\xef\x6c\x6b\xd5\x8a\x5d\x94' + #b'\x3f\x92' + ) + + # XXX will change later as decoder in tlslite-ng needs to be updated + with self.assertRaises(SyntaxError): + decodeX962Point(data, ecdsa.NIST256p) + +class TestCurveLookup(unittest.TestCase): + def test_with_correct_name(self): + curve = getCurveByName('secp256r1') + self.assertIs(curve, ecdsa.NIST256p) + + def test_with_invalid_name(self): + with self.assertRaises(ValueError): + getCurveByName('NIST256p') + +class TestGetPointByteSize(unittest.TestCase): + def test_with_curve(self): + self.assertEqual(getPointByteSize(ecdsa.NIST256p), 32) + + def test_with_point(self): + self.assertEqual(getPointByteSize(ecdsa.NIST384p.generator * 10), 48) + + def test_with_invalid_argument(self): + with self.assertRaises(ValueError): + getPointByteSize("P-256") From a210ba5e65c441ad673a3a8bb757553b9f6e8668 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 16:52:06 +0100 Subject: [PATCH 209/574] ECDH_anon suites in clients --- tlslite/constants.py | 44 ++++++++++++++++++++++++++++++++---- tlslite/handshakesettings.py | 2 +- tlslite/tlsconnection.py | 1 + 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index c26ed528..3d260fce 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -392,6 +392,16 @@ class CipherSuite: ietfNames[0xC013] = 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA' TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 ietfNames[0xC014] = 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA' + TLS_ECDH_ANON_WITH_NULL_SHA = 0xC015 + ietfNames[0xC015] = 'TLS_ECDH_ANON_WITH_NULL_SHA' + TLS_ECDH_ANON_WITH_RC4_128_SHA = 0xC016 + ietfNames[0xC016] = 'TLS_ECDH_ANON_WITH_RC4_128_SHA' + TLS_ECDH_ANON_WITH_3DES_EDE_CBC_SHA = 0xC017 + ietfNames[0xC017] = 'TLS_ECDH_ANON_WITH_3DES_EDE_CBC_SHA' + TLS_ECDH_ANON_WITH_AES_128_CBC_SHA = 0xC018 + ietfNames[0xC018] = 'TLS_ECDH_ANON_WITH_AES_128_CBC_SHA' + TLS_ECDH_ANON_WITH_AES_256_CBC_SHA = 0xC019 + ietfNames[0xC019] = 'TLS_ECDH_ANON_WITH_AES_256_CBC_SHA' # draft-ietf-tls-chacha20-poly1305-00 # ChaCha20/Poly1305 based Cipher Suites for TLS1.2 @@ -421,6 +431,7 @@ class CipherSuite: tripleDESSuites.append(TLS_RSA_WITH_3DES_EDE_CBC_SHA) tripleDESSuites.append(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) tripleDESSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) + tripleDESSuites.append(TLS_ECDH_ANON_WITH_3DES_EDE_CBC_SHA) # AES-128 CBC ciphers aes128Suites = [] @@ -434,6 +445,7 @@ class CipherSuite: aes128Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) aes128Suites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) + aes128Suites.append(TLS_ECDH_ANON_WITH_AES_128_CBC_SHA) # AES-256 CBC ciphers aes256Suites = [] @@ -447,6 +459,7 @@ class CipherSuite: aes256Suites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) aes256Suites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) + aes256Suites.append(TLS_ECDH_ANON_WITH_AES_256_CBC_SHA) # AES-128 GCM ciphers aes128GcmSuites = [] @@ -462,6 +475,7 @@ class CipherSuite: aes256GcmSuites.append(TLS_DH_ANON_WITH_AES_256_GCM_SHA384) aes256GcmSuites.append(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) + # CHACHA20 cipher (implicit POLY1305 authenticator) chacha20Suites = [] chacha20Suites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305) @@ -470,6 +484,7 @@ class CipherSuite: rc4Suites.append(TLS_DH_ANON_WITH_RC4_128_MD5) rc4Suites.append(TLS_RSA_WITH_RC4_128_SHA) rc4Suites.append(TLS_RSA_WITH_RC4_128_MD5) + rc4Suites.append(TLS_ECDH_ANON_WITH_RC4_128_SHA) # no encryption nullSuites = [] @@ -477,6 +492,7 @@ class CipherSuite: nullSuites.append(TLS_RSA_WITH_NULL_SHA) nullSuites.append(TLS_RSA_WITH_NULL_SHA256) nullSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) + nullSuites.append(TLS_ECDH_ANON_WITH_NULL_SHA) # SHA-1 HMAC, protocol default PRF shaSuites = [] @@ -500,6 +516,11 @@ class CipherSuite: shaSuites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) shaSuites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) shaSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) + shaSuites.append(TLS_ECDH_ANON_WITH_AES_256_CBC_SHA) + shaSuites.append(TLS_ECDH_ANON_WITH_AES_128_CBC_SHA) + shaSuites.append(TLS_ECDH_ANON_WITH_3DES_EDE_CBC_SHA) + shaSuites.append(TLS_ECDH_ANON_WITH_RC4_128_SHA) + shaSuites.append(TLS_ECDH_ANON_WITH_NULL_SHA) # SHA-256 HMAC, SHA-256 PRF sha256Suites = [] @@ -609,6 +630,8 @@ def _filterSuites(suites, settings, version=None): keyExchangeSuites += CipherSuite.srpCertSuites if "dh_anon" in keyExchangeNames: keyExchangeSuites += CipherSuite.anonSuites + if "ecdh_anon" in keyExchangeNames: + keyExchangeSuites += CipherSuite.ecdhAnonSuites return [s for s in suites if s in macSuites and s in cipherSuites and s in keyExchangeSuites] @@ -696,14 +719,14 @@ def getEcdheCertSuites(settings, version=None): # anon FFDHE key exchange anonSuites = [] + anonSuites.append(TLS_DH_ANON_WITH_AES_256_GCM_SHA384) + anonSuites.append(TLS_DH_ANON_WITH_AES_128_GCM_SHA256) + anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) + anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA) - anonSuites.append(TLS_DH_ANON_WITH_RC4_128_MD5) anonSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) - anonSuites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) - anonSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) - anonSuites.append(TLS_DH_ANON_WITH_AES_128_GCM_SHA256) - anonSuites.append(TLS_DH_ANON_WITH_AES_256_GCM_SHA384) + anonSuites.append(TLS_DH_ANON_WITH_RC4_128_MD5) @staticmethod def getAnonSuites(settings, version=None): @@ -715,6 +738,17 @@ def getAnonSuites(settings, version=None): # anon ECDHE key exchange ecdhAnonSuites = [] + ecdhAnonSuites.append(TLS_ECDH_ANON_WITH_AES_256_CBC_SHA) + ecdhAnonSuites.append(TLS_ECDH_ANON_WITH_AES_128_CBC_SHA) + ecdhAnonSuites.append(TLS_ECDH_ANON_WITH_3DES_EDE_CBC_SHA) + ecdhAnonSuites.append(TLS_ECDH_ANON_WITH_RC4_128_SHA) + ecdhAnonSuites.append(TLS_ECDH_ANON_WITH_NULL_SHA) + + @staticmethod + def getEcdhAnonSuites(settings, version=None): + """Provide anonymous ECDH ciphersuites matching settings""" + return CipherSuite._filterSuites(CipherSuite.ecdhAnonSuites, + settings, version) ecdhAllSuites = ecdheCertSuites + ecdhAnonSuites diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index c202602c..adb8ad2e 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -20,7 +20,7 @@ MAC_NAMES = ["sha", "sha256", "aead"] # Don't allow "md5" by default. ALL_MAC_NAMES = MAC_NAMES + ["md5"] KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "ecdhe_rsa", "srp_sha", "srp_sha_rsa", - "dh_anon"] + "ecdh_anon", "dh_anon"] CIPHER_IMPLEMENTATIONS = ["openssl", "pycrypto", "python"] CERTIFICATE_TYPES = ["x509"] RSA_SIGNATURE_HASHES = ["sha512", "sha384", "sha256", "sha224", "sha1"] diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 4f64e08c..7a9075aa 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -982,6 +982,7 @@ def _clientSendClientHello(self, settings, session, srpUsername, cipherSuites += CipherSuite.getDheCertSuites(settings) cipherSuites += CipherSuite.getCertSuites(settings) elif anonParams: + cipherSuites += CipherSuite.getEcdhAnonSuites(settings) cipherSuites += CipherSuite.getAnonSuites(settings) else: assert False From 28ecfee53dae9595dbddad29053d0303136cdf6c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 17:41:37 +0100 Subject: [PATCH 210/574] use a common constructor for ClientKeyExchange --- tlslite/tlsconnection.py | 28 +++++++++++------------- unit_tests/test_tlslite_tlsconnection.py | 7 +++--- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 7a9075aa..150f1567 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -64,7 +64,8 @@ def makeClientKeyExchange(self): Returns a ClientKeyExchange for the second flight from client in the handshake. """ - raise NotImplementedError() + return ClientKeyExchange(self.cipherSuite, + self.serverHello.server_version) def processClientKeyExchange(self, clientKeyExchange): """ @@ -232,8 +233,8 @@ def processServerKeyExchange(self, srvPublicKey, return premasterSecret def makeClientKeyExchange(self): - clientKeyExchange = ClientKeyExchange(self.cipherSuite, - self.serverHello.server_version) + """Return a client key exchange with clients key share""" + clientKeyExchange = super(RSAKeyExchange, self).makeClientKeyExchange() clientKeyExchange.createRSA(self.encPremasterSecret) return clientKeyExchange @@ -302,10 +303,9 @@ def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): def makeClientKeyExchange(self): """Create client key share for the key exchange""" - clientKeyExchange = ClientKeyExchange(self.cipherSuite, - self.serverHello.server_version) - clientKeyExchange.createDH(self.dh_Yc) - return clientKeyExchange + cke = super(DHE_RSAKeyExchange, self).makeClientKeyExchange() + cke.createDH(self.dh_Yc) + return cke # The ECDHE_RSA part comes from the IETF names of ciphersuites, so we want to # keep it @@ -383,10 +383,9 @@ def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): def makeClientKeyExchange(self): """Make client key exchange for ECDHE""" - clientKeyExchange = ClientKeyExchange(self.cipherSuite, - self.serverHello.server_version) - clientKeyExchange.createECDH(self.ecdhYc) - return clientKeyExchange + cke = super(ECDHE_RSAKeyExchange, self).makeClientKeyExchange() + cke.createECDH(self.ecdhYc) + return cke class SRPKeyExchange(KeyExchange): """Helper class for conducting SRP key exchange""" @@ -480,10 +479,9 @@ def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): def makeClientKeyExchange(self): """Create ClientKeyExchange""" - clientKeyExchange = ClientKeyExchange(self.cipherSuite, - self.serverHello.server_version) - clientKeyExchange.createSRP(self.A) - return clientKeyExchange + cke = super(SRPKeyExchange, self).makeClientKeyExchange() + cke.createSRP(self.A) + return cke class TLSConnection(TLSRecordLayer): """ diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index a28327e1..beb4b497 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -105,9 +105,10 @@ def test_makeServerKeyExchange(self): keyExchange.makeServerKeyExchange() def test_makeClientKeyExchange(self): - keyExchange = KeyExchange(0, None, None, None) - with self.assertRaises(NotImplementedError): - keyExchange.makeClientKeyExchange() + srv_h = ServerHello().create((3, 3), bytearray(32), bytearray(0), 0) + keyExchange = KeyExchange(0, None, srv_h, None) + self.assertIsInstance(keyExchange.makeClientKeyExchange(), + ClientKeyExchange) def test_processClientKeyExchange(self): keyExchange = KeyExchange(0, None, None, None) From 20fcb50a4ff7ec389a21dda43f229551a2bf38ca Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 18:06:35 +0100 Subject: [PATCH 211/574] break up HandshakeSettings validate method the method become rather large, group the methods and put them into separate methods --- tlslite/handshakesettings.py | 121 +++++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 47 deletions(-) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index adb8ad2e..18f01666 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -141,6 +141,73 @@ def __init__(self): self.rsaSigHashes = list(RSA_SIGNATURE_HASHES) self.eccCurves = list(CURVE_NAMES) + @staticmethod + def _sanityCheckKeySizes(other): + """Check if key size limits are sane""" + if other.minKeySize < 512: + raise ValueError("minKeySize too small") + if other.minKeySize > 16384: + raise ValueError("minKeySize too large") + if other.maxKeySize < 512: + raise ValueError("maxKeySize too small") + if other.maxKeySize > 16384: + raise ValueError("maxKeySize too large") + if other.maxKeySize < other.minKeySize: + raise ValueError("maxKeySize smaller than minKeySize") + + @staticmethod + def _sanityCheckPrimitivesNames(other): + """Check if specified cryptographic primitive names are known""" + unknownCiphers = [val for val in other.cipherNames \ + if val not in ALL_CIPHER_NAMES] + if unknownCiphers: + raise ValueError("Unknown cipher name: %s" % unknownCiphers) + + unknownMacs = [val for val in other.macNames \ + if val not in ALL_MAC_NAMES] + if unknownMacs: + raise ValueError("Unknown MAC name: %s" % unknownMacs) + + unknownKex = [val for val in other.keyExchangeNames \ + if val not in KEY_EXCHANGE_NAMES] + if unknownKex: + raise ValueError("Unknown key exchange name: %s" % unknownKex) + + unknownImpl = [val for val in other.cipherImplementations \ + if val not in CIPHER_IMPLEMENTATIONS] + if unknownImpl: + raise ValueError("Unknown cipher implementation: %s" % \ + unknownImpl) + + unknownType = [val for val in other.certificateTypes \ + if val not in CERTIFICATE_TYPES] + if unknownType: + raise ValueError("Unknown certificate type: %s" % unknownType) + + unknownCurve = [val for val in other.eccCurves \ + if val not in ALL_CURVE_NAMES] + if unknownCurve: + raise ValueError("Unknown ECC Curve name: {0}".format(unknownCurve)) + + if other.useEncryptThenMAC not in (True, False): + raise ValueError("useEncryptThenMAC can only be True or False") + + unknownSigHash = [val for val in other.rsaSigHashes \ + if val not in ALL_RSA_SIGNATURE_HASHES] + if unknownSigHash: + raise ValueError("Unknown RSA signature hash: '{0}'".\ + format(unknownSigHash)) + + @staticmethod + def _sanityCheckProtocolVersions(other): + """Check if set protocol version are sane""" + if other.minVersion > other.maxVersion: + raise ValueError("Versions set incorrectly") + if other.minVersion not in ((3, 0), (3, 1), (3, 2), (3, 3)): + raise ValueError("minVersion set incorrectly") + if other.maxVersion not in ((3, 0), (3, 1), (3, 2), (3, 3)): + raise ValueError("maxVersion set incorrectly") + def validate(self): """ Validate the settings, filter out unsupported ciphersuites and return @@ -166,10 +233,10 @@ def validate(self): other.eccCurves = self.eccCurves if not cipherfactory.tripleDESPresent: - other.cipherNames = [e for e in self.cipherNames if e != "3des"] - if len(other.cipherNames)==0: + other.cipherNames = [i for i in self.cipherNames if i != "3des"] + if len(other.cipherNames) == 0: raise ValueError("No supported ciphers") - if len(other.certificateTypes)==0: + if len(other.certificateTypes) == 0: raise ValueError("No supported certificate types") if not cryptomath.m2cryptoLoaded: @@ -178,63 +245,23 @@ def validate(self): if not cryptomath.pycryptoLoaded: other.cipherImplementations = \ [e for e in other.cipherImplementations if e != "pycrypto"] - if len(other.cipherImplementations)==0: + if len(other.cipherImplementations) == 0: raise ValueError("No supported cipher implementations") - if other.minKeySize<512: - raise ValueError("minKeySize too small") - if other.minKeySize>16384: - raise ValueError("minKeySize too large") - if other.maxKeySize<512: - raise ValueError("maxKeySize too small") - if other.maxKeySize>16384: - raise ValueError("maxKeySize too large") - if other.maxKeySize < other.minKeySize: - raise ValueError("maxKeySize smaller than minKeySize") - for s in other.cipherNames: - if s not in ALL_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) - for s in other.certificateTypes: - if s not in CERTIFICATE_TYPES: - raise ValueError("Unknown certificate type: '%s'" % s) - - if other.minVersion > other.maxVersion: - raise ValueError("Versions set incorrectly") + self._sanityCheckKeySizes(other) - if not other.minVersion in ((3,0), (3,1), (3,2), (3,3)): - raise ValueError("minVersion set incorrectly") + self._sanityCheckPrimitivesNames(other) - if not other.maxVersion in ((3,0), (3,1), (3,2), (3,3)): - raise ValueError("maxVersion set incorrectly") + self._sanityCheckProtocolVersions(other) if other.maxVersion < (3,3): # No sha-2 and AEAD pre TLS 1.2 other.macNames = [e for e in self.macNames if \ e == "sha" or e == "md5"] - if other.useEncryptThenMAC not in (True, False): - raise ValueError("useEncryptThenMAC can only be True or False") - - for val in other.rsaSigHashes: - if val not in ALL_RSA_SIGNATURE_HASHES: - raise ValueError("Unknown RSA signature hash: '{0}'".\ - format(val)) if len(other.rsaSigHashes) == 0 and other.maxVersion >= (3, 3): raise ValueError("TLS 1.2 requires signature algorithms to be set") - for val in other.eccCurves: - if val not in ALL_CURVE_NAMES: - raise ValueError("Unknown ECC Curve name: {0}".format(val)) - return other def getCertificateTypes(self): From 75e3573c23341bff26da8b9a3dc144befeeaf2da Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 18:15:55 +0100 Subject: [PATCH 212/574] small clean up in CipherSuite class since all staticmethods are standalone, accessing _filterSuites from within them accesses "external" class private method. Remake them to class methods so that the dependency is explicit --- tlslite/constants.py | 56 ++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 3d260fce..a31eb670 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -642,9 +642,10 @@ def _filterSuites(suites, settings, version=None): srpSuites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) srpSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) - @staticmethod - def getSrpSuites(settings, version=None): - return CipherSuite._filterSuites(CipherSuite.srpSuites, settings, version) + @classmethod + def getSrpSuites(cls, settings, version=None): + """Return SRP cipher suites matching settings""" + return cls._filterSuites(CipherSuite.srpSuites, settings, version) # SRP key exchange, RSA authentication srpCertSuites = [] @@ -652,15 +653,17 @@ def getSrpSuites(settings, version=None): srpCertSuites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) srpCertSuites.append(TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) - @staticmethod - def getSrpCertSuites(settings, version=None): - return CipherSuite._filterSuites(CipherSuite.srpCertSuites, settings, version) + @classmethod + def getSrpCertSuites(cls, settings, version=None): + """Return SRP cipher suites that use server certificates""" + return cls._filterSuites(CipherSuite.srpCertSuites, settings, version) srpAllSuites = srpSuites + srpCertSuites - @staticmethod - def getSrpAllSuites(settings, version=None): - return CipherSuite._filterSuites(CipherSuite.srpAllSuites, settings, version) + @classmethod + def getSrpAllSuites(cls, settings, version=None): + """Return all SRP cipher suites matching settings""" + return cls._filterSuites(CipherSuite.srpAllSuites, settings, version) # RSA key exchange, RSA authentication certSuites = [] @@ -677,9 +680,10 @@ def getSrpAllSuites(settings, version=None): certSuites.append(TLS_RSA_WITH_NULL_SHA) certSuites.append(TLS_RSA_WITH_NULL_SHA256) - @staticmethod - def getCertSuites(settings, version=None): - return CipherSuite._filterSuites(CipherSuite.certSuites, settings, version) + @classmethod + def getCertSuites(cls, settings, version=None): + """Return ciphers with RSA authentication matching settings""" + return cls._filterSuites(CipherSuite.certSuites, settings, version) # FFDHE key exchange, RSA authentication dheCertSuites = [] @@ -692,11 +696,10 @@ def getCertSuites(settings, version=None): dheCertSuites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA) dheCertSuites.append(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) - @staticmethod - def getDheCertSuites(settings, version=None): + @classmethod + def getDheCertSuites(cls, settings, version=None): """Provide authenticated DHE ciphersuites matching settings""" - return CipherSuite._filterSuites(CipherSuite.dheCertSuites, - settings, version) + return cls._filterSuites(CipherSuite.dheCertSuites, settings, version) # ECDHE key exchange, RSA authentication ecdheCertSuites = [] @@ -708,11 +711,10 @@ def getDheCertSuites(settings, version=None): ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) - @staticmethod - def getEcdheCertSuites(settings, version=None): + @classmethod + def getEcdheCertSuites(cls, settings, version=None): """Provide authenticated ECDHE ciphersuites matching settings""" - return CipherSuite._filterSuites(CipherSuite.ecdheCertSuites, settings, - version) + return cls._filterSuites(CipherSuite.ecdheCertSuites, settings, version) # RSA authentication certAllSuites = srpCertSuites + certSuites + dheCertSuites + ecdheCertSuites @@ -728,11 +730,10 @@ def getEcdheCertSuites(settings, version=None): anonSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) anonSuites.append(TLS_DH_ANON_WITH_RC4_128_MD5) - @staticmethod - def getAnonSuites(settings, version=None): + @classmethod + def getAnonSuites(cls, settings, version=None): """Provide anonymous DH ciphersuites matching settings""" - return CipherSuite._filterSuites(CipherSuite.anonSuites, - settings, version) + return cls._filterSuites(CipherSuite.anonSuites, settings, version) dhAllSuites = dheCertSuites + anonSuites @@ -744,11 +745,10 @@ def getAnonSuites(settings, version=None): ecdhAnonSuites.append(TLS_ECDH_ANON_WITH_RC4_128_SHA) ecdhAnonSuites.append(TLS_ECDH_ANON_WITH_NULL_SHA) - @staticmethod - def getEcdhAnonSuites(settings, version=None): + @classmethod + def getEcdhAnonSuites(cls, settings, version=None): """Provide anonymous ECDH ciphersuites matching settings""" - return CipherSuite._filterSuites(CipherSuite.ecdhAnonSuites, - settings, version) + return cls._filterSuites(CipherSuite.ecdhAnonSuites, settings, version) ecdhAllSuites = ecdheCertSuites + ecdhAnonSuites From b376aa8ed71286c6624c1eb131c7ee2a54af405e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 18:24:24 +0100 Subject: [PATCH 213/574] small clean up in RSAKey since all staticmethods are standalone, accessing _pkcs1Prefixes from within them accesses "external" class field. Remake them to class methods so that the dependency is explicit rather than implicit --- tlslite/utils/rsakey.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tlslite/utils/rsakey.py b/tlslite/utils/rsakey.py index a6b5ef8e..a09d56ba 100644 --- a/tlslite/utils/rsakey.py +++ b/tlslite/utils/rsakey.py @@ -221,8 +221,9 @@ def generate(bits): # Helper Functions for RSA Keys # ************************************************************************** - @staticmethod - def addPKCS1SHA1Prefix(bytes, withNULL=True): + @classmethod + def addPKCS1SHA1Prefix(cls, hashBytes, withNULL=True): + """Add PKCS#1 v1.5 algorithm identifier prefix to SHA1 hash bytes""" # There is a long history of confusion over whether the SHA1 # algorithmIdentifier should be encoded with a NULL parameter or # with the parameter omitted. While the original intention was @@ -235,8 +236,8 @@ def addPKCS1SHA1Prefix(bytes, withNULL=True): prefixBytes = bytearray([0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14]) else: - prefixBytes = RSAKey._pkcs1Prefixes['sha1'] - prefixedBytes = prefixBytes + bytes + prefixBytes = cls._pkcs1Prefixes['sha1'] + prefixedBytes = prefixBytes + hashBytes return prefixedBytes _pkcs1Prefixes = {'md5' : bytearray([0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, @@ -262,12 +263,12 @@ def addPKCS1SHA1Prefix(bytes, withNULL=True): 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40])} - @staticmethod - def addPKCS1Prefix(data, hashName): + @classmethod + def addPKCS1Prefix(cls, data, hashName): """Add the PKCS#1 v1.5 algorithm identifier prefix to hash bytes""" hashName = hashName.lower() - assert hashName in RSAKey._pkcs1Prefixes - prefixBytes = RSAKey._pkcs1Prefixes[hashName] + assert hashName in cls._pkcs1Prefixes + prefixBytes = cls._pkcs1Prefixes[hashName] return prefixBytes + data def _addPKCS1Padding(self, bytes, blockType): From 7bb95ea91a14ae794501d15ee978a37181fb35e7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 18:28:06 +0100 Subject: [PATCH 214/574] don't use len(x) to check if x is empty since all collections have a boolean representation use it to avoid optical clutter --- tlslite/tlsconnection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 150f1567..1b33ecf7 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -123,7 +123,7 @@ def verifyServerKeyExchange(serverKeyExchange, publicKey, clientRandom, hashBytes = RSAKey.addPKCS1Prefix(hashBytes, hashName) sigBytes = serverKeyExchange.signature - if len(sigBytes) == 0: + if not sigBytes: raise TLSIllegalParameterException("Empty signature") if not publicKey.verify(sigBytes, hashBytes): @@ -1015,7 +1015,7 @@ def _clientSendClientHello(self, settings, session, srpUsername, extensions.append(SignatureAlgorithmsExtension().\ create(sigList)) #don't send empty list of extensions - if len(extensions) == 0: + if not extensions: extensions = None #Either send ClientHello (with a resumable session)... @@ -1742,7 +1742,7 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, CipherSuite.getSrpCertSuites(settings, self.version) cipherSuites += CipherSuite.getSrpSuites(settings, self.version) elif certChain: - if len(group_intersect) > 0: + if group_intersect: cipherSuites += CipherSuite.getEcdheCertSuites(settings, self.version) cipherSuites += CipherSuite.getDheCertSuites(settings, self.version) From d04fbe037bbf696d5d1f858e9d3d9a532d9e2ae0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 18:31:35 +0100 Subject: [PATCH 215/574] remove excessive if statements no point in using multiple if statements if one will do --- tlslite/tlsconnection.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 1b33ecf7..5184efa6 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1278,11 +1278,8 @@ def _clientKeyExchange(self, settings, cipherSuite, if clientCertChain: #Check to make sure we have the same type of #certificates the server requested - wrongType = False - if certificateType == CertificateType.x509: - if not isinstance(clientCertChain, X509CertChain): - wrongType = True - if wrongType: + if certificateType == CertificateType.x509 \ + and not isinstance(clientCertChain, X509CertChain): for result in self._sendError(\ AlertDescription.handshake_failure, "Client certificate is of wrong type"): From 14b38f35784ecd9c39348ec9c71507fb1169d6c1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 19:04:32 +0100 Subject: [PATCH 216/574] move KeyExchange to separate module --- tlslite/keyexchange.py | 470 +++++++++++++ tlslite/tlsconnection.py | 461 +----------- unit_tests/test_tlslite_keyexchange.py | 860 +++++++++++++++++++++++ unit_tests/test_tlslite_tlsconnection.py | 815 +-------------------- 4 files changed, 1338 insertions(+), 1268 deletions(-) create mode 100644 tlslite/keyexchange.py create mode 100644 unit_tests/test_tlslite_keyexchange.py diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py new file mode 100644 index 00000000..785f5c2c --- /dev/null +++ b/tlslite/keyexchange.py @@ -0,0 +1,470 @@ +# Authors: +# Hubert Kario (2015) +# +# See the LICENSE file for legal information regarding use of this file. +"""Handling of cryptographic operations for key exchange""" + +from .mathtls import goodGroupParameters, makeK, makeU, makeX, calcMasterSecret +from .errors import TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ + TLSIllegalParameterException, TLSDecryptionFailed, TLSInternalError, \ + TLSLocalAlert +from .messages import ServerKeyExchange, ClientKeyExchange, CertificateVerify +from .constants import SignatureAlgorithm, HashAlgorithm, CipherSuite, \ + ExtensionType, GroupName, ECCurveType, AlertDescription +from .utils.ecc import decodeX962Point, encodeX962Point, getCurveByName, \ + getPointByteSize +from .utils.rsakey import RSAKey +from .utils.cryptomath import bytesToNumber, getRandomBytes, powMod, \ + numBits, numberToByteArray +import ecdsa + +class KeyExchange(object): + """ + Common API for calculating Premaster secret + + NOT stable, will get moved from this file + """ + + def __init__(self, cipherSuite, clientHello, serverHello, privateKey): + """Initialize KeyExchange. privateKey is the signing private key""" + self.cipherSuite = cipherSuite + self.clientHello = clientHello + self.serverHello = serverHello + self.privateKey = privateKey + + def makeServerKeyExchange(self, sigHash=None): + """ + Create a ServerKeyExchange object + + 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 makeClientKeyExchange(self): + """ + Create a ClientKeyExchange object + + Returns a ClientKeyExchange for the second flight from client in the + handshake. + """ + return ClientKeyExchange(self.cipherSuite, + self.serverHello.server_version) + + def processClientKeyExchange(self, clientKeyExchange): + """ + Process ClientKeyExchange and return premaster secret + + Processes the client's ClientKeyExchange message and returns the + premaster secret. Raises TLSLocalAlert on error. + """ + raise NotImplementedError() + + def processServerKeyExchange(self, srvPublicKey, + serverKeyExchange): + """Process the server KEX and return premaster secret""" + raise NotImplementedError() + + def signServerKeyExchange(self, serverKeyExchange, sigHash=None): + """ + Sign a server key best matching supported algorithms + + @type sigHash: str + @param sigHash: name of the hash used for signing + """ + if self.serverHello.server_version >= (3, 3): + serverKeyExchange.signAlg = SignatureAlgorithm.rsa + serverKeyExchange.hashAlg = getattr(HashAlgorithm, sigHash) + hashBytes = serverKeyExchange.hash(self.clientHello.random, + self.serverHello.random) + + if self.serverHello.server_version >= (3, 3): + hashBytes = RSAKey.addPKCS1Prefix(hashBytes, sigHash) + + serverKeyExchange.signature = self.privateKey.sign(hashBytes) + + @staticmethod + def verifyServerKeyExchange(serverKeyExchange, publicKey, clientRandom, + serverRandom, validSigAlgs): + """Verify signature on the Server Key Exchange message + + the only acceptable signature algorithms are specified by validSigAlgs + """ + if serverKeyExchange.version >= (3, 3): + if (serverKeyExchange.hashAlg, serverKeyExchange.signAlg) not in \ + validSigAlgs: + raise TLSIllegalParameterException("Server selected " + "invalid signature " + "algorithm") + assert serverKeyExchange.signAlg == SignatureAlgorithm.rsa + hashName = HashAlgorithm.toRepr(serverKeyExchange.hashAlg) + if hashName is None: + raise TLSIllegalParameterException("Unknown signature " + "algorithm") + hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) + + if serverKeyExchange.version == (3, 3): + hashBytes = RSAKey.addPKCS1Prefix(hashBytes, hashName) + + sigBytes = serverKeyExchange.signature + if not sigBytes: + raise TLSIllegalParameterException("Empty signature") + + if not publicKey.verify(sigBytes, hashBytes): + raise TLSDecryptionFailed("Server Key Exchange signature " + "invalid") + + @staticmethod + def calcVerifyBytes(version, handshakeHashes, signatureAlg, + premasterSecret, clientRandom, serverRandom): + """Calculate signed bytes for Certificate Verify""" + if version == (3, 0): + masterSecret = calcMasterSecret(version, + 0, + premasterSecret, + clientRandom, + serverRandom) + verifyBytes = handshakeHashes.digestSSL(masterSecret, b"") + elif version in ((3, 1), (3, 2)): + verifyBytes = handshakeHashes.digest() + elif version == (3, 3): + hashName = HashAlgorithm.toRepr(signatureAlg[0]) + verifyBytes = handshakeHashes.digest(hashName) + verifyBytes = RSAKey.addPKCS1Prefix(verifyBytes, hashName) + return verifyBytes + + @staticmethod + def makeCertificateVerify(version, handshakeHashes, validSigAlgs, + privateKey, certificateRequest, premasterSecret, + clientRandom, serverRandom): + """Create a Certificate Verify message + + @param version: protocol version in use + @param handshakeHashes: the running hash of all handshake messages + @param validSigAlgs: acceptable signature algorithms for client side, + applicable only to TLSv1.2 (or later) + @param certificateRequest: the server provided Certificate Request + message + @param premasterSecret: the premaster secret, needed only for SSLv3 + @param clientRandom: client provided random value, needed only for SSLv3 + @param serverRandom: server provided random value, needed only for SSLv3 + """ + signatureAlgorithm = None + # in TLS 1.2 we must decide which algorithm to use for signing + if version == (3, 3): + serverSigAlgs = certificateRequest.supported_signature_algs + signatureAlgorithm = next((sigAlg for sigAlg in validSigAlgs \ + if sigAlg in serverSigAlgs), None) + # if none acceptable, do a last resort: + if signatureAlgorithm is None: + signatureAlgorithm = validSigAlgs[0] + verifyBytes = KeyExchange.calcVerifyBytes(version, handshakeHashes, + signatureAlgorithm, + premasterSecret, + clientRandom, + serverRandom) + signedBytes = privateKey.sign(verifyBytes) + certificateVerify = CertificateVerify(version) + certificateVerify.create(signedBytes, signatureAlgorithm) + + return certificateVerify + +class RSAKeyExchange(KeyExchange): + """ + Handling of RSA key exchange + + NOT stable API, do NOT use + """ + + def __init__(self, cipherSuite, clientHello, serverHello, privateKey): + super(RSAKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello, privateKey) + self.encPremasterSecret = None + + def makeServerKeyExchange(self, sigHash=None): + """Don't create a server key exchange for RSA key exchange""" + return None + + def processClientKeyExchange(self, clientKeyExchange): + """Decrypt client key exchange, return premaster secret""" + 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 processServerKeyExchange(self, srvPublicKey, + serverKeyExchange): + """Generate premaster secret for server""" + del serverKeyExchange # not present in RSA key exchange + premasterSecret = getRandomBytes(48) + premasterSecret[0] = self.clientHello.client_version[0] + premasterSecret[1] = self.clientHello.client_version[1] + + self.encPremasterSecret = srvPublicKey.encrypt(premasterSecret) + return premasterSecret + + def makeClientKeyExchange(self): + """Return a client key exchange with clients key share""" + clientKeyExchange = super(RSAKeyExchange, self).makeClientKeyExchange() + clientKeyExchange.createRSA(self.encPremasterSecret) + return clientKeyExchange + +# the DHE_RSA part comes from IETF ciphersuite names, we want to keep it +#pylint: disable = invalid-name +class DHE_RSAKeyExchange(KeyExchange): + """ + Handling of ephemeral Diffe-Hellman Key exchange + + NOT stable API, do NOT use + """ + + def __init__(self, cipherSuite, clientHello, serverHello, privateKey): + super(DHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello, privateKey) +#pylint: enable = invalid-name + self.dh_Xs = None + self.dh_Yc = None + + # 2048-bit MODP Group (RFC 3526, Section 3) + dh_g, dh_p = goodGroupParameters[2] + + # RFC 3526, Section 8. + strength = 160 + + def makeServerKeyExchange(self, sigHash=None): + """Prepare server side of key exchange with selected parameters""" + # 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) + + version = self.serverHello.server_version + serverKeyExchange = ServerKeyExchange(self.cipherSuite, version) + serverKeyExchange.createDH(self.dh_p, self.dh_g, dh_Ys) + self.signServerKeyExchange(serverKeyExchange, sigHash) + return serverKeyExchange + + def processClientKeyExchange(self, clientKeyExchange): + """Use client provided parameters to establish premaster secret""" + 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) + + def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): + """Process the server key exchange, return premaster secret""" + del srvPublicKey + dh_p = serverKeyExchange.dh_p + # TODO make the minimum changeable + if dh_p < 2**1023: + raise TLSInsufficientSecurity("DH prime too small") + dh_g = serverKeyExchange.dh_g + dh_Xc = bytesToNumber(getRandomBytes(32)) + dh_Ys = serverKeyExchange.dh_Ys + self.dh_Yc = powMod(dh_g, dh_Xc, dh_p) + + S = powMod(dh_Ys, dh_Xc, dh_p) + return numberToByteArray(S) + + def makeClientKeyExchange(self): + """Create client key share for the key exchange""" + cke = super(DHE_RSAKeyExchange, self).makeClientKeyExchange() + cke.createDH(self.dh_Yc) + return cke + +# The ECDHE_RSA part comes from the IETF names of ciphersuites, so we want to +# keep it +#pylint: disable = invalid-name +class ECDHE_RSAKeyExchange(KeyExchange): + """Helper class for conducting ECDHE key exchange""" + + def __init__(self, cipherSuite, clientHello, serverHello, privateKey, + acceptedCurves): + super(ECDHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello, privateKey) +#pylint: enable = invalid-name + self.ecdhXs = None + self.acceptedCurves = acceptedCurves + self.group_id = None + self.ecdhYc = None + + def makeServerKeyExchange(self, sigHash=None): + """Create ECDHE version of Server Key Exchange""" + #Get client supported groups + client_curves = self.clientHello.getExtension(\ + ExtensionType.supported_groups) + if client_curves is None or client_curves.groups is None or \ + len(client_curves.groups) == 0: + raise TLSInternalError("Can't do ECDHE with no client curves") + client_curves = client_curves.groups + + #Pick first client preferred group we support + self.group_id = next((x for x in client_curves \ + if x in self.acceptedCurves), + None) + if self.group_id is None: + raise TLSInsufficientSecurity("No mutual groups") + generator = getCurveByName(GroupName.toRepr(self.group_id)).generator + self.ecdhXs = ecdsa.util.randrange(generator.order()) + + ecdhYs = encodeX962Point(generator * self.ecdhXs) + + version = self.serverHello.server_version + serverKeyExchange = ServerKeyExchange(self.cipherSuite, version) + serverKeyExchange.createECDH(ECCurveType.named_curve, + named_curve=self.group_id, + point=ecdhYs) + self.signServerKeyExchange(serverKeyExchange, sigHash) + return serverKeyExchange + + def processClientKeyExchange(self, clientKeyExchange): + """Calculate premaster secret from previously generated SKE and CKE""" + curveName = GroupName.toRepr(self.group_id) + ecdhYc = decodeX962Point(clientKeyExchange.ecdh_Yc, + getCurveByName(curveName)) + + sharedSecret = ecdhYc * self.ecdhXs + + return numberToByteArray(sharedSecret.x(), getPointByteSize(ecdhYc)) + + def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): + """Process the server key exchange, return premaster secret""" + del srvPublicKey + + if serverKeyExchange.curve_type != ECCurveType.named_curve \ + or serverKeyExchange.named_curve not in self.acceptedCurves: + raise TLSIllegalParameterException("Server picked curve we " + "didn't advertise") + + curveName = GroupName.toStr(serverKeyExchange.named_curve) + curve = getCurveByName(curveName) + generator = curve.generator + + ecdhXc = ecdsa.util.randrange(generator.order()) + ecdhYs = decodeX962Point(serverKeyExchange.ecdh_Ys, curve) + self.ecdhYc = encodeX962Point(generator * ecdhXc) + S = ecdhYs * ecdhXc + return numberToByteArray(S.x(), getPointByteSize(S)) + + def makeClientKeyExchange(self): + """Make client key exchange for ECDHE""" + cke = super(ECDHE_RSAKeyExchange, self).makeClientKeyExchange() + cke.createECDH(self.ecdhYc) + return cke + +class SRPKeyExchange(KeyExchange): + """Helper class for conducting SRP key exchange""" + + def __init__(self, cipherSuite, clientHello, serverHello, privateKey, + verifierDB, srpUsername=None, password=None, settings=None): + """Link Key Exchange options with verifierDB for SRP""" + super(SRPKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello, privateKey) + self.N = None + self.v = None + self.b = None + self.B = None + self.verifierDB = verifierDB + self.A = None + self.srpUsername = srpUsername + self.password = password + self.settings = settings + + def makeServerKeyExchange(self, sigHash=None): + """Create SRP version of Server Key Exchange""" + srpUsername = self.clientHello.srp_username.decode("utf-8") + #Get parameters from username + try: + entry = self.verifierDB[srpUsername] + except KeyError: + raise TLSUnknownPSKIdentity("Unknown identity") + (self.N, g, s, self.v) = entry + + #Calculate server's ephemeral DH values (b, B) + self.b = bytesToNumber(getRandomBytes(32)) + k = makeK(self.N, g) + self.B = (powMod(g, self.b, self.N) + (k * self.v)) % self.N + + #Create ServerKeyExchange, signing it if necessary + serverKeyExchange = ServerKeyExchange(self.cipherSuite, + self.serverHello.server_version) + serverKeyExchange.createSRP(self.N, g, s, self.B) + if self.cipherSuite in CipherSuite.srpCertSuites: + self.signServerKeyExchange(serverKeyExchange, sigHash) + return serverKeyExchange + + def processClientKeyExchange(self, clientKeyExchange): + """Calculate premaster secret from Client Key Exchange and sent SKE""" + A = clientKeyExchange.srp_A + if A % self.N == 0: + raise TLSIllegalParameterException("Invalid SRP A value") + + #Calculate u + u = makeU(self.N, A, self.B) + + #Calculate premaster secret + S = powMod((A * powMod(self.v, u, self.N)) % self.N, self.b, self.N) + return numberToByteArray(S) + + def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): + """Calculate premaster secret from ServerKeyExchange""" + del srvPublicKey # irrelevant for SRP + N = serverKeyExchange.srp_N + g = serverKeyExchange.srp_g + s = serverKeyExchange.srp_s + B = serverKeyExchange.srp_B + + if (g, N) not in goodGroupParameters: + raise TLSInsufficientSecurity("Unknown group parameters") + if numBits(N) < self.settings.minKeySize: + raise TLSInsufficientSecurity("N value is too small: {0}".\ + format(numBits(N))) + if numBits(N) > self.settings.maxKeySize: + raise TLSInsufficientSecurity("N value is too large: {0}".\ + format(numBits(N))) + if B % N == 0: + raise TLSIllegalParameterException("Suspicious B value") + + #Client ephemeral value + a = bytesToNumber(getRandomBytes(32)) + self.A = powMod(g, a, N) + + #Calculate client's static DH values (x, v) + x = makeX(s, bytearray(self.srpUsername, "utf-8"), + bytearray(self.password, "utf-8")) + v = powMod(g, x, N) + + #Calculate u + u = makeU(N, self.A, B) + + #Calculate premaster secret + k = makeK(N, g) + S = powMod((B - (k*v)) % N, a+(u*x), N) + return numberToByteArray(S) + + def makeClientKeyExchange(self): + """Create ClientKeyExchange""" + cke = super(SRPKeyExchange, self).makeClientKeyExchange() + cke.createSRP(self.A) + return cke + diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 5184efa6..d671e2d2 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -6,6 +6,8 @@ # Dimitris Moraitis - Anon ciphersuites # Martin von Loewis - python 3 port # Yngve Pettersen (ported by Paul Sokolovsky) - TLS 1.2 +# Hubert Kario - complete refactoring of key exchange methods, addition +# of ECDH support # # See the LICENSE file for legal information regarding use of this file. @@ -25,463 +27,8 @@ from .mathtls import * from .handshakesettings import HandshakeSettings from .utils.tackwrapper import * -from .utils.rsakey import RSAKey -from .utils.ecc import decodeX962Point, encodeX962Point, getCurveByName, \ - getPointByteSize -import ecdsa - -class KeyExchange(object): - - """ - Common API for calculating Premaster secret - - NOT stable, will get moved from this file - """ - - 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(self, sigHash=None): - """ - Create a ServerKeyExchange object - - 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 makeClientKeyExchange(self): - """ - Create a ClientKeyExchange object - - Returns a ClientKeyExchange for the second flight from client in the - handshake. - """ - return ClientKeyExchange(self.cipherSuite, - self.serverHello.server_version) - - def processClientKeyExchange(self, clientKeyExchange): - """ - Process ClientKeyExchange and return premaster secret - - Processes the client's ClientKeyExchange message and returns the - premaster secret. Raises TLSLocalAlert on error. - """ - raise NotImplementedError() - - def processServerKeyExchange(self, srvPublicKey, - serverKeyExchange): - """Process the server KEX and return premaster secret""" - raise NotImplementedError() - - def signServerKeyExchange(self, serverKeyExchange, sigHash=None): - """ - Sign a server key best matching supported algorithms - - @type sigHash: str - @param sigHash: name of the hash used for signing - """ - if self.serverHello.server_version >= (3, 3): - serverKeyExchange.signAlg = SignatureAlgorithm.rsa - serverKeyExchange.hashAlg = getattr(HashAlgorithm, sigHash) - hashBytes = serverKeyExchange.hash(self.clientHello.random, - self.serverHello.random) - - if self.serverHello.server_version >= (3, 3): - hashBytes = RSAKey.addPKCS1Prefix(hashBytes, sigHash) - - serverKeyExchange.signature = self.privateKey.sign(hashBytes) - - @staticmethod - def verifyServerKeyExchange(serverKeyExchange, publicKey, clientRandom, - serverRandom, validSigAlgs): - """Verify signature on the Server Key Exchange message - - the only acceptable signature algorithms are specified by validSigAlgs - """ - if serverKeyExchange.version >= (3, 3): - if (serverKeyExchange.hashAlg, serverKeyExchange.signAlg) not in \ - validSigAlgs: - raise TLSIllegalParameterException("Server selected " - "invalid signature " - "algorithm") - assert serverKeyExchange.signAlg == SignatureAlgorithm.rsa - hashName = HashAlgorithm.toRepr(serverKeyExchange.hashAlg) - if hashName is None: - raise TLSIllegalParameterException("Unknown signature " - "algorithm") - hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) - - if serverKeyExchange.version == (3, 3): - hashBytes = RSAKey.addPKCS1Prefix(hashBytes, hashName) - - sigBytes = serverKeyExchange.signature - if not sigBytes: - raise TLSIllegalParameterException("Empty signature") - - if not publicKey.verify(sigBytes, hashBytes): - raise TLSDecryptionFailed("Server Key Exchange signature " - "invalid") - - @staticmethod - def calcVerifyBytes(version, handshakeHashes, signatureAlg, - premasterSecret, clientRandom, serverRandom): - """Calculate signed bytes for Certificate Verify""" - if version == (3, 0): - masterSecret = calcMasterSecret(version, - 0, - premasterSecret, - clientRandom, - serverRandom) - verifyBytes = handshakeHashes.digestSSL(masterSecret, b"") - elif version in ((3, 1), (3, 2)): - verifyBytes = handshakeHashes.digest() - elif version == (3, 3): - hashName = HashAlgorithm.toRepr(signatureAlg[0]) - verifyBytes = handshakeHashes.digest(hashName) - verifyBytes = RSAKey.addPKCS1Prefix(verifyBytes, hashName) - return verifyBytes - - @staticmethod - def makeCertificateVerify(version, handshakeHashes, validSigAlgs, - privateKey, certificateRequest, premasterSecret, - clientRandom, serverRandom): - """Create a Certificate Verify message - - @param version: protocol version in use - @param handshakeHashes: the running hash of all handshake messages - @param validSigAlgs: acceptable signature algorithms for client side, - applicable only to TLSv1.2 (or later) - @param certificateRequest: the server provided Certificate Request - message - @param premasterSecret: the premaster secret, needed only for SSLv3 - @param clientRandom: client provided random value, needed only for SSLv3 - @param serverRandom: server provided random value, needed only for SSLv3 - """ - signatureAlgorithm = None - # in TLS 1.2 we must decide which algorithm to use for signing - if version == (3, 3): - serverSigAlgs = certificateRequest.supported_signature_algs - signatureAlgorithm = next((sigAlg for sigAlg in validSigAlgs \ - if sigAlg in serverSigAlgs), None) - # if none acceptable, do a last resort: - if signatureAlgorithm is None: - signatureAlgorithm = validSigAlgs[0] - verifyBytes = KeyExchange.calcVerifyBytes(version, handshakeHashes, - signatureAlgorithm, - premasterSecret, - clientRandom, - serverRandom) - signedBytes = privateKey.sign(verifyBytes) - certificateVerify = CertificateVerify(version) - certificateVerify.create(signedBytes, signatureAlgorithm) - - return certificateVerify - -class RSAKeyExchange(KeyExchange): - """ - Handling of RSA key exchange - - NOT stable API, do NOT use - """ - - def __init__(self, cipherSuite, clientHello, serverHello, privateKey): - super(RSAKeyExchange, self).__init__(cipherSuite, clientHello, - serverHello, privateKey) - self.encPremasterSecret = None - - def makeServerKeyExchange(self, sigHash=None): - """Don't create a server key exchange for RSA key exchange""" - return None - - def processClientKeyExchange(self, clientKeyExchange): - """Decrypt client key exchange, return premaster secret""" - 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 processServerKeyExchange(self, srvPublicKey, - serverKeyExchange): - """Generate premaster secret for server""" - del serverKeyExchange # not present in RSA key exchange - premasterSecret = getRandomBytes(48) - premasterSecret[0] = self.clientHello.client_version[0] - premasterSecret[1] = self.clientHello.client_version[1] - - self.encPremasterSecret = srvPublicKey.encrypt(premasterSecret) - return premasterSecret - - def makeClientKeyExchange(self): - """Return a client key exchange with clients key share""" - clientKeyExchange = super(RSAKeyExchange, self).makeClientKeyExchange() - clientKeyExchange.createRSA(self.encPremasterSecret) - return clientKeyExchange - -# the DHE_RSA part comes from IETF ciphersuite names, we want to keep it -#pylint: disable = invalid-name -class DHE_RSAKeyExchange(KeyExchange): - """ - Handling of ephemeral Diffe-Hellman Key exchange - - NOT stable API, do NOT use - """ - - def __init__(self, cipherSuite, clientHello, serverHello, privateKey): - super(DHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, - serverHello, privateKey) -#pylint: enable = invalid-name - self.dh_Xs = None - self.dh_Yc = None - - # 2048-bit MODP Group (RFC 3526, Section 3) - dh_g, dh_p = goodGroupParameters[2] - - # RFC 3526, Section 8. - strength = 160 - - def makeServerKeyExchange(self, sigHash=None): - """Prepare server side of key exchange with selected parameters""" - # 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) - - version = self.serverHello.server_version - serverKeyExchange = ServerKeyExchange(self.cipherSuite, version) - serverKeyExchange.createDH(self.dh_p, self.dh_g, dh_Ys) - self.signServerKeyExchange(serverKeyExchange, sigHash) - return serverKeyExchange - - def processClientKeyExchange(self, clientKeyExchange): - """Use client provided parameters to establish premaster secret""" - 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) - - def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): - """Process the server key exchange, return premaster secret""" - del srvPublicKey - dh_p = serverKeyExchange.dh_p - # TODO make the minimum changeable - if dh_p < 2**1023: - raise TLSInsufficientSecurity("DH prime too small") - dh_g = serverKeyExchange.dh_g - dh_Xc = bytesToNumber(getRandomBytes(32)) - dh_Ys = serverKeyExchange.dh_Ys - self.dh_Yc = powMod(dh_g, dh_Xc, dh_p) - - S = powMod(dh_Ys, dh_Xc, dh_p) - return numberToByteArray(S) - - def makeClientKeyExchange(self): - """Create client key share for the key exchange""" - cke = super(DHE_RSAKeyExchange, self).makeClientKeyExchange() - cke.createDH(self.dh_Yc) - return cke - -# The ECDHE_RSA part comes from the IETF names of ciphersuites, so we want to -# keep it -#pylint: disable = invalid-name -class ECDHE_RSAKeyExchange(KeyExchange): - """Helper class for conducting ECDHE key exchange""" - - def __init__(self, cipherSuite, clientHello, serverHello, privateKey, - acceptedCurves): - super(ECDHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, - serverHello, privateKey) -#pylint: enable = invalid-name - self.ecdhXs = None - self.acceptedCurves = acceptedCurves - self.group_id = None - self.ecdhYc = None - - def makeServerKeyExchange(self, sigHash=None): - """Create ECDHE version of Server Key Exchange""" - #Get client supported groups - client_curves = self.clientHello.getExtension(\ - ExtensionType.supported_groups) - if client_curves is None or client_curves.groups is None or \ - len(client_curves.groups) == 0: - raise TLSInternalError("Can't do ECDHE with no client curves") - client_curves = client_curves.groups - - #Pick first client preferred group we support - self.group_id = next((x for x in client_curves \ - if x in self.acceptedCurves), - None) - if self.group_id is None: - raise TLSInsufficientSecurity("No mutual groups") - generator = getCurveByName(GroupName.toRepr(self.group_id)).generator - self.ecdhXs = ecdsa.util.randrange(generator.order()) - - ecdhYs = encodeX962Point(generator * self.ecdhXs) - - version = self.serverHello.server_version - serverKeyExchange = ServerKeyExchange(self.cipherSuite, version) - serverKeyExchange.createECDH(ECCurveType.named_curve, - named_curve=self.group_id, - point=ecdhYs) - self.signServerKeyExchange(serverKeyExchange, sigHash) - return serverKeyExchange - - def processClientKeyExchange(self, clientKeyExchange): - """Calculate premaster secret from previously generated SKE and CKE""" - curveName = GroupName.toRepr(self.group_id) - ecdhYc = decodeX962Point(clientKeyExchange.ecdh_Yc, - getCurveByName(curveName)) - - sharedSecret = ecdhYc * self.ecdhXs - - return numberToByteArray(sharedSecret.x(), getPointByteSize(ecdhYc)) - - def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): - """Process the server key exchange, return premaster secret""" - del srvPublicKey - - if serverKeyExchange.curve_type != ECCurveType.named_curve \ - or serverKeyExchange.named_curve not in self.acceptedCurves: - raise TLSIllegalParameterException("Server picked curve we " - "didn't advertise") - - curveName = GroupName.toStr(serverKeyExchange.named_curve) - curve = getCurveByName(curveName) - generator = curve.generator - - ecdhXc = ecdsa.util.randrange(generator.order()) - ecdhYs = decodeX962Point(serverKeyExchange.ecdh_Ys, curve) - self.ecdhYc = encodeX962Point(generator * ecdhXc) - S = ecdhYs * ecdhXc - return numberToByteArray(S.x(), getPointByteSize(S)) - - def makeClientKeyExchange(self): - """Make client key exchange for ECDHE""" - cke = super(ECDHE_RSAKeyExchange, self).makeClientKeyExchange() - cke.createECDH(self.ecdhYc) - return cke - -class SRPKeyExchange(KeyExchange): - """Helper class for conducting SRP key exchange""" - - def __init__(self, cipherSuite, clientHello, serverHello, privateKey, - verifierDB, srpUsername=None, password=None, settings=None): - """Link Key Exchange options with verifierDB for SRP""" - super(SRPKeyExchange, self).__init__(cipherSuite, clientHello, - serverHello, privateKey) - self.N = None - self.v = None - self.b = None - self.B = None - self.verifierDB = verifierDB - self.A = None - self.srpUsername = srpUsername - self.password = password - self.settings = settings - - def makeServerKeyExchange(self, sigHash=None): - """Create SRP version of Server Key Exchange""" - srpUsername = self.clientHello.srp_username.decode("utf-8") - #Get parameters from username - try: - entry = self.verifierDB[srpUsername] - except KeyError: - raise TLSUnknownPSKIdentity("Unknown identity") - (self.N, g, s, self.v) = entry - - #Calculate server's ephemeral DH values (b, B) - self.b = bytesToNumber(getRandomBytes(32)) - k = makeK(self.N, g) - self.B = (powMod(g, self.b, self.N) + (k * self.v)) % self.N - - #Create ServerKeyExchange, signing it if necessary - serverKeyExchange = ServerKeyExchange(self.cipherSuite, - self.serverHello.server_version) - serverKeyExchange.createSRP(self.N, g, s, self.B) - if self.cipherSuite in CipherSuite.srpCertSuites: - self.signServerKeyExchange(serverKeyExchange, sigHash) - return serverKeyExchange - - def processClientKeyExchange(self, clientKeyExchange): - """Calculate premaster secret from Client Key Exchange and sent SKE""" - A = clientKeyExchange.srp_A - if A % self.N == 0: - raise TLSIllegalParameterException("Invalid SRP A value") - - #Calculate u - u = makeU(self.N, A, self.B) - - #Calculate premaster secret - S = powMod((A * powMod(self.v, u, self.N)) % self.N, self.b, self.N) - return numberToByteArray(S) - - def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): - """Calculate premaster secret from ServerKeyExchange""" - del srvPublicKey # irrelevant for SRP - N = serverKeyExchange.srp_N - g = serverKeyExchange.srp_g - s = serverKeyExchange.srp_s - B = serverKeyExchange.srp_B - - if (g, N) not in goodGroupParameters: - raise TLSInsufficientSecurity("Unknown group parameters") - if numBits(N) < self.settings.minKeySize: - raise TLSInsufficientSecurity("N value is too small: {0}".\ - format(numBits(N))) - if numBits(N) > self.settings.maxKeySize: - raise TLSInsufficientSecurity("N value is too large: {0}".\ - format(numBits(N))) - if B % N == 0: - raise TLSIllegalParameterException("Suspicious B value") - - #Client ephemeral value - a = bytesToNumber(getRandomBytes(32)) - self.A = powMod(g, a, N) - - #Calculate client's static DH values (x, v) - x = makeX(s, bytearray(self.srpUsername, "utf-8"), - bytearray(self.password, "utf-8")) - v = powMod(g, x, N) - - #Calculate u - u = makeU(N, self.A, B) - - #Calculate premaster secret - k = makeK(N, g) - S = powMod((B - (k*v)) % N, a+(u*x), N) - return numberToByteArray(S) - - def makeClientKeyExchange(self): - """Create ClientKeyExchange""" - cke = super(SRPKeyExchange, self).makeClientKeyExchange() - cke.createSRP(self.A) - return cke +from .keyexchange import KeyExchange, RSAKeyExchange, DHE_RSAKeyExchange, \ + ECDHE_RSAKeyExchange, SRPKeyExchange class TLSConnection(TLSRecordLayer): """ diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py new file mode 100644 index 00000000..ede69103 --- /dev/null +++ b/unit_tests/test_tlslite_keyexchange.py @@ -0,0 +1,860 @@ +# Copyright (c) 2015, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.handshakesettings import HandshakeSettings +from tlslite.messages import ServerHello, ClientHello, ServerKeyExchange,\ + CertificateRequest, ClientKeyExchange +from tlslite.constants import CipherSuite, CertificateType, AlertDescription, \ + HashAlgorithm, SignatureAlgorithm, GroupName +from tlslite.errors import TLSLocalAlert, TLSIllegalParameterException, \ + TLSDecryptionFailed, TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ + TLSInternalError +from tlslite.x509 import X509 +from tlslite.x509certchain import X509CertChain +from tlslite.utils.keyfactory import parsePEMKey +from tlslite.utils.codec import Parser +from tlslite.utils.cryptomath import bytesToNumber, getRandomBytes, powMod, \ + numberToByteArray +from tlslite.mathtls import makeX, makeU, makeK +from tlslite.handshakehashes import HandshakeHashes +from tlslite import VerifierDB +from tlslite.extensions import SupportedGroupsExtension, SNIExtension +from tlslite.utils.ecc import getCurveByName, decodeX962Point, encodeX962Point,\ + getPointByteSize +import ecdsa + +from tlslite.keyexchange import KeyExchange, RSAKeyExchange, \ + DHE_RSAKeyExchange, SRPKeyExchange, ECDHE_RSAKeyExchange + +srv_raw_key = str( + "-----BEGIN RSA PRIVATE KEY-----\n"\ + "MIICXQIBAAKBgQDRCQR5qRLJX8sy1N4BF1G1fml1vNW5S6o4h3PeWDtg7JEn+jIt\n"\ + "M/NZekrGv/+3gU9C9ixImJU6U+Tz3kU27qw0X+4lDJAZ8VZgqQTp/MWJ9Dqz2Syy\n"\ + "yQWUvUNUj90P9mfuyDO5rY/VLIskdBNOzUy0xvXvT99fYQE+QPP7aRgo3QIDAQAB\n"\ + "AoGAVSLbE8HsyN+fHwDbuo4I1Wa7BRz33xQWLBfe9TvyUzOGm0WnkgmKn3LTacdh\n"\ + "GxgrdBZXSun6PVtV8I0im5DxyVaNdi33sp+PIkZU386f1VUqcnYnmgsnsUQEBJQu\n"\ + "fUZmgNM+bfR+Rfli4Mew8lQ0sorZ+d2/5fsM0g80Qhi5M3ECQQDvXeCyrcy0u/HZ\n"\ + "FNjIloyXaAIvavZ6Lc6gfznCSfHc5YwplOY7dIWp8FRRJcyXkA370l5dJ0EXj5Gx\n"\ + "udV9QQ43AkEA34+RxjRk4DT7Zo+tbM/Fkoi7jh1/0hFkU5NDHweJeH/mJseiHtsH\n"\ + "KOcPGtEGBBqT2KNPWVz4Fj19LiUmmjWXiwJBAIBs49O5/+ywMdAAqVblv0S0nweF\n"\ + "4fwne4cM+5ZMSiH0XsEojGY13EkTEon/N8fRmE8VzV85YmkbtFWgmPR85P0CQQCs\n"\ + "elWbN10EZZv3+q1wH7RsYzVgZX3yEhz3JcxJKkVzRCnKjYaUi6MweWN76vvbOq4K\n"\ + "G6Tiawm0Duh/K4ZmvyYVAkBppE5RRQqXiv1KF9bArcAJHvLm0vnHPpf1yIQr5bW6\n"\ + "njBuL4qcxlaKJVGRXT7yFtj2fj0gv3914jY2suWqp8XJ\n"\ + "-----END RSA PRIVATE KEY-----\n"\ + ) + +srv_raw_certificate = str( + "-----BEGIN CERTIFICATE-----\n"\ + "MIIB9jCCAV+gAwIBAgIJAMyn9DpsTG55MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\n"\ + "BAMMCWxvY2FsaG9zdDAeFw0xNTAxMjExNDQzMDFaFw0xNTAyMjAxNDQzMDFaMBQx\n"\ + "EjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n"\ + "0QkEeakSyV/LMtTeARdRtX5pdbzVuUuqOIdz3lg7YOyRJ/oyLTPzWXpKxr//t4FP\n"\ + "QvYsSJiVOlPk895FNu6sNF/uJQyQGfFWYKkE6fzFifQ6s9kssskFlL1DVI/dD/Zn\n"\ + "7sgzua2P1SyLJHQTTs1MtMb170/fX2EBPkDz+2kYKN0CAwEAAaNQME4wHQYDVR0O\n"\ + "BBYEFJtvXbRmxRFXYVMOPH/29pXCpGmLMB8GA1UdIwQYMBaAFJtvXbRmxRFXYVMO\n"\ + "PH/29pXCpGmLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAkOgC7LP/\n"\ + "Rd6uJXY28HlD2K+/hMh1C3SRT855ggiCMiwstTHACGgNM+AZNqt6k8nSfXc6k1gw\n"\ + "5a7SGjzkWzMaZC3ChBeCzt/vIAGlMyXeqTRhjTCdc/ygRv3NPrhUKKsxUYyXRk5v\n"\ + "g/g6MwxzXfQP3IyFu3a9Jia/P89Z1rQCNRY=\n"\ + "-----END CERTIFICATE-----\n"\ + ) + +class TestKeyExchange(unittest.TestCase): + + expected_sha1_SKE = bytearray( + b"\x0c\x00\x00\x8d\x00\x01\x05\x00\x01\x02\x00\x01\x03" + b"\x02\x01" + b"\x00\x80" + b"\xb4\xe0t\xa3\x13\x8e\xc8z\'|>\x8c\x1d\x9e\x00\x8c\x1c\x18\xd7" + b"#a\xe5\x15JH\xd5\xde\x1f\x12\xcej\x02k,4\x00V5\x04\xb3}\x92\xfc" + b"\xbd@\x0c\x03\x06\x02J\xb8*\xafR2\x10\xd6\x9a\xa9\n\x8e\xe8\xb3" + b"Y\xaf\tm\x0cZ\xbdzL\xdf:/\x91^c~\xfc\xf4_\xf3\xfdv\x00\xc1d\x97" + b"\x95\xf4A\xd1\x9e&J@\xect\xc2\xe7\xff\xfc\xdf/d\xbd\x1c\xbc\xa1" + b"f\x14\x92\x06c\xb853\xaf\xf27\xda\xd1\xf9\x97\xea\xec\x90") + + expected_tls1_1_SKE = bytearray( + b'\x0c\x00\x00\x8b\x00\x01\x05\x00\x01\x02\x00\x01\x03' + b'\x00\x80' + b'=\xdf\xfaW+\x81!Q\xc7\xbf\x11\xeeQ\x88\xb2[\xe6n\xd1\x1f\x86\xa8' + b'\xe5\xac\\\xae0\x0fg:tA\x1b*1?$\xd6;XQ\xac\xfdw\x85\xae\xdaOd' + b'\xc8\xb0X_\xae\x80\x87\x11\xb1\x08\x1c3!\xb5\xe6\xcf\x11\xbcV' + b'\x8f\n\x7f\xe7\xfa\x9a\xed!\xf0\xccF\xdf\x9c<\xe7)=\x9d\xde\x0f' + b'\n3\x9d5\x14\x05\x06nA\xa0\x19\xd5\xaa\x9d\xd1\x16\xb3\xb9\xae' + b'\xd1\xe4\xc04\xc1h\xc3\xf5/\xb2\xf6P\r\x1b"\xe9\xc9\x84&\xe1Z') + + def test___init__(self): + keyExchange = KeyExchange(0, None, None, None) + + self.assertIsNotNone(keyExchange) + + def test_makeServerKeyExchange(self): + keyExchange = KeyExchange(0, None, None, None) + with self.assertRaises(NotImplementedError): + keyExchange.makeServerKeyExchange() + + def test_makeClientKeyExchange(self): + srv_h = ServerHello().create((3, 3), bytearray(32), bytearray(0), 0) + keyExchange = KeyExchange(0, None, srv_h, None) + self.assertIsInstance(keyExchange.makeClientKeyExchange(), + ClientKeyExchange) + + def test_processClientKeyExchange(self): + keyExchange = KeyExchange(0, None, None, None) + with self.assertRaises(NotImplementedError): + keyExchange.processClientKeyExchange(None) + + def test_processServerKeyExchange(self): + keyExchange = KeyExchange(0, None, None, None) + with self.assertRaises(NotImplementedError): + keyExchange.processServerKeyExchange(None, None) + + def test_signServerKeyExchange_with_sha1_in_TLS1_2(self): + srv_private_key = parsePEMKey(srv_raw_key, private=True) + client_hello = ClientHello() + cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + server_hello = ServerHello().create((3, 3), + bytearray(32), + bytearray(0), + cipher_suite) + keyExchange = KeyExchange(cipher_suite, + client_hello, + server_hello, + srv_private_key) + + server_key_exchange = ServerKeyExchange(cipher_suite, (3, 3))\ + .createDH(5, 2, 3) + + keyExchange.signServerKeyExchange(server_key_exchange, 'sha1') + + self.assertEqual(server_key_exchange.write(), self.expected_sha1_SKE) + + def test_signServerKeyExchange_in_TLS1_1(self): + srv_private_key = parsePEMKey(srv_raw_key, private=True) + client_hello = ClientHello() + cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + server_hello = ServerHello().create((3, 2), + bytearray(32), + bytearray(0), + cipher_suite) + keyExchange = KeyExchange(cipher_suite, + client_hello, + server_hello, + srv_private_key) + server_key_exchange = ServerKeyExchange(cipher_suite, (3, 2))\ + .createDH(5, 2, 3) + + keyExchange.signServerKeyExchange(server_key_exchange) + + self.assertEqual(server_key_exchange.write(), self.expected_tls1_1_SKE) + +class TestKeyExchangeVerifyServerKeyExchange(TestKeyExchange): + def setUp(self): + self.srv_cert_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = self.srv_cert_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + self.server_key_exchange = ServerKeyExchange(self.cipher_suite, (3, 3))\ + .parse(Parser(self.expected_sha1_SKE[1:])) + self.ske_tls1_1 = ServerKeyExchange(self.cipher_suite, (3, 2))\ + .parse(Parser(self.expected_tls1_1_SKE[1:])) + + self.client_hello = ClientHello() + + def test_verifyServerKeyExchange(self): + KeyExchange.verifyServerKeyExchange(self.server_key_exchange, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + def test_verifyServerKeyExchange_with_invalid_hash(self): + with self.assertRaises(TLSIllegalParameterException): + KeyExchange.verifyServerKeyExchange(self.server_key_exchange, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + [(HashAlgorithm.sha256, + SignatureAlgorithm.rsa)]) + + def test_verifyServerKeyExchange_with_unknown_hash(self): + self.server_key_exchange.hashAlg = 244 + with self.assertRaises(TLSIllegalParameterException): + KeyExchange.verifyServerKeyExchange(self.server_key_exchange, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + [(244, + SignatureAlgorithm.rsa)]) + + def test_verifyServerKeyExchange_with_empty_signature(self): + self.server_key_exchange.signature = bytearray(0) + + with self.assertRaises(TLSIllegalParameterException): + KeyExchange.verifyServerKeyExchange(self.server_key_exchange, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + def test_verifyServerKeyExchange_with_damaged_signature(self): + self.server_key_exchange.signature[-1] ^= 0x01 + + with self.assertRaises(TLSDecryptionFailed): + KeyExchange.verifyServerKeyExchange(self.server_key_exchange, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + def test_verifyServerKeyExchange_in_TLS1_1(self): + KeyExchange.verifyServerKeyExchange(self.ske_tls1_1, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + None) + +class TestCalcVerifyBytes(unittest.TestCase): + def setUp(self): + self.handshake_hashes = HandshakeHashes() + self.handshake_hashes.update(bytearray(b'\xab'*32)) + + def test_with_TLS1_2(self): + vrfy = KeyExchange.calcVerifyBytes((3, 3), + self.handshake_hashes, + (HashAlgorithm.sha1, + SignatureAlgorithm.rsa), + None, + None, + None) + self.assertEqual(vrfy, bytearray( + # PKCS#1 prefix + b'0!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14' + # SHA1 hash + b'L3\x81\xad\x1b\xc2\x14\xc0\x8e\xba\xe4\xb8\xa2\x9d(6V1\xfb\xb0')) + + def test_with_TLS1_1(self): + vrfy = KeyExchange.calcVerifyBytes((3, 2), + self.handshake_hashes, + None, None, None, None) + + self.assertEqual(vrfy, bytearray( + # MD5 hash + b'\xe9\x9f\xb4\xd24\xe9\xf41S\xe6?\xa5\xfe\xad\x16\x14' + # SHA1 hash + b'L3\x81\xad\x1b\xc2\x14\xc0\x8e\xba\xe4\xb8\xa2\x9d(6V1\xfb\xb0' + )) + + def test_with_SSL3(self): + vrfy = KeyExchange.calcVerifyBytes((3, 0), + self.handshake_hashes, + None, + bytearray(b'\x04'*48), + bytearray(b'\xaa'*32), + bytearray(b'\xbb'*32)) + + self.assertEqual(vrfy, bytearray( + b'r_\x06\xd2(\\}v\x87\xfc\xf5\xa2h\xd6S\xd8' + b'\xed=\x9b\xe3\xd9_%qe\xa3k\xf5\x85\x0e?\x9fr\xfaML' + )) + +class TestMakeCertificateVerify(unittest.TestCase): + def setUp(self): + cert_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.clnt_pub_key = cert_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + self.clnt_private_key = parsePEMKey(srv_raw_key, private=True) + self.handshake_hashes = HandshakeHashes() + self.handshake_hashes.update(bytearray(b'\xab'*32)) + + def test_with_TLS1_2(self): + certificate_request = CertificateRequest((3, 3)) + certificate_request.create([CertificateType.x509], + [], + [(HashAlgorithm.sha256, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + certVerify = KeyExchange.makeCertificateVerify((3, 3), + self.handshake_hashes, + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha512, + SignatureAlgorithm.rsa)], + self.clnt_private_key, + certificate_request, + None, None, None) + + self.assertIsNotNone(certVerify) + self.assertEqual(certVerify.version, (3, 3)) + self.assertEqual(certVerify.signatureAlgorithm, (HashAlgorithm.sha1, + SignatureAlgorithm.rsa)) + self.assertEqual(certVerify.signature, bytearray( + b'.\x03\xa2\xf0\xa0\xfb\xbeUs\xdb\x9b\xea\xcc(\xa6:l\x84\x8e\x13' + b'\xa1\xaa\xdb1P\xe9\x06\x876\xbe+\xe92\x89\xaa\xa5EU\x07\x9a\xde' + b'\xd37\xafGCR\xdam\xa2v\xde\xceeFI\x80:ZtL\x96\xafZ\xe2\xe2\xce/' + b'\x9f\x82\xfe\xdb*\x94\xa8\xbd\xd9Hl\xdc\xc8\xbf\x9b=o\xda\x06' + b'\xfa\x9e\xbfB+05\xc6\xda\xdf\x05\xf2m[\x18\x11\xaf\x184\x12\x9d' + b'\xb4:\x9b\xc1U\x1c\xba\xa3\x05\xceOn\x0fY\xcaK*\x0b\x04\xa5' + )) + + def test_with_TLS1_2_and_no_overlap(self): + certificate_request = CertificateRequest((3, 3)) + certificate_request.create([CertificateType.x509], + [], + [(HashAlgorithm.sha256, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha224, + SignatureAlgorithm.rsa)]) + + certVerify = KeyExchange.makeCertificateVerify((3, 3), + self.handshake_hashes, + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha512, + SignatureAlgorithm.rsa)], + self.clnt_private_key, + certificate_request, + None, None, None) + + self.assertIsNotNone(certVerify) + self.assertEqual(certVerify.version, (3, 3)) + # when there's no overlap, we select the most wanted from our side + self.assertEqual(certVerify.signatureAlgorithm, (HashAlgorithm.sha1, + SignatureAlgorithm.rsa)) + self.assertEqual(certVerify.signature, bytearray( + b'.\x03\xa2\xf0\xa0\xfb\xbeUs\xdb\x9b\xea\xcc(\xa6:l\x84\x8e\x13' + b'\xa1\xaa\xdb1P\xe9\x06\x876\xbe+\xe92\x89\xaa\xa5EU\x07\x9a\xde' + b'\xd37\xafGCR\xdam\xa2v\xde\xceeFI\x80:ZtL\x96\xafZ\xe2\xe2\xce/' + b'\x9f\x82\xfe\xdb*\x94\xa8\xbd\xd9Hl\xdc\xc8\xbf\x9b=o\xda\x06' + b'\xfa\x9e\xbfB+05\xc6\xda\xdf\x05\xf2m[\x18\x11\xaf\x184\x12\x9d' + b'\xb4:\x9b\xc1U\x1c\xba\xa3\x05\xceOn\x0fY\xcaK*\x0b\x04\xa5' + )) + + def test_with_TLS1_1(self): + certificate_request = CertificateRequest((3, 2)) + certificate_request.create([CertificateType.x509], + [], + None) + + certVerify = KeyExchange.makeCertificateVerify((3, 2), + self.handshake_hashes, + None, + self.clnt_private_key, + certificate_request, + None, None, None) + + self.assertIsNotNone(certVerify) + self.assertEqual(certVerify.version, (3, 2)) + self.assertIsNone(certVerify.signatureAlgorithm) + self.assertEqual(certVerify.signature, bytearray( + b'=X\x14\xf3\r6\x0b\x84\xde&J\x15\xa02M\xc8\xf1?\xa0\x10U\x1e\x0b' + b'\x95^\xa19\x14\xf5\xf1$\xe3U[\xb4/\xe7AY(\xee]\xff\x97H\xb8\xa9' + b'\x8b\x96n\xa6\xf5\x0f\xffd\r\x08/Hs6`wi8\xc4\x02\xa4}a\xcbS\x99' + b'\x01;\x0e\x88oj\x9a\x02\x98Y\xb5\x00$f@>\xd8\x0cS\x95\xa8\x9e' + b'\x14uU\\Z\xd0.\xe7\x01_y\x1d\xea\xad\x1b\xf8c\xa6\xc9@\xc6\x90' + b'\x19~&\xd9\xaa\xc2\t,s\xde\xb1' + )) + +class TestRSAKeyExchange(unittest.TestCase): + def setUp(self): + self.srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = srv_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA + self.client_hello = ClientHello().create((3, 3), + bytearray(32), + bytearray(0), + []) + self.server_hello = ServerHello().create((3, 2), + bytearray(32), + bytearray(0), + self.cipher_suite) + + self.keyExchange = RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key) + + def test_RSA_key_exchange(self): + + self.assertIsNone(self.keyExchange.makeServerKeyExchange()) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 3 + clientKeyExchange = ClientKeyExchange(self.cipher_suite, + (3, 2)) + clientKeyExchange.createRSA(self.srv_pub_key.encrypt(premaster_secret)) + + dec_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 3 + self.assertEqual(dec_premaster, premaster_secret) + + def test_RSA_key_exchange_with_client(self): + self.assertIsNone(self.keyExchange.makeServerKeyExchange()) + + client_keyExchange = RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None) + + client_premaster = client_keyExchange.processServerKeyExchange(\ + self.srv_pub_key, + None) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) + + def test_RSA_with_invalid_encryption(self): + + self.assertIsNone(self.keyExchange.makeServerKeyExchange()) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 3 + clientKeyExchange = ClientKeyExchange(self.cipher_suite, + (3, 2)) + enc_premaster = self.srv_pub_key.encrypt(premaster_secret) + enc_premaster[-1] ^= 0x01 + clientKeyExchange.createRSA(enc_premaster) + + dec_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 3 + self.assertNotEqual(dec_premaster, premaster_secret) + + def test_RSA_with_wrong_size_premaster(self): + + self.assertIsNone(self.keyExchange.makeServerKeyExchange()) + + premaster_secret = bytearray(b'\xf0'*47) + premaster_secret[0] = 3 + premaster_secret[1] = 3 + clientKeyExchange = ClientKeyExchange(self.cipher_suite, + (3, 2)) + enc_premaster = self.srv_pub_key.encrypt(premaster_secret) + clientKeyExchange.createRSA(enc_premaster) + + dec_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + premaster_secret = bytearray(b'\xf0'*47) + premaster_secret[0] = 3 + premaster_secret[1] = 3 + self.assertNotEqual(dec_premaster, premaster_secret) + + def test_RSA_with_wrong_version_in_IE(self): + # Internet Explorer sends the version from Server Hello not Client Hello + + self.assertIsNone(self.keyExchange.makeServerKeyExchange()) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 2 + clientKeyExchange = ClientKeyExchange(self.cipher_suite, + (3, 2)) + enc_premaster = self.srv_pub_key.encrypt(premaster_secret) + clientKeyExchange.createRSA(enc_premaster) + + dec_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 2 + self.assertEqual(dec_premaster, premaster_secret) + + def test_RSA_with_wrong_version(self): + + self.assertIsNone(self.keyExchange.makeServerKeyExchange()) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 1 + clientKeyExchange = ClientKeyExchange(self.cipher_suite, + (3, 2)) + clientKeyExchange.createRSA(self.srv_pub_key.encrypt(premaster_secret)) + + dec_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + premaster_secret = bytearray(b'\xf0'*48) + premaster_secret[0] = 3 + premaster_secret[1] = 1 + self.assertNotEqual(dec_premaster, premaster_secret) + +class TestDHE_RSAKeyExchange(unittest.TestCase): + def setUp(self): + self.srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = srv_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + self.client_hello = ClientHello().create((3, 3), + bytearray(32), + bytearray(0), + []) + self.server_hello = ServerHello().create((3, 3), + bytearray(32), + bytearray(0), + self.cipher_suite) + + self.keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key) + + def test_DHE_RSA_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + cln_X = bytesToNumber(getRandomBytes(32)) + cln_Yc = powMod(srv_key_ex.dh_g, + cln_X, + srv_key_ex.dh_p) + cln_secret = numberToByteArray(powMod(srv_key_ex.dh_Ys, + cln_X, + srv_key_ex.dh_p)) + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)) + cln_key_ex.createDH(cln_Yc) + + srv_secret = self.keyExchange.processClientKeyExchange(cln_key_ex) + + self.assertEqual(cln_secret, srv_secret) + + def test_DHE_RSA_key_exchange_with_client(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None) + client_premaster = client_keyExchange.processServerKeyExchange(\ + None, + srv_key_ex) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) + + def test_DHE_RSA_key_exchange_with_small_prime(self): + client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None) + + srv_key_ex = ServerKeyExchange(self.cipher_suite, + self.server_hello.server_version) + srv_key_ex.createDH(2**768, 2, 2**512-1) + + with self.assertRaises(TLSInsufficientSecurity): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) + +class TestSRPKeyExchange(unittest.TestCase): + def setUp(self): + self.srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = srv_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA + self.client_hello = ClientHello().create((3, 3), + bytearray(32), + bytearray(0), + [], + srpUsername='user') + self.server_hello = ServerHello().create((3, 3), + bytearray(32), + bytearray(0), + self.cipher_suite) + + verifierDB = VerifierDB() + verifierDB.create() + entry = verifierDB.makeVerifier('user', 'password', 2048) + verifierDB['user'] = entry + + self.keyExchange = SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key, + verifierDB) + + def test_SRP_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha256') + + KeyExchange.verifyServerKeyExchange(srv_key_ex, + self.srv_pub_key, + self.client_hello.random, + self.server_hello.random, + [(HashAlgorithm.sha256, + SignatureAlgorithm.rsa)]) + + a = bytesToNumber(getRandomBytes(32)) + A = powMod(srv_key_ex.srp_g, + a, + srv_key_ex.srp_N) + x = makeX(srv_key_ex.srp_s, bytearray(b'user'), bytearray(b'password')) + v = powMod(srv_key_ex.srp_g, + x, + srv_key_ex.srp_N) + u = makeU(srv_key_ex.srp_N, + A, + srv_key_ex.srp_B) + + k = makeK(srv_key_ex.srp_N, + srv_key_ex.srp_g) + S = powMod((srv_key_ex.srp_B - (k*v)) % srv_key_ex.srp_N, + a+(u*x), + srv_key_ex.srp_N) + + cln_premaster = numberToByteArray(S) + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)).createSRP(A) + + srv_premaster = self.keyExchange.processClientKeyExchange(cln_key_ex) + + self.assertEqual(cln_premaster, srv_premaster) + + def test_SRP_key_exchange_without_signature(self): + self.cipher_suite = CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA + self.keyExchange.cipherSuite = self.cipher_suite + self.server_hello.cipher_suite = self.cipher_suite + + srv_key_ex = self.keyExchange.makeServerKeyExchange() + + a = bytesToNumber(getRandomBytes(32)) + A = powMod(srv_key_ex.srp_g, + a, + srv_key_ex.srp_N) + x = makeX(srv_key_ex.srp_s, bytearray(b'user'), bytearray(b'password')) + v = powMod(srv_key_ex.srp_g, + x, + srv_key_ex.srp_N) + u = makeU(srv_key_ex.srp_N, + A, + srv_key_ex.srp_B) + + k = makeK(srv_key_ex.srp_N, + srv_key_ex.srp_g) + S = powMod((srv_key_ex.srp_B - (k*v)) % srv_key_ex.srp_N, + a+(u*x), + srv_key_ex.srp_N) + + cln_premaster = numberToByteArray(S) + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)).createSRP(A) + + srv_premaster = self.keyExchange.processClientKeyExchange(cln_key_ex) + + self.assertEqual(cln_premaster, srv_premaster) + + def test_SRP_with_invalid_name(self): + self.client_hello.srp_username = bytearray(b'test') + + with self.assertRaises(TLSUnknownPSKIdentity): + self.keyExchange.makeServerKeyExchange('sha1') + + def test_SRP_with_invalid_client_key_share(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + A = srv_key_ex.srp_N + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)).createSRP(A) + + with self.assertRaises(TLSIllegalParameterException): + self.keyExchange.processClientKeyExchange(cln_key_ex) + + def test_SRP_key_exchange_with_client(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, None, + srpUsername='user', + password='password', + settings=HandshakeSettings()) + + client_premaster = client_keyExchange.processServerKeyExchange(\ + None, + srv_key_ex) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) + + def test_client_SRP_key_exchange_with_unknown_params(self): + keyExchange = ServerKeyExchange(self.cipher_suite, + self.server_hello.server_version) + keyExchange.createSRP(1, 2, 3, 4) + + client_keyExchange = SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, None, + srpUsername='user', + password='password') + with self.assertRaises(TLSInsufficientSecurity): + client_keyExchange.processServerKeyExchange(None, keyExchange) + + def test_client_SRP_key_exchange_with_too_small_params(self): + keyExchange = self.keyExchange.makeServerKeyExchange('sha1') + + settings = HandshakeSettings() + settings.minKeySize = 3072 + client_keyExchange = SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, None, + srpUsername='user', + password='password', + settings=settings) + with self.assertRaises(TLSInsufficientSecurity): + client_keyExchange.processServerKeyExchange(None, keyExchange) + + def test_client_SRP_key_exchange_with_too_big_params(self): + keyExchange = self.keyExchange.makeServerKeyExchange('sha1') + + settings = HandshakeSettings() + settings.minKeySize = 512 + settings.maxKeySize = 1024 + client_keyExchange = SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, None, + srpUsername='user', + password='password', + settings=settings) + with self.assertRaises(TLSInsufficientSecurity): + client_keyExchange.processServerKeyExchange(None, keyExchange) + + def test_client_SRP_key_exchange_with_invalid_params(self): + keyExchange = self.keyExchange.makeServerKeyExchange('sha1') + keyExchange.srp_B = keyExchange.srp_N + + settings = HandshakeSettings() + client_keyExchange = SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, None, + srpUsername='user', + password='password', + settings=settings) + with self.assertRaises(TLSIllegalParameterException): + client_keyExchange.processServerKeyExchange(None, keyExchange) + +class TestECDHE_RSAKeyExchange(unittest.TestCase): + def setUp(self): + self.srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = srv_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + ext = [SupportedGroupsExtension().create([GroupName.secp256r1])] + self.client_hello = ClientHello().create((3, 3), + bytearray(32), + bytearray(0), + [], + extensions=ext) + self.server_hello = ServerHello().create((3, 3), + bytearray(32), + bytearray(0), + self.cipher_suite) + + self.keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key, + [GroupName.secp256r1]) + + def test_ECDHE_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + KeyExchange.verifyServerKeyExchange(srv_key_ex, + self.srv_pub_key, + self.client_hello.random, + self.server_hello.random, + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + curveName = GroupName.toStr(srv_key_ex.named_curve) + curve = getCurveByName(curveName) + generator = curve.generator + cln_Xc = ecdsa.util.randrange(generator.order()) + cln_Ys = decodeX962Point(srv_key_ex.ecdh_Ys, curve) + cln_Yc = encodeX962Point(generator * cln_Xc) + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)) + cln_key_ex.createECDH(cln_Yc) + + cln_S = cln_Ys * cln_Xc + cln_premaster = numberToByteArray(cln_S.x(), + getPointByteSize(cln_S)) + + srv_premaster = self.keyExchange.processClientKeyExchange(cln_key_ex) + + self.assertEqual(cln_premaster, srv_premaster) + + def test_ECDHE_key_exchange_with_missing_curves(self): + self.client_hello.extensions = [SNIExtension().create(bytearray(b"a"))] + with self.assertRaises(TLSInternalError): + self.keyExchange.makeServerKeyExchange('sha1') + + def test_ECDHE_key_exchange_with_no_mutual_curves(self): + ext = SupportedGroupsExtension().create([GroupName.secp160r1]) + self.client_hello.extensions = [ext] + with self.assertRaises(TLSInsufficientSecurity): + self.keyExchange.makeServerKeyExchange('sha1') + + def test_client_ECDHE_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.secp256r1]) + client_premaster = client_keyExchange.processServerKeyExchange(\ + None, + srv_key_ex) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) + + def test_client_ECDHE_key_exchange_with_invalid_server_curve(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + srv_key_ex.named_curve = GroupName.secp384r1 + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.secp256r1]) + with self.assertRaises(TLSIllegalParameterException): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) + diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index beb4b497..11a7bc1c 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -9,30 +9,14 @@ except ImportError: import unittest -from tlslite.handshakesettings import HandshakeSettings from tlslite.recordlayer import RecordLayer -from tlslite.messages import ServerHello, Certificate, ServerHelloDone, \ - ClientHello, ServerKeyExchange, CertificateRequest, ClientKeyExchange -from tlslite.constants import CipherSuite, CertificateType, AlertDescription, \ - HashAlgorithm, SignatureAlgorithm, GroupName -from tlslite.tlsconnection import TLSConnection, KeyExchange, RSAKeyExchange, \ - DHE_RSAKeyExchange, SRPKeyExchange, ECDHE_RSAKeyExchange -from tlslite.errors import TLSLocalAlert, TLSIllegalParameterException, \ - TLSDecryptionFailed, TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ - TLSInternalError +from tlslite.messages import ServerHello, ClientHello +from tlslite.constants import CipherSuite, AlertDescription +from tlslite.tlsconnection import TLSConnection +from tlslite.errors import TLSLocalAlert from tlslite.x509 import X509 from tlslite.x509certchain import X509CertChain from tlslite.utils.keyfactory import parsePEMKey -from tlslite.utils.codec import Parser -from tlslite.utils.cryptomath import bytesToNumber, getRandomBytes, powMod, \ - numberToByteArray -from tlslite.mathtls import makeX, makeU, makeK -from tlslite.handshakehashes import HandshakeHashes -from tlslite import VerifierDB -from tlslite.extensions import SupportedGroupsExtension, SNIExtension -from tlslite.utils.ecc import getCurveByName, decodeX962Point, encodeX962Point,\ - getPointByteSize -import ecdsa from unit_tests.mocksock import MockSocket @@ -70,797 +54,6 @@ "-----END CERTIFICATE-----\n"\ ) - -class TestKeyExchange(unittest.TestCase): - - expected_sha1_SKE = bytearray( - b"\x0c\x00\x00\x8d\x00\x01\x05\x00\x01\x02\x00\x01\x03" - b"\x02\x01" - b"\x00\x80" - b"\xb4\xe0t\xa3\x13\x8e\xc8z\'|>\x8c\x1d\x9e\x00\x8c\x1c\x18\xd7" - b"#a\xe5\x15JH\xd5\xde\x1f\x12\xcej\x02k,4\x00V5\x04\xb3}\x92\xfc" - b"\xbd@\x0c\x03\x06\x02J\xb8*\xafR2\x10\xd6\x9a\xa9\n\x8e\xe8\xb3" - b"Y\xaf\tm\x0cZ\xbdzL\xdf:/\x91^c~\xfc\xf4_\xf3\xfdv\x00\xc1d\x97" - b"\x95\xf4A\xd1\x9e&J@\xect\xc2\xe7\xff\xfc\xdf/d\xbd\x1c\xbc\xa1" - b"f\x14\x92\x06c\xb853\xaf\xf27\xda\xd1\xf9\x97\xea\xec\x90") - - expected_tls1_1_SKE = bytearray( - b'\x0c\x00\x00\x8b\x00\x01\x05\x00\x01\x02\x00\x01\x03' - b'\x00\x80' - b'=\xdf\xfaW+\x81!Q\xc7\xbf\x11\xeeQ\x88\xb2[\xe6n\xd1\x1f\x86\xa8' - b'\xe5\xac\\\xae0\x0fg:tA\x1b*1?$\xd6;XQ\xac\xfdw\x85\xae\xdaOd' - b'\xc8\xb0X_\xae\x80\x87\x11\xb1\x08\x1c3!\xb5\xe6\xcf\x11\xbcV' - b'\x8f\n\x7f\xe7\xfa\x9a\xed!\xf0\xccF\xdf\x9c<\xe7)=\x9d\xde\x0f' - b'\n3\x9d5\x14\x05\x06nA\xa0\x19\xd5\xaa\x9d\xd1\x16\xb3\xb9\xae' - b'\xd1\xe4\xc04\xc1h\xc3\xf5/\xb2\xf6P\r\x1b"\xe9\xc9\x84&\xe1Z') - - def test___init__(self): - keyExchange = KeyExchange(0, None, None, None) - - self.assertIsNotNone(keyExchange) - - def test_makeServerKeyExchange(self): - keyExchange = KeyExchange(0, None, None, None) - with self.assertRaises(NotImplementedError): - keyExchange.makeServerKeyExchange() - - def test_makeClientKeyExchange(self): - srv_h = ServerHello().create((3, 3), bytearray(32), bytearray(0), 0) - keyExchange = KeyExchange(0, None, srv_h, None) - self.assertIsInstance(keyExchange.makeClientKeyExchange(), - ClientKeyExchange) - - def test_processClientKeyExchange(self): - keyExchange = KeyExchange(0, None, None, None) - with self.assertRaises(NotImplementedError): - keyExchange.processClientKeyExchange(None) - - def test_processServerKeyExchange(self): - keyExchange = KeyExchange(0, None, None, None) - with self.assertRaises(NotImplementedError): - keyExchange.processServerKeyExchange(None, None) - - def test_signServerKeyExchange_with_sha1_in_TLS1_2(self): - srv_private_key = parsePEMKey(srv_raw_key, private=True) - client_hello = ClientHello() - cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA - server_hello = ServerHello().create((3, 3), - bytearray(32), - bytearray(0), - cipher_suite) - keyExchange = KeyExchange(cipher_suite, - client_hello, - server_hello, - srv_private_key) - - server_key_exchange = ServerKeyExchange(cipher_suite, (3, 3))\ - .createDH(5, 2, 3) - - keyExchange.signServerKeyExchange(server_key_exchange, 'sha1') - - self.assertEqual(server_key_exchange.write(), self.expected_sha1_SKE) - - def test_signServerKeyExchange_in_TLS1_1(self): - srv_private_key = parsePEMKey(srv_raw_key, private=True) - client_hello = ClientHello() - cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA - server_hello = ServerHello().create((3, 2), - bytearray(32), - bytearray(0), - cipher_suite) - keyExchange = KeyExchange(cipher_suite, - client_hello, - server_hello, - srv_private_key) - server_key_exchange = ServerKeyExchange(cipher_suite, (3, 2))\ - .createDH(5, 2, 3) - - keyExchange.signServerKeyExchange(server_key_exchange) - - self.assertEqual(server_key_exchange.write(), self.expected_tls1_1_SKE) - -class TestKeyExchangeVerifyServerKeyExchange(TestKeyExchange): - def setUp(self): - self.srv_cert_chain = X509CertChain([X509().parse(srv_raw_certificate)]) - self.srv_pub_key = self.srv_cert_chain.getEndEntityPublicKey() - self.cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA - self.server_key_exchange = ServerKeyExchange(self.cipher_suite, (3, 3))\ - .parse(Parser(self.expected_sha1_SKE[1:])) - self.ske_tls1_1 = ServerKeyExchange(self.cipher_suite, (3, 2))\ - .parse(Parser(self.expected_tls1_1_SKE[1:])) - - self.client_hello = ClientHello() - - def test_verifyServerKeyExchange(self): - KeyExchange.verifyServerKeyExchange(self.server_key_exchange, - self.srv_pub_key, - self.client_hello.random, - bytearray(32), - [(HashAlgorithm.sha1, - SignatureAlgorithm.rsa)]) - - def test_verifyServerKeyExchange_with_invalid_hash(self): - with self.assertRaises(TLSIllegalParameterException): - KeyExchange.verifyServerKeyExchange(self.server_key_exchange, - self.srv_pub_key, - self.client_hello.random, - bytearray(32), - [(HashAlgorithm.sha256, - SignatureAlgorithm.rsa)]) - - def test_verifyServerKeyExchange_with_unknown_hash(self): - self.server_key_exchange.hashAlg = 244 - with self.assertRaises(TLSIllegalParameterException): - KeyExchange.verifyServerKeyExchange(self.server_key_exchange, - self.srv_pub_key, - self.client_hello.random, - bytearray(32), - [(244, - SignatureAlgorithm.rsa)]) - - def test_verifyServerKeyExchange_with_empty_signature(self): - self.server_key_exchange.signature = bytearray(0) - - with self.assertRaises(TLSIllegalParameterException): - KeyExchange.verifyServerKeyExchange(self.server_key_exchange, - self.srv_pub_key, - self.client_hello.random, - bytearray(32), - [(HashAlgorithm.sha1, - SignatureAlgorithm.rsa)]) - - def test_verifyServerKeyExchange_with_damaged_signature(self): - self.server_key_exchange.signature[-1] ^= 0x01 - - with self.assertRaises(TLSDecryptionFailed): - KeyExchange.verifyServerKeyExchange(self.server_key_exchange, - self.srv_pub_key, - self.client_hello.random, - bytearray(32), - [(HashAlgorithm.sha1, - SignatureAlgorithm.rsa)]) - - def test_verifyServerKeyExchange_in_TLS1_1(self): - KeyExchange.verifyServerKeyExchange(self.ske_tls1_1, - self.srv_pub_key, - self.client_hello.random, - bytearray(32), - None) - -class TestCalcVerifyBytes(unittest.TestCase): - def setUp(self): - self.handshake_hashes = HandshakeHashes() - self.handshake_hashes.update(bytearray(b'\xab'*32)) - - def test_with_TLS1_2(self): - vrfy = KeyExchange.calcVerifyBytes((3, 3), - self.handshake_hashes, - (HashAlgorithm.sha1, - SignatureAlgorithm.rsa), - None, - None, - None) - self.assertEqual(vrfy, bytearray( - # PKCS#1 prefix - b'0!0\t\x06\x05+\x0e\x03\x02\x1a\x05\x00\x04\x14' - # SHA1 hash - b'L3\x81\xad\x1b\xc2\x14\xc0\x8e\xba\xe4\xb8\xa2\x9d(6V1\xfb\xb0')) - - def test_with_TLS1_1(self): - vrfy = KeyExchange.calcVerifyBytes((3, 2), - self.handshake_hashes, - None, None, None, None) - - self.assertEqual(vrfy, bytearray( - # MD5 hash - b'\xe9\x9f\xb4\xd24\xe9\xf41S\xe6?\xa5\xfe\xad\x16\x14' - # SHA1 hash - b'L3\x81\xad\x1b\xc2\x14\xc0\x8e\xba\xe4\xb8\xa2\x9d(6V1\xfb\xb0' - )) - - def test_with_SSL3(self): - vrfy = KeyExchange.calcVerifyBytes((3, 0), - self.handshake_hashes, - None, - bytearray(b'\x04'*48), - bytearray(b'\xaa'*32), - bytearray(b'\xbb'*32)) - - self.assertEqual(vrfy, bytearray( - b'r_\x06\xd2(\\}v\x87\xfc\xf5\xa2h\xd6S\xd8' - b'\xed=\x9b\xe3\xd9_%qe\xa3k\xf5\x85\x0e?\x9fr\xfaML' - )) - -class TestMakeCertificateVerify(unittest.TestCase): - def setUp(self): - cert_chain = X509CertChain([X509().parse(srv_raw_certificate)]) - self.clnt_pub_key = cert_chain.getEndEntityPublicKey() - self.cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA - self.clnt_private_key = parsePEMKey(srv_raw_key, private=True) - self.handshake_hashes = HandshakeHashes() - self.handshake_hashes.update(bytearray(b'\xab'*32)) - - def test_with_TLS1_2(self): - certificate_request = CertificateRequest((3, 3)) - certificate_request.create([CertificateType.x509], - [], - [(HashAlgorithm.sha256, - SignatureAlgorithm.rsa), - (HashAlgorithm.sha1, - SignatureAlgorithm.rsa)]) - - certVerify = KeyExchange.makeCertificateVerify((3, 3), - self.handshake_hashes, - [(HashAlgorithm.sha1, - SignatureAlgorithm.rsa), - (HashAlgorithm.sha512, - SignatureAlgorithm.rsa)], - self.clnt_private_key, - certificate_request, - None, None, None) - - self.assertIsNotNone(certVerify) - self.assertEqual(certVerify.version, (3, 3)) - self.assertEqual(certVerify.signatureAlgorithm, (HashAlgorithm.sha1, - SignatureAlgorithm.rsa)) - self.assertEqual(certVerify.signature, bytearray( - b'.\x03\xa2\xf0\xa0\xfb\xbeUs\xdb\x9b\xea\xcc(\xa6:l\x84\x8e\x13' - b'\xa1\xaa\xdb1P\xe9\x06\x876\xbe+\xe92\x89\xaa\xa5EU\x07\x9a\xde' - b'\xd37\xafGCR\xdam\xa2v\xde\xceeFI\x80:ZtL\x96\xafZ\xe2\xe2\xce/' - b'\x9f\x82\xfe\xdb*\x94\xa8\xbd\xd9Hl\xdc\xc8\xbf\x9b=o\xda\x06' - b'\xfa\x9e\xbfB+05\xc6\xda\xdf\x05\xf2m[\x18\x11\xaf\x184\x12\x9d' - b'\xb4:\x9b\xc1U\x1c\xba\xa3\x05\xceOn\x0fY\xcaK*\x0b\x04\xa5' - )) - - def test_with_TLS1_2_and_no_overlap(self): - certificate_request = CertificateRequest((3, 3)) - certificate_request.create([CertificateType.x509], - [], - [(HashAlgorithm.sha256, - SignatureAlgorithm.rsa), - (HashAlgorithm.sha224, - SignatureAlgorithm.rsa)]) - - certVerify = KeyExchange.makeCertificateVerify((3, 3), - self.handshake_hashes, - [(HashAlgorithm.sha1, - SignatureAlgorithm.rsa), - (HashAlgorithm.sha512, - SignatureAlgorithm.rsa)], - self.clnt_private_key, - certificate_request, - None, None, None) - - self.assertIsNotNone(certVerify) - self.assertEqual(certVerify.version, (3, 3)) - # when there's no overlap, we select the most wanted from our side - self.assertEqual(certVerify.signatureAlgorithm, (HashAlgorithm.sha1, - SignatureAlgorithm.rsa)) - self.assertEqual(certVerify.signature, bytearray( - b'.\x03\xa2\xf0\xa0\xfb\xbeUs\xdb\x9b\xea\xcc(\xa6:l\x84\x8e\x13' - b'\xa1\xaa\xdb1P\xe9\x06\x876\xbe+\xe92\x89\xaa\xa5EU\x07\x9a\xde' - b'\xd37\xafGCR\xdam\xa2v\xde\xceeFI\x80:ZtL\x96\xafZ\xe2\xe2\xce/' - b'\x9f\x82\xfe\xdb*\x94\xa8\xbd\xd9Hl\xdc\xc8\xbf\x9b=o\xda\x06' - b'\xfa\x9e\xbfB+05\xc6\xda\xdf\x05\xf2m[\x18\x11\xaf\x184\x12\x9d' - b'\xb4:\x9b\xc1U\x1c\xba\xa3\x05\xceOn\x0fY\xcaK*\x0b\x04\xa5' - )) - - def test_with_TLS1_1(self): - certificate_request = CertificateRequest((3, 2)) - certificate_request.create([CertificateType.x509], - [], - None) - - certVerify = KeyExchange.makeCertificateVerify((3, 2), - self.handshake_hashes, - None, - self.clnt_private_key, - certificate_request, - None, None, None) - - self.assertIsNotNone(certVerify) - self.assertEqual(certVerify.version, (3, 2)) - self.assertIsNone(certVerify.signatureAlgorithm) - self.assertEqual(certVerify.signature, bytearray( - b'=X\x14\xf3\r6\x0b\x84\xde&J\x15\xa02M\xc8\xf1?\xa0\x10U\x1e\x0b' - b'\x95^\xa19\x14\xf5\xf1$\xe3U[\xb4/\xe7AY(\xee]\xff\x97H\xb8\xa9' - b'\x8b\x96n\xa6\xf5\x0f\xffd\r\x08/Hs6`wi8\xc4\x02\xa4}a\xcbS\x99' - b'\x01;\x0e\x88oj\x9a\x02\x98Y\xb5\x00$f@>\xd8\x0cS\x95\xa8\x9e' - b'\x14uU\\Z\xd0.\xe7\x01_y\x1d\xea\xad\x1b\xf8c\xa6\xc9@\xc6\x90' - b'\x19~&\xd9\xaa\xc2\t,s\xde\xb1' - )) - -class TestRSAKeyExchange(unittest.TestCase): - def setUp(self): - self.srv_private_key = parsePEMKey(srv_raw_key, private=True) - srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) - self.srv_pub_key = srv_chain.getEndEntityPublicKey() - self.cipher_suite = CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA - self.client_hello = ClientHello().create((3, 3), - bytearray(32), - bytearray(0), - []) - self.server_hello = ServerHello().create((3, 2), - bytearray(32), - bytearray(0), - self.cipher_suite) - - self.keyExchange = RSAKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - self.srv_private_key) - - def test_RSA_key_exchange(self): - - self.assertIsNone(self.keyExchange.makeServerKeyExchange()) - - premaster_secret = bytearray(b'\xf0'*48) - premaster_secret[0] = 3 - premaster_secret[1] = 3 - clientKeyExchange = ClientKeyExchange(self.cipher_suite, - (3, 2)) - clientKeyExchange.createRSA(self.srv_pub_key.encrypt(premaster_secret)) - - dec_premaster = self.keyExchange.processClientKeyExchange(\ - clientKeyExchange) - - premaster_secret = bytearray(b'\xf0'*48) - premaster_secret[0] = 3 - premaster_secret[1] = 3 - self.assertEqual(dec_premaster, premaster_secret) - - def test_RSA_key_exchange_with_client(self): - self.assertIsNone(self.keyExchange.makeServerKeyExchange()) - - client_keyExchange = RSAKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - None) - - client_premaster = client_keyExchange.processServerKeyExchange(\ - self.srv_pub_key, - None) - clientKeyExchange = client_keyExchange.makeClientKeyExchange() - - server_premaster = self.keyExchange.processClientKeyExchange(\ - clientKeyExchange) - - self.assertEqual(client_premaster, server_premaster) - - def test_RSA_with_invalid_encryption(self): - - self.assertIsNone(self.keyExchange.makeServerKeyExchange()) - - premaster_secret = bytearray(b'\xf0'*48) - premaster_secret[0] = 3 - premaster_secret[1] = 3 - clientKeyExchange = ClientKeyExchange(self.cipher_suite, - (3, 2)) - enc_premaster = self.srv_pub_key.encrypt(premaster_secret) - enc_premaster[-1] ^= 0x01 - clientKeyExchange.createRSA(enc_premaster) - - dec_premaster = self.keyExchange.processClientKeyExchange(\ - clientKeyExchange) - - premaster_secret = bytearray(b'\xf0'*48) - premaster_secret[0] = 3 - premaster_secret[1] = 3 - self.assertNotEqual(dec_premaster, premaster_secret) - - def test_RSA_with_wrong_size_premaster(self): - - self.assertIsNone(self.keyExchange.makeServerKeyExchange()) - - premaster_secret = bytearray(b'\xf0'*47) - premaster_secret[0] = 3 - premaster_secret[1] = 3 - clientKeyExchange = ClientKeyExchange(self.cipher_suite, - (3, 2)) - enc_premaster = self.srv_pub_key.encrypt(premaster_secret) - clientKeyExchange.createRSA(enc_premaster) - - dec_premaster = self.keyExchange.processClientKeyExchange(\ - clientKeyExchange) - - premaster_secret = bytearray(b'\xf0'*47) - premaster_secret[0] = 3 - premaster_secret[1] = 3 - self.assertNotEqual(dec_premaster, premaster_secret) - - def test_RSA_with_wrong_version_in_IE(self): - # Internet Explorer sends the version from Server Hello not Client Hello - - self.assertIsNone(self.keyExchange.makeServerKeyExchange()) - - premaster_secret = bytearray(b'\xf0'*48) - premaster_secret[0] = 3 - premaster_secret[1] = 2 - clientKeyExchange = ClientKeyExchange(self.cipher_suite, - (3, 2)) - enc_premaster = self.srv_pub_key.encrypt(premaster_secret) - clientKeyExchange.createRSA(enc_premaster) - - dec_premaster = self.keyExchange.processClientKeyExchange(\ - clientKeyExchange) - - premaster_secret = bytearray(b'\xf0'*48) - premaster_secret[0] = 3 - premaster_secret[1] = 2 - self.assertEqual(dec_premaster, premaster_secret) - - def test_RSA_with_wrong_version(self): - - self.assertIsNone(self.keyExchange.makeServerKeyExchange()) - - premaster_secret = bytearray(b'\xf0'*48) - premaster_secret[0] = 3 - premaster_secret[1] = 1 - clientKeyExchange = ClientKeyExchange(self.cipher_suite, - (3, 2)) - clientKeyExchange.createRSA(self.srv_pub_key.encrypt(premaster_secret)) - - dec_premaster = self.keyExchange.processClientKeyExchange(\ - clientKeyExchange) - - premaster_secret = bytearray(b'\xf0'*48) - premaster_secret[0] = 3 - premaster_secret[1] = 1 - self.assertNotEqual(dec_premaster, premaster_secret) - -class TestDHE_RSAKeyExchange(unittest.TestCase): - def setUp(self): - self.srv_private_key = parsePEMKey(srv_raw_key, private=True) - srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) - self.srv_pub_key = srv_chain.getEndEntityPublicKey() - self.cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA - self.client_hello = ClientHello().create((3, 3), - bytearray(32), - bytearray(0), - []) - self.server_hello = ServerHello().create((3, 3), - bytearray(32), - bytearray(0), - self.cipher_suite) - - self.keyExchange = DHE_RSAKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - self.srv_private_key) - - def test_DHE_RSA_key_exchange(self): - srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') - - cln_X = bytesToNumber(getRandomBytes(32)) - cln_Yc = powMod(srv_key_ex.dh_g, - cln_X, - srv_key_ex.dh_p) - cln_secret = numberToByteArray(powMod(srv_key_ex.dh_Ys, - cln_X, - srv_key_ex.dh_p)) - - cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)) - cln_key_ex.createDH(cln_Yc) - - srv_secret = self.keyExchange.processClientKeyExchange(cln_key_ex) - - self.assertEqual(cln_secret, srv_secret) - - def test_DHE_RSA_key_exchange_with_client(self): - srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') - - client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - None) - client_premaster = client_keyExchange.processServerKeyExchange(\ - None, - srv_key_ex) - clientKeyExchange = client_keyExchange.makeClientKeyExchange() - - server_premaster = self.keyExchange.processClientKeyExchange(\ - clientKeyExchange) - - self.assertEqual(client_premaster, server_premaster) - - def test_DHE_RSA_key_exchange_with_small_prime(self): - client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - None) - - srv_key_ex = ServerKeyExchange(self.cipher_suite, - self.server_hello.server_version) - srv_key_ex.createDH(2**768, 2, 2**512-1) - - with self.assertRaises(TLSInsufficientSecurity): - client_keyExchange.processServerKeyExchange(None, srv_key_ex) - -class TestSRPKeyExchange(unittest.TestCase): - def setUp(self): - self.srv_private_key = parsePEMKey(srv_raw_key, private=True) - srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) - self.srv_pub_key = srv_chain.getEndEntityPublicKey() - self.cipher_suite = CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA - self.client_hello = ClientHello().create((3, 3), - bytearray(32), - bytearray(0), - [], - srpUsername='user') - self.server_hello = ServerHello().create((3, 3), - bytearray(32), - bytearray(0), - self.cipher_suite) - - verifierDB = VerifierDB() - verifierDB.create() - entry = verifierDB.makeVerifier('user', 'password', 2048) - verifierDB['user'] = entry - - self.keyExchange = SRPKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - self.srv_private_key, - verifierDB) - - def test_SRP_key_exchange(self): - srv_key_ex = self.keyExchange.makeServerKeyExchange('sha256') - - KeyExchange.verifyServerKeyExchange(srv_key_ex, - self.srv_pub_key, - self.client_hello.random, - self.server_hello.random, - [(HashAlgorithm.sha256, - SignatureAlgorithm.rsa)]) - - a = bytesToNumber(getRandomBytes(32)) - A = powMod(srv_key_ex.srp_g, - a, - srv_key_ex.srp_N) - x = makeX(srv_key_ex.srp_s, bytearray(b'user'), bytearray(b'password')) - v = powMod(srv_key_ex.srp_g, - x, - srv_key_ex.srp_N) - u = makeU(srv_key_ex.srp_N, - A, - srv_key_ex.srp_B) - - k = makeK(srv_key_ex.srp_N, - srv_key_ex.srp_g) - S = powMod((srv_key_ex.srp_B - (k*v)) % srv_key_ex.srp_N, - a+(u*x), - srv_key_ex.srp_N) - - cln_premaster = numberToByteArray(S) - - cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)).createSRP(A) - - srv_premaster = self.keyExchange.processClientKeyExchange(cln_key_ex) - - self.assertEqual(cln_premaster, srv_premaster) - - def test_SRP_key_exchange_without_signature(self): - self.cipher_suite = CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA - self.keyExchange.cipherSuite = self.cipher_suite - self.server_hello.cipher_suite = self.cipher_suite - - srv_key_ex = self.keyExchange.makeServerKeyExchange() - - a = bytesToNumber(getRandomBytes(32)) - A = powMod(srv_key_ex.srp_g, - a, - srv_key_ex.srp_N) - x = makeX(srv_key_ex.srp_s, bytearray(b'user'), bytearray(b'password')) - v = powMod(srv_key_ex.srp_g, - x, - srv_key_ex.srp_N) - u = makeU(srv_key_ex.srp_N, - A, - srv_key_ex.srp_B) - - k = makeK(srv_key_ex.srp_N, - srv_key_ex.srp_g) - S = powMod((srv_key_ex.srp_B - (k*v)) % srv_key_ex.srp_N, - a+(u*x), - srv_key_ex.srp_N) - - cln_premaster = numberToByteArray(S) - - cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)).createSRP(A) - - srv_premaster = self.keyExchange.processClientKeyExchange(cln_key_ex) - - self.assertEqual(cln_premaster, srv_premaster) - - def test_SRP_with_invalid_name(self): - self.client_hello.srp_username = bytearray(b'test') - - with self.assertRaises(TLSUnknownPSKIdentity): - self.keyExchange.makeServerKeyExchange('sha1') - - def test_SRP_with_invalid_client_key_share(self): - srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') - - A = srv_key_ex.srp_N - - cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)).createSRP(A) - - with self.assertRaises(TLSIllegalParameterException): - self.keyExchange.processClientKeyExchange(cln_key_ex) - - def test_SRP_key_exchange_with_client(self): - srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') - - client_keyExchange = SRPKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - None, None, - srpUsername='user', - password='password', - settings=HandshakeSettings()) - - client_premaster = client_keyExchange.processServerKeyExchange(\ - None, - srv_key_ex) - clientKeyExchange = client_keyExchange.makeClientKeyExchange() - - server_premaster = self.keyExchange.processClientKeyExchange(\ - clientKeyExchange) - - self.assertEqual(client_premaster, server_premaster) - - def test_client_SRP_key_exchange_with_unknown_params(self): - keyExchange = ServerKeyExchange(self.cipher_suite, - self.server_hello.server_version) - keyExchange.createSRP(1, 2, 3, 4) - - client_keyExchange = SRPKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - None, None, - srpUsername='user', - password='password') - with self.assertRaises(TLSInsufficientSecurity): - client_keyExchange.processServerKeyExchange(None, keyExchange) - - def test_client_SRP_key_exchange_with_too_small_params(self): - keyExchange = self.keyExchange.makeServerKeyExchange('sha1') - - settings = HandshakeSettings() - settings.minKeySize = 3072 - client_keyExchange = SRPKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - None, None, - srpUsername='user', - password='password', - settings=settings) - with self.assertRaises(TLSInsufficientSecurity): - client_keyExchange.processServerKeyExchange(None, keyExchange) - - def test_client_SRP_key_exchange_with_too_big_params(self): - keyExchange = self.keyExchange.makeServerKeyExchange('sha1') - - settings = HandshakeSettings() - settings.minKeySize = 512 - settings.maxKeySize = 1024 - client_keyExchange = SRPKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - None, None, - srpUsername='user', - password='password', - settings=settings) - with self.assertRaises(TLSInsufficientSecurity): - client_keyExchange.processServerKeyExchange(None, keyExchange) - - def test_client_SRP_key_exchange_with_invalid_params(self): - keyExchange = self.keyExchange.makeServerKeyExchange('sha1') - keyExchange.srp_B = keyExchange.srp_N - - settings = HandshakeSettings() - client_keyExchange = SRPKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - None, None, - srpUsername='user', - password='password', - settings=settings) - with self.assertRaises(TLSIllegalParameterException): - client_keyExchange.processServerKeyExchange(None, keyExchange) - -class TestECDHE_RSAKeyExchange(unittest.TestCase): - def setUp(self): - self.srv_private_key = parsePEMKey(srv_raw_key, private=True) - srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) - self.srv_pub_key = srv_chain.getEndEntityPublicKey() - self.cipher_suite = CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA - ext = [SupportedGroupsExtension().create([GroupName.secp256r1])] - self.client_hello = ClientHello().create((3, 3), - bytearray(32), - bytearray(0), - [], - extensions=ext) - self.server_hello = ServerHello().create((3, 3), - bytearray(32), - bytearray(0), - self.cipher_suite) - - self.keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - self.srv_private_key, - [GroupName.secp256r1]) - - def test_ECDHE_key_exchange(self): - srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') - - KeyExchange.verifyServerKeyExchange(srv_key_ex, - self.srv_pub_key, - self.client_hello.random, - self.server_hello.random, - [(HashAlgorithm.sha1, - SignatureAlgorithm.rsa)]) - - curveName = GroupName.toStr(srv_key_ex.named_curve) - curve = getCurveByName(curveName) - generator = curve.generator - cln_Xc = ecdsa.util.randrange(generator.order()) - cln_Ys = decodeX962Point(srv_key_ex.ecdh_Ys, curve) - cln_Yc = encodeX962Point(generator * cln_Xc) - - cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)) - cln_key_ex.createECDH(cln_Yc) - - cln_S = cln_Ys * cln_Xc - cln_premaster = numberToByteArray(cln_S.x(), - getPointByteSize(cln_S)) - - srv_premaster = self.keyExchange.processClientKeyExchange(cln_key_ex) - - self.assertEqual(cln_premaster, srv_premaster) - - def test_ECDHE_key_exchange_with_missing_curves(self): - self.client_hello.extensions = [SNIExtension().create(bytearray(b"a"))] - with self.assertRaises(TLSInternalError): - self.keyExchange.makeServerKeyExchange('sha1') - - def test_ECDHE_key_exchange_with_no_mutual_curves(self): - ext = SupportedGroupsExtension().create([GroupName.secp160r1]) - self.client_hello.extensions = [ext] - with self.assertRaises(TLSInsufficientSecurity): - self.keyExchange.makeServerKeyExchange('sha1') - - def test_client_ECDHE_key_exchange(self): - srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') - - client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - None, - [GroupName.secp256r1]) - client_premaster = client_keyExchange.processServerKeyExchange(\ - None, - srv_key_ex) - clientKeyExchange = client_keyExchange.makeClientKeyExchange() - - server_premaster = self.keyExchange.processClientKeyExchange(\ - clientKeyExchange) - - self.assertEqual(client_premaster, server_premaster) - - def test_client_ECDHE_key_exchange_with_invalid_server_curve(self): - srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') - srv_key_ex.named_curve = GroupName.secp384r1 - - client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, - self.client_hello, - self.server_hello, - None, - [GroupName.secp256r1]) - with self.assertRaises(TLSIllegalParameterException): - client_keyExchange.processServerKeyExchange(None, srv_key_ex) - class TestTLSConnection(unittest.TestCase): def test_client_with_server_responing_with_SHA256_on_TLSv1_1(self): From 9b87ab356d89e5f5f79331b3c935d3e8b3980fc0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 27 Nov 2015 14:52:24 +0100 Subject: [PATCH 217/574] update the version to 0.6.0-alpha3 --- README | 22 ++++++++++++++++------ README.md | 8 +++++++- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/README b/README index 5c464635..476ec15b 100644 --- a/README +++ b/README @@ -5,16 +5,26 @@ It can use pycrypto, m2crypto and gmp for acceleration of cryptographic operations but is not dependant upon them. Functionality implemented include: - - RC4, 3DES-CBC, AES-CBC and AES-GCM ciphers - - MD5, SHA1, SHA256 and SHA384 HMACs as well as AEAD mode of operation - - RSA and DHE_RSA key exchange + - all above mentioned protocols, including support for client certificates + (RFC 6101, RFC 2246, RFC 4346, RFC 5246) + - RC4, 3DES-CBC, AES-CBC, AES-GCM and ChaCha20 ciphers (RFC 5246, RFC 6347, + RFC 4492, RFC 5288, RFC 5289, RFC 7539) + - MD5, SHA1, SHA256 and SHA384 HMACs as well as AEAD mode of operation in GCM + or Poly1305 authenticator + - RSA, DHE_RSA and ECDHE_RSA key exchange + - full set of signature hashes (md5, sha1, sha224, sha256, sha384 and sha512) + for ServerKeyExchange and CertfificateVerify in TLS v1.2 + - secp256r1, secp384r1, secp521r1, secp256k1, secp224r1 and secp192r1 curves + for ECDHE_RSA key exchange (support for last two depends on the version + of ecdsa library used) - anonymous DHE key exchange + - anonymous ECDH key exchange in client - NULL encryption ciphersuites - - FALLBACK_SCSV - - encrypt-then-MAC mode of operation for CBC ciphersuites + - FALLBACK_SCSV (RFC 7507) + - encrypt-then-MAC mode of operation for CBC ciphersuites (RFC 7366) - client certificates - TACK certificate pinning - - SRP_SHA_RSA ciphersuites + - SRP_SHA_RSA and SRP_SHA ciphersuites (RFC 5054) tlslite-ng aims to be a drop-in replacement for tlslite while providing more comprehensive set of features and more secure defautls. diff --git a/README.md b/README.md index 1e3c84ae..07232113 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.6.0-alpha2 2015-11-12 +tlslite-ng version 0.6.0-alpha3 2015-11-27 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -523,6 +523,12 @@ encrypt-then-MAC mode for CBC ciphers. =========== 0.6.0 - WIP + - abitlity to perform reverse lookups on many of the TLS type enumerations + - added ECDHE_RSA key exchange together with associated ciphersuites + - refactor key exchange code to remove duplication and make adding new methods + easier + - add support for all hashes for ServerKeyExchange and CertificateVerify + messages in TLS 1.2 - mark library as compatible with Python 3.5 (it was previously, but now it is verified with Continous Integration) - small cleanups and more documentation diff --git a/setup.py b/setup.py index 1557b1bb..68811cc3 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.6.0-alpha2", + version="0.6.0-alpha3", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index f078a5ea..79b3768a 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.6.0-alpha2 +@version: 0.6.0-alpha3 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index e9680898..717456b8 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.6.0-alpha2" +__version__ = "0.6.0-alpha3" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From abb42a62fbb9de58d2a3d174aaf6d8e9c5fdb0e1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 28 Nov 2015 22:51:20 +0100 Subject: [PATCH 218/574] migrate to codeclimate platform --- .codeclimate.yml | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 0c7446b9..c3816b23 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,9 +1,19 @@ -languages: - Ruby: true - JavaScript: true - PHP: true - Python: true +engines: + radon: + enabled: true + pep8: + enabled: true + duplication: + enabled: true + config: + languages: + - python + +ratings: + paths: + -"**.py" + exclude_paths: - - "tests/*" - - "scripts/*" - - "unit_tests/*" + - "tests/**/*" + - "scripts/**/*" + - "unit_tests/**/*" From 07358fc4d8771809f32d5acb86c2a308d5d48684 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 28 Nov 2015 23:20:18 +0100 Subject: [PATCH 219/574] explicitly include code for analysis --- .codeclimate.yml | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index c3816b23..4bcb419b 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,18 +1,22 @@ engines: - radon: + radon: + enabled: true + pep8: + enabled: true + duplication: + enabled: true + config: + languages: + - python + fixme: + enabled: true + checks: + bug: enabled: true - pep8: - enabled: true - duplication: - enabled: true - config: - languages: - - python - ratings: - paths: - -"**.py" - + paths: + - "tlslite/**" + - "**.py" exclude_paths: - "tests/**/*" - "scripts/**/*" From 5bc7dc06a29a8d9656841be4510b588cda8f3d7a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 29 Nov 2015 15:51:25 +0100 Subject: [PATCH 220/574] We require ecdsa library for ECDHE --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 07232113..0b28be85 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Requirements: * Python 2.6 or higher is required. * Python 3.2 or higher is supported. + * python ecdsa library ([GitHub](https://github.com/warner/python-ecdsa) [PyPI](https://pypi.python.org/pypi/ecdsa)) Options: From 26d664b06673879df8291400af6408a445fd40be Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 22:37:31 +0100 Subject: [PATCH 221/574] use parser functions in extensions --- tlslite/extensions.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 26b6c7be..1d854838 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -973,12 +973,8 @@ def parse(self, parser): if parser.getRemainingLength() == 0: self.groups = None return self - self.groups = [] - parser.startLengthCheck(2) - while not parser.atLengthCheck(): - self.groups.append(parser.get(2)) - parser.stopLengthCheck() + self.groups = parser.getVarList(2, 2) return self @@ -1041,12 +1037,7 @@ def parse(self, parser): self.formats = None return self - self.formats = [] - - parser.startLengthCheck(1) - while not parser.atLengthCheck(): - self.formats.append(parser.get(1)) - parser.stopLengthCheck() + self.formats = parser.getVarList(1, 1) return self From f730d7b6ec8e4b8a57ffc67a9f88f6b568aa4b5d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Nov 2015 22:46:12 +0100 Subject: [PATCH 222/574] deduplicate code in extensions writers --- tlslite/extensions.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 1d854838..02f5de40 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -429,12 +429,9 @@ def extData(self): if self.certTypes is None: return bytearray(0) - w = Writer() - w.add(len(self.certTypes), 1) - for c_type in self.certTypes: - w.add(c_type, 1) - - return w.bytes + writer = Writer() + writer.addVarSeq(self.certTypes, 1, 1) + return writer.bytes def create(self, certTypes=None): """ @@ -947,10 +944,7 @@ def extData(self): return bytearray(0) writer = Writer() - # encode length of two bytes per group in two bytes - writer.add(len(self.groups) * 2, 2) - for group in self.groups: - writer.add(group, 2) + writer.addVarSeq(self.groups, 2, 2) return writer.bytes def create(self, groups): @@ -1010,10 +1004,7 @@ def extData(self): return bytearray(0) writer = Writer() - # the length is number of formats encoded in one byte - writer.add(len(self.formats), 1) - for fmt in self.formats: - writer.add(fmt, 1) + writer.addVarSeq(self.formats, 1, 1) return writer.bytes def create(self, formats): From 5f47c26fba29c755c8a356abe673ce53cbb6ba8a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 30 Nov 2015 18:04:59 +0100 Subject: [PATCH 223/574] deduplicate extensions using just a list of values since the on-the-wire encoding of those extensions is (almost) identical, we can create an "abstract" extension that implements them all and just depends on specific initialization --- tlslite/extensions.py | 266 ++++++++++---------------- unit_tests/test_tlslite_extensions.py | 35 +++- 2 files changed, 129 insertions(+), 172 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 02f5de40..aeea0a45 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -158,6 +158,87 @@ def __repr__(self): " serverType={2!r})".format(self.extType, self.extData, self.serverType) +class VarListExtension(TLSExtension): + """ + Abstract extension for handling extensions comprised only of a value list + + Extension for handling arbitrary extensions comprising of just a list + of same-sized elementes inside an array + """ + + def __init__(self, elemLength, lengthLength, fieldName, extType): + self._fieldName = fieldName + self._internalList = None + self._elemLength = elemLength + self._lengthLength = lengthLength + self._extType = extType + + @property + def extType(self): + """Type of extension""" + return self._extType + + @property + def extData(self): + """Return raw data encoding of the extension + + @rtype: bytearray + """ + if self._internalList is None: + return bytearray(0) + + writer = Writer() + writer.addVarSeq(self._internalList, + self._elemLength, + self._lengthLength) + return writer.bytes + + def create(self, values): + """Set the list to specified values + + @type values: list of int + @param values: list of values to save + """ + self._internalList = values + return self + + def parse(self, parser): + """ + Deserialise extension from on-the-wire data + + @type parser: L{Parser} + @rtype: Extension + """ + if parser.getRemainingLength() == 0: + self._internalList = None + return self + + self._internalList = parser.getVarList(self._elemLength, + self._lengthLength) + return self + + def __getattr__(self, name): + """Return the special field name value""" + if name == self._fieldName: + return self._internalList + raise AttributeError("type object '{0}' has no attribute '{1}'"\ + .format(self.__class__.__name__, name)) + + def __setattr__(self, name, value): + """Set the special field value""" + if name == '_fieldName': + super(VarListExtension, self).__setattr__(name, value) + return + if name == self._fieldName: + self._internalList = value + return + super(VarListExtension, self).__setattr__(name, value) + + def __repr__(self): + return "{0}({1}={2!r})".format(self.__class__.__name__, + self._fieldName, + self._internalList) + class SNIExtension(TLSExtension): """ Class for handling Server Name Indication (server_name) extension from @@ -376,10 +457,11 @@ def parse(self, p): return self -class ClientCertTypeExtension(TLSExtension): +class ClientCertTypeExtension(VarListExtension): """ - This class handles the Certificate Type extension (variant sent by client) - defined in RFC 6091. + This class handles the (client variant of) Certificate Type extension + + See RFC 6091. @type extType: int @ivar extType: numeric type of Certificate Type extension, i.e. 9 @@ -397,71 +479,8 @@ def __init__(self): See also: L{create} and L{parse} """ - - self.certTypes = None - - def __repr__(self): - """ Return programmer-centric representation of extension - - @rtype: str - """ - return "ClientCertTypeExtension(certTypes={0!r})"\ - .format(self.certTypes) - - @property - def extType(self): - """ - Return the type of TLS extension, in this case - 9 - - @rtype: int - """ - - return ExtensionType.cert_type - - @property - def extData(self): - """ - Return the raw encoding of this extension - - @rtype: bytearray - """ - - if self.certTypes is None: - return bytearray(0) - - writer = Writer() - writer.addVarSeq(self.certTypes, 1, 1) - return writer.bytes - - def create(self, certTypes=None): - """ - Return instance of this extension with specified certificate types - - @type certTypes: iterable list of int - @param certTypes: list of certificate types to advertise, all values - should be between 0 and 2^8-1 inclusive - - @raises ValueError: when the list includes too big or negative integers - """ - self.certTypes = certTypes - return self - - def parse(self, p): - """ - Parse the extension from binary data - - @type p: L{tlslite.util.codec.Parser} - @param p: data to be parsed - - @raise SyntaxError: when the size of the passed element doesn't match - the internal representation - - @rtype: L{ClientCertTypeExtension} - """ - - self.certTypes = p.getVarList(1, 1) - - return self + super(ClientCertTypeExtension, self).__init__(1, 1, 'certTypes', \ + ExtensionType.cert_type) class ServerCertTypeExtension(TLSExtension): """ @@ -909,8 +928,7 @@ def parse(self, p): return self -class SupportedGroupsExtension(TLSExtension): - +class SupportedGroupsExtension(VarListExtension): """ Client side list of supported groups of (EC)DHE key exchage. @@ -922,115 +940,23 @@ class SupportedGroupsExtension(TLSExtension): def __init__(self): """Create instance of class""" - self.groups = None - - @property - def extType(self): - """ - Type of extension, in this case - 10 - - @rtype: int - """ - return ExtensionType.supported_groups - - @property - def extData(self): - """ - Return raw data encoding of the extension - - @rtype: bytearray - """ - if self.groups is None: - return bytearray(0) - - writer = Writer() - writer.addVarSeq(self.groups, 2, 2) - return writer.bytes - - def create(self, groups): - """ - Set the supported groups in the extension - - @type groups: list of int - @param groups: list of supported groups - """ - self.groups = groups - return self - - def parse(self, parser): - """ - Deserialise extension from on-the-wire data - - @type parser: L{Parser} - @rtype: SupportedGroupsExtension - """ - if parser.getRemainingLength() == 0: - self.groups = None - return self - - self.groups = parser.getVarList(2, 2) - - return self - -class ECPointFormatsExtension(TLSExtension): + super(SupportedGroupsExtension, self).__init__(2, 2, 'groups', \ + ExtensionType.supported_groups) +class ECPointFormatsExtension(VarListExtension): """ Client side list of supported ECC point formats. See RFC4492. + + @type formats: list of int + @ivar formats: list of point formats supported by peer """ def __init__(self): """Create instance of class""" - self.formats = None - - @property - def extType(self): - """ - Type of extension, in this case - 11 - - @rtype: int - """ - return ExtensionType.ec_point_formats - - @property - def extData(self): - """ - Return raw encoding of the extension - - @rtype: bytearray - """ - if self.formats is None: - return bytearray(0) - - writer = Writer() - writer.addVarSeq(self.formats, 1, 1) - return writer.bytes - - def create(self, formats): - """ - Set the list of supported EC point formats - - @type formats: list of int - @param formats: list of supported EC point formats - """ - self.formats = formats - return self - - def parse(self, parser): - """ - Deserialise extension from on the wire data - - @type parser: L{Parser} - @rtype: ECPointFormatsExtension - """ - if parser.getRemainingLength() == 0: - self.formats = None - return self - - self.formats = parser.getVarList(1, 1) - - return self + super(ECPointFormatsExtension, self).__init__(1, 1, 'formats', \ + ExtensionType.ec_point_formats) class SignatureAlgorithmsExtension(TLSExtension): diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 847780f5..b41a319c 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -11,7 +11,7 @@ from tlslite.extensions import TLSExtension, SNIExtension, NPNExtension,\ SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\ TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\ - SignatureAlgorithmsExtension + SignatureAlgorithmsExtension, VarListExtension from tlslite.utils.codec import Parser from tlslite.constants import NameType, ExtensionType, GroupName,\ ECPointFormat, HashAlgorithm, SignatureAlgorithm @@ -293,6 +293,28 @@ def test___repr__(self): "extData=bytearray(b'\\x00\\x00'), serverType=False)", repr(ext)) +class TestVarListExtension(unittest.TestCase): + def setUp(self): + self.ext = VarListExtension(1, 1, 'groups', 42) + + def test___init__(self): + self.assertIsNotNone(self.ext) + + def test_get_attribute(self): + self.assertIsNone(self.ext.groups) + + def test_set_attribute(self): + self.ext.groups = [1, 2, 3] + + self.assertEqual(self.ext.groups, [1, 2, 3]) + + def test_get_non_existant_attribute(self): + with self.assertRaises(AttributeError) as e: + val = self.ext.gruppen + + self.assertEqual(str(e.exception), + "type object 'VarListExtension' has no attribute 'gruppen'") + class TestSNIExtension(unittest.TestCase): def test___init__(self): server_name = SNIExtension() @@ -648,7 +670,7 @@ def test___init___(self): def test_create(self): cert_type = ClientCertTypeExtension() - cert_type = cert_type.create() + cert_type = cert_type.create(None) self.assertEqual(9, cert_type.extType) self.assertEqual(bytearray(0), cert_type.extData) @@ -1217,6 +1239,11 @@ def test_parse_with_invalid_data(self): with self.assertRaises(SyntaxError): ext.parse(parser) + def test_repr(self): + ext = SupportedGroupsExtension().create([GroupName.secp256r1]) + self.assertEqual("SupportedGroupsExtension(groups=[23])", + repr(ext)) + class TestECPointFormatsExtension(unittest.TestCase): def test___init__(self): ext = ECPointFormatsExtension() @@ -1252,6 +1279,10 @@ def test_parse_with_empty_data(self): self.assertIsNone(ext.formats) + def test_repr(self): + ext = ECPointFormatsExtension().create([ECPointFormat.uncompressed]) + self.assertEqual("ECPointFormatsExtension(formats=[0])", repr(ext)) + class TestSignatureAlgorithmsExtension(unittest.TestCase): def test__init__(self): ext = SignatureAlgorithmsExtension() From d5efeb60d81d320dd398663a51cb65b567930b34 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 30 Nov 2015 18:13:15 +0100 Subject: [PATCH 224/574] deduplicate TLSExtension parser --- tlslite/extensions.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index aeea0a45..0bbaa02c 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -101,6 +101,14 @@ def write(self): w.addFixSeq(self.extData, 1) return w.bytes + @staticmethod + def _parseExt(parser, extType, extLength, extList): + """Parse a extension using a predefined constructor""" + ext = extList[extType]() + extParser = Parser(parser.getFixBytes(extLength)) + ext = ext.parse(extParser) + return ext + def parse(self, p): """ Parses extension from the wire format @@ -118,17 +126,13 @@ def parse(self, p): # first check if we shouldn't use server side parser if self.serverType and extType in self._serverExtensions: - ext = self._serverExtensions[extType]() - ext_parser = Parser(p.getFixBytes(ext_length)) - ext = ext.parse(ext_parser) - return ext + return self._parseExt(p, extType, ext_length, + self._serverExtensions) # then fallback to universal/ClientHello-specific parsers if extType in self._universalExtensions: - ext = self._universalExtensions[extType]() - ext_parser = Parser(p.getFixBytes(ext_length)) - ext = ext.parse(ext_parser) - return ext + return self._parseExt(p, extType, ext_length, + self._universalExtensions) # finally, just save the extension data as there are extensions which # don't require specific handlers and indicate option by mere presence From 3c31734892f4df395ca2c4c61e44b3f5b66b9706 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 30 Nov 2015 20:23:11 +0100 Subject: [PATCH 225/574] allow for magic methods methods like __getattr__ are necessary for python to work properly so allow them --- pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylintrc b/pylintrc index 5d838ebc..24a94bf2 100644 --- a/pylintrc +++ b/pylintrc @@ -198,7 +198,7 @@ module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression matching correct method names #method-rgx=[a-z_][a-z0-9_]{2,30}$ # mixedCase -method-rgx=_?[a-z][A-Za-z0-9]{1,30}$ +method-rgx=((_?[a-z][A-Za-z0-9]{1,30})|(__.*__))$ # Naming hint for method names method-name-hint=[a-z_][a-z0-9_]{2,30}$ From 38d49d5eb571f11e238e40566cf8fa4124e0f189 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 30 Nov 2015 18:15:14 +0100 Subject: [PATCH 226/574] use project-standard variable names --- tlslite/extensions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 0bbaa02c..ffb9b67d 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -122,23 +122,23 @@ def parse(self, p): """ extType = p.get(2) - ext_length = p.get(2) + extLength = p.get(2) # first check if we shouldn't use server side parser if self.serverType and extType in self._serverExtensions: - return self._parseExt(p, extType, ext_length, + return self._parseExt(p, extType, extLength, self._serverExtensions) # then fallback to universal/ClientHello-specific parsers if extType in self._universalExtensions: - return self._parseExt(p, extType, ext_length, + return self._parseExt(p, extType, extLength, self._universalExtensions) # finally, just save the extension data as there are extensions which # don't require specific handlers and indicate option by mere presence self.extType = extType - self.extData = p.getFixBytes(ext_length) - assert len(self.extData) == ext_length + self.extData = p.getFixBytes(extLength) + assert len(self.extData) == extLength return self def __eq__(self, that): From 703f3a5baf2aeb35a77d66d783b24199b8112ed1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 30 Nov 2015 20:16:53 +0100 Subject: [PATCH 227/574] make possible for child classes to call super init make sure that calling super().__init__ won't blow up in other Extension classes and that the ext type is defined in the topmost class --- tlslite/extensions.py | 80 ++++++++++++++++++--------- unit_tests/test_tlslite_extensions.py | 53 +++++++++++++++++- 2 files changed, 105 insertions(+), 28 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index ffb9b67d..f93f520e 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -50,10 +50,9 @@ class TLSExtension(object): _universalExtensions = {} _serverExtensions = {} - def __init__(self, server=False): + def __init__(self, server=False, extType=None): """ - Creates a generic TLS extension that can be used either for - client hello or server hello message parsing or creation. + Creates a generic TLS extension. You'll need to use L{create} or L{parse} methods to create an extension that is actually usable. @@ -61,29 +60,58 @@ def __init__(self, server=False): @type server: boolean @param server: whatever to select ClientHello or ServerHello version for parsing + @type extType: int + @param extType: type of extension encoded as an integer """ - self.extType = None - self.extData = bytearray(0) + self.extType = extType + self._extData = bytearray(0) self.serverType = server - def create(self, extType, data): + @property + def extData(self): + """Return the on the wire encoding of extension""" + return self._extData + + def _oldCreate(self, extType, data): + """Legacy handling of create method""" + self.extType = extType + self._extData = data + + def _newCreate(self, data): + """New format for create method""" + self._extData = data + + def create(self, *args, **kwargs): """ - Initializes a generic TLS extension that can later be used in - client hello or server hello messages + Initializes a generic TLS extension. + + The extension can carry arbitrary data and have arbitrary payload, can + be used in client hello or server hello messages. + + The legacy calling method uses two arguments - the extType and data. + If the new calling method is used, only one argument is passed in - + data. @type extType: int - @param extType: type of the extension encoded as an integer between - M{0} and M{2^16-1} + @param extType: if int: type of the extension encoded as an integer + between M{0} and M{2^16-1} @type data: bytearray @param data: raw data representing extension on the wire @rtype: L{TLSExtension} """ - self.extType = extType - self.extData = data + # old style + if len(args) + len(kwargs) == 2: + self._oldCreate(*args, **kwargs) + # new style + elif len(args) + len(kwargs) == 1: + self._newCreate(*args, **kwargs) + else: + raise TypeError("Invalid number of arguments") + return self def write(self): - """ Returns encoded extension, as encoded on the wire + """Returns encoded extension, as encoded on the wire @rtype: bytearray @return: An array of bytes formatted as is supposed to be written on @@ -92,7 +120,6 @@ def write(self): @raise AssertionError: when the object was not initialized """ - assert self.extType is not None w = Writer() @@ -110,7 +137,7 @@ def _parseExt(parser, extType, extLength, extList): return ext def parse(self, p): - """ Parses extension from the wire format + """Parses extension from on the wire format @type p: L{tlslite.util.codec.Parser} @param p: data to be parsed @@ -120,7 +147,6 @@ def parse(self, p): @rtype: L{TLSExtension} """ - extType = p.get(2) extLength = p.get(2) @@ -137,12 +163,14 @@ def parse(self, p): # finally, just save the extension data as there are extensions which # don't require specific handlers and indicate option by mere presence self.extType = extType - self.extData = p.getFixBytes(extLength) - assert len(self.extData) == extLength + self._extData = p.getFixBytes(extLength) + assert len(self._extData) == extLength return self def __eq__(self, that): - """ Test if two TLS extensions will result in the same on the wire + """Test if two TLS extensions are effectively the same + + Will check if encoding them will result in the same on the wire representation. Will return False for every object that's not an extension. @@ -154,7 +182,7 @@ def __eq__(self, that): return False def __repr__(self): - """ Output human readable representation of object + """Output human readable representation of object @rtype: str """ @@ -171,16 +199,11 @@ class VarListExtension(TLSExtension): """ def __init__(self, elemLength, lengthLength, fieldName, extType): + super(VarListExtension, self).__init__(extType=extType) self._fieldName = fieldName self._internalList = None self._elemLength = elemLength self._lengthLength = lengthLength - self._extType = extType - - @property - def extType(self): - """Type of extension""" - return self._extType @property def extData(self): @@ -223,6 +246,9 @@ def parse(self, parser): def __getattr__(self, name): """Return the special field name value""" + if name == '_fieldName': + raise AttributeError("type object '{0}' has no attribute '{1}'"\ + .format(self.__class__.__name__, name)) if name == self._fieldName: return self._internalList raise AttributeError("type object '{0}' has no attribute '{1}'"\ @@ -233,7 +259,7 @@ def __setattr__(self, name, value): if name == '_fieldName': super(VarListExtension, self).__setattr__(name, value) return - if name == self._fieldName: + if hasattr(self, '_fieldName') and name == self._fieldName: self._internalList = value return super(VarListExtension, self).__setattr__(name, value) diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index b41a319c..96e610f2 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -28,10 +28,61 @@ def test___init__(self): def test_create(self): tls_extension = TLSExtension().create(1, bytearray(b'\x01\x00')) - assert tls_extension + self.assertIsNotNone(tls_extension) self.assertEqual(1, tls_extension.extType) self.assertEqual(bytearray(b'\x01\x00'), tls_extension.extData) + def test_new_style_create(self): + tls_extension = TLSExtension(extType=1).create(bytearray(b'\x01\x00')) + + self.assertIsNotNone(tls_extension) + self.assertEqual(1, tls_extension.extType) + self.assertEqual(bytearray(b'\x01\x00'), tls_extension.extData) + + def test_new_style_create_with_keyword(self): + tls_extension = TLSExtension(extType=1).create(data=\ + bytearray(b'\x01\x00')) + + self.assertIsNotNone(tls_extension) + self.assertEqual(1, tls_extension.extType) + self.assertEqual(bytearray(b'\x01\x00'), tls_extension.extData) + + def test_new_style_create_with_invalid_keyword(self): + with self.assertRaises(TypeError): + TLSExtension(extType=1).create(extData=bytearray(b'\x01\x00')) + + def test_old_style_create_with_keyword_args(self): + tls_extension = TLSExtension().create(extType=1, + data=bytearray(b'\x01\x00')) + self.assertIsNotNone(tls_extension) + self.assertEqual(1, tls_extension.extType) + self.assertEqual(bytearray(b'\x01\x00'), tls_extension.extData) + + def test_old_style_create_with_one_keyword_arg(self): + tls_extension = TLSExtension().create(1, + data=bytearray(b'\x01\x00')) + self.assertIsNotNone(tls_extension) + self.assertEqual(1, tls_extension.extType) + self.assertEqual(bytearray(b'\x01\x00'), tls_extension.extData) + + def test_old_style_create_with_invalid_keyword_name(self): + with self.assertRaises(TypeError): + TLSExtension().create(1, + extData=bytearray(b'\x01\x00')) + + def test_old_style_create_with_duplicate_keyword_name(self): + with self.assertRaises(TypeError): + TLSExtension().create(1, + extType=1) + + def test_create_with_too_few_args(self): + with self.assertRaises(TypeError): + TLSExtension().create() + + def test_create_with_too_many_args(self): + with self.assertRaises(TypeError): + TLSExtension().create(1, 2, 3) + def test_write(self): tls_extension = TLSExtension() From ba2957b3464d271fa3bb94532544c67642b748b4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 30 Nov 2015 20:30:10 +0100 Subject: [PATCH 228/574] move extType out of rest of Extension classes --- tlslite/extensions.py | 65 +++++++------------------------------------ 1 file changed, 10 insertions(+), 55 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index f93f520e..0072b58a 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -320,6 +320,7 @@ def __init__(self): See also: L{create} and L{parse}. """ + super(SNIExtension, self).__init__(extType=ExtensionType.server_name) self.serverNames = None def __repr__(self): @@ -374,14 +375,6 @@ def create(self, hostname=None, hostNames=None, serverNames=None): return self - @property - def extType(self): - """ Return the type of TLS extension, in this case - 0 - - @rtype: int - """ - return ExtensionType.server_name - @property def hostNames(self): """ Returns a simulated list of hostNames from the extension. @@ -518,7 +511,7 @@ class ServerCertTypeExtension(TLSExtension): defined in RFC 6091. @type extType: int - @ivar extType: byneruc ttoe if Certificate Type extension, i.e. 9 + @ivar extType: binary type of Certificate Type extension, i.e. 9 @type extData: bytearray @ivar extData: raw representation of the extension data @@ -533,7 +526,8 @@ def __init__(self): See also: L{create} and L{parse} """ - + super(ServerCertTypeExtension, self).__init__(server=True, \ + extType=ExtensionType.cert_type) self.cert_type = None def __repr__(self): @@ -543,15 +537,6 @@ def __repr__(self): """ return "ServerCertTypeExtension(cert_type={0!r})".format(self.cert_type) - @property - def extType(self): - """ - Return the type of TLS extension, in this case - 9 - - @rtype: int - """ - return ExtensionType.cert_type - @property def extData(self): """ @@ -609,6 +594,7 @@ def __init__(self): See also: L{create} and L{parse} """ + super(SRPExtension, self).__init__(extType=ExtensionType.srp) self.identity = None @@ -620,16 +606,6 @@ def __repr__(self): """ return "SRPExtension(identity={0!r})".format(self.identity) - @property - def extType(self): - """ - Return the type of TLS extension, in this case - 12 - - @rtype: int - """ - - return ExtensionType.srp - @property def extData(self): """ @@ -702,6 +678,7 @@ def __init__(self): See also: L{create} and L{parse} """ + super(NPNExtension, self).__init__(extType=ExtensionType.supports_npn) self.protocols = None @@ -713,14 +690,6 @@ def __repr__(self): """ return "NPNExtension(protocols={0!r})".format(self.protocols) - @property - def extType(self): - """ Return the type of TLS extension, in this case - 13172 - - @rtype: int - """ - return ExtensionType.supports_npn - @property def extData(self): """ Return the raw data encoding of the extension @@ -888,6 +857,7 @@ def __init__(self): See also: L{create} and L{parse} """ + super(TACKExtension, self).__init__(extType=ExtensionType.tack) self.tacks = [] self.activation_flags = 0 @@ -901,15 +871,6 @@ def __repr__(self): return "TACKExtension(activation_flags={0!r}, tacks={1!r})".format( self.activation_flags, self.tacks) - @property - def extType(self): - """ - Returns the type of TLS extension, in this case - 62208 - - @rtype: int - """ - return ExtensionType.tack - @property def extData(self): """ @@ -1001,17 +962,11 @@ class SignatureAlgorithmsExtension(TLSExtension): def __init__(self): """Create instance of class""" + super(SignatureAlgorithmsExtension, self).__init__(extType= + ExtensionType. + signature_algorithms) self.sigalgs = None - @property - def extType(self): - """ - Type of extension, in this case - 13 - - @rtype: int - """ - return ExtensionType.signature_algorithms - @property def extData(self): """ From 9f80f59c9825150a4b7f032192e744528bd32099 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 2 Dec 2015 19:21:45 +0100 Subject: [PATCH 229/574] clarify README --- README.md | 46 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0b28be85..bee1fb2f 100644 --- a/README.md +++ b/README.md @@ -31,18 +31,32 @@ Table of Contents 1 Introduction =============== -tlslite-ng is an open source python library that implements SSL and TLS. -tlslite-ng supports RSA and SRP ciphersuites. tlslite-ng is pure python, however -it can use other libraries for faster crypto operations. tlslite-ng integrates -with several stdlib neworking libraries. +tlslite-ng is an open source python library that implements SSL and +[TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security) +cryptographic protocols. It can be used either as a standalone wrapper around +python socket interface or as a backend for multiple other libraries. +tlslite-ng is pure python, however it can use other libraries for faster crypto +operations. tlslite-ng integrates with several stdlib neworking libraries. -API documentation is available in the 'docs' directory. +API documentation is available in the 'docs' directory of the PyPI package +or can be automatically generated using `make docs` with Epydoc installed. If you have questions or feedback, feel free to contact me. Issues and pull -requests can also be submitted through github issue tracking system. +requests can also be submitted through github issue tracking system, see +CONTRIBUTING.md file for more information. tlslite-ng aims to be a drop in replacement for the original TLS Lite. +Implemented features of TLS include: + * SSLv3, TLSv1.0, TLSv1.1 and TLSv1.2 + * ciphersuites with DHE, ECDHE, RSA and SRP key exchange together with + AES (including GCM variant), 3DES, RC4 and (the experimental) ChaCha20 + symmetric ciphers. + * Secure Renegotiation + * Encrypt Then MAC extension + * TLS_FALLBACK_SCSV + * (experimental) TACK extension + 2 Licenses/Acknowledgements ============================ @@ -67,7 +81,8 @@ Requirements: * Python 2.6 or higher is required. * Python 3.2 or higher is supported. - * python ecdsa library ([GitHub](https://github.com/warner/python-ecdsa) [PyPI](https://pypi.python.org/pypi/ecdsa)) + * python ecdsa library ([GitHub](https://github.com/warner/python-ecdsa), + [PyPI](https://pypi.python.org/pypi/ecdsa)) Options: @@ -80,6 +95,23 @@ Options: * These modules don't need to be present at installation - you can install them any time. +3.1 Automatic +------------- + +Run: +``` +pip install tlslite-ng +``` + +In case your system doesn't have pip, you can install it by first downloading +[get-pip.py](https://bootstrap.pypa.io/get-pip.py) and running +``` +python get-pip.py +``` + +3.2 Manual +---------- + Run 'python setup.py install' Test the Installation From bbeedcba6c15c3c8f342e838ab79b41a0e91de78 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 2 Dec 2015 20:07:36 +0100 Subject: [PATCH 230/574] CONTRIBUTING file --- CONTRIBUTING.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..95e608e0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,102 @@ +# How to contribute + +## How to prepare + +* You need a [GitHub account](https://github.com/signup/free) +* Submit an [issue ticket](https://github.com/tomato42/tlslite-ng/issues) for + your issue if there is none yet. + * Describe the issue and include steps to reproduce if it's a bug, mention + the earliest version that you know is affected and the version you're using. + * Describe the enhancement and your general ideas on how to implement it + if you want to add new feature or extend existing one. This is not + necessary if the change is small. +* If you are able and want to fix the issue, fork the repository on GitHub + +## Technical requirements + +To be able to work on the code you will need few pieces of software installed. +The most important is `python` interpreter. Some development dependencies have +additional restrictions on the versions used, so I recommend Python 2.7 or +Python 3.4 as the lowest versions. Git client, make, text editor and ability to +install local python packages (ability to run pip). + +The list goes as follows: + * python (2.7 or 3.4) + * git + * GNU make + * pip + +The python module dependencies are as follows: + * ecdsa + * pylint + * diff_cover + * coverage + * hypothesis + +On Fedora they can be installed using: +``` +dnf install python-ecdsa python3-ecdsa pylint python3-pylint python-diff-cover \ + python3-diff-cover python-coverage python3-coverage python2-hypothesis \ + python3-hypothesis +``` + +Optional module dependencies: + * tackpy + * m2crypto + * pycrypto + * gmpy + +On Fedora they can be installed using: +``` +pip install tackpy +dnf install m2crypto python-crypto python3-crypto python-gmpy2 python3-gmpy2 +``` + +## Make changes + +* In your forked repository, create a topic branch for your upcoming patch + (e.g. 'implement-aria' or 'bugfix-osx-crash') + * usually this is based on the master branch + * to create branch based on master: `git branch ` then + checkout the branch `git checkout `. For your own convinience + avoid working directly on the `master` branch. +* Make sure you stick to the coding style that is used in surrounding code + * you can use `pylint --msg-template="{path}:{line}: [{msg_id}({symbol}), + {obj}] {msg}" tlslite > pylint_report.txt; diff-quality --violations=pylint + pylint_report.txt` to see if your changes do not violate the general + guidelines (alternatively you can just run `make test-dev` as described + below). +* Make commits of logical units and describe them properly in commits + * When creating a comment, keep the first line short and separate it from + the rest by whiteline +* Check for unnecessary whitespace with `git diff --check` before committing + +* Generally newly submitted code should have test coverage so that it can + be clearly shown that it works correctly. + * pull requests with code refactoring of code that does not have test + coverage should have test coverage of the code added first +* Assure nothing is broken by running all tests using `make test-dev` + * Pull requests that fail the last check of the `test-dev` target, + the test coverage check, may still be accepted, but making pull request + that passes it is the best way to make the review quick. + +## Submit changes +* Push your changes to a topic branch in your fork of the repository. +* Open a pull request to the original repository and choose the right original + branch you want to patch (that usually will be tomato42/master). +* If you posted issues previously, make sure you reference them in the opening + commit of the pull request (e.g. 'fixes #12'). But _please do not close the + issue yourself_. GitHub will do that automatically once the issue is merged. +* Wait for checks to pass. Travis-ci check is mandatory, pull requests which + fail it will not be merged. Landscape and coveralls failures are not blocking + but may require explanation. Going to codeclimate and quantified code + (see README.md for links) and checking the branch and pull request is also + a good idea. + * if you are not sure if the pull will pass the checks it is OK to submit + a test pull request, but please mark it as such ('[WIP]' in title is + enough) + +# Additional Resources + +* [General GitHub documentation](http://help.github.com/) +* [GitHub pull request documentation](http://help.github.com/send-pull-requests/) From cbc6be6c0446ad5a069a27d4a6547a2a97af711f Mon Sep 17 00:00:00 2001 From: Karel Srot Date: Thu, 3 Dec 2015 14:35:09 +0100 Subject: [PATCH 231/574] fixed 2 typos in extensions.py --- tlslite/extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 26b6c7be..9e381d54 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -883,7 +883,7 @@ def extData(self): def create(self, tacks, activation_flags): """ - Initialize the insance of TACKExtension + Initialize the instance of TACKExtension @rtype: TACKExtension """ @@ -1077,7 +1077,7 @@ def extType(self): @property def extData(self): """ - Return raw encoding of the exteion + Return raw encoding of the extension @rtype: bytearray """ From 4c6e0792a50ce016da1c6c0fdc399c7d05da43f4 Mon Sep 17 00:00:00 2001 From: Karel Srot Date: Thu, 3 Dec 2015 09:35:05 +0100 Subject: [PATCH 232/574] padding-extension implementation draft --- tlslite/constants.py | 1 + tlslite/extensions.py | 62 ++++++++++++++++++++++++++- unit_tests/test_tlslite_extensions.py | 50 ++++++++++++++++++++- 3 files changed, 111 insertions(+), 2 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index a31eb670..0f60a31c 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -86,6 +86,7 @@ class ExtensionType: # RFC 6066 / 4366 ec_point_formats = 11 # RFC 4492 srp = 12 # RFC 5054 signature_algorithms = 13 # RFC 5246 + client_hello_padding = 21 # RFC 7685 encrypt_then_mac = 22 # RFC 7366 tack = 0xF300 supports_npn = 13172 diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 26b6c7be..2db68352 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -1118,6 +1118,65 @@ def parse(self, parser): return self +class PaddingExtension(TLSExtension): + + """ + ClientHello message padding with a desired size. + + Can be used to pad ClientHello messages to a desired size + in order to avoid implementation bugs caused by certain + ClientHello sizes. + + See RFC7685. + """ + + def __init__(self): + """Create instance of class""" + self.paddingData = bytearray(0) + + @property + def extType(self): + """ + Type of extension, in this case - 21 + + @rtype: int + """ + return ExtensionType.client_hello_padding + + @property + def extData(self): + """ + Return raw encoding of the extension + + @rtype: bytearray + """ + return self.paddingData + + def create(self, size): + """ + Set the padding size and create null byte padding of defined size + + @type size: int + @param size: required padding size in bytes + """ + self.paddingData = bytearray(size) + return self + + def parse(self, p): + """ + Deserialise extension from on the wire data + + @type p: L{tlslite.util.codec.Parser} + @param p: data to be parsed + + @raise SyntaxError: when the size of the passed element doesn't match + the internal representation + + @rtype: L{TLSExtension} + """ + self.paddingData = p.getFixBytes(p.getRemainingLength()) + return self + TLSExtension._universalExtensions = \ { ExtensionType.server_name : SNIExtension, @@ -1126,7 +1185,8 @@ def parse(self, parser): ExtensionType.ec_point_formats : ECPointFormatsExtension, ExtensionType.srp : SRPExtension, ExtensionType.signature_algorithms : SignatureAlgorithmsExtension, - ExtensionType.supports_npn : NPNExtension} + ExtensionType.supports_npn : NPNExtension, + ExtensionType.client_hello_padding : PaddingExtension} TLSExtension._serverExtensions = \ { diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 847780f5..dd70d1af 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -11,7 +11,7 @@ from tlslite.extensions import TLSExtension, SNIExtension, NPNExtension,\ SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\ TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\ - SignatureAlgorithmsExtension + SignatureAlgorithmsExtension, PaddingExtension from tlslite.utils.codec import Parser from tlslite.constants import NameType, ExtensionType, GroupName,\ ECPointFormat, HashAlgorithm, SignatureAlgorithm @@ -1294,5 +1294,53 @@ def test_parse_with_extra_data_at_end(self): with self.assertRaises(SyntaxError): ext.parse(parser) +class TestPaddingExtension(unittest.TestCase): + def test__init__(self): + ext = PaddingExtension() + + self.assertIsNotNone(ext) + self.assertEqual(ext.extType, 21) + self.assertEqual(ext.paddingData, bytearray(0)) + + def test_create(self): + ext = PaddingExtension() + ext.create(3) + + self.assertIsNotNone(ext) + self.assertEqual(ext.extType, 21) + self.assertEqual(ext.paddingData, bytearray(b'\x00\x00\x00')) + + def test_write(self): + ext = PaddingExtension() + ext.create(6) + + self.assertEqual(bytearray( + b'\x00\x15' + # type of extension + b'\x00\x06' + # overall length of extension + b'\x00\x00' + # 1st and 2nd null byte + b'\x00\x00' + # 3rd and 4th null byte + b'\x00\x00' # 5th and 6th null byte + ), ext.write()) + + def test_parse_with_empty_data(self): + parser = Parser(bytearray(0)) + + ext = PaddingExtension() + + ext.parse(parser) + + self.assertEqual(bytearray(b''), ext.paddingData) + + def test_parse_with_nonempty_data(self): + parser = Parser(bytearray( + b'\x00\x00' + # 1st and 2nd null byte + b'\x00\x00')) # 3rd and 4th null byte + + ext = PaddingExtension() + + ext.parse(parser) + + self.assertEqual(bytearray(b'\x00\x00\x00\x00'), ext.paddingData) + if __name__ == '__main__': unittest.main() From dce92278b8688293fc7f621f78ab620697935e6c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 4 Dec 2015 19:41:57 +0100 Subject: [PATCH 233/574] add support for SNI in HTTPTLSConnection --- tlslite/integration/clienthelper.py | 45 ++++++++++++++++++------ tlslite/integration/httptlsconnection.py | 11 +++--- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/tlslite/integration/clienthelper.py b/tlslite/integration/clienthelper.py index 62e59616..aeaafa89 100644 --- a/tlslite/integration/clienthelper.py +++ b/tlslite/integration/clienthelper.py @@ -16,11 +16,12 @@ class ClientHelper(object): TLS clients (e.g. poplib, smtplib, httplib, etc.)""" def __init__(self, - username=None, password=None, - certChain=None, privateKey=None, - checker=None, - settings = None, - anon = False): + username=None, password=None, + certChain=None, privateKey=None, + checker=None, + settings=None, + anon=False, + host=None): """ For client authentication, use one of these argument combinations: @@ -102,21 +103,45 @@ def __init__(self, self.tlsSession = None + if not self._isIP(host): + self.serverName = host + else: + self.serverName = None + + @staticmethod + def _isIP(address): + """Return True if the address is an IPv4 address""" + if not address: + return False + vals = address.split('.') + if len(vals) != 4: + return False + for i in vals: + if not i.isdigit(): + return False + j = int(i) + if not 0 <= j <= 255: + return False + return True + def _handshake(self, tlsConnection): if self.username and self.password: tlsConnection.handshakeClientSRP(username=self.username, password=self.password, checker=self.checker, settings=self.settings, - session=self.tlsSession) + session=self.tlsSession, + serverName=self.serverName) elif self.anon: tlsConnection.handshakeClientAnonymous(session=self.tlsSession, - settings=self.settings, - checker=self.checker) + settings=self.settings, + checker=self.checker, + serverName=self.serverName) else: tlsConnection.handshakeClientCert(certChain=self.certChain, privateKey=self.privateKey, checker=self.checker, settings=self.settings, - session=self.tlsSession) - self.tlsSession = tlsConnection.session \ No newline at end of file + session=self.tlsSession, + serverName=self.serverName) + self.tlsSession = tlsConnection.session diff --git a/tlslite/integration/httptlsconnection.py b/tlslite/integration/httptlsconnection.py index 474dbf34..0fdc135f 100644 --- a/tlslite/integration/httptlsconnection.py +++ b/tlslite/integration/httptlsconnection.py @@ -104,11 +104,12 @@ def __init__(self, host, port=None, strict=None, timeout=timeout) self.ignoreAbruptClose = ignoreAbruptClose ClientHelper.__init__(self, - username, password, - certChain, privateKey, - checker, - settings, - anon) + username, password, + certChain, privateKey, + checker, + settings, + anon, + host) def connect(self): httplib.HTTPConnection.connect(self) From a2c835ff05a940a26d841dfbc1693c8550bfbf7c Mon Sep 17 00:00:00 2001 From: Karel Srot Date: Thu, 3 Dec 2015 12:56:47 +0100 Subject: [PATCH 234/574] added padding extension configuration --- tlslite/handshakesettings.py | 16 +++++++++++++--- unit_tests/test_tlslite_handshakesettings.py | 13 +++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 18f01666..c61d8c0f 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -140,6 +140,7 @@ def __init__(self): self.useEncryptThenMAC = True self.rsaSigHashes = list(RSA_SIGNATURE_HASHES) self.eccCurves = list(CURVE_NAMES) + self.usePaddingExtension = True @staticmethod def _sanityCheckKeySizes(other): @@ -189,9 +190,6 @@ def _sanityCheckPrimitivesNames(other): if unknownCurve: raise ValueError("Unknown ECC Curve name: {0}".format(unknownCurve)) - if other.useEncryptThenMAC not in (True, False): - raise ValueError("useEncryptThenMAC can only be True or False") - unknownSigHash = [val for val in other.rsaSigHashes \ if val not in ALL_RSA_SIGNATURE_HASHES] if unknownSigHash: @@ -208,6 +206,15 @@ def _sanityCheckProtocolVersions(other): if other.maxVersion not in ((3, 0), (3, 1), (3, 2), (3, 3)): raise ValueError("maxVersion set incorrectly") + @staticmethod + def _sanityCheckExtensions(other): + """Check if set extension settings are sane""" + if other.useEncryptThenMAC not in (True, False): + raise ValueError("useEncryptThenMAC can only be True or False") + + if other.usePaddingExtension not in (True, False): + raise ValueError("usePaddingExtension must be True or False") + def validate(self): """ Validate the settings, filter out unsupported ciphersuites and return @@ -229,6 +236,7 @@ def validate(self): other.maxVersion = self.maxVersion other.sendFallbackSCSV = self.sendFallbackSCSV other.useEncryptThenMAC = self.useEncryptThenMAC + other.usePaddingExtension = self.usePaddingExtension other.rsaSigHashes = self.rsaSigHashes other.eccCurves = self.eccCurves @@ -254,6 +262,8 @@ def validate(self): self._sanityCheckProtocolVersions(other) + self._sanityCheckExtensions(other) + if other.maxVersion < (3,3): # No sha-2 and AEAD pre TLS 1.2 other.macNames = [e for e in self.macNames if \ diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index 5e7003aa..ce911fe4 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -207,3 +207,16 @@ def test_invalid_curve_name(self): hs.eccCurves = ['P-256'] with self.assertRaises(ValueError): hs.validate() + + def test_usePaddingExtension(self): + hs = HandshakeSettings() + self.assertTrue(hs.usePaddingExtension) + + def test_invalid_usePaddingExtension(self): + hs = HandshakeSettings() + hs.usePaddingExtension = -1 + with self.assertRaises(ValueError): + hs.validate() + +if __name__ == '__main__': + unittest.main() From e64f788643e709dd20941708b78771599e925d85 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 5 Dec 2015 22:30:55 +0100 Subject: [PATCH 235/574] document how to create TLSExtension child classes since creating new extensions is not entirely straight-forward, describe which methods and properties must be overriden by subclasses to add implementation of a new extension --- tlslite/extensions.py | 53 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 0072b58a..ae3e01f0 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014, Hubert Kario +# Copyright (c) 2014, 2015 Hubert Kario # # See the LICENSE file for legal information regarding use of this file. @@ -14,6 +14,8 @@ class TLSExtension(object): """ + Base class for handling handshake protocol hello messages extensions. + This class handles the generic information about TLS extensions used by both sides of connection in Client Hello and Server Hello messages. See U{RFC 4366} for more info. @@ -21,6 +23,25 @@ class TLSExtension(object): It is used as a base class for specific users and as a way to store extensions that are not implemented in library. + To implement a new extension you will need to create a new class which + calls this class contructor (__init__), usually specifying just the + extType parameter. The other methods which need to be implemented are: + L{extData}, L{create}, L{parse} and L{__repr__}. Finally, the extension + constructor should be added to either L{_universalExtensions}, if the parser + can be used for client and optionally server extensions. When the + client and server extensions have completely different forms, you should + add client form to the L{_universalExtensions} and the server form to + L{_serverExtensions}. Since the server MUST NOT send extensions not + advertised by client, there are no purely server-side extensions. But + if the client side extension is just marked by presence and has no payload, + the client side (thus the L{_universalExtensions} may be skipped, then + the L{TLSExtension} class will be used for implementing it. See + end of the file for type-to-constructor bindings. + + Though please note that subclassing for the purpose of parsing extensions + is not an officially supported part of API (just as underscores in their + names would indicate. + @type extType: int @ivar extType: a 2^16-1 limited integer specifying the type of the extension that it contains, e.g. 0 indicates server name extension @@ -58,10 +79,11 @@ def __init__(self, server=False, extType=None): that is actually usable. @type server: boolean - @param server: whatever to select ClientHello or ServerHello version + @param server: whether to select ClientHello or ServerHello version for parsing @type extType: int - @param extType: type of extension encoded as an integer + @param extType: type of extension encoded as an integer, to be used + by subclasses """ self.extType = extType self._extData = bytearray(0) @@ -69,7 +91,16 @@ def __init__(self, server=False, extType=None): @property def extData(self): - """Return the on the wire encoding of extension""" + """ + Return the on the wire encoding of extension + + Child classes need to override this property so that it returns just + the payload of an extension, that is, without the 4 byte generic header + common to all extension. In other words, without the extension ID and + overall extension length. + + @rtype: bytearray + """ return self._extData def _oldCreate(self, extType, data): @@ -92,6 +123,9 @@ def create(self, *args, **kwargs): If the new calling method is used, only one argument is passed in - data. + Child classes need to override this method so that it is possible + to set values for all fields used by the extension. + @type extType: int @param extType: if int: type of the extension encoded as an integer between M{0} and M{2^16-1} @@ -113,6 +147,8 @@ def create(self, *args, **kwargs): def write(self): """Returns encoded extension, as encoded on the wire + Note that child classes in general don't need to override this method. + @rtype: bytearray @return: An array of bytes formatted as is supposed to be written on the wire, including the extension_type, length and the extension @@ -139,6 +175,12 @@ def _parseExt(parser, extType, extLength, extList): def parse(self, p): """Parses extension from on the wire format + Child classes should override this method so that it parses the + extension from on the wire data. Note that child class parsers will + not receive the generic header of the extension, but just a parser + with the payload. In other words, the method should be the exact + reverse of the L{extData} property. + @type p: L{tlslite.util.codec.Parser} @param p: data to be parsed @@ -184,6 +226,9 @@ def __eq__(self, that): def __repr__(self): """Output human readable representation of object + Child classes should override this method to support more appropriate + string rendering of the extension. + @rtype: str """ return "TLSExtension(extType={0!r}, extData={1!r},"\ From b8bb491bedd83805b4e6593574ce89e176fc6172 Mon Sep 17 00:00:00 2001 From: Karel Srot Date: Thu, 3 Dec 2015 13:16:08 +0100 Subject: [PATCH 236/574] added padding extension addition to _clientSendClientHello --- tlslite/handshakehelpers.py | 41 +++++ tlslite/tlsconnection.py | 7 + unit_tests/test_tlslite_handshakehelpers.py | 161 ++++++++++++++++++++ unit_tests/test_tlslite_tlsconnection.py | 45 +++++- 4 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 tlslite/handshakehelpers.py create mode 100644 unit_tests/test_tlslite_handshakehelpers.py diff --git a/tlslite/handshakehelpers.py b/tlslite/handshakehelpers.py new file mode 100644 index 00000000..9ee0d04d --- /dev/null +++ b/tlslite/handshakehelpers.py @@ -0,0 +1,41 @@ +# Authors: +# Karel Srot +# +# See the LICENSE file for legal information regarding use of this file. + +""" +Class with various handshake helpers. +""" + +from .extensions import PaddingExtension + +class HandshakeHelpers(object): + """ + This class encapsulates helper functions to be used with a TLS handshake. + + @sort: alignUsingPaddingExtension + """ + @staticmethod + def alignClientHelloPadding(clientHello): + """ + Aligns ClientHello using the Padding extension to 512 bytes at least. + + @type data: ClientHello + @param data: ClientHello to be aligned + """ + # Check clientHello size if padding extension should be added + # we want to add the extension even when using just SSLv3 + # cut-off 4 bytes with the Hello header (ClientHello type + Length) + clientHelloLength = len(clientHello.write()) - 4 + if 256 <= clientHelloLength <= 511: + if clientHello.extensions is None: + clientHello.extensions = [] + # we need to recalculate the size after extension list addition + # results in extra 2 bytes, equals to + # clientHelloLength = len(clientHello.write()) - 4 + clientHelloLength += 2 + # we want to get 512 bytes in total, including the padding + # extension header (4B) + paddingExtensionInstance = PaddingExtension().create( + max(512 - clientHelloLength - 4, 0)) + clientHello.extensions.append(paddingExtensionInstance) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index d671e2d2..69f7f3cb 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -29,6 +29,7 @@ from .utils.tackwrapper import * from .keyexchange import KeyExchange, RSAKeyExchange, DHE_RSAKeyExchange, \ ECDHE_RSAKeyExchange, SRPKeyExchange +from .handshakehelpers import HandshakeHelpers class TLSConnection(TLSRecordLayer): """ @@ -592,6 +593,12 @@ def _clientSendClientHello(self, settings, session, srpUsername, reqTack, nextProtos is not None, serverName, extensions=extensions) + + # Check if padding extension should be added + # we want to add extensions even when using just SSLv3 + if settings.usePaddingExtension: + HandshakeHelpers.alignClientHelloPadding(clientHello) + for result in self._sendMsg(clientHello): yield result yield clientHello diff --git a/unit_tests/test_tlslite_handshakehelpers.py b/unit_tests/test_tlslite_handshakehelpers.py new file mode 100644 index 00000000..7ff6427e --- /dev/null +++ b/unit_tests/test_tlslite_handshakehelpers.py @@ -0,0 +1,161 @@ +# Copyright (c) 2014, Karel Srot +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest +from tlslite.handshakehelpers import HandshakeHelpers +from tlslite.messages import ClientHello +from tlslite.extensions import SNIExtension + +class TestHandshakeHelpers(unittest.TestCase): + def test_alignClientHelloPadding_length_less_than_256_bytes(self): + clientHello = ClientHello() + clientHello.create((3,0), bytearray(32), bytearray(0), []) + + clientHelloLength = len(clientHello.write()) + self.assertTrue(clientHelloLength - 4 < 256) + + HandshakeHelpers.alignClientHelloPadding(clientHello) + + # clientHello should not be changed due to small length + self.assertEqual(clientHelloLength, len(clientHello.write())) + + def test_alignClientHelloPadding_length_256_bytes(self): + clientHello = ClientHello() + clientHello.create((3,0), bytearray(32), bytearray(0), []) + clientHello.extensions = [] + + ext = SNIExtension() + ext.create(hostNames=[ + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeee'), + ]) + clientHello.extensions.append(ext) + clientHelloLength = len(clientHello.write()) + # clientHello length (excluding 4B header) should equal to 256 + self.assertEqual(256, clientHelloLength - 4) + + HandshakeHelpers.alignClientHelloPadding(clientHello) + + # clientHello length (excluding 4B header) should equal to 512 + data = clientHello.write() + self.assertEqual(512, len(data) - 4) + # previously created data should be extended with the padding extension + # starting with the padding extension type \x00\x15 (21) + self.assertEqual(bytearray(b'\x00\x15'), data[clientHelloLength:clientHelloLength+2]) + + def test_alignClientHelloPadding_length_of_508_bytes(self): + clientHello = ClientHello() + clientHello.create((3,0), bytearray(32), bytearray(0), []) + clientHello.extensions = [] + + ext = SNIExtension() + ext.create(hostNames=[ + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccdddd'), + ]) + clientHello.extensions.append(ext) + clientHelloLength = len(clientHello.write()) + self.assertEqual(508, clientHelloLength - 4) + + HandshakeHelpers.alignClientHelloPadding(clientHello) + + # clientHello length should equal to 512, ignoring handshake + # protocol header (4B) + data = clientHello.write() + self.assertEqual(512, len(data) - 4) + # padding extension should have zero byte size + self.assertEqual(bytearray(b'\x00\x15\x00\x00'), data[clientHelloLength:]) + + def test_alignClientHelloPadding_length_of_511_bytes(self): + clientHello = ClientHello() + clientHello.create((3,0), bytearray(32), bytearray(0), []) + clientHello.extensions = [] + + ext = SNIExtension() + ext.create(hostNames=[ + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddd'), + ]) + clientHello.extensions.append(ext) + clientHelloLength = len(clientHello.write()) + self.assertEqual(511, clientHelloLength - 4) + + HandshakeHelpers.alignClientHelloPadding(clientHello) + + # clientHello length should equal to 515, ignoring handshake + # protocol header (4B) + data = clientHello.write() + self.assertEqual(515, len(data) - 4) + # padding extension should have zero byte size + self.assertEqual(bytearray(b'\x00\x15\x00\x00'), data[clientHelloLength:]) + + + def test_alignClientHelloPadding_length_of_512_bytes(self): + clientHello = ClientHello() + clientHello.create((3,0), bytearray(32), bytearray(0), []) + clientHello.extensions = [] + + ext = SNIExtension() + ext.create(hostNames=[ + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee'), + bytearray(b'aaaaaaaaaabbbbbbbbbbccccccccccdddddddd'), + ]) + clientHello.extensions.append(ext) + clientHelloLength = len(clientHello.write()) + self.assertEqual(512, clientHelloLength - 4) + + HandshakeHelpers.alignClientHelloPadding(clientHello) + + # clientHello should not be changed due to sufficient length (>=512) + self.assertEqual(clientHelloLength, len(clientHello.write())) + + def test_alignClientHelloPadding_extension_list_initialization(self): + clientHello = ClientHello() + clientHello.create((3,0), bytearray(32), bytearray(0), range(0, 129)) + + clientHelloLength = len(clientHello.write()) + self.assertTrue(512 > clientHelloLength - 4 > 255) + + HandshakeHelpers.alignClientHelloPadding(clientHello) + + # verify that the extension list has been added to clientHello + self.assertTrue(type(clientHello.extensions) is list) + # clientHello length should equal to 512, ignoring handshake + # protocol header (4B) + data = clientHello.write() + self.assertEqual(512, len(data) - 4) + # padding extension should have been added after 2 extra bytes + # added due to an extension list + self.assertEqual(bytearray(b'\x00\x15'), data[clientHelloLength+2:clientHelloLength+4]) + +if __name__ == '__main__': + unittest.main() diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index 11a7bc1c..954de9fc 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -10,13 +10,14 @@ import unittest from tlslite.recordlayer import RecordLayer -from tlslite.messages import ServerHello, ClientHello -from tlslite.constants import CipherSuite, AlertDescription +from tlslite.messages import ServerHello, ClientHello, Alert, RecordHeader3 +from tlslite.constants import CipherSuite, AlertDescription, ContentType from tlslite.tlsconnection import TLSConnection -from tlslite.errors import TLSLocalAlert +from tlslite.errors import TLSLocalAlert, TLSRemoteAlert from tlslite.x509 import X509 from tlslite.x509certchain import X509CertChain from tlslite.utils.keyfactory import parsePEMKey +from tlslite.handshakesettings import HandshakeSettings from unit_tests.mocksock import MockSocket @@ -124,3 +125,41 @@ def test_server_with_client_proposing_SHA256_on_TLSv1_1(self): self.assertEqual(err.exception.description, AlertDescription.handshake_failure) + + def prepare_mock_socket_with_handshake_failure(self): + alertObj = Alert().create(AlertDescription.handshake_failure) + alert = alertObj.write() + header = RecordHeader3().create((3, 3), ContentType.alert, len(alert)) + return MockSocket(header.write() + alert) + + def test_padding_extension_with_hello_over_256(self): + sock = self.prepare_mock_socket_with_handshake_failure() + + conn = TLSConnection(sock) + # create hostname extension + with self.assertRaises(TLSRemoteAlert): + # use serverName with 254 bytes + conn.handshakeClientCert( + serverName='aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd' + + 'eeeeeeeeeeffffffffffgggggggggghhhhhhhhhh' + + 'iiiiiiiiiijjjjjjjjjjkkkkkkkkkkllllllllll' + + 'mmmmmmmmmmnnnnnnnnnnoooooooooopppppppppp' + + 'qqqqqqqqqqrrrrrrrrrrsssssssssstttttttttt' + + 'uuuuuuuuuuvvvvvvvvvvwwwwwwwwwwxxxxxxxxxx' + + 'yyyyyyyyyy.com') + + self.assertEqual(len(sock.sent), 1) + # check for version and content type (handshake) + self.assertEqual(sock.sent[0][0:3], bytearray( + b'\x16' + + b'\x03\x03')) + # check for handshake message type (client_hello) + self.assertEqual(sock.sent[0][5:6], bytearray( + b'\x01')) + self.assertEqual(sock.sent[0][5:9], bytearray( + b'\x01\x00\x02\x00')) + # 5 bytes is record layer header, 4 bytes is handshake protocol header + self.assertEqual(len(sock.sent[0]) - 5 - 4, 512) + +if __name__ == '__main__': + unittest.main() From d8a2ef0430cfece60651541ae2e9b2b35548c393 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 9 Dec 2015 15:05:04 +0100 Subject: [PATCH 237/574] fix TLS_DHE_RSA_WITH_AES_128_CBC_SHA reverse lookup --- tlslite/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index a31eb670..31c73b0c 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -341,7 +341,7 @@ class CipherSuite: TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016 ietfNames[0x0016] = 'TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA' TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 - ietfNames[0x0016] = 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA' + ietfNames[0x0033] = 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA' TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 ietfNames[0x0039] = 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA' From f4a7735b6082392c86a7810fbe5679cefe1bfc01 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 10 Dec 2015 16:24:46 +0100 Subject: [PATCH 238/574] use Ondrej Moris rewording for TLSExtension doc --- tlslite/extensions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index ae3e01f0..633701c3 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -26,11 +26,11 @@ class TLSExtension(object): To implement a new extension you will need to create a new class which calls this class contructor (__init__), usually specifying just the extType parameter. The other methods which need to be implemented are: - L{extData}, L{create}, L{parse} and L{__repr__}. Finally, the extension - constructor should be added to either L{_universalExtensions}, if the parser - can be used for client and optionally server extensions. When the - client and server extensions have completely different forms, you should - add client form to the L{_universalExtensions} and the server form to + L{extData}, L{create}, L{parse} and L{__repr__}. If the parser can be used + for client and optionally server extensions, the extension constructor + should be added to L{_universalExtensions}. Otherwise, when the client and + server extensions have completely different forms, you should add client + form to the L{_universalExtensions} and the server form to L{_serverExtensions}. Since the server MUST NOT send extensions not advertised by client, there are no purely server-side extensions. But if the client side extension is just marked by presence and has no payload, From 4247d2e51f64e06363f508821f297627dc6a13e3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 10 Dec 2015 16:42:24 +0100 Subject: [PATCH 239/574] update README with latest changes --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bee1fb2f..94dd3078 100644 --- a/README.md +++ b/README.md @@ -556,6 +556,7 @@ encrypt-then-MAC mode for CBC ciphers. =========== 0.6.0 - WIP + - support for the padding extension from RFC 7685 (Karel Srot) - abitlity to perform reverse lookups on many of the TLS type enumerations - added ECDHE_RSA key exchange together with associated ciphersuites - refactor key exchange code to remove duplication and make adding new methods From 2e1f04c186b26922bae75ce6b772b0e15d2d7044 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 11 Dec 2015 22:07:06 +0100 Subject: [PATCH 240/574] ServerHello - print valid tuple for repr --- tlslite/messages.py | 2 +- unit_tests/test_tlslite_messages.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 1bcdc43b..b8545971 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -627,7 +627,7 @@ def __str__(self): return base + ret def __repr__(self): - return "ServerHello(server_version=({0[0]}.{0[1]}), random={1!r}, "\ + return "ServerHello(server_version=({0[0]}, {0[1]}), random={1!r}, "\ "session_id={2!r}, cipher_suite={3}, compression_method={4}, "\ "_tack_ext={5}, extensions={6!r})".format(\ self.server_version, self.random, self.session_id, diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 24f3e195..2a888727 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -913,7 +913,7 @@ def test___repr__(self): None, extensions=[]) self.maxDiff = None - self.assertEqual("ServerHello(server_version=(3.0), "\ + self.assertEqual("ServerHello(server_version=(3, 0), "\ "random=bytearray(b'\\x00\\x00"\ "\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00"\ "\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00"\ From d002d95929fe4d20808490d8200bd6831ce9301f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 11 Dec 2015 23:25:58 +0100 Subject: [PATCH 241/574] fix GroupName brainpool curve IDs we want to add elements of the list, not the list itself --- tlslite/constants.py | 2 +- unit_tests/test_tlslite_constants.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index f0fba01d..a5d456ed 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -146,7 +146,7 @@ class GroupName(TLSEnum): brainpoolP256r1 = 26 brainpoolP384r1 = 27 brainpoolP512r1 = 28 - allEC.append(list(range(26, 29))) + allEC.extend(list(range(26, 29))) # RFC-ietf-tls-negotiated-ff-dhe-10 ffdhe2048 = 256 diff --git a/unit_tests/test_tlslite_constants.py b/unit_tests/test_tlslite_constants.py index a115eca2..41887160 100644 --- a/unit_tests/test_tlslite_constants.py +++ b/unit_tests/test_tlslite_constants.py @@ -48,6 +48,9 @@ class TestGroupName(unittest.TestCase): def test_toRepr(self): self.assertEqual(GroupName.toRepr(256), 'ffdhe2048') + def test_toRepr_with_brainpool(self): + self.assertEqual(GroupName.toRepr(27), 'brainpoolP384r1') + class TestAlertDescription(unittest.TestCase): def test_toRepr(self): self.assertEqual(AlertDescription.toStr(40), 'handshake_failure') From ed002a9e652962349145dd2a84a832e12773f13b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 12 Dec 2015 22:31:05 +0100 Subject: [PATCH 242/574] human readable representation of ServerHelloDone object --- tlslite/messages.py | 4 ++++ unit_tests/test_tlslite_messages.py | 13 ++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 1bcdc43b..73b4471f 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1130,6 +1130,10 @@ def write(self): w = Writer() return self.postWrite(w) + def __repr__(self): + """Human readable representation of object""" + return "ServerHelloDone()" + class ClientKeyExchange(HandshakeMsg): """ diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 24f3e195..0d11328b 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -9,7 +9,7 @@ import unittest from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ - CertificateRequest, CertificateVerify + CertificateRequest, CertificateVerify, ServerHelloDone from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ @@ -1879,5 +1879,16 @@ def test_write_with_TLSv1_2(self): b'\xff\xba' # signature )) +class TestServerHelloDone(unittest.TestCase): + def test___init__(self): + shd = ServerHelloDone() + + self.assertIsNotNone(shd) + + def test___repr__(self): + shd = ServerHelloDone() + + self.assertEqual("ServerHelloDone()", repr(shd)) + if __name__ == '__main__': unittest.main() From 18685d72696d3e98c4023b0d9af9cfaf7bc8ecef Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 12 Dec 2015 22:56:35 +0100 Subject: [PATCH 243/574] allow subclassing TLSEnum subclasses because both the __dict__ and vars() return the values of the current class, not all the classes in hierarchy, we need to manually traverse the hierarchy and collect all the fields ourselves this allows for extending the subclasses of TLSEnum subclasses and making both toRepr() and toStr() continue to work --- tlslite/constants.py | 12 +++++++++++- unit_tests/test_tlslite_constants.py | 19 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index f0fba01d..e5066c2c 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -13,6 +13,15 @@ class TLSEnum(object): """Base class for different enums of TLS IDs""" + @classmethod + def _recursiveVars(cls, klass): + """Call vars recursively on base classes""" + fields = dict() + for basecls in klass.__bases__: + fields.update(cls._recursiveVars(basecls)) + fields.update(dict(vars(klass))) + return fields + @classmethod def toRepr(cls, value, blacklist=None): """ @@ -20,9 +29,10 @@ def toRepr(cls, value, blacklist=None): name if found, None otherwise """ + fields = cls._recursiveVars(cls) if blacklist is None: blacklist = [] - return next((key for key, val in cls.__dict__.items() \ + return next((key for key, val in fields.items() \ if key not in ('__weakref__', '__dict__', '__doc__', '__module__') and \ key not in blacklist and \ diff --git a/unit_tests/test_tlslite_constants.py b/unit_tests/test_tlslite_constants.py index a115eca2..5c0689c1 100644 --- a/unit_tests/test_tlslite_constants.py +++ b/unit_tests/test_tlslite_constants.py @@ -10,7 +10,24 @@ import unittest from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ - ContentType, AlertDescription, AlertLevel, HandshakeType, GroupName + ContentType, AlertDescription, AlertLevel, HandshakeType, GroupName, \ + TLSEnum + +class TestTLSEnumSubClassing(unittest.TestCase): + + class SubClass(TLSEnum): + value = 1 + + def test_toRepr(self): + self.assertEqual(self.SubClass.toStr(1), 'value') + + class SubSubClass(SubClass): + new_value = 2 + + def test_toRepr_SubSubClass(self): + self.assertEqual(self.SubSubClass.toStr(1), 'value') + self.assertEqual(self.SubSubClass.toStr(2), 'new_value') + class TestHashAlgorithm(unittest.TestCase): From 14cb6070b0ebf197513983bca8f031c17e29001d Mon Sep 17 00:00:00 2001 From: Karel Srot Date: Tue, 15 Dec 2015 12:55:10 +0100 Subject: [PATCH 244/574] update padding extension to call parent class constructor --- tlslite/extensions.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index fc8de5d3..414f3db3 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -1070,17 +1070,10 @@ class PaddingExtension(TLSExtension): def __init__(self): """Create instance of class""" + extType = ExtensionType.client_hello_padding + super(PaddingExtension, self).__init__(extType=extType) self.paddingData = bytearray(0) - @property - def extType(self): - """ - Type of extension, in this case - 21 - - @rtype: int - """ - return ExtensionType.client_hello_padding - @property def extData(self): """ From 47e738d5f45c9cabb9dc6d0a5509caac0650e50e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 16 Dec 2015 13:18:01 +0100 Subject: [PATCH 245/574] add dependency on unittest and mock for development --- CONTRIBUTING.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95e608e0..7b6ef237 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,8 @@ To be able to work on the code you will need few pieces of software installed. The most important is `python` interpreter. Some development dependencies have additional restrictions on the versions used, so I recommend Python 2.7 or -Python 3.4 as the lowest versions. Git client, make, text editor and ability to +Python 3.4 as the lowest versions (see .travis.yml if you want details). +Git client, make, text editor and ability to install local python packages (ability to run pip). The list goes as follows: @@ -27,6 +28,8 @@ The list goes as follows: * pip The python module dependencies are as follows: + * unittest (unittest2 on Python 2; should be part of Python 3 install) + * mock (should be part of Python 3 distribution of unittest) * ecdsa * pylint * diff_cover @@ -37,7 +40,7 @@ On Fedora they can be installed using: ``` dnf install python-ecdsa python3-ecdsa pylint python3-pylint python-diff-cover \ python3-diff-cover python-coverage python3-coverage python2-hypothesis \ - python3-hypothesis + python3-hypothesis python3-libs python-unittest2 python-mock ``` Optional module dependencies: From 3185aa52d5ba36d827453172180bc69ca5267cab Mon Sep 17 00:00:00 2001 From: Karel Srot Date: Wed, 16 Dec 2015 13:23:29 +0100 Subject: [PATCH 246/574] Fix pep8, pep257 and epydoc nits --- tlslite/extensions.py | 30 +++++++++++++++--------------- tlslite/handshakehelpers.py | 12 ++++++------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 414f3db3..0f0f1ff8 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -1056,8 +1056,8 @@ def parse(self, parser): return self -class PaddingExtension(TLSExtension): +class PaddingExtension(TLSExtension): """ ClientHello message padding with a desired size. @@ -1069,7 +1069,7 @@ class PaddingExtension(TLSExtension): """ def __init__(self): - """Create instance of class""" + """Create instance of class.""" extType = ExtensionType.client_hello_padding super(PaddingExtension, self).__init__(extType=extType) self.paddingData = bytearray(0) @@ -1077,7 +1077,7 @@ def __init__(self): @property def extData(self): """ - Return raw encoding of the extension + Return raw encoding of the extension. @rtype: bytearray """ @@ -1085,7 +1085,7 @@ def extData(self): def create(self, size): """ - Set the padding size and create null byte padding of defined size + Set the padding size and create null byte padding of defined size. @type size: int @param size: required padding size in bytes @@ -1095,7 +1095,7 @@ def create(self, size): def parse(self, p): """ - Deserialise extension from on the wire data + Deserialise extension from on the wire data. @type p: L{tlslite.util.codec.Parser} @param p: data to be parsed @@ -1110,16 +1110,16 @@ def parse(self, p): TLSExtension._universalExtensions = \ { - ExtensionType.server_name : SNIExtension, - ExtensionType.cert_type : ClientCertTypeExtension, - ExtensionType.supported_groups : SupportedGroupsExtension, - ExtensionType.ec_point_formats : ECPointFormatsExtension, - ExtensionType.srp : SRPExtension, - ExtensionType.signature_algorithms : SignatureAlgorithmsExtension, - ExtensionType.supports_npn : NPNExtension, - ExtensionType.client_hello_padding : PaddingExtension} + ExtensionType.server_name: SNIExtension, + ExtensionType.cert_type: ClientCertTypeExtension, + ExtensionType.supported_groups: SupportedGroupsExtension, + ExtensionType.ec_point_formats: ECPointFormatsExtension, + ExtensionType.srp: SRPExtension, + ExtensionType.signature_algorithms: SignatureAlgorithmsExtension, + ExtensionType.supports_npn: NPNExtension, + ExtensionType.client_hello_padding: PaddingExtension} TLSExtension._serverExtensions = \ { - ExtensionType.cert_type : ServerCertTypeExtension, - ExtensionType.tack : TACKExtension} + ExtensionType.cert_type: ServerCertTypeExtension, + ExtensionType.tack: TACKExtension} diff --git a/tlslite/handshakehelpers.py b/tlslite/handshakehelpers.py index 9ee0d04d..c935fc8e 100644 --- a/tlslite/handshakehelpers.py +++ b/tlslite/handshakehelpers.py @@ -3,25 +3,25 @@ # # See the LICENSE file for legal information regarding use of this file. -""" -Class with various handshake helpers. -""" +"""Class with various handshake helpers.""" from .extensions import PaddingExtension + class HandshakeHelpers(object): """ This class encapsulates helper functions to be used with a TLS handshake. @sort: alignUsingPaddingExtension """ + @staticmethod def alignClientHelloPadding(clientHello): """ - Aligns ClientHello using the Padding extension to 512 bytes at least. + Align ClientHello using the Padding extension to 512 bytes at least. - @type data: ClientHello - @param data: ClientHello to be aligned + @type clientHello: ClientHello + @param clientHello: ClientHello to be aligned """ # Check clientHello size if padding extension should be added # we want to add the extension even when using just SSLv3 From 99a1f9b59fb9d5188d765011545deb68d88cef59 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 16 Dec 2015 19:28:53 +0100 Subject: [PATCH 247/574] pylint - no warnings about disabled warnings disabling and enabling warnings is sometimes necessary (e.g. when matching behaviour of 3rd party code), verifying if local enabling or disabling of warnings is not abused is reviewers job, as pylint can't check if the disablement has a sensible comment before it --- pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylintrc b/pylintrc index 24a94bf2..cb75db12 100644 --- a/pylintrc +++ b/pylintrc @@ -44,7 +44,7 @@ symbols=no # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -#disable= +disable=locally-disabled,locally-enabled [REPORTS] From 4dd8719871aac65e98de0a7661b4b05651beddd6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 22 Dec 2015 13:14:47 +0100 Subject: [PATCH 248/574] clean up cryptomath imports by using * import we are importing a lot of extraneous garbage, use specific imports only --- tlslite/utils/cryptomath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index b30edb52..81b1b32b 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -15,7 +15,7 @@ import binascii import sys -from .compat import * +from .compat import compat26Str, compatHMAC, compatLong # ************************************************************************** From 00f1997b628b0be9844bbd5cc37f4ac72214dab1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 22 Dec 2015 13:16:57 +0100 Subject: [PATCH 249/574] Workaround FIPS limitations In FIPS mode use of MD5 is restricted while use of RC4 is disallowed, so we need to make the library indicate to hashlib that we will use MD5 "carefully" and in case of RC4, we simply don't use m2crypto in FIPS mode --- tlslite/handshakehashes.py | 2 +- tlslite/mathtls.py | 1 + tlslite/recordlayer.py | 2 +- tlslite/utils/cryptomath.py | 10 +- tlslite/utils/tlshashlib.py | 32 ++++++ unit_tests/test_tlslite_utils_constanttime.py | 2 +- .../test_tlslite_utils_cryptomath_m2crypto.py | 106 ++++++++++++++++++ unit_tests/test_tlslite_utils_tlshashlib.py | 27 +++++ 8 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 tlslite/utils/tlshashlib.py create mode 100644 unit_tests/test_tlslite_utils_cryptomath_m2crypto.py create mode 100644 unit_tests/test_tlslite_utils_tlshashlib.py diff --git a/tlslite/handshakehashes.py b/tlslite/handshakehashes.py index 3c03d560..666abbc1 100644 --- a/tlslite/handshakehashes.py +++ b/tlslite/handshakehashes.py @@ -5,7 +5,7 @@ from .utils.compat import compat26Str, compatHMAC from .utils.cryptomath import MD5, SHA1 -import hashlib +from .utils import tlshashlib as hashlib class HandshakeHashes(object): diff --git a/tlslite/mathtls.py b/tlslite/mathtls.py index aef0320e..5ee02921 100644 --- a/tlslite/mathtls.py +++ b/tlslite/mathtls.py @@ -11,6 +11,7 @@ from .utils.compat import * from .utils.cryptomath import * from .constants import CipherSuite +from .utils import tlshashlib as hashlib import hmac diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 8a535ab6..a9d5d81e 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -6,7 +6,7 @@ import socket import errno -import hashlib +from .utils import tlshashlib as hashlib from .constants import ContentType, CipherSuite from .messages import RecordHeader3, RecordHeader2, Message from .utils.cipherfactory import createAESGCM, createAES, createRC4, \ diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index 81b1b32b..2f8cb66a 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -27,6 +27,14 @@ from M2Crypto import m2 m2cryptoLoaded = True + try: + with open('/proc/sys/crypto/fips_enabled', 'r') as fipsFile: + if '1' in fipsFile.read(): + m2cryptoLoaded = False + except (IOError, OSError): + # looks like we're running in container, likely not FIPS mode + m2cryptoLoaded = True + except ImportError: m2cryptoLoaded = False @@ -67,7 +75,7 @@ def getRandomBytes(howMany): # ************************************************************************** import hmac -import hashlib +from . import tlshashlib as hashlib def MD5(b): """Return a MD5 digest of data""" diff --git a/tlslite/utils/tlshashlib.py b/tlslite/utils/tlshashlib.py new file mode 100644 index 00000000..346f5455 --- /dev/null +++ b/tlslite/utils/tlshashlib.py @@ -0,0 +1,32 @@ +# Author: Hubert Kario (c) 2015 +# see LICENCE file for legal information regarding use of this file + +"""hashlib that handles FIPS mode.""" + +# Because we are extending the hashlib module, we need to import all its +# fields to suppport the same uses +# pylint: disable=unused-wildcard-import, wildcard-import +from hashlib import * +# pylint: enable=unused-wildcard-import, wildcard-import +import hashlib + + +def _fipsFunction(func, *args, **kwargs): + """Make hash function support FIPS mode.""" + try: + return func(*args, **kwargs) + except ValueError: + return func(*args, usedforsecurity=False, **kwargs) + + +# redefining the function is exactly what we intend to do +# pylint: disable=function-redefined +def md5(*args, **kwargs): + """MD5 constructor that works in FIPS mode.""" + return _fipsFunction(hashlib.md5, *args, **kwargs) + + +def new(*args, **kwargs): + """General constructor that works in FIPS mode.""" + return _fipsFunction(hashlib.new, *args, **kwargs) +# pylint: enable=function-redefined diff --git a/unit_tests/test_tlslite_utils_constanttime.py b/unit_tests/test_tlslite_utils_constanttime.py index d719b25f..2fd63f10 100644 --- a/unit_tests/test_tlslite_utils_constanttime.py +++ b/unit_tests/test_tlslite_utils_constanttime.py @@ -15,7 +15,7 @@ from tlslite.utils.compat import compatHMAC from tlslite.recordlayer import RecordLayer -import hashlib +import tlslite.utils.tlshashlib as hashlib import hmac class TestContanttime(unittest.TestCase): diff --git a/unit_tests/test_tlslite_utils_cryptomath_m2crypto.py b/unit_tests/test_tlslite_utils_cryptomath_m2crypto.py new file mode 100644 index 00000000..0a713b99 --- /dev/null +++ b/unit_tests/test_tlslite_utils_cryptomath_m2crypto.py @@ -0,0 +1,106 @@ +# Copyright (c) 2014, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call +import sys +try: + # Python 2 + reload +except NameError: + try: + # Python >= 3.4 + from importlib import reload + except ImportError: + # Python <= 3.3 + from imp import reload +try: + import __builtin__ as builtins +except ImportError: + import builtins + +real_open = builtins.open + +class magic_open(object): + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + def __enter__(self): + if self.args[0] == '/proc/sys/crypto/fips_enabled': + m = mock.MagicMock() + m.read.return_value = '1' + self.f = m + return m + else: + self.f = real_open(*self.args, **self.kwargs) + return self.f + + def __exit__(self, exc_type, exc_value, exc_traceback): + self.f.close() + +class magic_open_error(object): + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + def __enter__(self): + if self.args[0] == '/proc/sys/crypto/fips_enabled': + m = mock.MagicMock() + self.f = m + raise IOError(12) + else: + self.f = real_open(*self.args, **self.kwargs) + return self.f + + def __exit__(self, exc_type, exc_value, exc_traceback): + self.f.close() + + +class TestM2CryptoLoaded(unittest.TestCase): + def test_import_without_m2crypto(self): + with mock.patch.dict('sys.modules', {'M2Crypto': None}): + import tlslite.utils.cryptomath + reload(tlslite.utils.cryptomath) + from tlslite.utils.cryptomath import m2cryptoLoaded + self.assertFalse(m2cryptoLoaded) + + def test_import_with_m2crypto(self): + fake_m2 = mock.MagicMock() + + with mock.patch.dict('sys.modules', {'M2Crypto': fake_m2}): + import tlslite.utils.cryptomath + reload(tlslite.utils.cryptomath) + from tlslite.utils.cryptomath import m2cryptoLoaded + self.assertTrue(m2cryptoLoaded) + + def test_import_with_m2crypto_in_fips_mode(self): + fake_m2 = mock.MagicMock() + + with mock.patch.dict('sys.modules', {'M2Crypto': fake_m2}): + with mock.patch.object(builtins, 'open', magic_open): + import tlslite.utils.cryptomath + reload(tlslite.utils.cryptomath) + from tlslite.utils.cryptomath import m2cryptoLoaded + self.assertFalse(m2cryptoLoaded) + + def test_import_with_m2crypto_in_container(self): + fake_m2 = mock.MagicMock() + + with mock.patch.dict('sys.modules', {'M2Crypto': fake_m2}): + with mock.patch.object(builtins, 'open', magic_open_error): + import tlslite.utils.cryptomath + reload(tlslite.utils.cryptomath) + from tlslite.utils.cryptomath import m2cryptoLoaded + self.assertTrue(m2cryptoLoaded) diff --git a/unit_tests/test_tlslite_utils_tlshashlib.py b/unit_tests/test_tlslite_utils_tlshashlib.py new file mode 100644 index 00000000..bf05e527 --- /dev/null +++ b/unit_tests/test_tlslite_utils_tlshashlib.py @@ -0,0 +1,27 @@ +# Copyright (c) 2014, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call + +class TestTLSHashlib(unittest.TestCase): + + def test_in_fips_mode(self): + def m(*args, **kwargs): + if 'usedforsecurity' not in kwargs: + raise ValueError("MD5 disabled in FIPS mode") + + with mock.patch('hashlib.md5', m): + from tlslite.utils.tlshashlib import md5 + md5() From a32977ee159317144401ff2c0bcc0f8417c55928 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 6 Jan 2016 02:48:07 +0100 Subject: [PATCH 250/574] fix testing of FIPS workaround because we're messing with how the whole module behaves, regular mock.patch cannot clean up after us, as such we need to reload the whole module after the testing is done to not affect other tests --- unit_tests/test_tlslite_utils_cryptomath_m2crypto.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/unit_tests/test_tlslite_utils_cryptomath_m2crypto.py b/unit_tests/test_tlslite_utils_cryptomath_m2crypto.py index 0a713b99..0d7140c0 100644 --- a/unit_tests/test_tlslite_utils_cryptomath_m2crypto.py +++ b/unit_tests/test_tlslite_utils_cryptomath_m2crypto.py @@ -104,3 +104,8 @@ def test_import_with_m2crypto_in_container(self): reload(tlslite.utils.cryptomath) from tlslite.utils.cryptomath import m2cryptoLoaded self.assertTrue(m2cryptoLoaded) + + @classmethod + def tearDownClass(cls): + import tlslite.utils.cryptomath + reload(tlslite.utils.cryptomath) From 1c17ba36d964254133493a0bcdbbe36f62ad50d0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 7 Jan 2016 17:02:31 +0100 Subject: [PATCH 251/574] cleanup encrypt in ChaCha20() --- tlslite/utils/chacha.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py index c5a7eb3b..85dfc645 100644 --- a/tlslite/utils/chacha.py +++ b/tlslite/utils/chacha.py @@ -117,8 +117,8 @@ def encrypt(self, plaintext): self.rounds) key_stream = ChaCha.word_to_bytearray(key_stream) block = plaintext[i*64:(i+1)*64] - encrypted_message += bytearray((x ^ y for x, y \ - in zip(key_stream, block))) + encrypted_message += bytearray(x ^ y for x, y + in zip(key_stream, block)) return encrypted_message From bfb6573c3d8a9d39fd9e5cfdfb9319a1326c2099 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 7 Jan 2016 18:46:37 +0100 Subject: [PATCH 252/574] use implicit loops where possible because implicit loops are faster than explicit loops writing code like this is not only more pythonic but also makes it run faster --- tlslite/utils/chacha.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py index 85dfc645..b4785082 100644 --- a/tlslite/utils/chacha.py +++ b/tlslite/utils/chacha.py @@ -66,18 +66,13 @@ def chacha_block(key, counter, nonce, rounds): for i in range(0, rounds // 2): ChaCha.double_round(working_state) - for i, _ in enumerate(working_state): - state[i] = (state[i] + working_state[i]) & 0xffffffff - - return state + return [(st + wrkSt) & 0xffffffff for st, wrkSt + in zip(state, working_state)] @staticmethod def word_to_bytearray(state): """Convert state to little endian bytestream""" - ret = bytearray() - for i in state: - ret += struct.pack(' Date: Thu, 7 Jan 2016 17:09:31 +0100 Subject: [PATCH 253/574] don't do dot dereferences over and over for the same objects --- tlslite/utils/chacha.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py index b4785082..20ff4cf9 100644 --- a/tlslite/utils/chacha.py +++ b/tlslite/utils/chacha.py @@ -25,33 +25,35 @@ def rotl32(v, c): @staticmethod def quarter_round(x, a, b, c, d): """Perform a ChaCha quarter round""" + rotl32 = ChaCha.rotl32 x[a] = (x[a] + x[b]) & 0xffffffff x[d] = x[d] ^ x[a] - x[d] = ChaCha.rotl32(x[d], 16) + x[d] = rotl32(x[d], 16) x[c] = (x[c] + x[d]) & 0xffffffff x[b] = x[b] ^ x[c] - x[b] = ChaCha.rotl32(x[b], 12) + x[b] = rotl32(x[b], 12) x[a] = (x[a] + x[b]) & 0xffffffff x[d] = x[d] ^ x[a] - x[d] = ChaCha.rotl32(x[d], 8) + x[d] = rotl32(x[d], 8) x[c] = (x[c] + x[d]) & 0xffffffff x[b] = x[b] ^ x[c] - x[b] = ChaCha.rotl32(x[b], 7) + x[b] = rotl32(x[b], 7) @staticmethod def double_round(x): """Perform two rounds of ChaCha cipher""" - ChaCha.quarter_round(x, 0, 4, 8, 12) - ChaCha.quarter_round(x, 1, 5, 9, 13) - ChaCha.quarter_round(x, 2, 6, 10, 14) - ChaCha.quarter_round(x, 3, 7, 11, 15) - ChaCha.quarter_round(x, 0, 5, 10, 15) - ChaCha.quarter_round(x, 1, 6, 11, 12) - ChaCha.quarter_round(x, 2, 7, 8, 13) - ChaCha.quarter_round(x, 3, 4, 9, 14) + qr = ChaCha.quarter_round + qr(x, 0, 4, 8, 12) + qr(x, 1, 5, 9, 13) + qr(x, 2, 6, 10, 14) + qr(x, 3, 7, 11, 15) + qr(x, 0, 5, 10, 15) + qr(x, 1, 6, 11, 12) + qr(x, 2, 7, 8, 13) + qr(x, 3, 4, 9, 14) @staticmethod def chacha_block(key, counter, nonce, rounds): @@ -63,8 +65,9 @@ def chacha_block(key, counter, nonce, rounds): state.extend(nonce) working_state = copy.copy(state) - for i in range(0, rounds // 2): - ChaCha.double_round(working_state) + dbl_round = ChaCha.double_round + for _ in range(0, rounds // 2): + dbl_round(working_state) return [(st + wrkSt) & 0xffffffff for st, wrkSt in zip(state, working_state)] From 2e2b58d13b167627d63a2aa18964ca3a6afad181 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 5 Jan 2016 19:56:06 +0100 Subject: [PATCH 254/574] use faster array copy --- tlslite/utils/chacha.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py index 20ff4cf9..4b13cf4c 100644 --- a/tlslite/utils/chacha.py +++ b/tlslite/utils/chacha.py @@ -64,7 +64,7 @@ def chacha_block(key, counter, nonce, rounds): state.append(counter) state.extend(nonce) - working_state = copy.copy(state) + working_state = state[:] dbl_round = ChaCha.double_round for _ in range(0, rounds // 2): dbl_round(working_state) From c9019914cabb250c35329eb3fc24ba201e918687 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 7 Jan 2016 17:16:39 +0100 Subject: [PATCH 255/574] don't do array dereferences constantly since every array dereference is a complex lookup, it is slower, just cache the values as local variables and store them in array after all calculation is done --- tlslite/utils/chacha.py | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py index 4b13cf4c..4386928e 100644 --- a/tlslite/utils/chacha.py +++ b/tlslite/utils/chacha.py @@ -26,21 +26,31 @@ def rotl32(v, c): def quarter_round(x, a, b, c, d): """Perform a ChaCha quarter round""" rotl32 = ChaCha.rotl32 - x[a] = (x[a] + x[b]) & 0xffffffff - x[d] = x[d] ^ x[a] - x[d] = rotl32(x[d], 16) - - x[c] = (x[c] + x[d]) & 0xffffffff - x[b] = x[b] ^ x[c] - x[b] = rotl32(x[b], 12) - - x[a] = (x[a] + x[b]) & 0xffffffff - x[d] = x[d] ^ x[a] - x[d] = rotl32(x[d], 8) - - x[c] = (x[c] + x[d]) & 0xffffffff - x[b] = x[b] ^ x[c] - x[b] = rotl32(x[b], 7) + xa = x[a] + xb = x[b] + xc = x[c] + xd = x[d] + + xa = (xa + xb) & 0xffffffff + xd = xd ^ xa + xd = rotl32(xd, 16) + + xc = (xc + xd) & 0xffffffff + xb = xb ^ xc + xb = rotl32(xb, 12) + + xa = (xa + xb) & 0xffffffff + xd = xd ^ xa + xd = rotl32(xd, 8) + + xc = (xc + xd) & 0xffffffff + xb = xb ^ xc + xb = rotl32(xb, 7) + + x[a] = xa + x[b] = xb + x[c] = xc + x[d] = xd @staticmethod def double_round(x): From e93108737fbfcb4136330c5193f66a65d3f8e81a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 7 Jan 2016 18:23:19 +0100 Subject: [PATCH 256/574] inline rotl32 calls --- tlslite/utils/chacha.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py index 4386928e..cf479996 100644 --- a/tlslite/utils/chacha.py +++ b/tlslite/utils/chacha.py @@ -25,7 +25,6 @@ def rotl32(v, c): @staticmethod def quarter_round(x, a, b, c, d): """Perform a ChaCha quarter round""" - rotl32 = ChaCha.rotl32 xa = x[a] xb = x[b] xc = x[c] @@ -33,19 +32,19 @@ def quarter_round(x, a, b, c, d): xa = (xa + xb) & 0xffffffff xd = xd ^ xa - xd = rotl32(xd, 16) + xd = ((xd << 16) & 0xffffffff | (xd >> 16)) xc = (xc + xd) & 0xffffffff xb = xb ^ xc - xb = rotl32(xb, 12) + xb = ((xb << 12) & 0xffffffff | (xb >> 20)) xa = (xa + xb) & 0xffffffff xd = xd ^ xa - xd = rotl32(xd, 8) + xd = ((xd << 8) & 0xffffffff | (xd >> 24)) xc = (xc + xd) & 0xffffffff xb = xb ^ xc - xb = rotl32(xb, 7) + xb = ((xb << 7) & 0xffffffff | (xb >> 25)) x[a] = xa x[b] = xb From 1a94ba2c98868029cbee46e860c9e39d980525ae Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 7 Jan 2016 18:32:55 +0100 Subject: [PATCH 257/574] quicker state array creation $ python2 -mtimeit -s'constants=[0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; key=[0xffffffff]*8; nonce=[0xffffffff]*3; counter = 0'\ 'x = []; x.extend(constants); x.extend(key); x.append(counter); x.extend(nonce)' 1000000 loops, best of 3: 0.469 usec per loop $ python3 -mtimeit -s'constants=[0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; key=[0xffffffff]*8; nonce=[0xffffffff]*3; counter = 0'\ 'x = []; x.extend(constants); x.extend(key); x.append(counter); x.extend(nonce)' 1000000 loops, best of 3: 0.51 usec per loop $ python2 -mtimeit -s'constants=[0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; key=[0xffffffff]*8; nonce=[0xffffffff]*3; counter = 0'\ 'x = constants[:]; x.extend(key); x.append(counter); x.extend(nonce)' 1000000 loops, best of 3: 0.417 usec per loop $ python3 -mtimeit -s'constants=[0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; key=[0xffffffff]*8; nonce=[0xffffffff]*3; counter = 0'\ 'x = constants[:]; x.extend(key); x.append(counter); x.extend(nonce)' 1000000 loops, best of 3: 0.481 usec per loop while the new one: $ python2 -mtimeit -s'constants=[0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; key=[0xffffffff]*8; nonce=[0xffffffff]*3; counter = 0'\ 'x = constants + key + [counter] + nonce' 1000000 loops, best of 3: 0.357 usec per loop $ python3 -mtimeit -s'constants=[0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]; key=[0xffffffff]*8; nonce=[0xffffffff]*3; counter = 0'\ 'x = constants + key + [counter] + nonce' 1000000 loops, best of 3: 0.364 usec per loop --- tlslite/utils/chacha.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py index cf479996..d7161137 100644 --- a/tlslite/utils/chacha.py +++ b/tlslite/utils/chacha.py @@ -67,11 +67,7 @@ def double_round(x): @staticmethod def chacha_block(key, counter, nonce, rounds): """Generate a state of a single block""" - state = [] - state.extend(ChaCha.constants) - state.extend(key) - state.append(counter) - state.extend(nonce) + state = ChaCha.constants + key + [counter] + nonce working_state = state[:] dbl_round = ChaCha.double_round From baef0559454bc083814727048590a8d90ef1dee9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 7 Jan 2016 18:37:28 +0100 Subject: [PATCH 258/574] inline quarter_round --- tlslite/utils/chacha.py | 48 +++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py index d7161137..8daecfeb 100644 --- a/tlslite/utils/chacha.py +++ b/tlslite/utils/chacha.py @@ -51,18 +51,44 @@ def quarter_round(x, a, b, c, d): x[c] = xc x[d] = xd - @staticmethod - def double_round(x): + _round_mixup_box = [(0, 4, 8, 12), + (1, 5, 9, 13), + (2, 6, 10, 14), + (3, 7, 11, 15), + (0, 5, 10, 15), + (1, 6, 11, 12), + (2, 7, 8, 13), + (3, 4, 9, 14)] + + @classmethod + def double_round(cls, x): """Perform two rounds of ChaCha cipher""" - qr = ChaCha.quarter_round - qr(x, 0, 4, 8, 12) - qr(x, 1, 5, 9, 13) - qr(x, 2, 6, 10, 14) - qr(x, 3, 7, 11, 15) - qr(x, 0, 5, 10, 15) - qr(x, 1, 6, 11, 12) - qr(x, 2, 7, 8, 13) - qr(x, 3, 4, 9, 14) + for a, b, c, d in cls._round_mixup_box: + xa = x[a] + xb = x[b] + xc = x[c] + xd = x[d] + + xa = (xa + xb) & 0xffffffff + xd = xd ^ xa + xd = ((xd << 16) & 0xffffffff | (xd >> 16)) + + xc = (xc + xd) & 0xffffffff + xb = xb ^ xc + xb = ((xb << 12) & 0xffffffff | (xb >> 20)) + + xa = (xa + xb) & 0xffffffff + xd = xd ^ xa + xd = ((xd << 8) & 0xffffffff | (xd >> 24)) + + xc = (xc + xd) & 0xffffffff + xb = xb ^ xc + xb = ((xb << 7) & 0xffffffff | (xb >> 25)) + + x[a] = xa + x[b] = xb + x[c] = xc + x[d] = xd @staticmethod def chacha_block(key, counter, nonce, rounds): From 56311fab3ae6db6591ea2df63db17ccef311bc74 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 7 Jan 2016 19:07:02 +0100 Subject: [PATCH 259/574] simplify iteration in encrypt --- tlslite/utils/chacha.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py index 8daecfeb..cff5a5f1 100644 --- a/tlslite/utils/chacha.py +++ b/tlslite/utils/chacha.py @@ -135,17 +135,13 @@ def __init__(self, key, nonce, counter=0, rounds=20): def encrypt(self, plaintext): """Encrypt the data""" encrypted_message = bytearray() - if len(plaintext) % 64 != 0: - extra = 1 - else: - extra = 0 - for i in range(0, len(plaintext) // 64 + extra): + for i, block in enumerate(plaintext[i:i+64] for i + in range(0, len(plaintext), 64)): key_stream = ChaCha.chacha_block(self.key, self.counter + i, self.nonce, self.rounds) key_stream = ChaCha.word_to_bytearray(key_stream) - block = plaintext[i*64:(i+1)*64] encrypted_message += bytearray(x ^ y for x, y in zip(key_stream, block)) From 8f80aaaae697d12937e50c7b5265a6c788a0ee57 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 2 Dec 2015 18:45:03 +0100 Subject: [PATCH 260/574] support for extended master secret, RFC 7627 --- scripts/tls.py | 3 +- tests/tlstest.py | 2 + tlslite/constants.py | 1 + tlslite/handshakesettings.py | 13 +++ tlslite/mathtls.py | 23 ++++++ tlslite/session.py | 5 +- tlslite/tlsconnection.py | 84 +++++++++++++++----- unit_tests/test_tlslite_handshakesettings.py | 17 ++++ unit_tests/test_tlslite_mathtls.py | 42 +++++++++- unit_tests/test_tlslite_session.py | 54 +++++++++++++ 10 files changed, 221 insertions(+), 23 deletions(-) create mode 100644 unit_tests/test_tlslite_session.py diff --git a/scripts/tls.py b/scripts/tls.py index 94da40a1..76f0d576 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -203,7 +203,8 @@ def printGoodConnection(connection, seconds): print(str(connection.session.tackExt)) print(" Next-Protocol Negotiated: %s" % connection.next_proto) print(" Encrypt-then-MAC: {0}".format(connection.encryptThenMAC)) - + print(" Extended Master Secret: {0}".format( + connection.extendedMasterSecret)) def clientCmd(argv): (address, privateKey, certChain, username, password) = \ diff --git a/tests/tlstest.py b/tests/tlstest.py index d57fb8cd..b61bbdda 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -762,6 +762,7 @@ def connect(): connection = connect() connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) assert(connection.session.serverName == address[0]) + assert(connection.extendedMasterSecret) testConnServer(connection) connection.close() @@ -774,6 +775,7 @@ def connect(): settings.minVersion = (3,0) settings.maxVersion = (3,0) connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, settings=settings) + assert(not connection.extendedMasterSecret) testConnServer(connection) connection.close() diff --git a/tlslite/constants.py b/tlslite/constants.py index a15a7ba4..9096a011 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -98,6 +98,7 @@ class ExtensionType: # RFC 6066 / 4366 signature_algorithms = 13 # RFC 5246 client_hello_padding = 21 # RFC 7685 encrypt_then_mac = 22 # RFC 7366 + extended_master_secret = 23 # RFC 7627 tack = 0xF300 supports_npn = 13172 renegotiation_info = 0xff01 diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index c61d8c0f..5355c546 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -124,6 +124,14 @@ class HandshakeSettings(object): @type eccCurves: list @ivar eccCurves: List of named curves that are to be supported + + @type useEncryptThenMAC: bool + @ivar useEncryptThenMAC: whether to support the encrypt then MAC extension + from RFC 7366. True by default. + + @type useExtendedMasterSecret: bool + @ivar useExtendedMasterSecret: whether to support the extended master + secret calculation from RFC 7627. True by default. """ def __init__(self): self.minKeySize = 1023 @@ -141,6 +149,7 @@ def __init__(self): self.rsaSigHashes = list(RSA_SIGNATURE_HASHES) self.eccCurves = list(CURVE_NAMES) self.usePaddingExtension = True + self.useExtendedMasterSecret = True @staticmethod def _sanityCheckKeySizes(other): @@ -212,6 +221,9 @@ def _sanityCheckExtensions(other): if other.useEncryptThenMAC not in (True, False): raise ValueError("useEncryptThenMAC can only be True or False") + if other.useExtendedMasterSecret not in (True, False): + raise ValueError("useExtendedMasterSecret must be True or False") + if other.usePaddingExtension not in (True, False): raise ValueError("usePaddingExtension must be True or False") @@ -239,6 +251,7 @@ def validate(self): other.usePaddingExtension = self.usePaddingExtension other.rsaSigHashes = self.rsaSigHashes other.eccCurves = self.eccCurves + other.useExtendedMasterSecret = self.useExtendedMasterSecret if not cipherfactory.tripleDESPresent: other.cipherNames = [i for i in self.cipherNames if i != "3des"] diff --git a/tlslite/mathtls.py b/tlslite/mathtls.py index 5ee02921..6884bc50 100644 --- a/tlslite/mathtls.py +++ b/tlslite/mathtls.py @@ -75,6 +75,29 @@ def PRF_SSL(secret, seed, length): index += 1 return bytes +def calcExtendedMasterSecret(version, cipherSuite, premasterSecret, + handshakeHashes): + """Derive Extended Master Secret from premaster and handshake msgs""" + assert version in ((3, 1), (3, 2), (3, 3)) + if version in ((3, 1), (3, 2)): + masterSecret = PRF(premasterSecret, b"extended master secret", + handshakeHashes.digest('md5') + + handshakeHashes.digest('sha1'), + 48) + else: + if cipherSuite in CipherSuite.sha384PrfSuites: + masterSecret = PRF_1_2_SHA384(premasterSecret, + b"extended master secret", + handshakeHashes.digest('sha384'), + 48) + else: + masterSecret = PRF_1_2(premasterSecret, + b"extended master secret", + handshakeHashes.digest('sha256'), + 48) + return masterSecret + + def calcMasterSecret(version, cipherSuite, premasterSecret, clientRandom, serverRandom): """Derive Master Secret from premaster secret and random values""" diff --git a/tlslite/session.py b/tlslite/session.py index d8641a60..c31e9dfb 100644 --- a/tlslite/session.py +++ b/tlslite/session.py @@ -60,11 +60,12 @@ def __init__(self): self.serverName = "" self.resumable = False self.encryptThenMAC = False + self.extendedMasterSecret = False def create(self, masterSecret, sessionID, cipherSuite, srpUsername, clientCertChain, serverCertChain, tackExt, tackInHelloExt, serverName, resumable=True, - encryptThenMAC=False): + encryptThenMAC=False, extendedMasterSecret=False): self.masterSecret = masterSecret self.sessionID = sessionID self.cipherSuite = cipherSuite @@ -76,6 +77,7 @@ def create(self, masterSecret, sessionID, cipherSuite, self.serverName = serverName self.resumable = resumable self.encryptThenMAC = encryptThenMAC + self.extendedMasterSecret = extendedMasterSecret def _clone(self): other = Session() @@ -90,6 +92,7 @@ def _clone(self): other.serverName = self.serverName other.resumable = self.resumable other.encryptThenMAC = self.encryptThenMAC + other.extendedMasterSecret = self.extendedMasterSecret return other def valid(self): diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 69f7f3cb..af29420c 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -67,6 +67,7 @@ def __init__(self, sock): self.serverSigAlg = None self.ecdhCurve = None self.dhGroupSize = None + self.extendedMasterSecret = False #********************************************************* # Client Handshake Functions @@ -438,6 +439,9 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, if serverHello.getExtension(ExtensionType.encrypt_then_mac): self._recordLayer.encryptThenMAC = True + if serverHello.getExtension(ExtensionType.extended_master_secret): + self.extendedMasterSecret = True + #If the server elected to resume the session, it is handled here. for result in self._clientResume(session, serverHello, clientHello.random, @@ -512,7 +516,8 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, srpUsername, clientCertChain, serverCertChain, tackExt, (serverHello.tackExt is not None), serverName, - encryptThenMAC=self._recordLayer.encryptThenMAC) + encryptThenMAC=self._recordLayer.encryptThenMAC, + extendedMasterSecret=self.extendedMasterSecret) self._handshakeDone(resumed=False) @@ -549,6 +554,10 @@ def _clientSendClientHello(self, settings, session, srpUsername, extensions.append(TLSExtension().\ create(ExtensionType.encrypt_then_mac, bytearray(0))) + if settings.useExtendedMasterSecret: + extensions.append(TLSExtension().create(ExtensionType. + extended_master_secret, + bytearray(0))) #Send the ECC extensions only if we advertise ECC ciphers if next((cipher for cipher in cipherSuites \ if cipher in CipherSuite.ecdhAllSuites), None) is not None: @@ -562,8 +571,8 @@ def _clientSendClientHello(self, settings, session, srpUsername, assert len(sigList) > 0 extensions.append(SignatureAlgorithmsExtension().\ create(sigList)) - #don't send empty list of extensions - if not extensions: + # don't send empty list of extensions or extensions in SSLv3 + if not extensions or settings.maxVersion == (3, 0): extensions = None #Either send ClientHello (with a resumable session)... @@ -887,12 +896,17 @@ def _clientKeyExchange(self, settings, cipherSuite, def _clientFinished(self, premasterSecret, clientRandom, serverRandom, cipherSuite, cipherImplementations, nextProto): - - masterSecret = calcMasterSecret(self.version, - cipherSuite, - premasterSecret, - clientRandom, - serverRandom) + if self.extendedMasterSecret: + masterSecret = calcExtendedMasterSecret(self.version, + cipherSuite, + premasterSecret, + self._handshake_hash) + else: + masterSecret = calcMasterSecret(self.version, + cipherSuite, + premasterSecret, + clientRandom, + serverRandom) self._calcPendingStates(cipherSuite, masterSecret, clientRandom, serverRandom, cipherImplementations) @@ -1132,15 +1146,26 @@ def _handshakeServerAsyncHelper(self, verifierDB, else: tackExt = None + extensions = [] # Prepare other extensions if requested if settings.useEncryptThenMAC and \ clientHello.getExtension(ExtensionType.encrypt_then_mac) and \ cipherSuite not in CipherSuite.streamSuites and \ cipherSuite not in CipherSuite.aeadSuites: - extensions = [TLSExtension().create(ExtensionType.encrypt_then_mac, - bytearray(0))] + extensions.append(TLSExtension().create(ExtensionType. + encrypt_then_mac, + bytearray(0))) self._recordLayer.encryptThenMAC = True - else: + + if settings.useExtendedMasterSecret and \ + clientHello.getExtension(ExtensionType.extended_master_secret): + extensions.append(TLSExtension().create(ExtensionType. + extended_master_secret, + bytearray(0))) + self.extendedMasterSecret = True + + # don't send empty list of extensions + if not extensions: extensions = None serverHello = ServerHello() @@ -1343,18 +1368,31 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, for result in self._sendError(\ AlertDescription.handshake_failure): yield result + if session.extendedMasterSecret and \ + not clientHello.getExtension( + ExtensionType.extended_master_secret): + for result in self._sendError(\ + AlertDescription.handshake_failure): + yield result except KeyError: pass #If a session is found.. if session: #Send ServerHello + extensions = [] if session.encryptThenMAC: self._recordLayer.encryptThenMAC = True mte = TLSExtension().create(ExtensionType.encrypt_then_mac, bytearray(0)) - extensions = [mte] - else: + extensions.append(mte) + if session.extendedMasterSecret: + ems = TLSExtension().create(ExtensionType. + extended_master_secret, + bytearray(0)) + extensions.append(ems) + # don't send empty extensions + if not extensions: extensions = None serverHello = ServerHello() serverHello.create(self.version, getRandomBytes(32), @@ -1645,12 +1683,18 @@ def _serverAnonKeyExchange(self, clientHello, serverHello, cipherSuite, def _serverFinished(self, premasterSecret, clientRandom, serverRandom, cipherSuite, cipherImplementations, nextProtos): - masterSecret = calcMasterSecret(self.version, - cipherSuite, - premasterSecret, - clientRandom, - serverRandom) - + if self.extendedMasterSecret: + masterSecret = calcExtendedMasterSecret(self.version, + cipherSuite, + premasterSecret, + self._handshake_hash) + else: + masterSecret = calcMasterSecret(self.version, + cipherSuite, + premasterSecret, + clientRandom, + serverRandom) + #Calculate pending connection states self._calcPendingStates(cipherSuite, masterSecret, clientRandom, serverRandom, diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index ce911fe4..38394f02 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -170,6 +170,23 @@ def test_useEncryptThenMAC_with_wrong_value(self): with self.assertRaises(ValueError): hs.validate() + def test_useExtendedMasterSecret(self): + hs = HandshakeSettings() + self.assertTrue(hs.useExtendedMasterSecret) + + hs.useExtendedMasterSecret = False + + n_hs = hs.validate() + + self.assertFalse(n_hs.useExtendedMasterSecret) + + def test_useExtendedMasterSecret_with_wrong_value(self): + hs = HandshakeSettings() + hs.useExtendedMasterSecret = None + + with self.assertRaises(ValueError): + hs.validate() + def test_invalid_MAC(self): hs = HandshakeSettings() hs.macNames = ['sha1', 'whirpool'] diff --git a/unit_tests/test_tlslite_mathtls.py b/unit_tests/test_tlslite_mathtls.py index d8d5eb4d..62df4787 100644 --- a/unit_tests/test_tlslite_mathtls.py +++ b/unit_tests/test_tlslite_mathtls.py @@ -9,7 +9,8 @@ except ImportError: import unittest -from tlslite.mathtls import PRF_1_2, calcMasterSecret, calcFinished +from tlslite.mathtls import PRF_1_2, calcMasterSecret, calcFinished, \ + calcExtendedMasterSecret from tlslite.handshakehashes import HandshakeHashes from tlslite.constants import CipherSuite @@ -25,6 +26,45 @@ def test_with_empty_values(self): ), ret) self.assertEqual(48, len(ret)) +class TestCalcExtendedMasterSecret(unittest.TestCase): + def setUp(self): + self.handshakeHashes = HandshakeHashes() + self.handshakeHashes.update(bytearray(48)) + + def test_with_TLS_1_0(self): + ret = calcExtendedMasterSecret((3, 1), + 0, + bytearray(48), + self.handshakeHashes) + self.assertEqual(ret, bytearray( + b'/\xe9\x86\xda\xda\xa9)\x1eyJ\xc9\x13E\xe4\xfc\xe7\x842m7(\xb4' + b'\x98\xb7\xbc\xa5\xda\x1d\xf3\x15\xea\xdf:i\xeb\x9bA\x8f\xe7' + b'\xd4<\xe0\xe8\x1d\xa0\xf0\x10\x83' + )) + + def test_with_TLS_1_2(self): + ret = calcExtendedMasterSecret((3, 3), + 0, + bytearray(48), + self.handshakeHashes) + self.assertEqual(ret, bytearray( + b'\x03\xc93Yx\xcbjSEmz*\x0b\xc3\xc04G\xf3\xe3{\xee\x13\x8b\xac' + b'\xd7\xb7\xe6\xbaY\x86\xd5\xf2o?\x8f\xc6\xf2\x19\x1d\x06\xe0N' + b'\xb5\xcaJX\xe8\x1d' + )) + + def test_with_TLS_1_2_and_SHA384_PRF(self): + ret = calcExtendedMasterSecret((3, 3), + CipherSuite. + TLS_RSA_WITH_AES_256_GCM_SHA384, + bytearray(48), + self.handshakeHashes) + self.assertEqual(ret, bytearray( + b"\xd6\xed}K\xfbo\xb2\xdb\xa4\xee\xa1\x0f\x8f\x07*\x84w/\xbf_" + b"\xbd\xc1U^\x93\xcf\xe8\xca\x82\xb7_B\xa3O\xd9V\x86\x12\xfd\x08" + b"$\x92\'L\xae\xc0@\x01" + )) + class TestPRF1_2(unittest.TestCase): def test_with_bogus_values(self): ret = PRF_1_2(bytearray(1), b"key expansion", bytearray(1), 10) diff --git a/unit_tests/test_tlslite_session.py b/unit_tests/test_tlslite_session.py new file mode 100644 index 00000000..826d5a96 --- /dev/null +++ b/unit_tests/test_tlslite_session.py @@ -0,0 +1,54 @@ +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.session import Session + +class TestSession(unittest.TestCase): + + def test___init__(self): + session = Session() + + self.assertIsNotNone(session) + self.assertFalse(session.resumable) + self.assertFalse(session.encryptThenMAC) + self.assertFalse(session.extendedMasterSecret) + + def test_create(self): + session = Session() + session.create(masterSecret=1, + sessionID=2, + cipherSuite=3, + srpUsername=4, + clientCertChain=5, + serverCertChain=6, + tackExt=7, + tackInHelloExt=8, + serverName=9) + + self.assertEqual(session.masterSecret, 1) + self.assertEqual(session.sessionID, 2) + self.assertEqual(session.cipherSuite, 3) + self.assertEqual(session.srpUsername, 4) + self.assertEqual(session.clientCertChain, 5) + self.assertEqual(session.serverCertChain, 6) + self.assertEqual(session.tackExt, 7) + self.assertEqual(session.tackInHelloExt, 8) + self.assertEqual(session.serverName, 9) + + self.assertTrue(session.resumable) + self.assertFalse(session.encryptThenMAC) + self.assertFalse(session.extendedMasterSecret) + + def test_create_with_new_additions(self): + session = Session() + session.create(1, 2, 3, 4, 5, 6, 7, 8, 9, + encryptThenMAC=10, + extendedMasterSecret=11) + + self.assertEqual(session.encryptThenMAC, 10) + self.assertEqual(session.extendedMasterSecret, 11) + From 67cc7362f5e10bc6917039fe8177a4c598f4e75d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 8 Jan 2016 18:12:07 +0100 Subject: [PATCH 261/574] ability to require/mandate use of extended master secret --- tlslite/handshakesettings.py | 14 ++++ tlslite/tlsconnection.py | 23 ++++-- unit_tests/test_tlslite_handshakesettings.py | 25 +++++++ unit_tests/test_tlslite_tlsconnection.py | 75 ++++++++++++++++++++ 4 files changed, 131 insertions(+), 6 deletions(-) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 5355c546..0d5faf9a 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -132,6 +132,11 @@ class HandshakeSettings(object): @type useExtendedMasterSecret: bool @ivar useExtendedMasterSecret: whether to support the extended master secret calculation from RFC 7627. True by default. + + @type requireExtendedMasterSecret: bool + @ivar requireExtendedMasterSecret: whether to require negotiation of + extended master secret calculation for successful connection. Requires + useExtendedMasterSecret to be set to true. False by default. """ def __init__(self): self.minKeySize = 1023 @@ -150,6 +155,7 @@ def __init__(self): self.eccCurves = list(CURVE_NAMES) self.usePaddingExtension = True self.useExtendedMasterSecret = True + self.requireExtendedMasterSecret = False @staticmethod def _sanityCheckKeySizes(other): @@ -223,6 +229,13 @@ def _sanityCheckExtensions(other): if other.useExtendedMasterSecret not in (True, False): raise ValueError("useExtendedMasterSecret must be True or False") + if other.requireExtendedMasterSecret not in (True, False): + raise ValueError("requireExtendedMasterSecret must be True " + "or False") + if other.requireExtendedMasterSecret and \ + not other.useExtendedMasterSecret: + raise ValueError("requireExtendedMasterSecret requires " + "useExtendedMasterSecret") if other.usePaddingExtension not in (True, False): raise ValueError("usePaddingExtension must be True or False") @@ -252,6 +265,7 @@ def validate(self): other.rsaSigHashes = self.rsaSigHashes other.eccCurves = self.eccCurves other.useExtendedMasterSecret = self.useExtendedMasterSecret + other.requireExtendedMasterSecret = self.requireExtendedMasterSecret if not cipherfactory.tripleDESPresent: other.cipherNames = [i for i in self.cipherNames if i != "3des"] diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index af29420c..c8529363 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -670,6 +670,12 @@ def _clientGetServerHello(self, settings, clientHello): AlertDescription.illegal_parameter, "Server responded with unrequested NPN Extension"): yield result + if not serverHello.getExtension(ExtensionType.extended_master_secret)\ + and settings.requireExtendedMasterSecret: + for result in self._sendError( + AlertDescription.insufficient_security, + "Negotiation of Extended master Secret failed"): + yield result yield serverHello def _clientSelectNextProto(self, nextProtos, serverHello): @@ -1157,12 +1163,17 @@ def _handshakeServerAsyncHelper(self, verifierDB, bytearray(0))) self._recordLayer.encryptThenMAC = True - if settings.useExtendedMasterSecret and \ - clientHello.getExtension(ExtensionType.extended_master_secret): - extensions.append(TLSExtension().create(ExtensionType. - extended_master_secret, - bytearray(0))) - self.extendedMasterSecret = True + if settings.useExtendedMasterSecret: + if clientHello.getExtension(ExtensionType.extended_master_secret): + extensions.append(TLSExtension().create(ExtensionType. + extended_master_secret, + bytearray(0))) + self.extendedMasterSecret = True + elif settings.requireExtendedMasterSecret: + for result in self._sendError( + AlertDescription.insufficient_security, + "Failed to negotiate Extended Master Secret"): + yield result # don't send empty list of extensions if not extensions: diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index 38394f02..fd24acf6 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -187,6 +187,31 @@ def test_useExtendedMasterSecret_with_wrong_value(self): with self.assertRaises(ValueError): hs.validate() + def test_requireExtendedMasterSecret(self): + hs = HandshakeSettings() + self.assertFalse(hs.requireExtendedMasterSecret) + + hs.requireExtendedMasterSecret = True + + n_hs = hs.validate() + + self.assertTrue(n_hs.requireExtendedMasterSecret) + + def test_requireExtendedMasterSecret_with_wrong_value(self): + hs = HandshakeSettings() + hs.requireExtendedMasterSecret = None + + with self.assertRaises(ValueError): + hs.validate() + + def test_requireExtendedMasterSecret_with_incompatible_use_EMS(self): + hs = HandshakeSettings() + hs.useExtendedMasterSecret = False + hs.requireExtendedMasterSecret = True + + with self.assertRaises(ValueError): + hs.validate() + def test_invalid_MAC(self): hs = HandshakeSettings() hs.macNames = ['sha1', 'whirpool'] diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index 954de9fc..25ad4bac 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -126,6 +126,81 @@ def test_server_with_client_proposing_SHA256_on_TLSv1_1(self): self.assertEqual(err.exception.description, AlertDescription.handshake_failure) + def test_client_with_server_responing_without_EMS(self): + # socket to generate the faux response + gen_sock = MockSocket(bytearray(0)) + + gen_record_layer = RecordLayer(gen_sock) + gen_record_layer.version = (3, 2) + + server_hello = ServerHello().create( + version=(3, 3), + random=bytearray(32), + session_id=bytearray(0), + cipher_suite=CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + certificate_type=None, + tackExt=None, + next_protos_advertised=None) + + for res in gen_record_layer.sendRecord(server_hello): + if res in (0, 1): + self.assertTrue(False, "Blocking socket") + else: + break + + # test proper + sock = MockSocket(gen_sock.sent[0]) + + hs = HandshakeSettings() + hs.requireExtendedMasterSecret = True + + conn = TLSConnection(sock) + + with self.assertRaises(TLSLocalAlert) as err: + conn.handshakeClientCert(settings=hs) + + self.assertEqual(err.exception.description, + AlertDescription.insufficient_security) + + def test_server_with_client_not_using_required_EMS(self): + gen_sock = MockSocket(bytearray(0)) + + gen_record_layer = RecordLayer(gen_sock) + gen_record_layer.version = (3, 0) + + ciphers = [CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV] + + client_hello = ClientHello().create(version=(3, 3), + random=bytearray(32), + session_id=bytearray(0), + cipher_suites=ciphers) + + for res in gen_record_layer.sendRecord(client_hello): + if res in (0, 1): + self.assertTrue(False, "Blocking socket") + else: + break + + # test proper + sock = MockSocket(gen_sock.sent[0]) + + conn = TLSConnection(sock) + + hs = HandshakeSettings() + hs.requireExtendedMasterSecret = True + + srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_cert_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + with self.assertRaises(TLSLocalAlert) as err: + conn.handshakeServer(certChain=srv_cert_chain, + privateKey=srv_private_key, + settings=hs) + + self.assertEqual(err.exception.description, + AlertDescription.insufficient_security) + def prepare_mock_socket_with_handshake_failure(self): alertObj = Alert().create(AlertDescription.handshake_failure) alert = alertObj.write() From b2d7b362aa0ab625dff2b202427d16ca0b3cc800 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 14 Jan 2016 19:32:19 +0100 Subject: [PATCH 262/574] release 0.6.0-alpha4 --- README | 2 ++ README.md | 4 +++- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README b/README index 476ec15b..75f216fb 100644 --- a/README +++ b/README @@ -25,6 +25,8 @@ Functionality implemented include: - client certificates - TACK certificate pinning - SRP_SHA_RSA and SRP_SHA ciphersuites (RFC 5054) + - Extended Master Secret calculation for TLS connections (RFC 7627) + tlslite-ng aims to be a drop-in replacement for tlslite while providing more comprehensive set of features and more secure defautls. diff --git a/README.md b/README.md index 94dd3078..23a9d345 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.6.0-alpha3 2015-11-27 +tlslite-ng version 0.6.0-alpha4 2016-01-14 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -556,6 +556,8 @@ encrypt-then-MAC mode for CBC ciphers. =========== 0.6.0 - WIP + - Session Hash a.k.a. Extended Master Secret extension from RFC 7627 + - make the library work on systems working in FIPS mode - support for the padding extension from RFC 7685 (Karel Srot) - abitlity to perform reverse lookups on many of the TLS type enumerations - added ECDHE_RSA key exchange together with associated ciphersuites diff --git a/setup.py b/setup.py index 68811cc3..20806544 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.6.0-alpha3", + version="0.6.0-alpha4", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 79b3768a..a09937be 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.6.0-alpha3 +@version: 0.6.0-alpha4 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 717456b8..c5f097df 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.6.0-alpha3" +__version__ = "0.6.0-alpha4" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 158a55f3373b1594cd5b1e367a475fdf64ea2ac9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 18 Jan 2016 19:19:39 +0100 Subject: [PATCH 263/574] use iterative zip because the zip in Python2 returns a list, not an iterator, it needs to create the list to input, that incurs unnecessary copies. Use izip from itertools. about 2% speedup --- tlslite/utils/chacha.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py index cff5a5f1..49dc4185 100644 --- a/tlslite/utils/chacha.py +++ b/tlslite/utils/chacha.py @@ -10,6 +10,11 @@ from .compat import compat26Str import copy import struct +try: + # in Python 3 the native zip returns iterator + from itertools import izip +except ImportError: + izip = zip class ChaCha(object): @@ -101,7 +106,7 @@ def chacha_block(key, counter, nonce, rounds): dbl_round(working_state) return [(st + wrkSt) & 0xffffffff for st, wrkSt - in zip(state, working_state)] + in izip(state, working_state)] @staticmethod def word_to_bytearray(state): @@ -143,7 +148,7 @@ def encrypt(self, plaintext): self.rounds) key_stream = ChaCha.word_to_bytearray(key_stream) encrypted_message += bytearray(x ^ y for x, y - in zip(key_stream, block)) + in izip(key_stream, block)) return encrypted_message From 432de5799ac6b3efdc161f9d3ac3c82bb5adf5ac Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 18 Jan 2016 19:28:01 +0100 Subject: [PATCH 264/574] don't call struct.pack over and over since the size of state is known, we can encode it with a single call to struct.pack, without using iteration causes about 4% speedup --- tlslite/utils/chacha.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py index 49dc4185..ac6e94c1 100644 --- a/tlslite/utils/chacha.py +++ b/tlslite/utils/chacha.py @@ -111,7 +111,7 @@ def chacha_block(key, counter, nonce, rounds): @staticmethod def word_to_bytearray(state): """Convert state to little endian bytestream""" - return bytearray(b"".join(struct.pack(' Date: Tue, 26 Jan 2016 17:06:44 +0100 Subject: [PATCH 265/574] support parsing of SSLv2 messages longer than 255 bytes the SSLv2 is unique in that the header can be either 2 or 3 byte long, additionally the 2nd most significant bit meaning in both of those situations changes add handling of all those situations --- tlslite/messages.py | 24 ++++++++++++---- tlslite/recordlayer.py | 24 ++++++++++------ unit_tests/test_tlslite_messages.py | 43 ++++++++++++++++++++++++++--- 3 files changed, 73 insertions(+), 18 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 9bc4b5f5..c0993a09 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -82,21 +82,35 @@ def __repr__(self): format(self.type, self.version, self.length) class RecordHeader2(RecordHeader): + """ + SSLv2 record header (just reading) - """SSLv2 record header (just reading)""" + @type padding: int + @ivar padding: number of bytes added at end of message to make it multiple + of block cipher size + @type securityEscape: boolean + @ivar securityEscape: whether the record contains a security escape message + """ def __init__(self): """Define a SSLv2 style class""" super(RecordHeader2, self).__init__(ssl2=True) + self.padding = 0 + self.securityEscape = False def parse(self, parser): """Deserialise object from Parser""" - if parser.get(1) != 128: - raise SyntaxError() + firstByte = parser.get(1) + secondByte = parser.get(1) + if firstByte & 0x80: + self.length = ((firstByte & 0x7f) << 8) | secondByte + else: + self.length = ((firstByte & 0x3f) << 8) | secondByte + self.securityEscape = firstByte & 0x40 != 0 + self.padding = parser.get(1) + self.type = ContentType.handshake self.version = (2, 0) - #XXX We don't support 2-byte-length-headers; could be a problem - self.length = parser.get(1) return self class Message(object): diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index a9d5d81e..f8f77c31 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -131,26 +131,32 @@ def _recvHeader(self): else: break assert result is not None buf += result - # XXX this should be 'buf[0] & 128', otherwise hello messages longer - # than 127 bytes won't be properly parsed - elif buf[0] == 128: + else: + # if header has no pading the header is 2 bytes long, 3 otherwise + # at the same time we already read 1 byte ssl2 = True - # in SSLv2 we need to read 2 bytes in total to know the size of - # header, we already read 1 + if buf[0] & 0x80: + readLen = 1 + else: + readLen = 2 result = None - for result in self._sockRecvAll(1): + for result in self._sockRecvAll(readLen): if result in (0, 1): yield result else: break assert result is not None buf += result - else: - raise TLSIllegalParameterException( - "Record header type doesn't specify known type") + #Parse the record header if ssl2: record = RecordHeader2().parse(Parser(buf)) + # padding can't be longer than overall length and if it is present + # the overall size must be a multiple of cipher block size + if ((record.padding > record.length) or + (record.padding and record.length % 8)): + raise TLSIllegalParameterException(\ + "Malformed record layer header") else: record = RecordHeader3().parse(Parser(buf)) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 2cd61984..0b9f5995 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -962,11 +962,46 @@ def test_parse_with_very_long_message(self): rh = RecordHeader2() - #XXX can't handle two-byte length - with self.assertRaises(SyntaxError): - rh = rh.parse(parser) + rh = rh.parse(parser) + + self.assertEqual(512, rh.length) + self.assertEqual(0, rh.padding) + + def test_parse_with_3_byte_long_header(self): + parser = Parser(bytearray( + b'\x02' + # 3 byte header and nibble of length + b'\x00' + # second byte of length + b'\x0a' # padding length + )) + + rh = RecordHeader2() + rh = rh.parse(parser) + + self.assertEqual(512, rh.length) + self.assertEqual(10, rh.padding) - #self.assertEqual(512, rh.length) + def test_parse_with_2_byte_header_and_security_escape_bit_set(self): + parser = Parser(bytearray( + b'\xc0' + + b'\x12')) + + rh = RecordHeader2() + rh = rh.parse(parser) + self.assertEqual(0x4012, rh.length) + self.assertEqual(0, rh.padding) + self.assertFalse(rh.securityEscape) + + def test_parse_with_3_byte_header_and_security_escape_bit_set(self): + parser = Parser(bytearray( + b'\x40' + + b'\x12' + + b'\x01')) + + rh = RecordHeader2() + rh = rh.parse(parser) + self.assertEqual(0x0012, rh.length) + self.assertEqual(1, rh.padding) + self.assertTrue(rh.securityEscape) class TestRecordHeader3(unittest.TestCase): def test___init__(self): From 1d62a34444eed1385732c64fe4ad16945a7e3154 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 26 Jan 2016 17:51:34 +0100 Subject: [PATCH 266/574] support for writing SSLv2 record headers --- tlslite/messages.py | 36 +++++++++++++- unit_tests/test_tlslite_messages.py | 75 +++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index c0993a09..bd90e24c 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -83,7 +83,7 @@ def __repr__(self): class RecordHeader2(RecordHeader): """ - SSLv2 record header (just reading) + SSLv2 record header @type padding: int @ivar padding: number of bytes added at end of message to make it multiple @@ -113,6 +113,40 @@ def parse(self, parser): self.version = (2, 0) return self + def create(self, length, padding=0, securityEscape=False): + """Set object's values""" + self.length = length + self.padding = padding + self.securityEscape = securityEscape + + def write(self): + """Serialise object to bytearray""" + writer = Writer() + + if not (self.padding or self.securityEscape): + shortHeader = True + else: + shortHeader = False + + if ((shortHeader and self.length >= 0x8000) or + (not shortHeader and self.length >= 0x4000)): + raise ValueError("length too large") + + firstByte = 0 + if shortHeader: + firstByte |= 0x80 + if self.securityEscape: + firstByte |= 0x40 + firstByte |= self.length >> 8 + secondByte = self.length & 0xff + + writer.add(firstByte, 1) + writer.add(secondByte, 1) + if not shortHeader: + writer.add(self.padding, 1) + + return writer.bytes + class Message(object): """Generic TLS message""" diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 0b9f5995..a9df5628 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1003,6 +1003,81 @@ def test_parse_with_3_byte_header_and_security_escape_bit_set(self): self.assertEqual(1, rh.padding) self.assertTrue(rh.securityEscape) + def test_create(self): + rh = RecordHeader2() + rh.create(512) + + self.assertEqual(512, rh.length) + self.assertEqual(0, rh.padding) + self.assertFalse(rh.securityEscape) + + def test_write(self): + rh = RecordHeader2() + rh.create(0x0123) + + data = rh.write() + + self.assertEqual(bytearray( + b'\x81' + b'\x23'), data) + + def test_write_with_padding(self): + rh = RecordHeader2() + rh.create(0x0123, padding=12) + + data = rh.write() + + self.assertEqual(bytearray( + b'\x01' + b'\x23' + b'\x0c'), data) + + def test_write_with_security_escape(self): + rh = RecordHeader2() + rh.create(0x0123, securityEscape=True) + + data = rh.write() + + self.assertEqual(bytearray( + b'\x41' + b'\x23' + b'\x00'), data) + + def test_write_with_large_data_and_short_header(self): + rh = RecordHeader2() + rh.create(0x7fff) + + data = rh.write() + + self.assertEqual(bytearray( + b'\xff' + b'\xff'), data) + + def test_write_with_too_long_length_and_short_header(self): + rh = RecordHeader2() + rh.create(0x8000) + + with self.assertRaises(ValueError): + rh.write() + + def test_write_with_long_length_and_long_header(self): + rh = RecordHeader2() + rh.create(0x3fff, padding=1) + + data = rh.write() + + self.assertEqual(bytearray( + b'\x3f' + b'\xff' + b'\x01'), data) + + def test_write_with_too_long_length_and_long_header(self): + rh = RecordHeader2() + rh.create(0x4000, padding=1) + + with self.assertRaises(ValueError): + rh.write() + class TestRecordHeader3(unittest.TestCase): def test___init__(self): rh = RecordHeader3() From deea29f279842f2812d78c43cf312d8885be85fa Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 26 Jan 2016 17:58:04 +0100 Subject: [PATCH 267/574] more SSLv2 test coverage for RecordSocket --- unit_tests/test_tlslite_recordlayer.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index e2ba440e..ad5e20ee 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -300,6 +300,29 @@ def test_recv_with_SSL2_record_with_incomplete_header(self): self.assertEqual(0, result) + def test_recv_with_long_SSL2_header(self): + mockSock = MockSocket(bytearray( + b'\x40' + # security escape data + b'\x04' + # length + b'\x00' + # padding length + b'\xaa'*4)) + + sock = RecordSocket(mockSock) + + for result in sock.recv(): + if result in (0, 1): + self.assertTrue(True, "blocking socket") + else: break + + header, data = result + + self.assertTrue(header.ssl2) + self.assertTrue(header.securityEscape) + self.assertEqual(4, header.length) + self.assertEqual((2, 0), header.version) + + self.assertEqual(bytearray(b'\xaa'*4), data) + class TestConnectionState(unittest.TestCase): def test___init__(self): connState = ConnectionState() From 761c5c248a50a75d36af3c69ca531448876ed749 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 26 Jan 2016 18:15:05 +0100 Subject: [PATCH 268/574] length check for SSLv2 CLIENT-HELLO parsing --- tlslite/messages.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index bd90e24c..bab88d1d 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -519,7 +519,8 @@ def create(self, version, random, session_id, cipher_suites, @type random: bytearray @param random: client provided random value, in old versions of TLS - (before 1.2) the first 32 bits should include system time + (before 1.2) the first 32 bits should include system time, also + used as the "challange" field in SSLv2 @type session_id: bytearray @param session_id: ID of session, set when doing session resumption @@ -568,20 +569,23 @@ def create(self, version, random, session_id, cipher_suites, return self def parse(self, p): + """Deserialise object from on the wire data""" if self.ssl2: self.client_version = (p.get(1), p.get(1)) cipherSpecsLength = p.get(2) sessionIDLength = p.get(2) randomLength = p.get(2) + p.setLengthCheck(cipherSpecsLength + + sessionIDLength + + randomLength) self.cipher_suites = p.getFixList(3, cipherSpecsLength//3) self.session_id = p.getFixBytes(sessionIDLength) self.random = p.getFixBytes(randomLength) if len(self.random) < 32: zeroBytes = 32-len(self.random) self.random = bytearray(zeroBytes) + self.random - self.compression_methods = [0]#Fake this value - - #We're not doing a stopLengthCheck() for SSLv2, oh well.. + self.compression_methods = [0] # Fake this value + p.stopLengthCheck() else: p.startLengthCheck(3) self.client_version = (p.get(1), p.get(1)) From 8a695558d8bbe58edcf0a6eccfa19d7f07c8059e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 26 Jan 2016 19:06:53 +0100 Subject: [PATCH 269/574] serialisation of SSLv2 CLIENT-HELLO --- tlslite/messages.py | 31 +++++++++++++++++- unit_tests/test_tlslite_messages.py | 50 ++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index bab88d1d..76e94f75 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -602,7 +602,29 @@ def parse(self, p): p.stopLengthCheck() return self - def write(self): + def _writeSSL2(self): + """Serialise SSLv2 object to on the wire data""" + writer = Writer() + writer.add(self.handshakeType, 1) + writer.add(self.client_version[0], 1) + writer.add(self.client_version[1], 1) + + ciphersWriter = Writer() + ciphersWriter.addFixSeq(self.cipher_suites, 3) + + writer.add(len(ciphersWriter.bytes), 2) + writer.add(len(self.session_id), 2) + writer.add(len(self.random), 2) + + writer.bytes += ciphersWriter.bytes + writer.bytes += self.session_id + writer.bytes += self.random + + # postWrite() is necessary only for SSLv3/TLS + return writer.bytes + + def _write(self): + """Serialise SSLv3 or TLS object to on the wire data""" w = Writer() w.add(self.client_version[0], 1) w.add(self.client_version[1], 1) @@ -620,6 +642,13 @@ def write(self): w.bytes += w2.bytes return self.postWrite(w) + def write(self): + """Serialise object to on the wire data""" + if self.ssl2: + return self._writeSSL2() + else: + return self._write() + class ServerHello(HandshakeMsg): """server_hello message diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index a9df5628..67b22f4b 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -590,7 +590,7 @@ def test_parse_with_SSLv2_client_hello(self): client_hello = client_hello.parse(parser) - # XXX the value on the wire is LSB, but should be interpreted MSB for + # the value on the wire is LSB, but should be interpreted MSB for # SSL2 self.assertEqual((0, 2), client_hello.client_version) self.assertEqual(bytearray(0), client_hello.session_id) @@ -601,6 +601,54 @@ def test_parse_with_SSLv2_client_hello(self): client_hello.random) self.assertEqual([0], client_hello.compression_methods) + def test_parse_with_SSLv2_client_hello(self): + parser = Parser(bytearray( + # length and type is handled by hello protocol parser + #b'\x80\x2e' + # length - 46 bytes + #b'\x01' + # message type - client hello + b'\x03\x02' + # version - TLSv1.1 + b'\x00\x06' + # cipher spec length - 6 bytes + b'\x00\x10' + # session ID length - 16 bytes + b'\x00\x20' + # challange length - 32 bytes + b'\x07\x00\xc0' + # cipher - SSL2_DES_192_EDE3_CBC_WITH_MD5 + b'\x00\x00\x2f' + # cipher - TLS_RSA_WITH_AES_128_CBC_SHA + b'\xff' * 16 + # session_id + b'\x01' * 32 # challenge + )) + client_hello = ClientHello(ssl2=True) + + client_hello = client_hello.parse(parser) + + # the value on the wire is LSB, but should be interpreted MSB for + # SSL2 + self.assertEqual((3, 2), client_hello.client_version) + self.assertEqual(bytearray(b'\xff'*16), client_hello.session_id) + self.assertEqual([458944, CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA], + client_hello.cipher_suites) + self.assertEqual(bytearray(b'\x01'*32), + client_hello.random) + self.assertEqual([0], client_hello.compression_methods) + + def test_write_with_SSLv2(self): + client_hello = ClientHello(ssl2=True) + ciphers = [0x0700c0, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA] + + client_hello.create((3, 2), random=bytearray(b'\xab'*16), + session_id=bytearray(0), + cipher_suites=ciphers) + + self.assertEqual(bytearray( + b'\x01' + # type of message - CLIENT HELLO + b'\x03\x02' + # version - TLSv1.1 + b'\x00\x06' + # cipher list length + b'\x00\x00' + # session id length + b'\x00\x10' + # challange length + b'\x07\x00\xc0' + # cipher - SSL2_DES_192_EDE3_CBC_WITH_MD5 + b'\x00\x00\x2f' + # cipher - TLS_RSA_WITH_AES_128_CBC_SHA + b'\xab'*16), # challange + client_hello.write()) + class TestServerHello(unittest.TestCase): def test___init__(self): server_hello = ServerHello() From 85e4bc2c1fbc25a74218e1202452ef84f2113d3b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 26 Jan 2016 19:31:49 +0100 Subject: [PATCH 270/574] fixup support for writing SSLv2 record headers --- tlslite/messages.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tlslite/messages.py b/tlslite/messages.py index 76e94f75..ae5294db 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -118,6 +118,7 @@ def create(self, length, padding=0, securityEscape=False): self.length = length self.padding = padding self.securityEscape = securityEscape + return self def write(self): """Serialise object to bytearray""" From 9c9e4f1ca54f089ea647ff001c242115ae007f33 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 26 Jan 2016 19:41:39 +0100 Subject: [PATCH 271/574] RecordSocket support for sending SSLv2 messages --- tlslite/recordlayer.py | 14 +++++++---- unit_tests/test_tlslite_recordlayer.py | 33 +++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index f8f77c31..48827ddd 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -55,19 +55,25 @@ def _sockSendAll(self, data): data = data[bytesSent:] yield 1 - def send(self, msg): + def send(self, msg, padding=0): """ Send the message through socket. @type msg: bytearray @param msg: TLS message to send + @type padding: int + @param padding: amount of padding to specify for SSLv2 @raise socket.error: when write to socket failed """ data = msg.write() - header = RecordHeader3().create(self.version, - msg.contentType, - len(data)) + if self.version in ((2, 0), (0, 2)): + header = RecordHeader2().create(len(data), + padding) + else: + header = RecordHeader3().create(self.version, + msg.contentType, + len(data)) data = header.write() + data diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index ad5e20ee..f5fc5d64 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -20,7 +20,8 @@ import errno import tlslite.utils.cryptomath as cryptomath -from tlslite.messages import Message, ApplicationData, RecordHeader3 +from tlslite.messages import Message, ApplicationData, RecordHeader3, \ + ClientHello from tlslite.recordlayer import RecordSocket, ConnectionState, RecordLayer from tlslite.constants import ContentType, CipherSuite from unit_tests.mocksock import MockSocket @@ -55,6 +56,36 @@ def test_send(self): b'\x00'*10 # payload ), mockSock.sent[0]) + def test_send_SSLv2_message(self): + mock_sock = MockSocket(bytearray(0)) + sock = RecordSocket(mock_sock) + sock.version = (0, 2) + + msg = ClientHello(ssl2=True) + msg.create((3, 3), random=bytearray(b'\xaa'*16), + session_id=bytearray(0), + cipher_suites=[CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_RC4_128_MD5]) + + for result in sock.send(msg): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(mock_sock.sent), 1) + self.assertEqual(bytearray( + b'\x80' + # short header + b'\x1f' + # length - 31 bytes + b'\x01' + # CLIENT-HELLO + b'\x03\x03' + # TLSv1.2 + b'\x00\x06' + # cipher suite length + b'\x00\x00' + # session_id length + b'\x00\x10' + # Challange length + b'\x00\x00\x2f' + # cipher: TLS_RSA_WITH_AES_128_CBC_SHA + b'\x00\x00\x04' + # cipher: TLS_RSA_WITH_RC4_128_MD5 + b'\xaa'*16 # challange + ), mock_sock.sent[0]) + def test_send_with_very_slow_socket(self): mockSock = MockSocket(bytearray(0), maxWrite=1, blockEveryOther=True) sock = RecordSocket(mockSock) From 9ce5614262a2736e4e7eb728d2ef6a5854f21b49 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 26 Jan 2016 20:30:37 +0100 Subject: [PATCH 272/574] SSLv2 SERVER-HELLO message support parsing and serialising SSLv2 SERVER-HELLO message --- tlslite/constants.py | 1 + tlslite/messages.py | 85 +++++++++++++++++++++++++++++ unit_tests/test_tlslite_messages.py | 74 ++++++++++++++++++++++++- 3 files changed, 159 insertions(+), 1 deletion(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 9096a011..f99a88f9 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -63,6 +63,7 @@ class HandshakeType(TLSEnum): hello_request = 0 client_hello = 1 server_hello = 2 + ssl2_server_hello = 4 certificate = 11 server_key_exchange = 12 certificate_request = 13 diff --git a/tlslite/messages.py b/tlslite/messages.py index ae5294db..be77c3bc 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -907,6 +907,91 @@ def write(self): w.bytes += w2.bytes return self.postWrite(w) +class ServerHello2(HandshakeMsg): + """ + SERVER-HELLO message from SSLv2 + + @type session_id_hit: int + @ivar session_id_hit: non zero if the client provided session ID was + matched in server's session cache + + @type certificate_type: int + @ivar certificate_type: type of certificate sent + + @type server_version: tuple of ints + @ivar server_version: protocol version selected by server + + @type certificate: bytearray + @ivar certificate: certificate sent by server + + @type ciphers: array of int + @ivar ciphers: list of ciphers supported by server + + @type session_id: bytearray + @ivar session_id: idendifier of negotiated session + """ + + def __init__(self): + super(ServerHello2, self).__init__(HandshakeType.ssl2_server_hello) + self.session_id_hit = 0 + self.certificate_type = 0 + self.server_version = (0, 0) + self.certificate = bytearray(0) + self.ciphers = [] + self.session_id = bytearray(0) + + def create(self, session_id_hit, certificate_type, server_version, + certificate, ciphers, session_id): + """Initialize fields of the SERVER-HELLO message""" + self.session_id_hit = session_id_hit + self.certificate_type = certificate_type + self.server_version = server_version + self.certificate = certificate + self.ciphers = ciphers + self.session_id = session_id + return self + + def write(self): + """Serialise object to on the wire data""" + writer = Writer() + writer.add(self.handshakeType, 1) + writer.add(self.session_id_hit, 1) + writer.add(self.certificate_type, 1) + if len(self.server_version) != 2: + raise ValueError("server version must be a 2-element tuple") + writer.addFixSeq(self.server_version, 1) + writer.add(len(self.certificate), 2) + + ciphersWriter = Writer() + ciphersWriter.addFixSeq(self.ciphers, 3) + + writer.add(len(ciphersWriter.bytes), 2) + writer.add(len(self.session_id), 2) + + writer.bytes += self.certificate + writer.bytes += ciphersWriter.bytes + writer.bytes += self.session_id + + # postWrite() is necessary only for SSLv3/TLS + return writer.bytes + + def parse(self, parser): + """Deserialise object from on the wire data""" + self.session_id_hit = parser.get(1) + self.certificate_type = parser.get(1) + self.server_version = (parser.get(1), parser.get(1)) + certificateLength = parser.get(2) + ciphersLength = parser.get(2) + sessionIDLength = parser.get(2) + parser.setLengthCheck(certificateLength + + ciphersLength + + sessionIDLength) + self.certificate = parser.getFixBytes(certificateLength) + self.ciphers = parser.getFixList(3, ciphersLength // 3) + self.session_id = parser.getFixBytes(sessionIDLength) + parser.stopLengthCheck() + return self + class Certificate(HandshakeMsg): def __init__(self, certificateType): HandshakeMsg.__init__(self, HandshakeType.certificate) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 67b22f4b..7b10dbd8 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -9,7 +9,7 @@ import unittest from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ - CertificateRequest, CertificateVerify, ServerHelloDone + CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2 from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ @@ -970,6 +970,78 @@ def test___repr__(self): "cipher_suite=34500, compression_method=0, _tack_ext=None, "\ "extensions=[])", repr(server_hello)) +class TestServerHello2(unittest.TestCase): + def test___init__(self): + sh = ServerHello2() + + self.assertIsNotNone(sh) + + def test_create(self): + sh = ServerHello2() + + sh = sh.create(1, 2, (3, 4), bytearray(b'\x05'), [6, 7], + bytearray(b'\x08\x09')) + + self.assertEqual(sh.session_id_hit, 1) + self.assertEqual(sh.certificate_type, 2) + self.assertEqual(sh.server_version, (3, 4)) + self.assertEqual(sh.certificate, bytearray(b'\x05')) + self.assertEqual(sh.ciphers, [6, 7]) + self.assertEqual(sh.session_id, bytearray(b'\x08\x09')) + + def test_write(self): + sh = ServerHello2() + sh = sh.create(1, 2, (3, 4), bytearray(b'\x05'), [6, 7], + bytearray(b'\x08\x09')) + + self.assertEqual(bytearray( + b'\x04' + # type - SERVER-HELLO + b'\x01' + # session ID hit + b'\x02' + # certificate_type + b'\x03\x04' + # version + b'\x00\x01' + # certificate length + b'\x00\x06' + # ciphers length + b'\x00\x02' + # session ID length + b'\x05' + # certificate + b'\x00\x00\x06' + # first cipher + b'\x00\x00\x07' + # second cipher + b'\x08\x09' # session ID + ), sh.write()) + + def test_parse(self): + p = Parser(bytearray( + # don't include type of message as it is handled by the + # record layer protocol + #b'\x04' + # type - SERVER-HELLO + b'\x01' + # session ID hit + b'\x02' + # certificate_type + b'\x03\x04' + # version + b'\x00\x01' + # certificate length + b'\x00\x06' + # ciphers length + b'\x00\x02' + # session ID length + b'\x05' + # certificate + b'\x00\x00\x06' + # first cipher + b'\x00\x00\x07' + # second cipher + b'\x08\x09' # session ID + )) + sh = ServerHello2() + sh = sh.parse(p) + + self.assertEqual(sh.session_id_hit, 1) + self.assertEqual(sh.certificate_type, 2) + self.assertEqual(sh.server_version, (3, 4)) + self.assertEqual(sh.certificate, bytearray(b'\x05')) + self.assertEqual(sh.ciphers, [6, 7]) + self.assertEqual(sh.session_id, bytearray(b'\x08\x09')) + + def test_write_with_invalid_version(self): + sh = ServerHello2() + sh = sh.create(1, 2, (3, 4, 12), bytearray(b'\x05'), [6, 7], + bytearray(b'\x08\x09')) + + with self.assertRaises(ValueError): + sh.write() + class TestRecordHeader2(unittest.TestCase): def test___init__(self): rh = RecordHeader2() From f32541c6ec899ba4b0ef6d81b97234c6698fd8b7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 27 Jan 2016 11:45:28 +0100 Subject: [PATCH 273/574] move SSLv2 handshake protocol definitions to separate enum because there are conflicts for other types (like CLIENT-MASTER-KEY), make the SSLv2 definitions SSLv2 specific --- tlslite/constants.py | 6 +++++- tlslite/messages.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index f99a88f9..35a8304e 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -57,13 +57,17 @@ class ClientCertificateType: rsa_fixed_dh = 3 dss_fixed_dh = 4 +class SSL2HandshakeType(TLSEnum): + """SSL2 Handshake Protocol message types""" + client_hello = 1 + server_hello = 4 + class HandshakeType(TLSEnum): """Message types in TLS Handshake protocol""" hello_request = 0 client_hello = 1 server_hello = 2 - ssl2_server_hello = 4 certificate = 11 server_key_exchange = 12 certificate_request = 13 diff --git a/tlslite/messages.py b/tlslite/messages.py index be77c3bc..74215503 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -932,7 +932,7 @@ class ServerHello2(HandshakeMsg): """ def __init__(self): - super(ServerHello2, self).__init__(HandshakeType.ssl2_server_hello) + super(ServerHello2, self).__init__(SSL2HandshakeType.server_hello) self.session_id_hit = 0 self.certificate_type = 0 self.server_version = (0, 0) From 97c7a5bab48056e9ed10abeff03e1bd373a1b6d5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 27 Jan 2016 12:46:17 +0100 Subject: [PATCH 274/574] SSLv2 CLIENT-MASTER-KEY message handling --- tlslite/constants.py | 1 + tlslite/messages.py | 62 +++++++++++++++++++++++++++++ unit_tests/test_tlslite_messages.py | 61 +++++++++++++++++++++++++++- 3 files changed, 122 insertions(+), 2 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 35a8304e..0e5363e8 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -60,6 +60,7 @@ class ClientCertificateType: class SSL2HandshakeType(TLSEnum): """SSL2 Handshake Protocol message types""" client_hello = 1 + client_master_key = 2 server_hello = 4 class HandshakeType(TLSEnum): diff --git a/tlslite/messages.py b/tlslite/messages.py index 74215503..12a2a2af 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1440,6 +1440,68 @@ def write(self): raise AssertionError() return self.postWrite(w) +class ClientMasterKey(HandshakeMsg): + """ + Handling of SSLv2 CLIENT-MASTER-KEY message + + @type cipher: int + @ivar cipher: negotiated cipher + + @type clear_key: bytearray + @ivar clear_key: the part of master secret key that is sent in clear for + export cipher suites + + @type encrypted_key: bytearray + @ivar encrypted_key: (part of) master secret encrypted using server key + + @type key_argument: bytearray + @ivar key_argument: additional key argument for block ciphers + """ + + def __init__(self): + super(ClientMasterKey, + self).__init__(SSL2HandshakeType.client_master_key) + self.cipher = 0 + self.clear_key = bytearray(0) + self.encrypted_key = bytearray(0) + self.key_argument = bytearray(0) + + def create(self, cipher, clear_key, encrypted_key, key_argument): + """Set values of the CLIENT-MASTER-KEY object""" + self.cipher = cipher + self.clear_key = clear_key + self.encrypted_key = encrypted_key + self.key_argument = key_argument + return self + + def write(self): + """Serialise the object to on the wire data""" + writer = Writer() + writer.add(self.handshakeType, 1) + writer.add(self.cipher, 3) + writer.add(len(self.clear_key), 2) + writer.add(len(self.encrypted_key), 2) + writer.add(len(self.key_argument), 2) + writer.bytes += self.clear_key + writer.bytes += self.encrypted_key + writer.bytes += self.key_argument + return writer.bytes + + def parse(self, parser): + """Deserialise object from on the wire data""" + self.cipher = parser.get(3) + clear_key_length = parser.get(2) + encrypted_key_length = parser.get(2) + key_argument_length = parser.get(2) + parser.setLengthCheck(clear_key_length + + encrypted_key_length + + key_argument_length) + self.clear_key = parser.getFixBytes(clear_key_length) + self.encrypted_key = parser.getFixBytes(encrypted_key_length) + self.key_argument = parser.getFixBytes(key_argument_length) + parser.stopLengthCheck() + return self + class CertificateVerify(HandshakeMsg): """Serializer for TLS handshake protocol Certificate Verify message""" diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 7b10dbd8..35355e76 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -9,11 +9,13 @@ import unittest from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ - CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2 + CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ + ClientMasterKey from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ - HashAlgorithm, SignatureAlgorithm, ECCurveType, GroupName + HashAlgorithm, SignatureAlgorithm, ECCurveType, GroupName, \ + SSL2HandshakeType from tlslite.extensions import SNIExtension, ClientCertTypeExtension, \ SRPExtension, TLSExtension from tlslite.errors import TLSInternalError @@ -1538,6 +1540,61 @@ def test_parse_with_unknown_cipher(self): with self.assertRaises(AssertionError): cke.parse(parser) +class TestClientMasterKey(unittest.TestCase): + def test___init__(self): + cmk = ClientMasterKey() + + self.assertIsNotNone(cmk) + self.assertEqual(cmk.handshakeType, + SSL2HandshakeType.client_master_key) + + def test_create(self): + cmk = ClientMasterKey() + cmk = cmk.create(1, bytearray(b'\x02'), bytearray(b'\x03\x04'), + bytearray(b'\x05\x06\x07')) + + self.assertEqual(cmk.cipher, 1) + self.assertEqual(cmk.clear_key, bytearray(b'\x02')) + self.assertEqual(cmk.encrypted_key, bytearray(b'\x03\x04')) + self.assertEqual(cmk.key_argument, bytearray(b'\x05\x06\x07')) + + def test_write(self): + cmk = ClientMasterKey() + cmk = cmk.create(1, bytearray(b'\x02'), bytearray(b'\x03\x04'), + bytearray(b'\x05\x06\x07')) + + self.assertEqual(bytearray( + b'\x02' + # message type + b'\x00\x00\x01' + # cipher spec + b'\x00\x01' + # clear key length + b'\x00\x02' + # encrypted key length + b'\x00\x03' + # key argument length + b'\x02' + # clear key + b'\x03\x04' + # encrypted key + b'\x05\x06\x07' # key argument + ), cmk.write()) + + def test_parse(self): + cmk = ClientMasterKey() + + parser = Parser(bytearray( + # type is handled by handshake protocol + #b'\x02' + # message type + b'\x00\x00\x01' + # cipher spec + b'\x00\x01' + # clear key length + b'\x00\x02' + # encrypted key length + b'\x00\x03' + # key argument length + b'\x02' + # clear key + b'\x03\x04' + # encrypted key + b'\x05\x06\x07')) # key argument + + cmk = cmk.parse(parser) + + self.assertEqual(cmk.cipher, 1) + self.assertEqual(cmk.clear_key, bytearray(b'\x02')) + self.assertEqual(cmk.encrypted_key, bytearray(b'\x03\x04')) + self.assertEqual(cmk.key_argument, bytearray(b'\x05\x06\x07')) + class TestServerKeyExchange(unittest.TestCase): def test___init__(self): ske = ServerKeyExchange(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, From 5cf6184bba7542688e1d64c34acee068d76f2ed2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Jan 2016 19:01:22 +0100 Subject: [PATCH 275/574] add SSLv2 specific constants and ciphersuites --- tlslite/constants.py | 60 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 0e5363e8..fb26758b 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -59,9 +59,15 @@ class ClientCertificateType: class SSL2HandshakeType(TLSEnum): """SSL2 Handshake Protocol message types""" + error = 0 client_hello = 1 client_master_key = 2 + client_finished = 3 server_hello = 4 + server_verify = 5 + server_finished = 6 + request_certificate = 7 + client_certificate = 8 class HandshakeType(TLSEnum): """Message types in TLS Handshake protocol""" @@ -305,6 +311,60 @@ class CipherSuite: # the ciphesuite names come from IETF, we want to keep them #pylint: disable = invalid-name + # SSLv2 from draft-hickman-netscape-ssl-00.txt + SSL_CK_RC4_128_WITH_MD5 = 0x010080 + ietfNames[0x010080] = 'SSL_CK_RC4_128_WITH_MD5' + SSL_CK_RC4_128_EXPORT40_WITH_MD5 = 0x020080 + ietfNames[0x020080] = 'SSL_CK_RC4_128_EXPORT40_WITH_MD5' + SSL_CK_RC2_128_CBC_WITH_MD5 = 0x030080 + ietfNames[0x030080] = 'SSL_CK_RC2_128_CBC_WITH_MD5' + SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5 = 0x040080 + ietfNames[0x040080] = 'SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5' + SSL_CK_IDEA_128_CBC_WITH_MD5 = 0x050080 + ietfNames[0x050080] = 'SSL_CK_IDEA_128_CBC_WITH_MD5' + SSL_CK_DES_64_CBC_WITH_MD5 = 0x060040 + ietfNames[0x060040] = 'SSL_CK_DES_64_CBC_WITH_MD5' + SSL_CK_DES_192_EDE3_CBC_WITH_MD5 = 0x0700C0 + ietfNames[0x0700C0] = 'SSL_CK_DES_192_EDE3_CBC_WITH_MD5' + + # SSL2 ciphersuites which use RC4 symmetric cipher + ssl2rc4 = [] + ssl2rc4.append(SSL_CK_RC4_128_WITH_MD5) + ssl2rc4.append(SSL_CK_RC4_128_EXPORT40_WITH_MD5) + + # SSL2 ciphersuites which use RC2 symmetric cipher + ssl2rc2 = [] + ssl2rc2.append(SSL_CK_RC2_128_CBC_WITH_MD5) + ssl2rc2.append(SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5) + + # SSL2 ciphersuites which use IDEA symmetric cipher + ssl2idea = [SSL_CK_IDEA_128_CBC_WITH_MD5] + + # SSL2 ciphersuites which use (single) DES symmetric cipher + ssl2des = [SSL_CK_DES_64_CBC_WITH_MD5] + + # SSL2 ciphersuites which use 3DES symmetric cipher + ssl2_3des = [SSL_CK_DES_192_EDE3_CBC_WITH_MD5] + + # SSL2 ciphersuites which encrypt only part (40 bits) of the key + ssl2export = [] + ssl2export.append(SSL_CK_RC4_128_EXPORT40_WITH_MD5) + ssl2export.append(SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5) + + # SSL2 ciphersuties which use 128 bit key + ssl2_128Key = [] + ssl2_128Key.append(SSL_CK_RC4_128_WITH_MD5) + ssl2_128Key.append(SSL_CK_RC4_128_EXPORT40_WITH_MD5) + ssl2_128Key.append(SSL_CK_RC2_128_CBC_WITH_MD5) + ssl2_128Key.append(SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5) + ssl2_128Key.append(SSL_CK_IDEA_128_CBC_WITH_MD5) + + # SSL2 ciphersuites which use 64 bit key + ssl2_64Key = [SSL_CK_DES_64_CBC_WITH_MD5] + + # SSL2 ciphersuites which use 192 bit key + ssl2_192Key = [SSL_CK_DES_192_EDE3_CBC_WITH_MD5] + # Weird pseudo-ciphersuite from RFC 5746 # Signals that "secure renegotiation" is supported # We actually don't do any renegotiation, but this From 26fbedaae99403cf7a9ba2a13a91af8fd45cf514 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Jan 2016 19:50:46 +0100 Subject: [PATCH 276/574] SSLv2 CLIENT-FINISHED and SERVER-FINISHED messages --- tlslite/messages.py | 51 ++++++++++++++++++++++ unit_tests/test_tlslite_messages.py | 68 ++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 12a2a2af..429e7740 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1624,6 +1624,57 @@ def write(self): w.addFixSeq(self.verify_data, 1) return self.postWrite(w) + +class SSL2Finished(HandshakeMsg): + """Handling of the SSL2 FINISHED messages""" + + def __init__(self, msg_type): + super(SSL2Finished, self).__init__(msg_type) + self.verify_data = bytearray(0) + + def create(self, verify_data): + """Set the message payload""" + self.verify_data = verify_data + return self + + def parse(self, parser): + """Deserialise the message from on the wire data""" + self.verify_data = parser.getFixBytes(parser.getRemainingLength()) + return self + + def write(self): + """Serialise the message to on the wire data""" + writer = Writer() + writer.add(self.handshakeType, 1) + writer.addFixSeq(self.verify_data, 1) + # does not use postWrite() as it's a SSLv2 message + return writer.bytes + + +class ClientFinished(SSL2Finished): + """ + Handling of SSLv2 CLIENT-FINISHED message + + @type verify_data: bytearray + @ivar verify_data: payload of the message, should be the CONNECTION-ID + """ + + def __init__(self): + super(ClientFinished, self).__init__(SSL2HandshakeType.client_finished) + + +class ServerFinished(SSL2Finished): + """ + Handling of SSLv2 SERVER-FINISHED message + + @type verify_data: bytearray + @ivar verify_data: payload of the message, should be SESSION-ID + """ + + def __init__(self): + super(ServerFinished, self).__init__(SSL2HandshakeType.server_finished) + + class ApplicationData(object): def __init__(self): self.contentType = ContentType.application_data diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 35355e76..41246b29 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -10,7 +10,7 @@ from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ - ClientMasterKey + ClientMasterKey, ClientFinished, ServerFinished from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ @@ -2177,5 +2177,71 @@ def test___repr__(self): self.assertEqual("ServerHelloDone()", repr(shd)) +class TestClientFinished(unittest.TestCase): + def test___init__(self): + fin = ClientFinished() + + self.assertIsNotNone(fin) + self.assertEqual(fin.handshakeType, SSL2HandshakeType.client_finished) + + def test_create(self): + fin = ClientFinished() + fin.create(bytearray(b'\xc0\xfe')) + + self.assertEqual(fin.verify_data, bytearray(b'\xc0\xfe')) + + def test_write(self): + fin = ClientFinished() + fin = fin.create(bytearray(b'\xc0\xfe')) + + self.assertEqual(bytearray( + b'\x03' + # message type + b'\xc0\xfe' # message payload + ), fin.write()) + + def test_parse(self): + parser = Parser(bytearray( + # type is handled by higher protocol level + b'\xc0\xfe')) + + fin = ClientFinished() + fin = fin.parse(parser) + + self.assertEqual(fin.verify_data, bytearray(b'\xc0\xfe')) + self.assertEqual(fin.handshakeType, SSL2HandshakeType.client_finished) + +class TestServerFinished(unittest.TestCase): + def test___init__(self): + fin = ServerFinished() + + self.assertIsNotNone(fin) + self.assertEqual(fin.handshakeType, SSL2HandshakeType.server_finished) + + def test_create(self): + fin = ServerFinished() + fin = fin.create(bytearray(b'\xc0\xfe')) + + self.assertEqual(fin.verify_data, bytearray(b'\xc0\xfe')) + + def test_write(self): + fin = ServerFinished() + fin.create(bytearray(b'\xc0\xfe')) + + self.assertEqual(bytearray( + b'\x06' + # message type + b'\xc0\xfe' # message payload + ), fin.write()) + + def test_parse(self): + parser = Parser(bytearray( + # type is handled by higher protocol level + b'\xc0\xfe')) + + fin = ServerFinished() + fin = fin.parse(parser) + + self.assertEqual(fin.verify_data, bytearray(b'\xc0\xfe')) + self.assertEqual(fin.handshakeType, SSL2HandshakeType.server_finished) + if __name__ == '__main__': unittest.main() From 35302225133b2278addaef3198f9f83b2f961729 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jan 2016 14:00:15 +0100 Subject: [PATCH 277/574] SSLv2 record layer encryption and decryption --- tlslite/recordlayer.py | 161 ++++++++++++++++++++++++- unit_tests/test_tlslite_recordlayer.py | 126 ++++++++++++++++++- 2 files changed, 282 insertions(+), 5 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 48827ddd..836be3f8 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -13,7 +13,7 @@ createTripleDES, createCHACHA20 from .utils.codec import Parser, Writer from .utils.compat import compatHMAC -from .utils.cryptomath import getRandomBytes +from .utils.cryptomath import getRandomBytes, MD5 from .utils.constanttime import ct_compare_digest, ct_check_cbc_mac_and_pad from .errors import TLSRecordOverflow, TLSIllegalParameterException,\ TLSAbruptCloseError, TLSDecryptionFailed, TLSBadRecordMAC @@ -239,6 +239,7 @@ class RecordLayer(object): @ivar client: whether the connection should use encryption @ivar encryptThenMAC: use the encrypt-then-MAC mechanism for record integrity + @ivar handshake_finished: used in SSL2, True if handshake protocol is over """ def __init__(self, sock): @@ -256,6 +257,8 @@ def __init__(self, sock): self.encryptThenMAC = False + self.handshake_finished = False + @property def blockSize(self): """Return the size of block used by current symmetric cipher (R/O)""" @@ -406,6 +409,31 @@ def _encryptThenSeal(self, buf, contentType): return buf + def _ssl2Encrypt(self, data): + """Encrypt in SSL2 mode""" + # in SSLv2 sequence numbers are incremented for plaintext records too + seqnumBytes = self._writeState.getSeqNumBytes() + + if (self._writeState.encContext and + self._writeState.encContext.isBlockCipher): + plaintext_len = len(data) + data = self.addPadding(data) + padding = len(data) - plaintext_len + else: + padding = 0 + + if self._writeState.macContext: + mac = self._writeState.macContext.copy() + mac.update(compatHMAC(data)) + mac.update(compatHMAC(seqnumBytes[-4:])) + + data = bytearray(mac.digest()) + data + + if self._writeState.encContext: + data = self._writeState.encContext.encrypt(data) + + return data, padding + def sendRecord(self, msg): """ Encrypt, MAC and send arbitrary message as-is through socket. @@ -419,7 +447,10 @@ def sendRecord(self, msg): data = msg.write() contentType = msg.contentType - if self._writeState and \ + padding = 0 + if self.version in ((0, 2), (2, 0)): + data, padding = self._ssl2Encrypt(data) + elif self._writeState and \ self._writeState.encContext and \ self._writeState.encContext.isAEAD: data = self._encryptThenSeal(data, contentType) @@ -430,7 +461,7 @@ def sendRecord(self, msg): encryptedMessage = Message(contentType, data) - for result in self._recordSocket.send(encryptedMessage): + for result in self._recordSocket.send(encryptedMessage, padding): yield result # @@ -601,6 +632,42 @@ def _decryptAndUnseal(self, recordType, buf): raise TLSBadRecordMAC("Invalid tag, decryption failure") return buf + def _decryptSSL2(self, data, padding): + """Decrypt SSL2 encrypted data""" + # sequence numbers are incremented for plaintext records too + seqnumBytes = self._readState.getSeqNumBytes() + + # + # decrypt + # + if self._readState.encContext: + if self._readState.encContext.isBlockCipher: + blockLength = self._readState.encContext.block_size + if len(data) % blockLength: + raise TLSDecryptionFailed() + data = self._readState.encContext.decrypt(data) + + # + # strip and check MAC + # + if self._readState.macContext: + macBytes = data[:16] + data = data[16:] + + mac = self._readState.macContext.copy() + mac.update(compatHMAC(data)) + mac.update(compatHMAC(seqnumBytes[-4:])) + calcMac = bytearray(mac.digest()) + if macBytes != calcMac: + raise TLSBadRecordMAC() + + # + # strip padding + # + if padding: + data = data[:-padding] + return data + def recvRecord(self): """ Read, decrypt and check integrity of a single record @@ -620,7 +687,11 @@ def recvRecord(self): (header, data) = result - if self._readState and \ + if self.version in ((0, 2), (2, 0)): + data = self._decryptSSL2(data, header.padding) + if self.handshake_finished: + header.type = ContentType.application_data + elif self._readState and \ self._readState.encContext and \ self._readState.encContext.isAEAD: data = self._decryptAndUnseal(header.type, data) @@ -646,6 +717,10 @@ def changeWriteState(self): This should be done only once after a call to L{calcPendingStates} was performed and directly after sending a L{ChangeCipherSpec} message. """ + if self.version in ((0, 2), (2, 0)): + # in SSLv2 sequence numbers carry over from plaintext to encrypted + # context + self._pendingWriteState.seqnum = self._writeState.seqnum self._writeState = self._pendingWriteState self._pendingWriteState = ConnectionState() @@ -656,6 +731,10 @@ def changeReadState(self): This should be done only once after a call to L{calcPendingStates} was performed and directly after receiving a L{ChangeCipherSpec} message. """ + if self.version in ((0, 2), (2, 0)): + # in SSLv2 sequence numbers carry over from plaintext to encrypted + # context + self._pendingReadState.seqnum = self._readState.seqnum self._readState = self._pendingReadState self._pendingReadState = ConnectionState() @@ -758,6 +837,80 @@ def _calcKeyBlock(self, cipherSuite, masterSecret, clientRandom, return keyBlock + def calcSSL2PendingStates(self, cipherSuite, masterSecret, clientRandom, + serverRandom, implementations): + """ + Create the keys for encryption and decryption in SSLv2 + + While we could reuse calcPendingStates(), we need to provide the + key-arg data for the server that needs to be passed up to handshake + protocol. + """ + if cipherSuite in CipherSuite.ssl2_128Key: + key_length = 16 + elif cipherSuite in CipherSuite.ssl2_192Key: + key_length = 24 + elif cipherSuite in CipherSuite.ssl2_64Key: + key_length = 8 + else: + raise ValueError("Unknown cipher specified") + + key_material = bytearray(key_length * 2) + md5_output_size = 16 + for i, pos in enumerate(range(0, key_length * 2, md5_output_size)): + key_material[pos:pos+md5_output_size] = MD5(\ + masterSecret + + bytearray(str(i), "ascii") + + clientRandom + serverRandom) + + serverWriteKey = key_material[:key_length] + clientWriteKey = key_material[key_length:] + + # specification draft says that DES key should not use the + # incrementing label but all implementations use it anyway + #elif cipherSuite in CipherSuite.ssl2_64Key: + # key_material = MD5(masterSecret + clientRandom + serverRandom) + # serverWriteKey = key_material[0:8] + # clientWriteKey = key_material[8:16] + + # RC4 cannot use initialisation vector + if cipherSuite not in CipherSuite.ssl2rc4: + iv = getRandomBytes(8) + else: + iv = bytearray(0) + + clientPendingState = ConnectionState() + serverPendingState = ConnectionState() + + # MAC + clientPendingState.macContext = hashlib.md5() + clientPendingState.macContext.update(compatHMAC(clientWriteKey)) + serverPendingState.macContext = hashlib.md5() + serverPendingState.macContext.update(compatHMAC(serverWriteKey)) + + # ciphers + if cipherSuite in CipherSuite.ssl2rc4: + cipherMethod = createRC4 + elif cipherSuite in CipherSuite.ssl2_3des: + cipherMethod = createTripleDES + else: + raise NotImplementedError("Unknown cipher") + + clientPendingState.encContext = cipherMethod(clientWriteKey, iv, + implementations) + serverPendingState.encContext = cipherMethod(serverWriteKey, iv, + implementations) + + # Assign new connection states to pending states + if self.client: + self._pendingWriteState = clientPendingState + self._pendingReadState = serverPendingState + else: + self._pendingWriteState = serverPendingState + self._pendingReadState = clientPendingState + + return iv + def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, serverRandom, implementations): """Create pending states for encryption and decryption.""" diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index f5fc5d64..b84c1082 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -21,7 +21,7 @@ import tlslite.utils.cryptomath as cryptomath from tlslite.messages import Message, ApplicationData, RecordHeader3, \ - ClientHello + ClientHello, ClientMasterKey, ServerHello2, RecordHeader2 from tlslite.recordlayer import RecordSocket, ConnectionState, RecordLayer from tlslite.constants import ContentType, CipherSuite from unit_tests.mocksock import MockSocket @@ -2209,3 +2209,127 @@ def broken_padding(data): with self.assertRaises(TLSBadRecordMAC): next(gen) + + def test_sendRecord_with_ssl2(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (0, 2) + + recordLayer.calcSSL2PendingStates( + CipherSuite.SSL_CK_RC4_128_WITH_MD5, + bytearray(24), # master secret + bytearray(16), # client random + bytearray(16), # server random + None) + + # make sequence number correct + hello = ClientHello().create((0, 2), bytearray(0), bytearray(0), + []) + master_key = ClientMasterKey() + + for result in recordLayer.sendRecord(hello): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + for result in recordLayer.sendRecord(master_key): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else:break + # sequence number tweaking end + + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsNotNone(app_data) + self.assertTrue(len(app_data.write()) > 3) + + for result in recordLayer.sendRecord(app_data): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 3) + self.assertEqual(sock.sent[2][:2], bytearray( + b'\x80' + # 2 byte header + b'\x14' # overall length + )) + self.assertEqual(sock.sent[2][2:], bytearray( + b'\xa7\xaai.\x8a\x7ff\x12\xf8T\xcf[)\xc6\xd4\x11\xb85\x13\x0c' + )) + + def test_recvRecord_with_ssl2(self): + # prepare encrypted message + srv_sock = MockSocket(bytearray(0)) + + srv_recordLayer = RecordLayer(srv_sock) + srv_recordLayer.client = False + srv_recordLayer.version = (0, 2) + # make the sequence number match + srv_hello = ServerHello2() + for result in srv_recordLayer.sendRecord(srv_hello): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + # setup encryption + srv_recordLayer.calcSSL2PendingStates( + CipherSuite.SSL_CK_RC4_128_WITH_MD5, + bytearray(24), # master secret + bytearray(16), # client random + bytearray(16), # server random + None) + srv_recordLayer.changeWriteState() + # actually encrypt the message + srv_data = ApplicationData().create(bytearray(b'test')) + for result in srv_recordLayer.sendRecord(srv_data): + if result in (0, 1): + self.assertRaises(False, "blocking socket") + else: break + + # + # check sanity of encrypted message + # + self.assertEqual(len(srv_sock.sent), 2) + self.assertEqual(srv_sock.sent[1][:2], bytearray( + b'\x80' + # 2 byte header + b'\x14')) # overall length + self.assertEqual(srv_sock.sent[1][2:], bytearray( + b'(\x07\xf9\xde`\x80\xa77s\x13Q\xc6%\n\x7f\xbd\xb0,8\xc4' + )) + + # + # prepare socket for client + # + sock = MockSocket(srv_sock.sent[0] + srv_sock.sent[1]) + + recordLayer = RecordLayer(sock) + recordLayer.version = (0, 2) + # first match the sequence numbers + for result in recordLayer.recvRecord(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + header, parser = result + # setup encryption + recordLayer.calcSSL2PendingStates( + CipherSuite.SSL_CK_RC4_128_WITH_MD5, + bytearray(24), # master secret + bytearray(16), # client random + bytearray(16), # server random + None) + recordLayer.changeReadState() + recordLayer.handshake_finished = True + + # + # Test proper - recv encrypted message + # + for result in recordLayer.recvRecord(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + header, parser = result + + self.assertIsInstance(header, RecordHeader2) + self.assertEqual(header.type, ContentType.application_data) + self.assertEqual(parser.bytes, bytearray(b'test')) From 775944366c75327f2e04f720c5cc73c7a5db4f46 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jan 2016 15:25:34 +0100 Subject: [PATCH 278/574] SSL2 Error message descriptions --- tlslite/constants.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index fb26758b..389bf4a6 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -69,6 +69,16 @@ class SSL2HandshakeType(TLSEnum): request_certificate = 7 client_certificate = 8 + +class SSL2ErrorDescription(TLSEnum): + """SSL2 Handshake protocol error message descriptions""" + + no_cipher = 0x0001 + no_certificate = 0x0002 + bad_certificate = 0x0004 + unsupported_certificate_type = 0x0006 + + class HandshakeType(TLSEnum): """Message types in TLS Handshake protocol""" From 00ab258d98bf305e548023da4497db8921c43be3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jan 2016 16:51:19 +0100 Subject: [PATCH 279/574] support SSLv2Hello protocol in case we're sending a SSLv2 CLIENT-HELLO, but indicate high protocol version, the server may respond with SSLv3 or TLS message, so we need to parse those messages as SSLv3 record layer even if we're set up to send SSLv2 --- tlslite/recordlayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 836be3f8..47265df0 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -687,7 +687,7 @@ def recvRecord(self): (header, data) = result - if self.version in ((0, 2), (2, 0)): + if isinstance(header, RecordHeader2): data = self._decryptSSL2(data, header.padding) if self.handshake_finished: header.type = ContentType.application_data From adc2006fb450f75e7815a6cd6fc6f1e551c8504e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 24 Feb 2016 16:23:05 +0100 Subject: [PATCH 280/574] add reference to openstack comit message guide Since writing good commit messages is non trivial in general, reference the very good guide from OpenStack project. --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b6ef237..b4f7c64e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,6 +72,8 @@ dnf install m2crypto python-crypto python3-crypto python-gmpy2 python3-gmpy2 * Make commits of logical units and describe them properly in commits * When creating a comment, keep the first line short and separate it from the rest by whiteline + * See also [OpenStack guide](https://wiki.openstack.org/wiki/GitCommitMessages) + for general good ideas about git commit messages * Check for unnecessary whitespace with `git diff --check` before committing * Generally newly submitted code should have test coverage so that it can From e4dfd1da93d35a5fa09949e1ffb6b3e95335f11d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 24 Feb 2016 16:49:35 +0100 Subject: [PATCH 281/574] workaround hypothesis on python 2.6 Looks like hypothesis 3.x have removed support for Python 2.6, try to workaround it by using older version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4e1e9fc7..8cc0825a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -80,7 +80,7 @@ install: - if [[ $M2CRYPTO == 'true' ]]; then travis_retry pip install --pre m2crypto; fi - if [[ $PYCRYPTO == 'true' ]]; then travis_retry pip install pycrypto; fi - if [[ $GMPY == 'true' ]]; then travis_retry pip install gmpy; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then travis_retry pip install 'coverage<4' 'hypothesis<1.10'; else travis_retry pip install coverage hypothesis; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then travis_retry pip install 'coverage<4' 'hypothesis<1.10'; elif [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then travis_retry pip install coverage 'hypothesis<3'; else travis_retry pip install coverage hypothesis; fi - travis_retry pip install -r requirements.txt - travis_retry pip install -r build-requirements.txt From 0c2b40692be8fa86b8fadd39e87b91599a36945a Mon Sep 17 00:00:00 2001 From: Ondrej Moris Date: Wed, 24 Feb 2016 22:14:09 +0100 Subject: [PATCH 282/574] unit_test: constanttime module tests accelerated Tests from constanttime module using huge number of tested values took signifficant amount of time. This is now reduced using package hypothesis by testing only extreme values and a fixed number (200) of randomly chosen values. --- unit_tests/test_tlslite_utils_constanttime.py | 141 ++++++++---------- 1 file changed, 64 insertions(+), 77 deletions(-) diff --git a/unit_tests/test_tlslite_utils_constanttime.py b/unit_tests/test_tlslite_utils_constanttime.py index d719b25f..004065a6 100644 --- a/unit_tests/test_tlslite_utils_constanttime.py +++ b/unit_tests/test_tlslite_utils_constanttime.py @@ -13,89 +13,76 @@ ct_lsb_prop_u8, ct_isnonzero_u32, ct_neq_u32, ct_eq_u32, \ ct_check_cbc_mac_and_pad, ct_compare_digest +from hypothesis import given, example +import hypothesis.strategies as st from tlslite.utils.compat import compatHMAC from tlslite.recordlayer import RecordLayer import hashlib import hmac class TestContanttime(unittest.TestCase): - def test_ct_lt_u32(self): - for i in range(0, 256): - for j in range(0, 256): - self.assertEqual((i < j), (ct_lt_u32(i, j) == 1)) - - for i in range(2**32-256, 2**32): - for j in range(2**32-256, 2**32): - self.assertEqual((i < j), (ct_lt_u32(i, j) == 1)) - - for i in range(0, 256): - for j in range(2**32-256, 2**32): - self.assertEqual((i < j), (ct_lt_u32(i, j) == 1)) - - for i in range(2**32-256, 2**32): - for j in range(0, 256): - self.assertEqual((i < j), (ct_lt_u32(i, j) == 1)) - - def test_ct_gt_u32(self): - for i in range(0, 256): - for j in range(0, 256): - self.assertEqual((i > j), (ct_gt_u32(i, j) == 1)) - - for i in range(2**32-256, 2**32): - for j in range(2**32-256, 2**32): - self.assertEqual((i > j), (ct_gt_u32(i, j) == 1)) - - for i in range(0, 256): - for j in range(2**32-256, 2**32): - self.assertEqual((i > j), (ct_gt_u32(i, j) == 1)) - - for i in range(2**32-256, 2**32): - for j in range(0, 256): - self.assertEqual((i > j), (ct_gt_u32(i, j) == 1)) - - def test_ct_le_u32(self): - for i in range(0, 256): - for j in range(0, 256): - self.assertEqual((i <= j), (ct_le_u32(i, j) == 1)) - - for i in range(2**32-256, 2**32): - for j in range(2**32-256, 2**32): - self.assertEqual((i <= j), (ct_le_u32(i, j) == 1)) - - for i in range(0, 256): - for j in range(2**32-256, 2**32): - self.assertEqual((i <= j), (ct_le_u32(i, j) == 1)) - - for i in range(2**32-256, 2**32): - for j in range(0, 256): - self.assertEqual((i <= j), (ct_le_u32(i, j) == 1)) - - def test_ct_lsb_prop_u8(self): - for i in range(0, 256): - self.assertEqual(((i & 0x1) == 1), (ct_lsb_prop_u8(i) == 0xff)) - self.assertEqual(((i & 0x1) == 0), (ct_lsb_prop_u8(i) == 0x00)) - - def test_ct_isnonzero_u32(self): - for i in range(0, 256): - self.assertEqual((i != 0), (ct_isnonzero_u32(i) == 1)) - - def test_ct_neq_u32(self): - for i in range(0, 256): - for j in range(0, 256): - self.assertEqual((i != j), (ct_neq_u32(i, j) == 1)) - - for i in range(2**32-128, 2**32): - for j in range(2**32-128, 2**32): - self.assertEqual((i != j), (ct_neq_u32(i, j) == 1)) - - def test_ct_eq_u32(self): - for i in range(0, 256): - for j in range(0, 256): - self.assertEqual((i == j), (ct_eq_u32(i, j) == 1)) - - for i in range(2**32-128, 2**32): - for j in range(2**32-128, 2**32): - self.assertEqual((i == j), (ct_eq_u32(i, j) == 1)) + + @given(i=st.integers(0,2**32 - 1), j=st.integers(0,2**32 - 1)) + @example(i=0, j=0) + @example(i=0, j=1) + @example(i=1, j=0) + @example(i=2**32 - 1, j=2**32 - 1) + @example(i=2**32 - 2, j=2**32 - 1) + @example(i=2**32 - 1, j=2**32 - 2) + def test_ct_lt_u32(self, i, j): + self.assertEqual((i < j), (ct_lt_u32(i, j) == 1)) + + @given(i=st.integers(0,2**32 - 1), j=st.integers(0,2**32 - 1)) + @example(i=0, j=0) + @example(i=0, j=1) + @example(i=1, j=0) + @example(i=2**32 - 1, j=2**32 - 1) + @example(i=2**32 - 2, j=2**32 - 1) + @example(i=2**32 - 1, j=2**32 - 2) + def test_ct_gt_u32(self, i, j): + self.assertEqual((i > j), (ct_gt_u32(i, j) == 1)) + + @given(i=st.integers(0,2**32 - 1), j=st.integers(0,2**32 - 1)) + @example(i=0, j=0) + @example(i=0, j=1) + @example(i=1, j=0) + @example(i=2**32 - 1, j=2**32 - 1) + @example(i=2**32 - 2, j=2**32 - 1) + @example(i=2**32 - 1, j=2**32 - 2) + def test_ct_le_u32(self, i, j): + self.assertEqual((i <= j), (ct_le_u32(i, j) == 1)) + + @given(i=st.integers(0,2**32 - 1), j=st.integers(0,2**32 - 1)) + @example(i=0, j=0) + @example(i=0, j=1) + @example(i=1, j=0) + @example(i=2**32 - 1, j=2**32 - 1) + @example(i=2**32 - 2, j=2**32 - 1) + @example(i=2**32 - 1, j=2**32 - 2) + def test_ct_neq_u32(self, i, j): + self.assertEqual((i != j), (ct_neq_u32(i, j) == 1)) + + @given(i=st.integers(0,2**32 - 1), j=st.integers(0,2**32 - 1)) + @example(i=0, j=0) + @example(i=0, j=1) + @example(i=1, j=0) + @example(i=2**32 - 1, j=2**32 - 1) + @example(i=2**32 - 2, j=2**32 - 1) + @example(i=2**32 - 1, j=2**32 - 2) + def test_ct_eq_u32(self, i, j): + self.assertEqual((i == j), (ct_eq_u32(i, j) == 1)) + + @given(i=st.integers(0,255)) + @example(i=0) + @example(i=255) + def test_ct_lsb_prop_u8(self, i): + self.assertEqual(((i & 0x1) == 1), (ct_lsb_prop_u8(i) == 0xff)) + self.assertEqual(((i & 0x1) == 0), (ct_lsb_prop_u8(i) == 0x00)) + + @given(i=st.integers(0,2**32 - 1)) + @example(i=0) + def test_ct_isnonzero_u32(self, i): + self.assertEqual((i != 0), (ct_isnonzero_u32(i) == 1)) class TestContanttimeCBCCheck(unittest.TestCase): From 4749d46fbc503aaf0b4c9aba7dd156a4c59328ac Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 9 Mar 2016 18:49:31 +0100 Subject: [PATCH 283/574] fix typo from 761c5c2 --- tlslite/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 429e7740..1d9ae8ad 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -521,7 +521,7 @@ def create(self, version, random, session_id, cipher_suites, @type random: bytearray @param random: client provided random value, in old versions of TLS (before 1.2) the first 32 bits should include system time, also - used as the "challange" field in SSLv2 + used as the "challenge" field in SSLv2 @type session_id: bytearray @param session_id: ID of session, set when doing session resumption From 521d3e9cd2c14d658de856f836397cee30fa9dfd Mon Sep 17 00:00:00 2001 From: "E.Iosifidis" Date: Sat, 19 Mar 2016 14:22:22 +0200 Subject: [PATCH 284/574] Optimization improvements read the object reference before the loop and not every-time inside it. --- tlslite/utils/aesgcm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tlslite/utils/aesgcm.py b/tlslite/utils/aesgcm.py index 54b48faa..cbf361dc 100644 --- a/tlslite/utils/aesgcm.py +++ b/tlslite/utils/aesgcm.py @@ -57,8 +57,9 @@ def _rawAesCtrEncrypt(self, counter, inp): Encrypts (or decrypts) plaintext with AES-CTR. counter is modified. """ out = bytearray(len(inp)) + rawAesEncrypt = self._rawAesEncrypt for i in range(0, len(out), 16): - mask = self._rawAesEncrypt(counter) + mask = rawAesEncrypt(counter) for j in range(i, min(len(out), i + 16)): out[j] = inp[j] ^ mask[j-i] self._inc32(counter) From afd2fc415c7737ceb00c409b7baa4b4cf0e17958 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 14 Apr 2016 16:33:51 +0200 Subject: [PATCH 285/574] add socket wrapper for buffering socket writes --- tlslite/bufferedsocket.py | 92 +++++++++++++ unit_tests/test_tlslite_bufferedsocket.py | 151 ++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 tlslite/bufferedsocket.py create mode 100644 unit_tests/test_tlslite_bufferedsocket.py diff --git a/tlslite/bufferedsocket.py b/tlslite/bufferedsocket.py new file mode 100644 index 00000000..4694a26f --- /dev/null +++ b/tlslite/bufferedsocket.py @@ -0,0 +1,92 @@ +# Copyright (c) 2016, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +"""Wrapper around the socket.socket interface that provides buffering""" + +from collections import deque + + +class BufferedSocket(object): + """ + Socket that will buffer reads and writes to a real socket object + + When buffer_writes is enabled, writes won't be passed to the real socket + until flush() is called. + + Not multithread safe. + + @type buffer_writes: boolean + @ivar buffer_writes: whether to buffer data writes, False by default + """ + + def __init__(self, socket): + """Associate socket with the object""" + self.socket = socket + self._write_queue = deque() + self.buffer_writes = False + + def send(self, data): + """Send data to the socket""" + if self.buffer_writes: + self._write_queue.append(data) + return len(data) + return self.socket.send(data) + + def sendall(self, data): + """Send data to the socket""" + if self.buffer_writes: + self._write_queue.append(data) + return None + return self.socket.sendall(data) + + def flush(self): + """Send all buffered data""" + buf = bytearray() + for i in self._write_queue: + buf += i + self._write_queue.clear() + if buf: + self.socket.sendall(buf) + + def recv(self, bufsize): + """Receive data from socket (socket emulation)""" + return self.socket.recv(bufsize) + + def getsockname(self): + """Return the socket's own address (socket emulation).""" + return self.socket.getsockname() + + def getpeername(self): + """ + Return the remote address to which the socket is connected + + (socket emulation) + """ + return self.socket.getpeername() + + def settimeout(self, value): + """Set a timeout on blocking socket operations (socket emulation).""" + return self.socket.settimeout(value) + + def gettimeout(self): + """ + Return the timeout associated with socket operations + + (socket emulation) + """ + return self.socket.gettimeout() + + def setsockopt(self, level, optname, value): + """Set the value of the given socket option (socket emulation).""" + return self.socket.setsockopt(level, optname, value) + + def shutdown(self, how): + """Shutdown the underlying socket.""" + self.flush() + return self.socket.shutdown(how) + + def close(self): + """Close the underlying socket.""" + self.flush() + return self.socket.close() diff --git a/unit_tests/test_tlslite_bufferedsocket.py b/unit_tests/test_tlslite_bufferedsocket.py new file mode 100644 index 00000000..f625e4f2 --- /dev/null +++ b/unit_tests/test_tlslite_bufferedsocket.py @@ -0,0 +1,151 @@ +# Author: Hubert Kario (c) 2016 +# see LICENCE file for legal information regarding use of this file + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call + +from tlslite.bufferedsocket import BufferedSocket + +class TestBufferedSocket(unittest.TestCase): + def setUp(self): + self.raw_sock = mock.MagicMock() + self.sock = BufferedSocket(self.raw_sock) + + def test___init__(self): + self.assertFalse(self.sock.buffer_writes) + self.assertIs(self.sock.socket, self.raw_sock) + + def test_send(self): + data = mock.Mock() + ret = self.sock.send(data) + + self.raw_sock.send.assert_called_once_with(data) + self.assertIs(ret, self.raw_sock.send.return_value) + + def test_send_with_buffering(self): + self.sock.buffer_writes = True + + data = mock.Mock() + data.__len__ = mock.Mock(return_value=42) + ret = self.sock.send(data) + + self.raw_sock.send.assert_not_called() + self.assertEqual(ret, 42) + + def test_sendall(self): + data = mock.Mock() + ret = self.sock.sendall(data) + + self.assertIs(ret, self.raw_sock.sendall.return_value) + self.raw_sock.sendall.assert_called_once_with(data) + + def test_sendall_with_buffering(self): + self.sock.buffer_writes = True + + data = mock.Mock() + ret = self.sock.sendall(data) + + self.raw_sock.sendall.assert_not_called() + self.assertIsNone(ret) + + def test_flush(self): + self.sock.flush() + self.raw_sock.sendall.assert_not_called() + + def test_flush_with_data(self): + self.sock.buffer_writes = True + + ret = self.sock.send(bytearray(b'abc')) + + self.assertEqual(ret, 3) + self.raw_sock.sendall.assert_not_called() + + self.sock.flush() + self.raw_sock.sendall.assert_called_once_with(bytearray(b'abc')) + + def test_flush_with_data_and_multiple_messages(self): + self.sock.buffer_writes = True + + ret = self.sock.send(bytearray(b'abc')) + self.assertEqual(ret, 3) + + ret = self.sock.send(bytearray(b'defg')) + self.assertEqual(ret, 4) + + self.sock.flush() + self.raw_sock.sendall.assert_called_once_with(bytearray(b'abcdefg')) + + self.sock.flush() + self.raw_sock.sendall.assert_called_once_with(bytearray(b'abcdefg')) + + def test_recv(self): + value = mock.Mock() + ret = self.sock.recv(value) + + self.raw_sock.recv.assert_called_once_with(value) + self.assertIs(ret, self.raw_sock.recv.return_value) + + def test_getsockname(self): + ret = self.sock.getsockname() + + self.raw_sock.getsockname.assert_called_once_with() + self.assertIs(ret, self.raw_sock.getsockname.return_value) + + def test_getpeername(self): + ret = self.sock.getpeername() + + self.raw_sock.getpeername.assert_called_once_with() + self.assertIs(ret, self.raw_sock.getpeername.return_value) + + def test_settimeout(self): + value = mock.Mock() + ret = self.sock.settimeout(value) + + self.raw_sock.settimeout.assert_called_once_with(value) + self.assertIs(ret, self.raw_sock.settimeout.return_value) + + def test_gettimeout(self): + ret = self.sock.gettimeout() + + self.raw_sock.gettimeout.assert_called_once_with() + self.assertIs(ret, self.raw_sock.gettimeout.return_value) + + def test_setsockopt(self): + level = mock.Mock() + optname = mock.Mock() + value = mock.Mock() + + ret = self.sock.setsockopt(level, optname, value) + + self.raw_sock.setsockopt.assert_called_once_with(level, optname, value) + self.assertIs(ret, self.raw_sock.setsockopt.return_value) + + def test_shutdown(self): + self.sock.buffer_writes = True + + self.sock.send(bytearray(b'ghi')) + how = mock.Mock() + + self.sock.shutdown(how) + + self.raw_sock.sendall.assert_called_once_with(bytearray(b'ghi')) + self.raw_sock.shutdown.assert_called_once_with(how) + + def test_close(self): + self.sock.buffer_writes = True + + self.sock.send(bytearray(b'jkl')) + + self.sock.close() + self.raw_sock.sendall.assert_called_once_with(bytearray(b'jkl')) + self.raw_sock.close.assert_called_once_with() From f146142a9472a7d1af71edeccc5063bbf1c6f5eb Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 14 Apr 2016 16:35:59 +0200 Subject: [PATCH 286/574] buffer writes in handshake on client side --- tlslite/tlsconnection.py | 10 ++++++++++ tlslite/tlsrecordlayer.py | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index c8529363..3fd14d1c 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -30,6 +30,7 @@ from .keyexchange import KeyExchange, RSAKeyExchange, DHE_RSAKeyExchange, \ ECDHE_RSAKeyExchange, SRPKeyExchange from .handshakehelpers import HandshakeHelpers +from .bufferedsocket import BufferedSocket class TLSConnection(TLSRecordLayer): """ @@ -485,6 +486,8 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, keyExchange = RSAKeyExchange(cipherSuite, clientHello, serverHello, None) + # we'll send few messages here, send them in single TCP packet + self.sock.buffer_writes = True for result in self._clientKeyExchange(settings, cipherSuite, clientCertChain, privateKey, @@ -501,6 +504,7 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, #After having previously sent a ClientKeyExchange, the client now #initiates an exchange of Finished messages. + # socket buffering is turned off in _clientFinished for result in self._clientFinished(premasterSecret, clientHello.random, serverHello.random, @@ -719,10 +723,14 @@ def _clientResume(self, session, serverHello, clientRandom, for result in self._getFinished(session.masterSecret, session.cipherSuite): yield result + # buffer writes so that CCS and Finished go out in one TCP packet + self.sock.buffer_writes = True for result in self._sendFinished(session.masterSecret, session.cipherSuite, nextProto): yield result + self.sock.flush() + self.sock.buffer_writes = False #Set the session for this connection self.session = session @@ -920,6 +928,8 @@ def _clientFinished(self, premasterSecret, clientRandom, serverRandom, #Exchange ChangeCipherSpec and Finished messages for result in self._sendFinished(masterSecret, cipherSuite, nextProto): yield result + self.sock.flush() + self.sock.buffer_writes = False for result in self._getFinished(masterSecret, cipherSuite, nextProto=nextProto): diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index a0e7bd0d..c682bfaf 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -21,6 +21,7 @@ from .recordlayer import RecordLayer from .defragmenter import Defragmenter from .handshakehashes import HandshakeHashes +from .bufferedsocket import BufferedSocket import socket import traceback @@ -105,6 +106,7 @@ class TLSRecordLayer(object): """ def __init__(self, sock): + sock = BufferedSocket(sock) self.sock = sock self._recordLayer = RecordLayer(sock) @@ -528,6 +530,9 @@ def _shutdown(self, resumable): def _sendError(self, alertDescription, errorStr=None): + # make sure that the message goes out + self.sock.flush() + self.sock.buffer_writes = False alert = Alert().create(alertDescription, AlertLevel.fatal) for result in self._sendMsg(alert): yield result From 6e26184400212e3fdea80f0e9eb8c20d47641615 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 14 Apr 2016 18:25:32 +0200 Subject: [PATCH 287/574] buffer writes during handshake on server side --- tlslite/tlsconnection.py | 5 ++++- tlslite/tlsrecordlayer.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 3fd14d1c..c926e516 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -30,7 +30,6 @@ from .keyexchange import KeyExchange, RSAKeyExchange, DHE_RSAKeyExchange, \ ECDHE_RSAKeyExchange, SRPKeyExchange from .handshakehelpers import HandshakeHelpers -from .bufferedsocket import BufferedSocket class TLSConnection(TLSRecordLayer): """ @@ -1739,6 +1738,8 @@ def _serverFinished(self, premasterSecret, clientRandom, serverRandom, def _sendFinished(self, masterSecret, cipherSuite=None, nextProto=None): + # send the CCS and Finished in single TCP packet + self.sock.buffer_writes = True #Send ChangeCipherSpec for result in self._sendMsg(ChangeCipherSpec()): yield result @@ -1764,6 +1765,8 @@ def _sendFinished(self, masterSecret, cipherSuite=None, nextProto=None): finished = Finished(self.version).create(verifyData) for result in self._sendMsg(finished): yield result + self.sock.flush() + self.sock.buffer_writes = False def _getFinished(self, masterSecret, cipherSuite=None, expect_next_protocol=False, nextProto=None): diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index c682bfaf..21c4eaf8 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -540,11 +540,15 @@ def _sendError(self, alertDescription, errorStr=None): raise TLSLocalAlert(alert, errorStr) def _sendMsgs(self, msgs): + # send messages together + self.sock.buffer_writes = True randomizeFirstBlock = True for msg in msgs: for result in self._sendMsg(msg, randomizeFirstBlock): yield result randomizeFirstBlock = True + self.sock.flush() + self.sock.buffer_writes = False def _sendMsg(self, msg, randomizeFirstBlock = True): """Fragment and send message through socket""" From 0e232aa7b11b1916cb818a99b21f63343f06c2ea Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 30 May 2016 17:19:29 +0200 Subject: [PATCH 288/574] add alerts from RFC 5246 and 6066 since we support both RFCs we should be able to identify those alert descriptions too --- tlslite/constants.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 389bf4a6..e1d78954 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -280,6 +280,8 @@ class AlertDescription(TLSEnum): inappropriate_fallback = 86 user_canceled = 90 no_renegotiation = 100 + unsupported_extension = 110 # RFC 5246 + unrecognized_name = 112 # RFC 6066 unknown_psk_identity = 115 From f19b49f2724206f19d960550ea1184015f7e5e77 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 30 May 2016 17:24:32 +0200 Subject: [PATCH 289/574] add markdownlint to codeclimate checker --- .codeclimate.yml | 2 + CONTRIBUTING.md | 38 +-- README.md | 600 ++++++++++++++++++++++++++--------------------- 3 files changed, 356 insertions(+), 284 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 4bcb419b..713a2970 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -13,6 +13,8 @@ engines: checks: bug: enabled: true + markdownlint: + enabled: true ratings: paths: - "tlslite/**" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b4f7c64e..b61d702c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,21 +22,24 @@ Git client, make, text editor and ability to install local python packages (ability to run pip). The list goes as follows: - * python (2.7 or 3.4) - * git - * GNU make - * pip + +* python (2.7 or 3.4) +* git +* GNU make +* pip The python module dependencies are as follows: - * unittest (unittest2 on Python 2; should be part of Python 3 install) - * mock (should be part of Python 3 distribution of unittest) - * ecdsa - * pylint - * diff_cover - * coverage - * hypothesis + +* unittest (unittest2 on Python 2; should be part of Python 3 install) +* mock (should be part of Python 3 distribution of unittest) +* ecdsa +* pylint +* diff_cover +* coverage +* hypothesis On Fedora they can be installed using: + ``` dnf install python-ecdsa python3-ecdsa pylint python3-pylint python-diff-cover \ python3-diff-cover python-coverage python3-coverage python2-hypothesis \ @@ -44,12 +47,14 @@ dnf install python-ecdsa python3-ecdsa pylint python3-pylint python-diff-cover \ ``` Optional module dependencies: - * tackpy - * m2crypto - * pycrypto - * gmpy + +* tackpy +* m2crypto +* pycrypto +* gmpy On Fedora they can be installed using: + ``` pip install tackpy dnf install m2crypto python-crypto python3-crypto python-gmpy2 python3-gmpy2 @@ -86,6 +91,7 @@ dnf install m2crypto python-crypto python3-crypto python-gmpy2 python3-gmpy2 that passes it is the best way to make the review quick. ## Submit changes + * Push your changes to a topic branch in your fork of the repository. * Open a pull request to the original repository and choose the right original branch you want to patch (that usually will be tomato42/master). @@ -101,7 +107,7 @@ dnf install m2crypto python-crypto python3-crypto python-gmpy2 python3-gmpy2 a test pull request, but please mark it as such ('[WIP]' in title is enough) -# Additional Resources +## Additional Resources * [General GitHub documentation](http://help.github.com/) * [GitHub pull request documentation](http://help.github.com/send-pull-requests/) diff --git a/README.md b/README.md index 23a9d345..4592a665 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,21 @@ https://github.com/tomato42/tlslite-ng/ [![Code Climate](https://codeclimate.com/github/tomato42/tlslite-ng/badges/gpa.svg)](https://codeclimate.com/github/tomato42/tlslite-ng) [![Code Issues](https://www.quantifiedcode.com/api/v1/project/59e6019ef3c84ad7ba30c49ec46d990f/badge.svg)](https://www.quantifiedcode.com/app/project/59e6019ef3c84ad7ba30c49ec46d990f) - Table of Contents ================== -1. Introduction -2. License/Acknowledgements -3. Installation -4. Getting Started with the Command-Line Tools -5. Getting Started with the Library -6. Using tlslite-ng with httplib -7. Using tlslite-ng with poplib or imaplib -8. Using tlslite-ng with smtplib -9. Using tlslite-ng with SocketServer -10. Using tlslite-ng with asyncore -11. SECURITY CONSIDERATIONS -12. History - +1. Introduction +1. License/Acknowledgements +1. Installation +1. Getting Started with the Command-Line Tools +1. Getting Started with the Library +1. Using tlslite-ng with httplib +1. Using tlslite-ng with poplib or imaplib +1. Using tlslite-ng with smtplib +1. Using tlslite-ng with SocketServer +1. Using tlslite-ng with asyncore +1. SECURITY CONSIDERATIONS +1. History 1 Introduction =============== @@ -48,14 +46,15 @@ CONTRIBUTING.md file for more information. tlslite-ng aims to be a drop in replacement for the original TLS Lite. Implemented features of TLS include: - * SSLv3, TLSv1.0, TLSv1.1 and TLSv1.2 - * ciphersuites with DHE, ECDHE, RSA and SRP key exchange together with - AES (including GCM variant), 3DES, RC4 and (the experimental) ChaCha20 - symmetric ciphers. - * Secure Renegotiation - * Encrypt Then MAC extension - * TLS_FALLBACK_SCSV - * (experimental) TACK extension + +* SSLv3, TLSv1.0, TLSv1.1 and TLSv1.2 +* ciphersuites with DHE, ECDHE, RSA and SRP key exchange together with + AES (including GCM variant), 3DES, RC4 and (the experimental) ChaCha20 + symmetric ciphers. +* Secure Renegotiation +* Encrypt Then MAC extension +* TLS_FALLBACK_SCSV +* (experimental) TACK extension 2 Licenses/Acknowledgements ============================ @@ -79,32 +78,34 @@ Thanks to Edward Loper for Epydoc, which generated the API docs. Requirements: - * Python 2.6 or higher is required. - * Python 3.2 or higher is supported. - * python ecdsa library ([GitHub](https://github.com/warner/python-ecdsa), - [PyPI](https://pypi.python.org/pypi/ecdsa)) +* Python 2.6 or higher is required. +* Python 3.2 or higher is supported. +* python ecdsa library ([GitHub](https://github.com/warner/python-ecdsa), + [PyPI](https://pypi.python.org/pypi/ecdsa)) Options: - * If you have the M2Crypto interface to OpenSSL, this will be used for fast - RSA operations and fast ciphers. - * If you have pycrypto this will be used for fast RSA operations and fast - ciphers. - * If you have the GMPY interface to GMP, this will be used for fast RSA and - SRP operations. - * These modules don't need to be present at installation - you can install - them any time. +* If you have the M2Crypto interface to OpenSSL, this will be used for fast + RSA operations and fast ciphers. +* If you have pycrypto this will be used for fast RSA operations and fast + ciphers. +* If you have the GMPY interface to GMP, this will be used for fast RSA and + SRP operations. +* These modules don't need to be present at installation - you can install + them any time. 3.1 Automatic ------------- Run: + ``` pip install tlslite-ng ``` In case your system doesn't have pip, you can install it by first downloading [get-pip.py](https://bootstrap.pypa.io/get-pip.py) and running + ``` python get-pip.py ``` @@ -115,15 +116,18 @@ python get-pip.py Run 'python setup.py install' Test the Installation - * From the distribution's directory, run: + +* From the distribution's directory, run: + ``` make test ``` - * If it says "Test succeeded" at the end, you're ready to go. +* If it says "Test succeeded" at the end, you're ready to go. 4 Getting Started with the Command-Line Tools ============================================== + tlslite-ng installs two command-line scripts: `tlsdb.py` and `tls.py`. `tls.py` lets you run test clients and servers. It can be used for testing @@ -136,6 +140,7 @@ a TLS server when authenticating clients with SRP. X.509 ------ + To run an X.509 server, go to the ./tests directory and do: ``` @@ -150,6 +155,7 @@ tls.py client localhost:4443 X.509 with TACK ---------------- + To run an X.509 server using a TACK, install TACKpy, then run the same server command as above with added arguments: @@ -159,6 +165,7 @@ command as above with added arguments: SRP ---- + To run an SRP server, try something like: ``` @@ -177,28 +184,29 @@ tls.py client localhost:4443 alice abra123cadabra HTTPS ------ + To run an HTTPS server with less typing, run `./tests/httpsserver.sh`. To run an HTTPS client, run `./tests/httpsclient.py`. - 5 Getting Started with the Library =================================== Whether you're writing a client or server, there are six steps: 1. Create a socket and connect it to the other party. -2. Construct a TLSConnection instance with the socket. -3. Call a handshake function on TLSConnection to perform the TLS handshake. -4. Check the results to make sure you're talking to the right party. -5. Use the TLSConnection to exchange data. -6. Call close() on the TLSConnection when you're done. +1. Construct a TLSConnection instance with the socket. +1. Call a handshake function on TLSConnection to perform the TLS handshake. +1. Check the results to make sure you're talking to the right party. +1. Use the TLSConnection to exchange data. +1. Call close() on the TLSConnection when you're done. tlslite-ng also integrates with several stdlib python libraries. See the sections following this one for details. 5 Step 1 - create a socket --------------------------- + Below demonstrates a socket connection to Amazon's secure site. ``` @@ -209,23 +217,28 @@ Below demonstrates a socket connection to Amazon's secure site. 5 Step 2 - construct a TLSConnection ------------------------------------- + You can import tlslite objects individually, such as: + ``` from tlslite import TLSConnection ``` Or import the most useful objects through: + ``` from tlslite.api import * ``` Then do: + ``` connection = TLSConnection(sock) ``` 5 Step 3 - call a handshake function (client) ---------------------------------------------- + If you're a client, there's two different handshake functions you can call, depending on how you want to authenticate: @@ -243,9 +256,10 @@ authenticate itself using an X.509 certificate chain. The ClientCert function can also be used to do client authentication with an X.509 certificate chain and corresponding private key. To use X.509 chains, you'll need some way of creating these, such as OpenSSL (see -http://www.openssl.org/docs/HOWTO/ for details). +[HOWTO](http://www.openssl.org/docs/HOWTO/) for details). Below is an example of loading an X.509 chain and private key: + ``` from tlslite import X509, X509CertChain, parsePEMKey s = open("./test/clientX509Cert.pem").read() @@ -298,6 +312,7 @@ finish more quickly. Otherwise, the full handshake will be done. For example: 5 Step 3 - call a handshake function (server) ---------------------------------------------- + If you're a server, there's only one handshake function, but you can pass it several different parameters, depending on which types of authentication you're willing to perform. @@ -353,6 +368,7 @@ thread-safe. 5 Step 4 - check the results ----------------------------- + If the handshake completes without raising an exception, authentication results will be stored in the connection's session object. The following variables will be populated if applicable, or else set to None: @@ -399,29 +415,37 @@ Below are some common alerts and their probable causes, and whether they are signalled by the client or server. Client `handshake_failure`: - * SRP parameters are not recognized by client - * Server's TACK was unrelated to its certificate chain + +* SRP parameters are not recognized by client +* Server's TACK was unrelated to its certificate chain Client `insufficient_security`: - * SRP parameters are too small + +* SRP parameters are too small Client `protocol_version`: - * Client doesn't support the server's protocol version + +* Client doesn't support the server's protocol version Server `protocol_version`: - * Server doesn't support the client's protocol version + +* Server doesn't support the client's protocol version Server `bad_record_mac`: - * bad SRP username or password + +* bad SRP username or password Server `unknown_psk_identity`: - * bad SRP username (`bad_record_mac` could be used for the same thing) + +* bad SRP username (`bad_record_mac` could be used for the same thing) Server `handshake_failure`: - * no matching cipher suites + +* no matching cipher suites 5 Step 5 - exchange data ------------------------- + Now that you have a connection, you can call read() and write() as if it were a socket.SSL object. You can also call send(), sendall(), recv(), and makefile() as if it were a socket. These calls may raise TLSLocalAlert, @@ -436,6 +460,7 @@ socket.error. 5 Step 6 - close the connection -------------------------------- + When you're finished sending data, you should call close() to close the connection and socket. When the connection is closed properly, the session object can be used for session resumption. @@ -451,9 +476,9 @@ close. (NOTE: some TLS implementations will not respond properly to the `close_notify` alert that close() generates, so the connection will hang if closeSocket is set to True.) - 6 Using tlslite-ng with httplib =============================== + tlslite-ng comes with an HTTPTLSConnection class that extends httplib to work over SSL/TLS connections. Depending on how you construct it, it will do different types of authentication. @@ -476,9 +501,9 @@ different types of authentication. [...] ``` - 7 Using tlslite-ng with poplib or imaplib ========================================= + tlslite-ng comes with `POP3_TLS` and `IMAP4_TLS` classes that extend poplib and imaplib to work over SSL/TLS connections. These classes can be constructed with the same parameters as HTTPTLSConnection (see previous section), and @@ -498,9 +523,9 @@ behave similarly. [...] ``` - 8 Using tlslite-ng with smtplib =============================== + tlslite-ng comes with an `SMTP_TLS` class that extends smtplib to work over SSL/TLS connections. This class accepts the same parameters as HTTPTLSConnection (see previous section), and behaves similarly. Depending @@ -517,6 +542,7 @@ on how you call starttls(), it will do different types of authentication. 9 Using tlslite-ng with SocketServer ==================================== + You can use tlslite-ng to implement servers using Python's SocketServer framework. tlslite-ng comes with a TLSSocketServerMixIn class. You can combine this with a TCPServer such as HTTPServer. To combine them, define a new class @@ -525,16 +551,16 @@ handshake() method, doing some sort of server handshake on the connection argument. If the handshake method returns True, the RequestHandler will be triggered. See the tests/httpsserver.py example. - 10 Using tlslite-ng with asyncore ================================= + tlslite-ng can be used with subclasses of asyncore.dispatcher. See the comments in TLSAsyncDispatcherMixIn.py for details. This is still experimental, and may not work with all asyncore.dispatcher subclasses. - 11 Security Considerations =========================== + tlslite-ng is beta-quality code. It hasn't received much security analysis. Use at your own risk. @@ -556,298 +582,336 @@ encrypt-then-MAC mode for CBC ciphers. =========== 0.6.0 - WIP - - Session Hash a.k.a. Extended Master Secret extension from RFC 7627 - - make the library work on systems working in FIPS mode - - support for the padding extension from RFC 7685 (Karel Srot) - - abitlity to perform reverse lookups on many of the TLS type enumerations - - added ECDHE_RSA key exchange together with associated ciphersuites - - refactor key exchange code to remove duplication and make adding new methods - easier - - add support for all hashes for ServerKeyExchange and CertificateVerify - messages in TLS 1.2 - - mark library as compatible with Python 3.5 (it was previously, but now - it is verified with Continous Integration) - - small cleanups and more documentation - - add support for ChaCha20 and Poly1305 - - add TLS_DHE_RSA_WITH_CHACHA20_POLY1305 ciphersuite - - expose padding and MAC-ing functions and blockSize property in RecordLayer + +* Session Hash a.k.a. Extended Master Secret extension from RFC 7627 +* make the library work on systems working in FIPS mode +* support for the padding extension from RFC 7685 (Karel Srot) +* abitlity to perform reverse lookups on many of the TLS type enumerations +* added ECDHE_RSA key exchange together with associated ciphersuites +* refactor key exchange code to remove duplication and make adding new methods + easier +* add support for all hashes for ServerKeyExchange and CertificateVerify + messages in TLS 1.2 +* mark library as compatible with Python 3.5 (it was previously, but now + it is verified with Continous Integration) +* small cleanups and more documentation +* add support for ChaCha20 and Poly1305 +* add TLS_DHE_RSA_WITH_CHACHA20_POLY1305 ciphersuite +* expose padding and MAC-ing functions and blockSize property in RecordLayer 0.5.1 - 2015-11-05 - - fix SRP_SHA_RSA ciphersuites in TLSv1.2 (for real this time) - - minor enchancements in test scripts - - NOTE: KeyExchange class is not part of stable API yet (it will be moved to - different module later)! + +* fix SRP_SHA_RSA ciphersuites in TLSv1.2 (for real this time) +* minor enchancements in test scripts +* NOTE: KeyExchange class is not part of stable API yet (it will be moved to + different module later)! 0.5.0 - 10/10/2015 - - fix generators in AsyncStateMachine to work on Python3 (Theron Lewis) - - fix CVE-2015-3220 - remote DoS caused by incorrect malformed packet handling - - removed RC4 from ciphers supported by default - - add supported_groups, supported_point_formats, signature_algorithms and - renegotiation_info extensions - - remove most CBC MAC-ing and padding timing side-channel leaks (should fix - CVE-2013-0169, a.k.a. Lucky13) - - add support for NULL encryption - TLS_RSA_WITH_NULL_MD5, - TLS_RSA_WITH_NULL_SHA and TLS_RSA_WITH_NULL_SHA256 ciphersuites - - add more ADH ciphers (TLS_DH_ANON_WITH_RC4_128_MD5, - TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA, TLS_DH_ANON_WITH_AES_128_CBC_SHA256, - TLS_DH_ANON_WITH_AES_256_CBC_SHA256, TLS_DH_ANON_WITH_AES_128_GCM_SHA256, - TLS_DH_ANON_WITH_AES_256_GCM_SHA384) - - implement a TLS record layer abstraction that makes it very easy to handle - TLS handshake and alert protocol messages (MessageSocket) - - fix reqCert option in tls.py server - - implement AES-256-GCM ciphersuites and SHA384 PRF - - implement AES-GCM cipher and AES-128-GCM ciphersuites (David Benjamin - - Chromium) - - implement client side DHE_RSA key exchange and DHE with certificate based - client authentication - - implement server side DHE_RSA key exchange (David Benjamin - Chromium) - - don't use TLSv1.2 ciphers in earlier protocols (David Benjamin - Chromium) - - fix certificate-based client authentication in TLSv1.2 (David Benjamin - - Chromium) - - fix SRP_SHA_RSA ciphersuites - - properly implement record layer fragmentation (previously worked just for - Application Data) - RFC 5246 Section 6.2.1 - - Implement RFC 7366 - Encrypt-then-MAC - - generate minimal padding for CBC ciphers (David Benjamin - Chromium) - - implementation of `FALLBACK_SCSV` (David Benjamin - Chromium) - - fix issue with handling keys in session cache (Mirko Dziadzka) - - coverage measurement for unit tests - - introduced Continous Integration, targetting 2.6, 2.7, 3.2, 3.3 and 3.4 - - support PKCS#8 files with m2crypto installed for loading private keys - - fix Writer not to silently overflow integers - - fix Parser getFixBytes boundary checking - - big code refactors, mainly TLSRecordLayer and TLSConnection, lot of code put - under unit test coverage + +* fix generators in AsyncStateMachine to work on Python3 (Theron Lewis) +* fix CVE-2015-3220 - remote DoS caused by incorrect malformed packet handling +* removed RC4 from ciphers supported by default +* add supported_groups, supported_point_formats, signature_algorithms and + renegotiation_info extensions +* remove most CBC MAC-ing and padding timing side-channel leaks (should fix + CVE-2013-0169, a.k.a. Lucky13) +* add support for NULL encryption - TLS_RSA_WITH_NULL_MD5, + TLS_RSA_WITH_NULL_SHA and TLS_RSA_WITH_NULL_SHA256 ciphersuites +* add more ADH ciphers (TLS_DH_ANON_WITH_RC4_128_MD5, + TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA, TLS_DH_ANON_WITH_AES_128_CBC_SHA256, + TLS_DH_ANON_WITH_AES_256_CBC_SHA256, TLS_DH_ANON_WITH_AES_128_GCM_SHA256, + TLS_DH_ANON_WITH_AES_256_GCM_SHA384) +* implement a TLS record layer abstraction that makes it very easy to handle + TLS handshake and alert protocol messages (MessageSocket) +* fix reqCert option in tls.py server +* implement AES-256-GCM ciphersuites and SHA384 PRF +* implement AES-GCM cipher and AES-128-GCM ciphersuites (David Benjamin - + Chromium) +* implement client side DHE_RSA key exchange and DHE with certificate based + client authentication +* implement server side DHE_RSA key exchange (David Benjamin - Chromium) +* don't use TLSv1.2 ciphers in earlier protocols (David Benjamin - Chromium) +* fix certificate-based client authentication in TLSv1.2 (David Benjamin - + Chromium) +* fix SRP_SHA_RSA ciphersuites +* properly implement record layer fragmentation (previously worked just for + Application Data) - RFC 5246 Section 6.2.1 +* Implement RFC 7366 - Encrypt-then-MAC +* generate minimal padding for CBC ciphers (David Benjamin - Chromium) +* implementation of `FALLBACK_SCSV` (David Benjamin - Chromium) +* fix issue with handling keys in session cache (Mirko Dziadzka) +* coverage measurement for unit tests +* introduced Continous Integration, targetting 2.6, 2.7, 3.2, 3.3 and 3.4 +* support PKCS#8 files with m2crypto installed for loading private keys +* fix Writer not to silently overflow integers +* fix Parser getFixBytes boundary checking +* big code refactors, mainly TLSRecordLayer and TLSConnection, lot of code put + under unit test coverage 0.4.8 - 11/12/2014 - - Added more acknowledgements and security considerations + +* Added more acknowledgements and security considerations 0.4.7 - 11/12/2014 - - Added TLS 1.2 support (Yngve Pettersen and Paul Sokolovsky) - - Don't offer SSLv3 by default (e.g. POODLE) - - Fixed bug with `PyCrypto_RSA` integration - - Fixed harmless bug that added non-prime into sieves list - - Added "make test" and "make test-dev" targets (Hubert Kario) + +* Added TLS 1.2 support (Yngve Pettersen and Paul Sokolovsky) +* Don't offer SSLv3 by default (e.g. POODLE) +* Fixed bug with `PyCrypto_RSA` integration +* Fixed harmless bug that added non-prime into sieves list +* Added "make test" and "make test-dev" targets (Hubert Kario) 0.4.5 - 3/20/2013 - - **API CHANGE**: TLSClosedConnectionError instead of ValueError when writing - to a closed connection. This inherits from socket.error, so should - interact better with SocketServer (see http://bugs.python.org/issue14574) - and other things expecting a socket.error in this situation. - - Added support for RC4-MD5 ciphersuite (if enabled in settings) - - This is allegedly necessary to connect to some Internet servers. - - Added TLSConnection.unread() function - - Switched to New-style classes (inherit from 'object') - - Minor cleanups + +* **API CHANGE**: TLSClosedConnectionError instead of ValueError when writing + to a closed connection. This inherits from socket.error, so should + interact better with SocketServer (see [issue14574](http://bugs.python.org/issue14574)) + and other things expecting a socket.error in this situation. +* Added support for RC4-MD5 ciphersuite (if enabled in settings) + * This is allegedly necessary to connect to some Internet servers. +* Added TLSConnection.unread() function +* Switched to New-style classes (inherit from 'object') +* Minor cleanups 0.4.4 - 2/25/2013 - - Added Python 3 support (Martin von Loewis) - - Added NPN client support (Marcelo Fernandez) - - Switched to RC4 as preferred cipher - - faster in Python, avoids "Lucky 13" timing attacks - - Fixed bug when specifying ciphers for anon ciphersuites - - Made RSA hashAndVerify() tolerant of sigs w/o encoded NULL AlgorithmParam - - (this function is not used for TLS currently, and this tolerance may - not even be necessary) + +* Added Python 3 support (Martin von Loewis) +* Added NPN client support (Marcelo Fernandez) +* Switched to RC4 as preferred cipher + * faster in Python, avoids "Lucky 13" timing attacks +* Fixed bug when specifying ciphers for anon ciphersuites +* Made RSA hashAndVerify() tolerant of sigs w/o encoded NULL AlgorithmParam + * (this function is not used for TLS currently, and this tolerance may + not even be necessary) 0.4.3 - 9/27/2012 - - Minor bugfix (0.4.2 doesn't load tackpy) + +* Minor bugfix (0.4.2 doesn't load tackpy) 0.4.2 - 9/25/2012 - - Updated TACK (compatible with tackpy 0.9.9) + +* Updated TACK (compatible with tackpy 0.9.9) 0.4.1 - 5/22/2012 - - Fixed RSA padding bugs (w/help from John Randolph) - - Updated TACK (compatible with tackpy 0.9.7) - - Added SNI - - Added NPN server support (Sam Rushing/Google) - - Added AnonDH (Dimitris Moraitis) - - Added X509CertChain.parsePemList - - Improved XML-RPC (Kees Bos) + +* Fixed RSA padding bugs (w/help from John Randolph) +* Updated TACK (compatible with tackpy 0.9.7) +* Added SNI +* Added NPN server support (Sam Rushing/Google) +* Added AnonDH (Dimitris Moraitis) +* Added X509CertChain.parsePemList +* Improved XML-RPC (Kees Bos) 0.4.0 - 2/11/2012 - - Fixed pycrypto support - - Fixed python 2.6 problems + +* Fixed pycrypto support +* Fixed python 2.6 problems 0.3.9.x - 2/7/2012 Much code cleanup, in particular decomposing the handshake functions so they are readable. The main new feature is support for TACK, an experimental authentication method that provides a new way to pin server certificates (See -https://github.com/moxie0/Convergence/wiki/TACK ). +[moxie0/Convergance](https://github.com/moxie0/Convergence/wiki/TACK) ). Also: - - Security Fixes - - Sends SCSV ciphersuite as per RFC 5746, to signal non-renegotiated - Client Hello. Does not support renegotiation (never has). - - Change from e=3 to e=65537 for generated RSA keys, not strictly - necessary but mitigates risk of sloppy verifier. - - 1/(n-1) countermeasure for BEAST. - - - Behavior changes: - - Split cmdline into tls.py and tlstest.py, improved options. - - Formalized LICENSE. - - Defaults to closing socket after sending `close_notify`, fixes hanging. - problem that would occur sometime when waiting for other party's - close_notify. - - Update SRP to RFC 5054 compliance. - - Removed client handshake "callbacks", no longer support the SRP - re-handshake idiom within a single handshake function. - - - Bugfixes - - Added hashlib support, removes Deprecation Warning due to sha and md5. - - Handled GeneratorExit exceptions that are a new Python feature, and - interfere with the async code if not handled. - - - Removed: - - Shared keys (it was based on an ancient I-D, not TLS-PSK). - - cryptlib support, it wasn't used much, we have enough other options. - - cryptoIDs (TACK is better). - - win32prng extension module, as os.urandom is now available. - - Twisted integration (unused?, slowed down loading). - - Jython code (ancient, didn't work). - - Compat support for python versions < 2.7. - - - Additions - - Support for TACK via TACKpy. - - Support for `CertificateRequest.certificate_authorities` ("reqCAs") - - Added TLSConnection.shutdown() to better mimic socket. - - Enabled Session resumption for XMLRPCTransport. +* Security Fixes + * Sends SCSV ciphersuite as per RFC 5746, to signal non-renegotiated + Client Hello. Does not support renegotiation (never has). + * Change from e=3 to e=65537 for generated RSA keys, not strictly + necessary but mitigates risk of sloppy verifier. + * 1/(n-1) countermeasure for BEAST. + +* Behavior changes: + * Split cmdline into tls.py and tlstest.py, improved options. + * Formalized LICENSE. + * Defaults to closing socket after sending `close_notify`, fixes hanging. + problem that would occur sometime when waiting for other party's + close_notify. + * Update SRP to RFC 5054 compliance. + * Removed client handshake "callbacks", no longer support the SRP + re-handshake idiom within a single handshake function. + +* Bugfixes + * Added hashlib support, removes Deprecation Warning due to sha and md5. + * Handled GeneratorExit exceptions that are a new Python feature, and + interfere with the async code if not handled. + +* Removed: + * Shared keys (it was based on an ancient I-D, not TLS-PSK). + * cryptlib support, it wasn't used much, we have enough other options. + * cryptoIDs (TACK is better). + * win32prng extension module, as os.urandom is now available. + * Twisted integration (unused?, slowed down loading). + * Jython code (ancient, didn't work). + * Compat support for python versions < 2.7. + +* Additions + * Support for TACK via TACKpy. + * Support for `CertificateRequest.certificate_authorities` ("reqCAs") + * Added TLSConnection.shutdown() to better mimic socket. + * Enabled Session resumption for XMLRPCTransport. 0.3.8 - 2/21/2005 - - Added support for poplib, imaplib, and smtplib - - Added python 2.4 windows installer - - Fixed occassional timing problems with test suite + +* Added support for poplib, imaplib, and smtplib +* Added python 2.4 windows installer +* Fixed occassional timing problems with test suite 0.3.7 - 10/05/2004 - - Added support for Python 2.2 - - Cleaned up compatibility code, and docs, a bit + +* Added support for Python 2.2 +* Cleaned up compatibility code, and docs, a bit 0.3.6 - 9/28/2004 - - Fixed script installation on UNIX - - Give better error message on old Python versions + +* Fixed script installation on UNIX +* Give better error message on old Python versions 0.3.5 - 9/16/2004 - - TLS 1.1 support - - os.urandom() support - - Fixed win32prng on some systems + +* TLS 1.1 support +* os.urandom() support +* Fixed win32prng on some systems 0.3.4 - 9/12/2004 - - Updated for TLS/SRP draft 8 - - Bugfix: was setting `_versioncheck` on SRP 1st hello, causing problems - with GnuTLS (which was offering TLS 1.1) - - Removed `_versioncheck` checking, since it could cause interop problems - - Minor bugfix: when `cryptlib_py` and and cryptoIDlib present, cryptlib - was complaining about being initialized twice + +* Updated for TLS/SRP draft 8 +* Bugfix: was setting `_versioncheck` on SRP 1st hello, causing problems + with GnuTLS (which was offering TLS 1.1) +* Removed `_versioncheck` checking, since it could cause interop problems +* Minor bugfix: when `cryptlib_py` and and cryptoIDlib present, cryptlib + was complaining about being initialized twice 0.3.3 - 6/10/2004 - - Updated for TLS/SRP draft 7 - - Updated test cryptoID cert chains for cryptoIDlib 0.3.1 + +* Updated for TLS/SRP draft 7 +* Updated test cryptoID cert chains for cryptoIDlib 0.3.1 0.3.2 - 5/21/2004 - - fixed bug when handling multiple handshake messages per record (e.g. IIS) + +* fixed bug when handling multiple handshake messages per record (e.g. IIS) 0.3.1 - 4/21/2004 - - added xmlrpclib integration - - fixed hanging bug in Twisted integration - - fixed win32prng to work on a wider range of win32 sytems - - fixed import problem with cryptoIDlib - - fixed port allocation problem when test scripts are run on some UNIXes - - made tolerant of buggy IE sending wrong version in premaster secret + +* added xmlrpclib integration +* fixed hanging bug in Twisted integration +* fixed win32prng to work on a wider range of win32 sytems +* fixed import problem with cryptoIDlib +* fixed port allocation problem when test scripts are run on some UNIXes +* made tolerant of buggy IE sending wrong version in premaster secret 0.3.0 - 3/20/2004 - - added API docs thanks to epydoc - - added X.509 path validation via cryptlib - - much cleaning/tweaking/re-factoring/minor fixes + +* added API docs thanks to epydoc +* added X.509 path validation via cryptlib +* much cleaning/tweaking/re-factoring/minor fixes 0.2.7 - 3/12/2004 - - changed Twisted error handling to use connectionLost() - - added ignoreAbruptClose + +* changed Twisted error handling to use connectionLost() +* added ignoreAbruptClose 0.2.6 - 3/11/2004 - - added Twisted errorHandler - - added TLSAbruptCloseError - - added 'integration' subdirectory + +* added Twisted errorHandler +* added TLSAbruptCloseError +* added 'integration' subdirectory 0.2.5 - 3/10/2004 - - improved asynchronous support a bit - - added first-draft of Twisted support + +* improved asynchronous support a bit +* added first-draft of Twisted support 0.2.4 - 3/5/2004 - - cleaned up asyncore support - - added proof-of-concept for Twisted + +* cleaned up asyncore support +* added proof-of-concept for Twisted 0.2.3 - 3/4/2004 - - added pycrypto RSA support - - added asyncore support + +* added pycrypto RSA support +* added asyncore support 0.2.2 - 3/1/2004 - - added GMPY support - - added pycrypto support - - added support for PEM-encoded private keys, in pure python + +* added GMPY support +* added pycrypto support +* added support for PEM-encoded private keys, in pure python 0.2.1 - 2/23/2004 - - improved PRNG use (cryptlib, or /dev/random, or CryptoAPI) - - added RSA blinding, to avoid timing attacks - - don't install local copy of M2Crypto, too problematic + +* improved PRNG use (cryptlib, or /dev/random, or CryptoAPI) +* added RSA blinding, to avoid timing attacks +* don't install local copy of M2Crypto, too problematic 0.2.0 - 2/19/2004 - - changed VerifierDB to take per-user parameters - - renamed `tls_lite` -> tlslite + +* changed VerifierDB to take per-user parameters +* renamed `tls_lite` -> tlslite 0.1.9 - 2/16/2004 - - added post-handshake 'Checker' - - made compatible with Python 2.2 - - made more forgiving of abrupt closure, since everyone does it: - if the socket is closed while sending/recv'ing `close_notify`, - just ignore it. + +* added post-handshake 'Checker' +* made compatible with Python 2.2 +* made more forgiving of abrupt closure, since everyone does it: + if the socket is closed while sending/recv'ing `close_notify`, + just ignore it. 0.1.8 - 2/12/2004 - - TLSConnections now emulate sockets, including makefile() - - HTTPTLSConnection and TLSMixIn simplified as a result + +* TLSConnections now emulate sockets, including makefile() +* HTTPTLSConnection and TLSMixIn simplified as a result 0.1.7 - 2/11/2004 - - fixed httplib.HTTPTLSConnection with multiple requests - - fixed SocketServer to handle `close_notify` - - changed handshakeClientNoAuth() to ignore CertificateRequests - - changed handshakeClient() to ignore non-resumable session arguments + +* fixed httplib.HTTPTLSConnection with multiple requests +* fixed SocketServer to handle `close_notify` +* changed handshakeClientNoAuth() to ignore CertificateRequests +* changed handshakeClient() to ignore non-resumable session arguments 0.1.6 - 2/10/2004 - - fixed httplib support + +* fixed httplib support 0.1.5 - 2/09/2004 - - added support for httplib and SocketServer - - added support for SSLv3 - - added support for 3DES - - cleaned up read()/write() behavior - - improved HMAC speed + +* added support for httplib and SocketServer +* added support for SSLv3 +* added support for 3DES +* cleaned up read()/write() behavior +* improved HMAC speed 0.1.4 - 2/06/2004 - - fixed dumb bug in tls.py + +* fixed dumb bug in tls.py 0.1.3 - 2/05/2004 - - change read() to only return requested number of bytes - - added support for shared-key and in-memory databases - - added support for PEM-encoded X.509 certificates - - added support for SSLv2 ClientHello - - fixed shutdown/re-handshaking behavior - - cleaned up handling of `missing_srp_username` - - renamed readString()/writeString() -> read()/write() - - added documentation + +* change read() to only return requested number of bytes +* added support for shared-key and in-memory databases +* added support for PEM-encoded X.509 certificates +* added support for SSLv2 ClientHello +* fixed shutdown/re-handshaking behavior +* cleaned up handling of `missing_srp_username` +* renamed readString()/writeString() -> read()/write() +* added documentation 0.1.2 - 2/04/2004 - - added clienttest/servertest functions - - improved OpenSSL cipher wrappers speed - - fixed server when it has a key, but client selects plain SRP - - fixed server to postpone errors until it has read client's messages - - fixed ServerHello to only include extension data if necessary + +* added clienttest/servertest functions +* improved OpenSSL cipher wrappers speed +* fixed server when it has a key, but client selects plain SRP +* fixed server to postpone errors until it has read client's messages +* fixed ServerHello to only include extension data if necessary 0.1.1 - 2/02/2004 - - fixed `close_notify` behavior - - fixed handling of empty application data packets - - fixed socket reads to not consume extra bytes - - added testing functions to tls.py + +* fixed `close_notify` behavior +* fixed handling of empty application data packets +* fixed socket reads to not consume extra bytes +* added testing functions to tls.py 0.1.0 - 2/01/2004 - - first release + +* first release From 6279f3bf35f78fade5ca1305449c25d9f4a8d7c0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 9 Jun 2016 16:56:20 +0200 Subject: [PATCH 290/574] release 0.6.0-alpha5 --- README.md | 2 +- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 23a9d345..58f31a5c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.6.0-alpha4 2016-01-14 +tlslite-ng version 0.6.0-alpha5 2016-06-09 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` diff --git a/setup.py b/setup.py index 20806544..17d7d9a2 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.6.0-alpha4", + version="0.6.0-alpha5", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index a09937be..a10d4529 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.6.0-alpha4 +@version: 0.6.0-alpha5 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index c5f097df..1aed6ad4 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.6.0-alpha4" +__version__ = "0.6.0-alpha5" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From b05ed8dd6c4807da539c78c82936f907df10c60e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jun 2016 14:25:30 +0200 Subject: [PATCH 291/574] don't pollute module namespace while the del does "fix" it, static analysers don't like it --- tlslite/utils/cryptomath.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index 2f8cb66a..39e42af3 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -59,9 +59,7 @@ # Check that os.urandom works import zlib -length = len(zlib.compress(os.urandom(1000))) -assert(length > 900) -del length +assert len(zlib.compress(os.urandom(1000))) > 900 def getRandomBytes(howMany): b = bytearray(os.urandom(howMany)) From 3991c242251e933ac8101bce9cb28b91be5fdd89 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Jun 2016 14:36:14 +0200 Subject: [PATCH 292/574] add support for keying material exporter --- scripts/tls.py | 42 ++++++++++-- tlslite/tlsconnection.py | 41 +++++++++++- unit_tests/test_tlslite_tlsconnection.py | 84 ++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 7 deletions(-) diff --git a/scripts/tls.py b/scripts/tls.py index 76f0d576..d66c7077 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -13,6 +13,7 @@ import socket import time import getopt +import binascii try: import httplib from SocketServer import * @@ -32,6 +33,7 @@ from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ GroupName from tlslite import __version__ +from tlslite.utils.compat import b2a_hex try: from tack.structures.Tack import Tack @@ -70,12 +72,15 @@ def printUsage(s=None): print("""Commands: server - [-k KEY] [-c CERT] [-t TACK] [-v VERIFIERDB] [-d DIR] + [-k KEY] [-c CERT] [-t TACK] [-v VERIFIERDB] [-d DIR] [-l LABEL] [-L LENGTH] [--reqcert] HOST:PORT client - [-k KEY] [-c CERT] [-u USER] [-p PASS] + [-k KEY] [-c CERT] [-u USER] [-p PASS] [-l LABEL] [-L LENGTH] HOST:PORT + + LABEL - TLS exporter label + LENGTH - amount of info to export using TLS exporter """) sys.exit(-1) @@ -102,6 +107,8 @@ def handleArgs(argv, argString, flagsList=[]): verifierDB = None reqCert = False directory = None + expLabel = None + expLength = 20 for opt, arg in opts: if opt == "-k": @@ -131,6 +138,10 @@ def handleArgs(argv, argString, flagsList=[]): directory = arg elif opt == "--reqcert": reqCert = True + elif opt == "-l": + expLabel = arg + elif opt == "-L": + expLength = int(arg) else: assert(False) @@ -163,6 +174,10 @@ def handleArgs(argv, argString, flagsList=[]): retList.append(directory) if "reqcert" in flagsList: retList.append(reqCert) + if "l" in argString: + retList.append(expLabel) + if "L" in argString: + retList.append(expLength) return retList @@ -206,9 +221,20 @@ def printGoodConnection(connection, seconds): print(" Extended Master Secret: {0}".format( connection.extendedMasterSecret)) +def printExporter(connection, expLabel, expLength): + if expLabel is None: + return + expLabel = bytearray(expLabel, "utf-8") + exp = connection.keyingMaterialExporter(expLabel, expLength) + exp = b2a_hex(exp).upper() + print(" Exporter label: {0}".format(expLabel)) + print(" Exporter length: {0}".format(expLength)) + print(" Keying material: {0}".format(exp)) + def clientCmd(argv): - (address, privateKey, certChain, username, password) = \ - handleArgs(argv, "kcup") + (address, privateKey, certChain, username, password, expLabel, + expLength) = \ + handleArgs(argv, "kcuplL") if (certChain and not privateKey) or (not certChain and privateKey): raise SyntaxError("Must specify CERT and KEY together") @@ -216,6 +242,8 @@ def clientCmd(argv): raise SyntaxError("Must specify USER with PASS") if certChain and username: raise SyntaxError("Can use SRP or client cert for auth, not both") + if expLabel is not None and not expLabel: + raise ValueError("Label must be non-empty") #Connect to server sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -259,12 +287,13 @@ def clientCmd(argv): raise sys.exit(-1) printGoodConnection(connection, stop-start) + printExporter(connection, expLabel, expLength) connection.close() def serverCmd(argv): - (address, privateKey, certChain, tacks, - verifierDB, directory, reqCert) = handleArgs(argv, "kctbvd", ["reqcert"]) + (address, privateKey, certChain, tacks, verifierDB, directory, reqCert, + expLabel, expLength) = handleArgs(argv, "kctbvdlL", ["reqcert"]) if (certChain and not privateKey) or (not certChain and privateKey): @@ -344,6 +373,7 @@ def handshake(self, connection): connection.ignoreAbruptClose = True printGoodConnection(connection, stop-start) + printExporter(connection, expLabel, expLength) return True httpd = MyHTTPServer(address, SimpleHTTPRequestHandler) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index c926e516..3b890903 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -68,6 +68,38 @@ def __init__(self, sock): self.ecdhCurve = None self.dhGroupSize = None self.extendedMasterSecret = False + self._clientRandom = bytearray(0) + self._serverRandom = bytearray(0) + + def keyingMaterialExporter(self, label, length=20): + """Return keying material as described in RFC 5705 + + @type label: bytearray + @param label: label to be provided for the exporter + + @type length: int + @param length: number of bytes of the keying material to export + """ + if label in (b'server finished', b'client finished', + b'master secret', b'key expansion'): + raise ValueError("Forbidden label value") + if self.version < (3, 1): + raise ValueError("Supported only in TLSv1.0 and later") + elif self.version < (3, 3): + return PRF(self.session.masterSecret, label, + self._clientRandom + self._serverRandom, + length) + elif self.version == (3, 3): + if self.session.cipherSuite in CipherSuite.sha384PrfSuites: + return PRF_1_2_SHA384(self.session.masterSecret, label, + self._clientRandom + self._serverRandom, + length) + else: + return PRF_1_2(self.session.masterSecret, label, + self._clientRandom + self._serverRandom, + length) + else: + raise AssertionError("Unknown protocol version") #********************************************************* # Client Handshake Functions @@ -451,6 +483,8 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, else: break if result == "resumed_and_finished": self._handshakeDone(resumed=True) + self._serverRandom = serverHello.random + self._clientRandom = clientHello.random return #If the server selected an SRP ciphersuite, the client finishes @@ -522,6 +556,8 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, encryptThenMAC=self._recordLayer.encryptThenMAC, extendedMasterSecret=self.extendedMasterSecret) self._handshakeDone(resumed=False) + self._serverRandom = serverHello.random + self._clientRandom = clientHello.random def _clientSendClientHello(self, settings, session, srpUsername, @@ -1279,6 +1315,8 @@ def _handshakeServerAsyncHelper(self, verifierDB, sessionCache[sessionID] = self.session self._handshakeDone(resumed=False) + self._serverRandom = serverHello.random + self._clientRandom = clientHello.random def _serverGetClientHello(self, settings, certChain, verifierDB, @@ -1439,7 +1477,8 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, #Set the session self.session = session - + self._clientRandom = clientHello.random + self._serverRandom = serverHello.random yield None # Handshake done! #Calculate the first cipher suite intersection. diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index 25ad4bac..0d376ba4 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -18,6 +18,7 @@ from tlslite.x509certchain import X509CertChain from tlslite.utils.keyfactory import parsePEMKey from tlslite.handshakesettings import HandshakeSettings +from tlslite.session import Session from unit_tests.mocksock import MockSocket @@ -236,5 +237,88 @@ def test_padding_extension_with_hello_over_256(self): # 5 bytes is record layer header, 4 bytes is handshake protocol header self.assertEqual(len(sock.sent[0]) - 5 - 4, 512) + def test_keyingMaterialExporter_tls1_2_sha384(self): + sock = MockSocket(bytearray(0)) + conn = TLSConnection(sock) + conn._clientRandom = bytearray(b'012345678901234567890123456789ab') + conn._serverRandom = bytearray(b'987654321098765432109876543210ab') + conn._recordLayer.version = (3, 3) + conn.session = Session() + conn.session.cipherSuite = \ + CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + + mat = conn.keyingMaterialExporter(bytearray(b'test'), 20) + self.assertEqual(mat, + bytearray(b'1\xb8X\xef\x9b\xa5\n9p\x13\xfaxXI\\$\xdf\xb5\xc7i')) + + def test_keyingMaterialExporter_ssl3(self): + sock = MockSocket(bytearray(0)) + conn = TLSConnection(sock) + conn._clientRandom = bytearray(b'012345678901234567890123456789ab') + conn._serverRandom = bytearray(b'987654321098765432109876543210ab') + conn._recordLayer.version = (3, 0) + conn.session = Session() + conn.session.cipherSuite = \ + CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + + with self.assertRaises(ValueError): + conn.keyingMaterialExporter(bytearray(b'test'), 20) + + def test_keyingMaterialExporter_tls1_3(self): + sock = MockSocket(bytearray(0)) + conn = TLSConnection(sock) + conn._clientRandom = bytearray(b'012345678901234567890123456789ab') + conn._serverRandom = bytearray(b'987654321098765432109876543210ab') + conn._recordLayer.version = (3, 4) + conn.session = Session() + conn.session.cipherSuite = \ + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + + with self.assertRaises(AssertionError): + conn.keyingMaterialExporter(bytearray(b'test'), 20) + + def test_keyingMaterialExporter_invalid_label(self): + sock = MockSocket(bytearray(0)) + conn = TLSConnection(sock) + conn._clientRandom = bytearray(b'012345678901234567890123456789ab') + conn._serverRandom = bytearray(b'987654321098765432109876543210ab') + conn._recordLayer.version = (3, 1) + conn.session = Session() + conn.session.cipherSuite = \ + CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + + with self.assertRaises(ValueError): + conn.keyingMaterialExporter(bytearray(b'server finished'), 20) + + def test_keyingMaterialExporter_tls1_2_sha256(self): + sock = MockSocket(bytearray(0)) + conn = TLSConnection(sock) + conn._clientRandom = bytearray(b'012345678901234567890123456789ab') + conn._serverRandom = bytearray(b'987654321098765432109876543210ab') + conn._recordLayer.version = (3, 3) + conn.session = Session() + conn.session.cipherSuite = \ + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + + mat = conn.keyingMaterialExporter(bytearray(b'test'), 20) + self.assertEqual(mat, + bytearray(b'\xe6EQ\x93\xcb!\xe7\x87\x1e\xdd\x85' + + b'\xb2\x08|\xc9\xbfDh\r\x90')) + + def test_keyingMaterialExporter_tls1_1(self): + sock = MockSocket(bytearray(0)) + conn = TLSConnection(sock) + conn._clientRandom = bytearray(b'012345678901234567890123456789ab') + conn._serverRandom = bytearray(b'987654321098765432109876543210ab') + conn._recordLayer.version = (3, 2) + conn.session = Session() + conn.session.cipherSuite = \ + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + + mat = conn.keyingMaterialExporter(bytearray(b'test'), 20) + self.assertEqual(mat, + bytearray(b'\x1f\xf8\x18\x01:\x9f\x15a\xd5x\xaa;Y>' + + b'\xafG\x92AH\xa4')) + if __name__ == '__main__': unittest.main() From 2825d5e4c975e32abc087091c6448291ae001e92 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 29 Jun 2016 17:19:44 +0200 Subject: [PATCH 293/574] add support for SHA384 HMAC ciphers --- tlslite/handshakesettings.py | 2 +- tlslite/recordlayer.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 0d5faf9a..1a9605a0 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -17,7 +17,7 @@ "aes256", "aes128", "3des"] ALL_CIPHER_NAMES = CIPHER_NAMES + ["rc4", "null"] -MAC_NAMES = ["sha", "sha256", "aead"] # Don't allow "md5" by default. +MAC_NAMES = ["sha", "sha256", "sha384", "aead"] # Don't allow "md5" by default. ALL_MAC_NAMES = MAC_NAMES + ["md5"] KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "ecdhe_rsa", "srp_sha", "srp_sha_rsa", "ecdh_anon", "dh_anon"] diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 47265df0..5a0e349d 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -790,6 +790,9 @@ def _getMacSettings(cipherSuite): elif cipherSuite in CipherSuite.sha256Suites: macLength = 32 digestmod = hashlib.sha256 + elif cipherSuite in CipherSuite.sha384Suites: + macLength = 48 + digestmod = hashlib.sha384 elif cipherSuite in CipherSuite.md5Suites: macLength = 16 digestmod = hashlib.md5 From ce6e337796383437f8b690273f6b573fb5bce180 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 18 Jul 2016 17:11:31 +0200 Subject: [PATCH 294/574] RHEL 7 dependencies --- CONTRIBUTING.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b61d702c..8385d26e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,6 +46,18 @@ dnf install python-ecdsa python3-ecdsa pylint python3-pylint python-diff-cover \ python3-hypothesis python3-libs python-unittest2 python-mock ``` +On RHEL 7 you will need to enable [EPEL](https://fedoraproject.org/wiki/EPEL), +and install [pip](https://pip.pypa.io/en/stable/installing/) for Python3, +after which you can install the dependencies using: + +``` +yum install python-ecdsa python34-ecdsa pylint \ + python-coverage python34-coverage python2-hypothesis \ + python34-libs python-unittest2 python-mock python-pip +pip2 install diff-cover +pip3 install hypothesis diff-cover pylint +``` + Optional module dependencies: * tackpy From 94fb231afc01d1e9cb2eba1fedbaaa6713ba9597 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 21 Jul 2016 14:58:31 +0200 Subject: [PATCH 295/574] information oh what to do after feedback it's not exactly obvious what needs to be done after CI reports errors or review indicates needed changes, so spell those things out --- CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8385d26e..04d906a7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -118,6 +118,13 @@ dnf install m2crypto python-crypto python3-crypto python-gmpy2 python3-gmpy2 * if you are not sure if the pull will pass the checks it is OK to submit a test pull request, but please mark it as such ('[WIP]' in title is enough) +* Once you recieve feedback from reviewers or from the automated systems, + modify your local patches (that usually means that you need to prepare + "fixup" patches or interactively + [rebase](https://help.github.com/articles/about-git-rebase/)) and push + updated branch to github (that usually will require to perform a + [force push](http://movingfast.io/articles/git-force-pushing/)) +* Wait again for review or automated checks. ## Additional Resources From 815b8527dee3b013323ff679a8d90f0910d94518 Mon Sep 17 00:00:00 2001 From: Tomas Foukal Date: Thu, 21 Jul 2016 15:53:50 +0200 Subject: [PATCH 296/574] verification of RSA signatures + test cases for it --- tlslite/keyexchange.py | 6 ++++++ unit_tests/test_tlslite_keyexchange.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 785f5c2c..f51de9a7 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -84,6 +84,12 @@ def signServerKeyExchange(self, serverKeyExchange, sigHash=None): serverKeyExchange.signature = self.privateKey.sign(hashBytes) + if not serverKeyExchange.signature: + raise TLSInternalError("Empty signature") + + if not self.privateKey.verify(serverKeyExchange.signature, hashBytes): + raise TLSInternalError("Server Key Exchange signature invalid") + @staticmethod def verifyServerKeyExchange(serverKeyExchange, publicKey, clientRandom, serverRandom, validSigAlgs): diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index ede69103..af45a460 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -8,6 +8,12 @@ import unittest2 as unittest except ImportError: import unittest +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call from tlslite.handshakesettings import HandshakeSettings from tlslite.messages import ServerHello, ClientHello, ServerKeyExchange,\ @@ -573,6 +579,16 @@ def test_DHE_RSA_key_exchange_with_small_prime(self): with self.assertRaises(TLSInsufficientSecurity): client_keyExchange.processServerKeyExchange(None, srv_key_ex) + def test_DHE_RSA_key_exchange_empty_signature(self): + self.keyExchange.privateKey.sign = mock.Mock(return_value=bytearray(0)) + with self.assertRaises(TLSInternalError): + self.keyExchange.makeServerKeyExchange('sha1') + + def test_DHE_RSA_key_exchange_wrong_signature(self): + self.keyExchange.privateKey.sign = mock.Mock(return_value=bytearray(20)) + with self.assertRaises(TLSInternalError): + self.keyExchange.makeServerKeyExchange('sha1') + class TestSRPKeyExchange(unittest.TestCase): def setUp(self): self.srv_private_key = parsePEMKey(srv_raw_key, private=True) From 08444652315c34b17a45632e38eccc64ddb30a0c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 21 Jul 2016 18:09:59 +0200 Subject: [PATCH 297/574] add epydoc, and how to make it work --- CONTRIBUTING.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04d906a7..7bdf7cfd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,8 @@ The list goes as follows: * git * GNU make * pip +* epydoc (you need to [patch](https://sourceforge.net/p/epydoc/bugs/342/) + the `epydoc/docparser.py`!) The python module dependencies are as follows: @@ -43,9 +45,12 @@ On Fedora they can be installed using: ``` dnf install python-ecdsa python3-ecdsa pylint python3-pylint python-diff-cover \ python3-diff-cover python-coverage python3-coverage python2-hypothesis \ - python3-hypothesis python3-libs python-unittest2 python-mock + python3-hypothesis python3-libs python-unittest2 python-mock epydoc ``` +Then edit `epydoc/docparser.py` using +[patch](https://sourceforge.net/p/epydoc/bugs/342/) + On RHEL 7 you will need to enable [EPEL](https://fedoraproject.org/wiki/EPEL), and install [pip](https://pip.pypa.io/en/stable/installing/) for Python3, after which you can install the dependencies using: @@ -54,10 +59,13 @@ after which you can install the dependencies using: yum install python-ecdsa python34-ecdsa pylint \ python-coverage python34-coverage python2-hypothesis \ python34-libs python-unittest2 python-mock python-pip -pip2 install diff-cover +pip2 install diff-cover epydoc pip3 install hypothesis diff-cover pylint ``` +Then edit `epydoc/docparser.py` using +[patch](https://sourceforge.net/p/epydoc/bugs/342/) + Optional module dependencies: * tackpy From 6bd835ede5546edffb5b79b72f4c81c85d0accb3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 25 Jul 2016 22:31:46 +0200 Subject: [PATCH 298/574] NPN being overwritten by the default parameter the NPN extension is being overwritten by the default for the `supports_npn` option for `ClientHello.create()` --- unit_tests/test_tlslite_messages.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 41246b29..d2498322 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -17,7 +17,7 @@ HashAlgorithm, SignatureAlgorithm, ECCurveType, GroupName, \ SSL2HandshakeType from tlslite.extensions import SNIExtension, ClientCertTypeExtension, \ - SRPExtension, TLSExtension + SRPExtension, TLSExtension, NPNExtension from tlslite.errors import TLSInternalError class TestMessage(unittest.TestCase): @@ -80,6 +80,15 @@ def test_create_with_random(self): self.assertEqual([], client_hello.cipher_suites) self.assertEqual([0], client_hello.compression_methods) + def test_create_with_NPN_in_extensions(self): + client_hello = ClientHello() + client_hello.create((3, 0), bytearray(32), bytearray(0), [1], + extensions=[NPNExtension()]) + + self.assertEqual((3, 0), client_hello.client_version) + # XXX + self.assertEqual([], client_hello.extensions) + def test_parse(self): p = Parser(bytearray( # we don't include the type of message as it is handled by the From 5524835fee5b631a79094f75d9016c414fa264f0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 25 Jul 2016 22:34:18 +0200 Subject: [PATCH 299/574] don't filter out NPN extension from extensions by setting ClientHello.supports_npn property to False, the extensions array is cleared from any NPNextensions already there, which might have been the ones set explicitly by user --- tlslite/messages.py | 5 +++-- unit_tests/test_tlslite_messages.py | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 1d9ae8ad..8524d313 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -509,7 +509,7 @@ def server_name(self, hostname): def create(self, version, random, session_id, cipher_suites, certificate_types=None, srpUsername=None, - tack=False, supports_npn=False, serverName=None, + tack=False, supports_npn=None, serverName=None, extensions=None): """ Create a ClientHello message for sending. @@ -564,7 +564,8 @@ def create(self, version, random, session_id, cipher_suites, if not srpUsername is None: self.srp_username = bytearray(srpUsername, "utf-8") self.tack = tack - self.supports_npn = supports_npn + if supports_npn is not None: + self.supports_npn = supports_npn if not serverName is None: self.server_name = bytearray(serverName, "utf-8") return self diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index d2498322..f7f8d027 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -86,8 +86,7 @@ def test_create_with_NPN_in_extensions(self): extensions=[NPNExtension()]) self.assertEqual((3, 0), client_hello.client_version) - # XXX - self.assertEqual([], client_hello.extensions) + self.assertEqual([NPNExtension()], client_hello.extensions) def test_parse(self): p = Parser(bytearray( From 4554de7d4be564822b0e38ea4b68ae9cb443fbeb Mon Sep 17 00:00:00 2001 From: almond29 Date: Tue, 26 Jul 2016 18:58:03 +0200 Subject: [PATCH 300/574] test cases for HMAC functions --- unit_tests/test_tlslite_utils_cryptomath.py | 27 ++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index 936d497e..4ff4310a 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -13,7 +13,8 @@ import math from tlslite.utils.cryptomath import isPrime, numBits, numBytes, \ - numberToByteArray, MD5, SHA1, secureHash + numberToByteArray, MD5, SHA1, secureHash, HMAC_MD5, HMAC_SHA1, \ + HMAC_SHA256, HMAC_SHA384 class TestIsPrime(unittest.TestCase): def test_with_small_primes(self): @@ -117,6 +118,30 @@ def test_numBits(self, number): def test_numBytes(self, number): self.assertEqual(numBytes(number), self.num_bytes(number)) +class TestHMACMethods(unittest.TestCase): + def test_HMAC_MD5(self): + self.assertEqual(HMAC_MD5(b'abc', b'def'), + bytearray(b'\xde\xbd\xa7{|\xc3\xe7\xa1\x0e\xe7' + b'\x01\x04\xe6qzk')) + + def test_HMAC_SHA1(self): + self.assertEqual(HMAC_SHA1(b'abc', b'def'), + bytearray(b'\x12UN\xab\xba\xf7\xe8\xe1.G7\x02' + b'\x0f\x98|\xa7\x90\x10\x16\xe5')) + + def test_HMAC_SHA256(self): + self.assertEqual(HMAC_SHA256(b'abc', b'def'), + bytearray(b' \xeb\xc0\xf0\x93DG\x014\xf3P@\xf6>' + b'\xa9\x8b\x1d\x8eAB\x12\x94\x9e\xe5\xc5\x00B' + b'\x9d\x15\xea\xb0\x81')) + + def test_HMAC_SHA384(self): + self.assertEqual(HMAC_SHA384(b'abc', b'def'), + bytearray(b'\xec\x14\xd6\x94\x86\tHp\x84\x07\xect\x0e\t~' + b'\x85?\xe8\xfd\xba\xd4\x86s\x05\xaa\xe8\xfcB\xd0' + b'\xe8\xaa\xa6V\xe07\x9e\xc5\xc9n\x15\x97\xe0\xbc' + b'\xefZ\xa6\xdb\x05')) + class TestHashMethods(unittest.TestCase): def test_MD5(self): self.assertEqual(MD5(b"message digest"), From 5c506007d80cfde72a007de3a0e07fe45fea2420 Mon Sep 17 00:00:00 2001 From: almond29 Date: Tue, 26 Jul 2016 19:50:15 +0200 Subject: [PATCH 301/574] shortening of HMAC algorithms and creating secureHMAC function --- tlslite/utils/cryptomath.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index 39e42af3..4222662b 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -89,25 +89,23 @@ def secureHash(data, algorithm): hashInstance.update(compat26Str(data)) return bytearray(hashInstance.digest()) -def HMAC_MD5(k, b): +def secureHMAC(k, b, algorithm): + """Return a HMAC using `b` and `k` using `algorithm`""" k = compatHMAC(k) b = compatHMAC(b) - return bytearray(hmac.new(k, b, hashlib.md5).digest()) + return bytearray(hmac.new(k, b, getattr(hashlib, algorithm)).digest()) + +def HMAC_MD5(k, b): + return secureHMAC(k, b, 'md5') def HMAC_SHA1(k, b): - k = compatHMAC(k) - b = compatHMAC(b) - return bytearray(hmac.new(k, b, hashlib.sha1).digest()) + return secureHMAC(k, b, 'sha1') def HMAC_SHA256(k, b): - k = compatHMAC(k) - b = compatHMAC(b) - return bytearray(hmac.new(k, b, hashlib.sha256).digest()) + return secureHMAC(k, b, 'sha256') def HMAC_SHA384(k, b): - k = compatHMAC(k) - b = compatHMAC(b) - return bytearray(hmac.new(k, b, hashlib.sha384).digest()) + return secureHMAC(k, b, 'sha384') # ************************************************************************** # Converter Functions From 22e45984daef5bb1b04c4b6b9a1be1920cf4203e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 18 Jan 2016 19:41:13 +0100 Subject: [PATCH 302/574] ECDHE_RSA_WITH_CHACHA20_POLY1305 from draft 00 add the ECDHE variant of CHACHA20 cipher from draft-ietf-tls-chacha20-poly1305-00 fixes #56 --- tlslite/constants.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index e1d78954..b1d33b39 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -495,6 +495,8 @@ class CipherSuite: # draft-ietf-tls-chacha20-poly1305-00 # ChaCha20/Poly1305 based Cipher Suites for TLS1.2 + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 = 0xcca1 + ietfNames[0xcca1] = 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305' TLS_DHE_RSA_WITH_CHACHA20_POLY1305 = 0xcca3 ietfNames[0xcca3] = 'TLS_DHE_RSA_WITH_CHACHA20_POLY1305' @@ -567,6 +569,7 @@ class CipherSuite: # CHACHA20 cipher (implicit POLY1305 authenticator) chacha20Suites = [] + chacha20Suites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305) chacha20Suites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305) # RC4 128 stream cipher @@ -793,6 +796,7 @@ def getDheCertSuites(cls, settings, version=None): # ECDHE key exchange, RSA authentication ecdheCertSuites = [] + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) From ed0b6faf8dd85515a2026cfb516b034a3e5a88c5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 27 Jul 2016 17:26:20 +0200 Subject: [PATCH 303/574] change the names to indicate that it is a draft implementation --- tests/tlstest.py | 8 ++++---- tlslite/constants.py | 28 ++++++++++++++-------------- tlslite/handshakesettings.py | 2 +- tlslite/recordlayer.py | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index b61bbdda..4cf98fc1 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -414,7 +414,7 @@ def connect(): print("Test {0} - throughput test".format(test_no)) for implementation in implementations: for cipher in ["aes128gcm", "aes256gcm", "aes128", "aes256", "3des", - "rc4", "chacha20-poly1305"]: + "rc4", "chacha20-poly1305_draft00"]: # skip tests with implementations that don't support them if cipher == "3des" and implementation not in ("openssl", "pycrypto"): @@ -423,7 +423,7 @@ def connect(): implementation not in ("pycrypto", "python"): continue - if cipher in ("chacha20-poly1305", ) and \ + if cipher in ("chacha20-poly1305_draft00", ) and \ implementation not in ("python", ): continue @@ -1039,7 +1039,7 @@ def server_bind(self): print("Test {0} - throughput test".format(test_no)) for implementation in implementations: for cipher in ["aes128gcm", "aes256gcm", "aes128", "aes256", "3des", - "rc4", "chacha20-poly1305"]: + "rc4", "chacha20-poly1305_draft00"]: # skip tests with implementations that don't support them if cipher == "3des" and implementation not in ("openssl", "pycrypto"): @@ -1048,7 +1048,7 @@ def server_bind(self): implementation not in ("pycrypto", "python"): continue - if cipher in ("chacha20-poly1305", ) and \ + if cipher in ("chacha20-poly1305_draft00", ) and \ implementation not in ("python", ): continue diff --git a/tlslite/constants.py b/tlslite/constants.py index b1d33b39..55117370 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -495,10 +495,10 @@ class CipherSuite: # draft-ietf-tls-chacha20-poly1305-00 # ChaCha20/Poly1305 based Cipher Suites for TLS1.2 - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 = 0xcca1 - ietfNames[0xcca1] = 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305' - TLS_DHE_RSA_WITH_CHACHA20_POLY1305 = 0xcca3 - ietfNames[0xcca3] = 'TLS_DHE_RSA_WITH_CHACHA20_POLY1305' + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00 = 0xcca1 + ietfNames[0xcca1] = 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00' + TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00 = 0xcca3 + ietfNames[0xcca3] = 'TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00' # RFC 5289 - ECC Ciphers with SHA-256/SHA284 HMAC and AES-GCM @@ -568,9 +568,9 @@ class CipherSuite: aes256GcmSuites.append(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) # CHACHA20 cipher (implicit POLY1305 authenticator) - chacha20Suites = [] - chacha20Suites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305) - chacha20Suites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305) + chacha20draft00Suites = [] + chacha20draft00Suites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00) + chacha20draft00Suites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00) # RC4 128 stream cipher rc4Suites = [] @@ -639,7 +639,7 @@ class CipherSuite: aeadSuites = [] aeadSuites.extend(aes128GcmSuites) aeadSuites.extend(aes256GcmSuites) - aeadSuites.extend(chacha20Suites) + aeadSuites.extend(chacha20draft00Suites) # TLS1.2 with SHA384 PRF sha384PrfSuites = [] @@ -693,8 +693,8 @@ def _filterSuites(suites, settings, version=None): macSuites += CipherSuite.aeadSuites cipherSuites = [] - if "chacha20-poly1305" in cipherNames and version >= (3, 3): - cipherSuites += CipherSuite.chacha20Suites + if "chacha20-poly1305_draft00" in cipherNames and version >= (3, 3): + cipherSuites += CipherSuite.chacha20draft00Suites if "aes128gcm" in cipherNames and version >= (3, 3): cipherSuites += CipherSuite.aes128GcmSuites if "aes256gcm" in cipherNames and version >= (3, 3): @@ -780,7 +780,7 @@ def getCertSuites(cls, settings, version=None): # FFDHE key exchange, RSA authentication dheCertSuites = [] - dheCertSuites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305) + dheCertSuites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) @@ -796,7 +796,7 @@ def getDheCertSuites(cls, settings, version=None): # ECDHE key exchange, RSA authentication ecdheCertSuites = [] - ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305) + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) @@ -863,8 +863,8 @@ def canonicalCipherName(ciphersuite): return "3des" elif ciphersuite in CipherSuite.nullSuites: return "null" - elif ciphersuite in CipherSuite.chacha20Suites: - return "chacha20-poly1305" + elif ciphersuite in CipherSuite.chacha20draft00Suites: + return "chacha20-poly1305_draft00" else: return None diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 1a9605a0..ac80d772 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -12,7 +12,7 @@ from .utils import cipherfactory from .utils.compat import ecdsaAllCurves -CIPHER_NAMES = ["chacha20-poly1305", +CIPHER_NAMES = ["chacha20-poly1305_draft00", "aes256gcm", "aes128gcm", "aes256", "aes128", "3des"] diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 5a0e349d..0a69a86a 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -749,7 +749,7 @@ def _getCipherSettings(cipherSuite): keyLength = 16 ivLength = 4 createCipherFunc = createAESGCM - elif cipherSuite in CipherSuite.chacha20Suites: + elif cipherSuite in CipherSuite.chacha20draft00Suites: keyLength = 32 ivLength = 4 createCipherFunc = createCHACHA20 From aed9536c718338ebc5dccc4b69c3acb355b0b6ed Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 27 Jul 2016 18:11:36 +0200 Subject: [PATCH 304/574] test coverage for recordlayer chacha20-draft00 --- unit_tests/test_tlslite_recordlayer.py | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index b84c1082..329a039f 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -906,6 +906,69 @@ def test_sendRecord_with_AES256GCM(self): b'\x00\x00\x00\x00\x00\x00\x00\x00\xb5c\x15\x8c' + b'\xe3\x92H6l\x90\x19\xef\x96\xbfT}\xe8\xbaE\xa3')) + def test_sendRecord_with_ChaCha20_draft00(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 3) + + ciph = CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00 + recordLayer.calcPendingStates(ciph, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsNotNone(app_data) + + for result in recordLayer.sendRecord(app_data): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x03' + # TLSv1.2 + b'\x00\x14' # length + )) + self.assertEqual(sock.sent[0][5:], bytearray( + b'8k9<\xf3\xdc\x86d,\xf4\xb7\xe8L7\xecA\x7fi\xb1\xdc')) + + def test_recvRecord_with_ChaCha20_draft00(self): + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x03' + # TLSv1.2 + b'\x00\x14' + # length + b'8k9<\xf3\xdc\x86d,\xf4\xb7\xe8L7\xecA\x7fi\xb1\xdc')) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 3) + recordLayer.client = False + + ciph = CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00 + recordLayer.calcPendingStates(ciph, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + for result in recordLayer.recvRecord(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: + break + + head, parser = result + + self.assertEqual((3, 3), head.version) + self.assertEqual(head.type, ContentType.application_data) + self.assertEqual(bytearray(b'test'), parser.bytes) + # tlslite has no pure python implementation of 3DES @unittest.skipUnless(cryptomath.m2cryptoLoaded or cryptomath.pycryptoLoaded, "requires native 3DES implementation") From dee07ce62a84f29cbf27fd1b06f1be67614b98e9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 27 Jul 2016 18:19:48 +0200 Subject: [PATCH 305/574] update ChaCha20 TLS to RFC 7905 there are slight differences between draft-00 and draft-04 (released version), so implement them --- tests/tlstest.py | 14 +++--- tlslite/constants.py | 19 +++++++- tlslite/handshakesettings.py | 5 +- tlslite/recordlayer.py | 21 ++++++++- unit_tests/test_tlslite_recordlayer.py | 64 ++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 11 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index 4cf98fc1..d7ded655 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -414,7 +414,8 @@ def connect(): print("Test {0} - throughput test".format(test_no)) for implementation in implementations: for cipher in ["aes128gcm", "aes256gcm", "aes128", "aes256", "3des", - "rc4", "chacha20-poly1305_draft00"]: + "rc4", "chacha20-poly1305_draft00", + "chacha20-poly1305"]: # skip tests with implementations that don't support them if cipher == "3des" and implementation not in ("openssl", "pycrypto"): @@ -423,8 +424,8 @@ def connect(): implementation not in ("pycrypto", "python"): continue - if cipher in ("chacha20-poly1305_draft00", ) and \ - implementation not in ("python", ): + if cipher in ("chacha20-poly1305_draft00", "chacha20-poly1305") \ + and implementation not in ("python", ): continue test_no += 1 @@ -1039,7 +1040,8 @@ def server_bind(self): print("Test {0} - throughput test".format(test_no)) for implementation in implementations: for cipher in ["aes128gcm", "aes256gcm", "aes128", "aes256", "3des", - "rc4", "chacha20-poly1305_draft00"]: + "rc4", "chacha20-poly1305_draft00", + "chacha20-poly1305"]: # skip tests with implementations that don't support them if cipher == "3des" and implementation not in ("openssl", "pycrypto"): @@ -1048,8 +1050,8 @@ def server_bind(self): implementation not in ("pycrypto", "python"): continue - if cipher in ("chacha20-poly1305_draft00", ) and \ - implementation not in ("python", ): + if cipher in ("chacha20-poly1305_draft00", "chacha20-poly1305") \ + and implementation not in ("python", ): continue test_no += 1 diff --git a/tlslite/constants.py b/tlslite/constants.py index 55117370..dde9f396 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -500,6 +500,11 @@ class CipherSuite: TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00 = 0xcca3 ietfNames[0xcca3] = 'TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00' + # RFC 7905 - ChaCha20-Poly1305 Cipher Suites for TLS + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xcca8 + ietfNames[0xcca8] = 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256' + TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xccaa + ietfNames[0xccaa] = 'TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256' # RFC 5289 - ECC Ciphers with SHA-256/SHA284 HMAC and AES-GCM TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 @@ -567,11 +572,16 @@ class CipherSuite: aes256GcmSuites.append(TLS_DH_ANON_WITH_AES_256_GCM_SHA384) aes256GcmSuites.append(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) - # CHACHA20 cipher (implicit POLY1305 authenticator) + # CHACHA20 cipher, 00'th IETF draft (implicit POLY1305 authenticator) chacha20draft00Suites = [] chacha20draft00Suites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00) chacha20draft00Suites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00) + # CHACHA20 cipher (implicit POLY1305 authenticator, SHA256 PRF) + chacha20Suites = [] + chacha20Suites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) + chacha20Suites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) + # RC4 128 stream cipher rc4Suites = [] rc4Suites.append(TLS_DH_ANON_WITH_RC4_128_MD5) @@ -639,6 +649,7 @@ class CipherSuite: aeadSuites = [] aeadSuites.extend(aes128GcmSuites) aeadSuites.extend(aes256GcmSuites) + aeadSuites.extend(chacha20Suites) aeadSuites.extend(chacha20draft00Suites) # TLS1.2 with SHA384 PRF @@ -693,6 +704,8 @@ def _filterSuites(suites, settings, version=None): macSuites += CipherSuite.aeadSuites cipherSuites = [] + if "chacha20-poly1305" in cipherNames and version >= (3, 3): + cipherSuites += CipherSuite.chacha20Suites if "chacha20-poly1305_draft00" in cipherNames and version >= (3, 3): cipherSuites += CipherSuite.chacha20draft00Suites if "aes128gcm" in cipherNames and version >= (3, 3): @@ -780,6 +793,7 @@ def getCertSuites(cls, settings, version=None): # FFDHE key exchange, RSA authentication dheCertSuites = [] + dheCertSuites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) dheCertSuites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) dheCertSuites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) @@ -796,6 +810,7 @@ def getDheCertSuites(cls, settings, version=None): # ECDHE key exchange, RSA authentication ecdheCertSuites = [] + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) @@ -865,6 +880,8 @@ def canonicalCipherName(ciphersuite): return "null" elif ciphersuite in CipherSuite.chacha20draft00Suites: return "chacha20-poly1305_draft00" + elif ciphersuite in CipherSuite.chacha20Suites: + return "chacha20-poly1305" else: return None diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index ac80d772..a1d61562 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -12,11 +12,12 @@ from .utils import cipherfactory from .utils.compat import ecdsaAllCurves -CIPHER_NAMES = ["chacha20-poly1305_draft00", +CIPHER_NAMES = ["chacha20-poly1305", "aes256gcm", "aes128gcm", "aes256", "aes128", "3des"] -ALL_CIPHER_NAMES = CIPHER_NAMES + ["rc4", "null"] +ALL_CIPHER_NAMES = CIPHER_NAMES + ["chacha20-poly1305_draft00", + "rc4", "null"] MAC_NAMES = ["sha", "sha256", "sha384", "aead"] # Don't allow "md5" by default. ALL_MAC_NAMES = MAC_NAMES + ["md5"] KEY_EXCHANGE_NAMES = ["rsa", "dhe_rsa", "ecdhe_rsa", "srp_sha", "srp_sha_rsa", diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 0a69a86a..16a7de3b 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -396,8 +396,15 @@ def _encryptThenSeal(self, buf, contentType): len(buf)//256, len(buf)%256]) - #The nonce is always the fixed nonce and the sequence number. - nonce = self._writeState.fixedNonce + seqNumBytes + # ChaCha is using the draft-TLS1.3-like nonce derivation + if self._writeState.encContext.name == "chacha20-poly1305" and \ + len(self._writeState.fixedNonce) == 12: + # 4 byte nonce is used by the draft cipher + nonce = bytearray(i ^ j for i, j in zip(bytearray(4) + seqNumBytes, + self._writeState. + fixedNonce)) + else: + nonce = self._writeState.fixedNonce + seqNumBytes assert len(nonce) == self._writeState.encContext.nonceLength @@ -614,6 +621,12 @@ def _decryptAndUnseal(self, recordType, buf): raise TLSBadRecordMAC("Truncated nonce") nonce = self._readState.fixedNonce + buf[:explicitNonceLength] buf = buf[8:] + elif self._readState.encContext.name == "chacha20-poly1305" and \ + len(self._readState.fixedNonce) == 12: + # the shorter nonce is used by the draft-00 version + nonce = bytearray(i ^ j for i, j in zip(bytearray(4) + seqnumBytes, + self._readState. + fixedNonce)) else: nonce = self._readState.fixedNonce + seqnumBytes @@ -749,6 +762,10 @@ def _getCipherSettings(cipherSuite): keyLength = 16 ivLength = 4 createCipherFunc = createAESGCM + elif cipherSuite in CipherSuite.chacha20Suites: + keyLength = 32 + ivLength = 12 + createCipherFunc = createCHACHA20 elif cipherSuite in CipherSuite.chacha20draft00Suites: keyLength = 32 ivLength = 4 diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 329a039f..ebcb19c8 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -969,6 +969,70 @@ def test_recvRecord_with_ChaCha20_draft00(self): self.assertEqual(head.type, ContentType.application_data) self.assertEqual(bytearray(b'test'), parser.bytes) + def test_sendRecord_with_ChaCha20(self): + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 3) + + ciph = CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + recordLayer.calcPendingStates(ciph, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeWriteState() + + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsNotNone(app_data) + + for result in recordLayer.sendRecord(app_data): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: break + + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application data + b'\x03\x03' + # TLSv1.2 + b'\x00\x14' # length + )) + self.assertEqual(sock.sent[0][5:], bytearray( + b's\x1e\xe8(+h0\x84\xbf\x96,\xbc\xdf\\\x8c\xad=\x95\xa1\x9a')) + + def test_recvRecord_with_ChaCha20(self): + sock = MockSocket(bytearray( + b'\x17' + # application data + b'\x03\x03' + # TLSv1.2 + b'\x00\x14' + # length + b's\x1e\xe8(+h0\x84\xbf\x96,\xbc\xdf\\\x8c\xad=\x95\xa1\x9a')) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 3) + recordLayer.client = False + + ciph = CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + recordLayer.calcPendingStates(ciph, + bytearray(48), # master secret + bytearray(32), # client random + bytearray(32), # server random + None) + recordLayer.changeReadState() + + for result in recordLayer.recvRecord(): + if result in (0, 1): + self.assertTrue(False, "blocking socket") + else: + break + + head, parser = result + + self.assertEqual((3, 3), head.version) + self.assertEqual(head.type, ContentType.application_data) + self.assertEqual(bytearray(b'test'), parser.bytes) + + # tlslite has no pure python implementation of 3DES @unittest.skipUnless(cryptomath.m2cryptoLoaded or cryptomath.pycryptoLoaded, "requires native 3DES implementation") From c6b643d319248f2f870d294fe5e9e3a7e3abda57 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 27 Jul 2016 18:40:17 +0200 Subject: [PATCH 306/574] deduplicate nonce generation code --- tlslite/recordlayer.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 16a7de3b..684701ba 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -386,6 +386,20 @@ def _encryptThenMAC(self, buf, contentType): return buf + @staticmethod + def _getNonce(state, seqnum): + """Calculate a nonce for a given enc/dec context""" + # ChaCha is using the draft-TLS1.3-like nonce derivation + if state.encContext.name == "chacha20-poly1305" and \ + len(state.fixedNonce) == 12: + # 4 byte nonce is used by the draft cipher + nonce = bytearray(i ^ j for i, j in zip(bytearray(4) + seqnum, + state.fixedNonce)) + else: + nonce = state.fixedNonce + seqnum + return nonce + + def _encryptThenSeal(self, buf, contentType): """Encrypt with AEAD cipher""" #Assemble the authenticated data. @@ -396,15 +410,7 @@ def _encryptThenSeal(self, buf, contentType): len(buf)//256, len(buf)%256]) - # ChaCha is using the draft-TLS1.3-like nonce derivation - if self._writeState.encContext.name == "chacha20-poly1305" and \ - len(self._writeState.fixedNonce) == 12: - # 4 byte nonce is used by the draft cipher - nonce = bytearray(i ^ j for i, j in zip(bytearray(4) + seqNumBytes, - self._writeState. - fixedNonce)) - else: - nonce = self._writeState.fixedNonce + seqNumBytes + nonce = self._getNonce(self._writeState, seqNumBytes) assert len(nonce) == self._writeState.encContext.nonceLength @@ -621,14 +627,8 @@ def _decryptAndUnseal(self, recordType, buf): raise TLSBadRecordMAC("Truncated nonce") nonce = self._readState.fixedNonce + buf[:explicitNonceLength] buf = buf[8:] - elif self._readState.encContext.name == "chacha20-poly1305" and \ - len(self._readState.fixedNonce) == 12: - # the shorter nonce is used by the draft-00 version - nonce = bytearray(i ^ j for i, j in zip(bytearray(4) + seqnumBytes, - self._readState. - fixedNonce)) else: - nonce = self._readState.fixedNonce + seqnumBytes + nonce = self._getNonce(self._readState, seqnumBytes) if self._readState.encContext.tagLength > len(buf): #Publicly invalid. From 073e6b38478e38052ba02cdb1ebcdc1a4684d9fb Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Jul 2016 15:01:59 +0200 Subject: [PATCH 307/574] buffer writes on fileobjects since buffer objects have a flush() method, we should output data (create a record layer message) only when either all of it was prepared (and flush called) or enough data to fill full record this is necessary as otherwise servers like the one in scripts/tls.py leak information about size of headers as every header is packages into a single record (and record boundaries are publicly visible) --- tlslite/tlsrecordlayer.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 21c4eaf8..14ece5e7 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -476,6 +476,12 @@ def makefile(self, mode='r', bufsize=-1): # If this is the last close() on the outstanding fileobjects / # TLSConnection, then the "actual" close alerts will be sent, # socket closed, etc. + + # for writes, we MUST buffer otherwise the lengths of headers leak + # through record layer boundaries + if 'w' in mode and bufsize == 0: + bufsize = 2**14 + if sys.version_info < (3,): return socket._fileobject(self, mode, bufsize, close=True) else: From 37fae942a705c9b7f5b2bb7b3ab90ef1b0d38038 Mon Sep 17 00:00:00 2001 From: almond29 Date: Wed, 27 Jul 2016 18:49:25 +0200 Subject: [PATCH 308/574] HKDF_expand function + test cases for it --- tlslite/utils/cryptomath.py | 11 ++ unit_tests/test_tlslite_utils_cryptomath.py | 121 +++++++++++++++++++- 2 files changed, 126 insertions(+), 6 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index 4222662b..d7f6f8b7 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -15,6 +15,8 @@ import binascii import sys +from .poly1305 import Poly1305 + from .compat import compat26Str, compatHMAC, compatLong @@ -107,6 +109,15 @@ def HMAC_SHA256(k, b): def HMAC_SHA384(k, b): return secureHMAC(k, b, 'sha384') +def HKDF_expand(PRK, info, L, algorithm): + N = Poly1305.divceil(L, getattr(hashlib, algorithm)().digest_size) + T = bytearray() + Titer = bytearray() + for x in range(1, N+2): + T += Titer + Titer = secureHMAC(PRK, Titer + info + bytearray([x]), algorithm) + return T[:L] + # ************************************************************************** # Converter Functions # ************************************************************************** diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index 4ff4310a..ff4924bc 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -14,7 +14,7 @@ from tlslite.utils.cryptomath import isPrime, numBits, numBytes, \ numberToByteArray, MD5, SHA1, secureHash, HMAC_MD5, HMAC_SHA1, \ - HMAC_SHA256, HMAC_SHA384 + HMAC_SHA256, HMAC_SHA384, HKDF_expand class TestIsPrime(unittest.TestCase): def test_with_small_primes(self): @@ -132,15 +132,124 @@ def test_HMAC_SHA1(self): def test_HMAC_SHA256(self): self.assertEqual(HMAC_SHA256(b'abc', b'def'), bytearray(b' \xeb\xc0\xf0\x93DG\x014\xf3P@\xf6>' - b'\xa9\x8b\x1d\x8eAB\x12\x94\x9e\xe5\xc5\x00B' + b'\xa9\x8b\x1d\x8eAB\x12\x94\x9e\xe5' + b'\xc5\x00B' b'\x9d\x15\xea\xb0\x81')) def test_HMAC_SHA384(self): self.assertEqual(HMAC_SHA384(b'abc', b'def'), - bytearray(b'\xec\x14\xd6\x94\x86\tHp\x84\x07\xect\x0e\t~' - b'\x85?\xe8\xfd\xba\xd4\x86s\x05\xaa\xe8\xfcB\xd0' - b'\xe8\xaa\xa6V\xe07\x9e\xc5\xc9n\x15\x97\xe0\xbc' - b'\xefZ\xa6\xdb\x05')) + bytearray(b'\xec\x14\xd6\x94\x86\tHp\x84\x07\xect\x0e' + b'\t~\x85?\xe8\xfd\xba\xd4\x86s\x05\xaa\xe8' + b'\xfcB\xd0\xe8\xaa\xa6V\xe07\x9e\xc5\xc9n' + b'\x15\x97\xe0\xbc\xefZ\xa6\xdb\x05')) + + def test_HMAC_expand_1(self): + # RFC 5869 Appendix A.1 Test Vector 1 + self.assertEqual(HKDF_expand(numberToByteArray(int('0x077709362c2e32df' + '0ddc3f0dc47bba6390' + 'b6c73bb50f9c3122ec' + '844ad7c2b3e5', + 16), 32), + numberToByteArray(0xf0f1f2f3f4f5f6f7f8f9, + 10), 42, 'sha256'), + numberToByteArray(int('0x3cb25f25faacd57a90434f64d036' + '2f2a2d2d0a90cf1a5a4c5db02d56ec' + 'c4c5bf34007208d5b887185865', + 16), 42)) + + def test_HMAC_expand_2(self): + # RFC 5869 Appendix A.2 Test Vector 2 + self.assertEqual(HKDF_expand( + numberToByteArray(int('0x06a6b88c5853361a06104c9ceb35b45cef7600149' + '04671014a193f40c15fc244', 16), 32), + numberToByteArray(int('0xb0b1b2b3b4b5b6b7' + 'b8b9babbbcbdbebfc0' + 'c1c2c3c4c5c6c7c8c9' + 'cacbcccdcecfd0d1d2' + 'd3d4d5d6d7d8d9dadb' + 'dcdddedfe0e1e2e3e4' + 'e5e6e7e8e9eaebeced' + 'eeeff0f1f2f3f4f5f6' + 'f7f8f9fafbfcfdf' + 'eff', 16), + 80), 82, 'sha256'), + numberToByteArray(int('0xb11e398dc80327a1c8e7f78c596a' + '49344f012eda2d4efad8a050cc4c19' + 'afa97c59045a99cac7827271cb41c6' + '5e590e09da3275600c2f09b8367793' + 'a9aca3db71cc30c58179ec3e87c14c' + '01d5c1f3434f1d87', 16), 82)) + + def test_HMAC_expand_3(self): + # RFC 5869 Appendix A.3 Test Vector 3 + self.assertEqual(HKDF_expand(numberToByteArray(int('0x19ef24a32c717b16' + '7f33a91d6f648bdf96' + '596776afdb6377ac43' + '4c1c293ccb04', 16), + 32), bytearray(), + 42, 'sha256'), + numberToByteArray(int('0x8da4e775a563c18f715f802a063c' + '5a31b8a11f5c5ee1879ec3454e5f3c' + '738d2d9d201395faa4b61a96c8', + 16), 42)) + + def test_HMAC_expand_4(self): + # RFC 5869 Appendix A.4 Test Vector 4 + self.assertEqual(HKDF_expand(numberToByteArray(int('0x9b6c18c432a7bf8f' + '0e71c8eb88f4b30baa' + '2ba243', 16), 20), + numberToByteArray(int('0xf0f1f2f3f4f5f6f7' + 'f8f9', 16), + 10), 42, 'sha1'), + numberToByteArray(int('0x085a01ea1b10f36933068b56efa5' + 'ad81a4f14b822f5b091568a9cdd4f1' + '55fda2c22e422478d305f3f896', + 16), 42)) + + def test_HMAC_expand_5(self): + # RFC 5869 Appendix A.5 Test Vector 5 + self.assertEqual(HKDF_expand(numberToByteArray(int('0x8adae09a2a307059' + '478d309b26c4115a22' + '4cfaf6', 16), 20), + numberToByteArray(int('0xb0b1b2b3b4b5b6b7' + 'b8b9babbbcbdbebfc0' + 'c1c2c3c4c5c6c7c8c9' + 'cacbcccdcecfd0d1d2' + 'd3d4d5d6d7d8d9dadb' + 'dcdddedfe0e1e2e3e4' + 'e5e6e7e8e9eaebeced' + 'eeeff0f1f2f3f4f5f6' + 'f7f8f9fafbfcfdfe' + 'ff', 16), 80), + 82, 'sha1'), + numberToByteArray(int('0x0bd770a74d1160f7c9f12cd5912a' + '06ebff6adcae899d92191fe4305673' + 'ba2ffe8fa3f1a4e5ad79f3f334b3b2' + '02b2173c486ea37ce3d397ed034c7f' + '9dfeb15c5e927336d0441f4c4300e2' + 'cff0d0900b52d3b4', 16), 82)) + + def test_HMAC_expand_6(self): + # RFC 5869 Appendix A.6 Test Vector 6 + self.assertEqual(HKDF_expand(numberToByteArray(int('0xda8c8a73c7fa7728' + '8ec6f5e7c297786aa0' + 'd32d01', 16), 20), + bytearray(), 42, 'sha1'), + numberToByteArray(int('0x0ac1af7002b3d761d1e55298da9d' + '0506b9ae52057220a306e07b6b87e8' + 'df21d0ea00033de03984d34918', + 16), 42)) + + def test_HMAC_expand_7(self): + # RFC 5869 Appendix A.7 Test Vector 7 + self.assertEqual(HKDF_expand(numberToByteArray(int('0x2adccada18779e7c' + '2077ad2eb19d3f3e73' + '1385dd', 16), 20), + bytearray(), 42, 'sha1'), + numberToByteArray(int('0x2c91117204d745f3500d636a62f6' + '4f0ab3bae548aa53d423b0d1f27ebb' + 'a6f5e5673a081d70cce7acfc48', + 16), 42)) class TestHashMethods(unittest.TestCase): def test_MD5(self): From 3ae19cedccd282f10beb20b20ea5a0533c88489a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Jul 2016 18:57:57 +0200 Subject: [PATCH 309/574] add renegotiation info extension to indicate to clients that we correctly handle renegotiation we need to be able to parse and create renegotiation info extension, even if we don't support renegotiation --- tlslite/extensions.py | 56 +++++++++++++++++++++++- unit_tests/test_tlslite_extensions.py | 63 ++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 0f0f1ff8..0a0e5456 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -1108,6 +1108,59 @@ def parse(self, p): self.paddingData = p.getFixBytes(p.getRemainingLength()) return self +class RenegotiationInfoExtension(TLSExtension): + """ + Client and Server Hello secure renegotiation extension from RFC 5746 + + Should have an empty renegotiated_connection field in case of initial + connection + """ + + def __init__(self): + """Create instance""" + extType = ExtensionType.renegotiation_info + super(RenegotiationInfoExtension, self).__init__(extType=extType) + self.renegotiated_connection = None + + @property + def extData(self): + """ + Return raw encoding of the extension. + + @rtype: bytearray + """ + if self.renegotiated_connection is None: + return bytearray(0) + writer = Writer() + writer.add(len(self.renegotiated_connection), 1) + writer.bytes += self.renegotiated_connection + return writer.bytes + + def create(self, renegotiated_connection): + """ + Set the finished message payload from previous connection. + + @type renegotiated_connection: bytearray + """ + self.renegotiated_connection = renegotiated_connection + return self + + def parse(self, parser): + """ + Deserialise extension from on the wire data. + + @type parser: L{tlslite.util.codec.Parser} + @param parser: data to be parsed + + @rtype: L{RenegotiationInfoExtension} + """ + if parser.getRemainingLength() == 0: + self.renegotiated_connection = None + else: + self.renegotiated_connection = parser.getVarBytes(1) + + return self + TLSExtension._universalExtensions = \ { ExtensionType.server_name: SNIExtension, @@ -1117,7 +1170,8 @@ def parse(self, p): ExtensionType.srp: SRPExtension, ExtensionType.signature_algorithms: SignatureAlgorithmsExtension, ExtensionType.supports_npn: NPNExtension, - ExtensionType.client_hello_padding: PaddingExtension} + ExtensionType.client_hello_padding: PaddingExtension, + ExtensionType.renegotiation_info: RenegotiationInfoExtension} TLSExtension._serverExtensions = \ { diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index a5083051..782e2967 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -11,7 +11,8 @@ from tlslite.extensions import TLSExtension, SNIExtension, NPNExtension,\ SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\ TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\ - SignatureAlgorithmsExtension, PaddingExtension, VarListExtension + SignatureAlgorithmsExtension, PaddingExtension, VarListExtension, \ + RenegotiationInfoExtension from tlslite.utils.codec import Parser from tlslite.constants import NameType, ExtensionType, GroupName,\ ECPointFormat, HashAlgorithm, SignatureAlgorithm @@ -1424,5 +1425,65 @@ def test_parse_with_nonempty_data(self): self.assertEqual(bytearray(b'\x00\x00\x00\x00'), ext.paddingData) +class TestRenegotiationInfoExtension(unittest.TestCase): + def test__init__(self): + ext = RenegotiationInfoExtension() + + self.assertIsNotNone(ext) + self.assertEqual(ext.extType, 0xff01) + self.assertIsNone(ext.renegotiated_connection) + + def test_create(self): + ext = RenegotiationInfoExtension() + ext = ext.create(bytearray(0)) + + self.assertIsNotNone(ext) + self.assertEqual(ext.extType, 0xff01) + self.assertEqual(ext.renegotiated_connection, bytearray(0)) + + def test_write(self): + ext = RenegotiationInfoExtension() + ext.create(bytearray(range(0, 6))) + + self.assertEqual(bytearray( + b'\xff\x01' + b'\x00\x07' + b'\x06' + b'\x00\x01\x02\x03\x04\x05'), + ext.write()) + + def test_write_with_empty_data(self): + ext = RenegotiationInfoExtension() + + self.assertEqual(bytearray( + b'\xff\x01' + b'\x00\x00'), + ext.write()) + + def test_parse_with_empty_data(self): + parser = Parser(bytearray(0)) + + ext = RenegotiationInfoExtension() + ext.parse(parser) + + self.assertIsNone(ext.renegotiated_connection) + + def test_parse_with_empty_array(self): + parser = Parser(bytearray(b'\x00')) + + ext = RenegotiationInfoExtension() + ext.parse(parser) + + self.assertEqual(ext.renegotiated_connection, bytearray(0)) + + def test_parse_with_data(self): + parser = Parser(bytearray(b'\x03abc')) + + ext = RenegotiationInfoExtension() + ext.parse(parser) + + self.assertEqual(ext.renegotiated_connection, bytearray(b'abc')) + + if __name__ == '__main__': unittest.main() From 35f226215b91b964b77d524473f53cd405bb6e12 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Jul 2016 19:20:09 +0200 Subject: [PATCH 310/574] make server advertise renegotiation info ext even if we don't support renegotiation, we need to answer with the secure renegotiation info extension to protect clients --- tlslite/tlsconnection.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 3b890903..c0bd62e2 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1220,6 +1220,25 @@ def _handshakeServerAsyncHelper(self, verifierDB, "Failed to negotiate Extended Master Secret"): yield result + # notify client that we understood it's renegotiation info extension + # or SCSV + secureRenego = False + renegoExt = clientHello.getExtension(ExtensionType.renegotiation_info) + if renegoExt: + if renegoExt.renegotiated_connection: + for result in self._sendError( + AlertDescription.handshake_failure, + "Non empty renegotiation info extension in " + "initial Client Hello"): + yield result + secureRenego = True + elif CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV in \ + clientHello.cipher_suites: + secureRenego = True + if secureRenego: + extensions.append(RenegotiationInfoExtension() + .create(bytearray(0))) + # don't send empty list of extensions if not extensions: extensions = None From 44a883a92174366213340abc6c9d914ae493f648 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Jul 2016 19:26:18 +0200 Subject: [PATCH 311/574] correctly handle invalid DHE_RSA CKE messages we need to send alert message in case the message is malformed, for that the raised exception needs to be caught and processed --- tlslite/keyexchange.py | 8 +++----- tlslite/tlsconnection.py | 7 ++++--- unit_tests/test_tlslite_keyexchange.py | 9 +++++++++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index f51de9a7..6b4cd7c5 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -6,11 +6,10 @@ from .mathtls import goodGroupParameters, makeK, makeU, makeX, calcMasterSecret from .errors import TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ - TLSIllegalParameterException, TLSDecryptionFailed, TLSInternalError, \ - TLSLocalAlert + TLSIllegalParameterException, TLSDecryptionFailed, TLSInternalError from .messages import ServerKeyExchange, ClientKeyExchange, CertificateVerify from .constants import SignatureAlgorithm, HashAlgorithm, CipherSuite, \ - ExtensionType, GroupName, ECCurveType, AlertDescription + ExtensionType, GroupName, ECCurveType from .utils.ecc import decodeX962Point, encodeX962Point, getCurveByName, \ getPointByteSize from .utils.rsakey import RSAKey @@ -271,8 +270,7 @@ def processClientKeyExchange(self, clientKeyExchange): # 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") + raise TLSIllegalParameterException("Invalid dh_Yc value") S = powMod(dh_Yc, self.dh_Xs, self.dh_p) return numberToByteArray(S) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index c0bd62e2..2f162d7c 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1220,7 +1220,7 @@ def _handshakeServerAsyncHelper(self, verifierDB, "Failed to negotiate Extended Master Secret"): yield result - # notify client that we understood it's renegotiation info extension + # notify client that we understood its renegotiation info extension # or SCSV secureRenego = False renegoExt = clientHello.getExtension(ExtensionType.renegotiation_info) @@ -1664,8 +1664,9 @@ def _serverCertKeyExchange(self, clientHello, serverHello, try: premasterSecret = \ keyExchange.processClientKeyExchange(clientKeyExchange) - except TLSLocalAlert as alert: - for result in self._sendError(alert.description, alert.message): + except TLSIllegalParameterException as alert: + for result in self._sendError(AlertDescription.illegal_parameter, + str(alert)): yield result #Get and check CertificateVerify, if relevant diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index af45a460..fbe3bce6 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -566,6 +566,15 @@ def test_DHE_RSA_key_exchange_with_client(self): self.assertEqual(client_premaster, server_premaster) + def test_DHE_RSA_key_exchange_with_invalid_client_key_share(self): + clientKeyExchange = ClientKeyExchange(self.cipher_suite, + (3, 3)) + clientKeyExchange.createDH(2**16000-1) + + with self.assertRaises(TLSIllegalParameterException): + self.keyExchange.processClientKeyExchange(clientKeyExchange) + + def test_DHE_RSA_key_exchange_with_small_prime(self): client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, self.client_hello, From 72e0b300ba2ad53f85b5ee9edce37b1118372e11 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Jul 2016 19:31:54 +0200 Subject: [PATCH 312/574] send ec_point_formats as a server that's necessary for ECDHE interoperability, and good form anyway --- tlslite/tlsconnection.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 2f162d7c..c7f12410 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1239,6 +1239,13 @@ def _handshakeServerAsyncHelper(self, verifierDB, extensions.append(RenegotiationInfoExtension() .create(bytearray(0))) + # tell the client what point formats we support + if clientHello.getExtension(ExtensionType.ec_point_formats): + # even though the selected cipher may not use ECC, client may want + # to send a CA certificate with ECDSA... + extensions.append(ECPointFormatsExtension().create( + [ECPointFormat.uncompressed])) + # don't send empty list of extensions if not extensions: extensions = None From f322031d188a153e76a7abe1c48289d2465256ee Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Jul 2016 19:38:57 +0200 Subject: [PATCH 313/574] send alerts on invalid ECDHE_RSA CKE messages too --- tlslite/keyexchange.py | 8 ++++++-- unit_tests/test_tlslite_keyexchange.py | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 6b4cd7c5..cb844284 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -344,8 +344,12 @@ def makeServerKeyExchange(self, sigHash=None): def processClientKeyExchange(self, clientKeyExchange): """Calculate premaster secret from previously generated SKE and CKE""" curveName = GroupName.toRepr(self.group_id) - ecdhYc = decodeX962Point(clientKeyExchange.ecdh_Yc, - getCurveByName(curveName)) + try: + ecdhYc = decodeX962Point(clientKeyExchange.ecdh_Yc, + getCurveByName(curveName)) + # TODO update python-ecdsa library to raise something more on point + except AssertionError: + raise TLSIllegalParameterException("Invalid ECC point") sharedSecret = ecdhYc * self.ecdhXs diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index fbe3bce6..bba3e75b 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -842,6 +842,31 @@ def test_ECDHE_key_exchange(self): self.assertEqual(cln_premaster, srv_premaster) + def test_ECDHE_key_exchange_with_invalid_CKE(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + KeyExchange.verifyServerKeyExchange(srv_key_ex, + self.srv_pub_key, + self.client_hello.random, + self.server_hello.random, + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + curveName = GroupName.toStr(srv_key_ex.named_curve) + curve = getCurveByName(curveName) + generator = curve.generator + cln_Xc = ecdsa.util.randrange(generator.order()) + cln_Ys = decodeX962Point(srv_key_ex.ecdh_Ys, curve) + cln_Yc = encodeX962Point(generator * cln_Xc) + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)) + cln_key_ex.createECDH(cln_Yc) + + cln_key_ex.ecdh_Yc[-1] ^= 0x01 + + with self.assertRaises(TLSIllegalParameterException): + self.keyExchange.processClientKeyExchange(cln_key_ex) + def test_ECDHE_key_exchange_with_missing_curves(self): self.client_hello.extensions = [SNIExtension().create(bytearray(b"a"))] with self.assertRaises(TLSInternalError): From 8252dde55a68cea391cbab8111573d806d64085e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 28 Jul 2016 19:49:19 +0200 Subject: [PATCH 314/574] send renegotiation info in resumed sessions too --- tlslite/tlsconnection.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index c7f12410..73c4b4e5 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1475,6 +1475,21 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, extended_master_secret, bytearray(0)) extensions.append(ems) + secureRenego = False + renegoExt = clientHello.\ + getExtension(ExtensionType.renegotiation_info) + if renegoExt: + if renegoExt.renegotiated_connection: + for result in self._sendError( + AlertDescription.handshake_failure): + yield result + secureRenego = True + elif CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV in \ + clientHello.cipher_suites: + secureRenego = True + if secureRenego: + extensions.append(RenegotiationInfoExtension() + .create(bytearray(0))) # don't send empty extensions if not extensions: extensions = None From bfad475de8a29aacf6e9bd347767b6d8c65a4285 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jul 2016 15:40:59 +0200 Subject: [PATCH 315/574] check against record overflow post decryption (and decompression, but tlslite-ng does not support it) the data portion can't exceed 2**14 bytes --- tlslite/recordlayer.py | 4 ++++ unit_tests/test_tlslite_tlsrecordlayer.py | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 5a0e349d..b99bc85b 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -704,6 +704,10 @@ def recvRecord(self): else: data = self._decryptStreamThenMAC(header.type, data) + # RFC 5246, section 6.2.1 + if len(data) > 2**14: + raise TLSRecordOverflow() + yield (header, Parser(data)) # diff --git a/unit_tests/test_tlslite_tlsrecordlayer.py b/unit_tests/test_tlslite_tlsrecordlayer.py index 1a95aff5..7f57244f 100644 --- a/unit_tests/test_tlslite_tlsrecordlayer.py +++ b/unit_tests/test_tlslite_tlsrecordlayer.py @@ -567,9 +567,8 @@ def test__getMsg_with_oversized_message(self): gen = record_layer._getMsg(ContentType.handshake, HandshakeType.server_hello) - # XXX decoder handles messages over the 2**14 limit! - #with self.assertRaises(TLSLocalAlert): - message = next(gen) + with self.assertRaises(TLSLocalAlert): + message = next(gen) # # Temporary tests below From e3acdb98a5266e15bf84380978cc5120bb0d42d2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jul 2016 15:56:50 +0200 Subject: [PATCH 316/574] more unit tests for client hello parser --- unit_tests/test_tlslite_messages.py | 91 +++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 41246b29..86a47acc 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -651,6 +651,97 @@ def test_write_with_SSLv2(self): b'\xab'*16), # challange client_hello.write()) + def test_parse_with_short_message(self): + p = Parser(bytearray( + #b'\x01' type of message + b'\x00\x00\x25' + # length + b'\x03\x03' + # version + b'\x00'*32 + # client random + b'\x00' + # session id length + b'\x00\x00' # cipher suites length + #b'\x00' # compression methods + )) + + client_hello = ClientHello() + with self.assertRaises(SyntaxError): + client_hello.parse(p) + + def test_parse_with_short_message_trunc_extensions_list(self): + p = Parser(bytearray( + #b'\x01' type of message + b'\x00\x00\x28' + # length + b'\x03\x03' + # version + b'\x00'*32 + # client random + b'\x00' + # session id length + b'\x00\x00' # cipher suites length + b'\x00' # compression methods + b'\x00\x04' # extensions length + #b'\xff\xab' # extension ID + #b'\x00\x00' # extensions length + )) + + client_hello = ClientHello() + # XXX for TLS1.0 and later this is invalid message + #with self.assertRaises(SyntaxError): + client_hello.parse(p) + + def test_parse_with_short_message_trunc_extension_header(self): + p = Parser(bytearray( + #b'\x01' type of message + b'\x00\x00\x2a' + # length + b'\x03\x03' + # version + b'\x00'*32 + # client random + b'\x00' + # session id length + b'\x00\x00' # cipher suites length + b'\x00' # compression methods + b'\x00\x02' # extensions length (invalid) + b'\xff\xab' # extension ID + #b'\x00\x00' # extensions length + )) + + client_hello = ClientHello() + with self.assertRaises(SyntaxError): + client_hello.parse(p) + + def test_parse_with_short_message_trunc_extensions_with_ext(self): + p = Parser(bytearray( + #b'\x01' type of message + b'\x00\x00\x2c' + # length + b'\x03\x03' + # version + b'\x00'*32 + # client random + b'\x00' + # session id length + b'\x00\x00' # cipher suites length + b'\x00' # compression methods + b'\x00\x08' # extensions length (invalid) + b'\xff\xab' # extension ID + b'\x00\x00' # extensions length + )) + + client_hello = ClientHello() + # XXX does not check if extensions were not truncated + #with self.assertRaises(SyntaxError): + client_hello.parse(p) + + def test_parse_with_padded_message(self): + p = Parser(bytearray( + #b'\x01' type of message + b'\x00\x00\x2c' + # length + b'\x03\x03' + # version + b'\x00'*32 + # client random + b'\x00' + # session id length + b'\x00\x00' # cipher suites length + b'\x00' # compression methods + b'\x00\x00' # extensions length + b'\xff\xff' # garbage + b'\x00\x00' # more garbage + )) + + client_hello = ClientHello() + # XXX does not check if there is extreneus data after extensions + #with self.assertRaises(SyntaxError): + client_hello.parse(p) + + class TestServerHello(unittest.TestCase): def test___init__(self): server_hello = ServerHello() From 826cd3d3c60dea4bb4436da165ee15d3a4a3d81b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jul 2016 16:01:12 +0200 Subject: [PATCH 317/574] strict parsing of client hello client hello lengths need to match exactly what's sent, that includes extensions length field --- tlslite/messages.py | 5 +++-- unit_tests/test_tlslite_messages.py | 15 ++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 1d9ae8ad..0f22a080 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -597,8 +597,9 @@ def parse(self, p): if not p.atLengthCheck(): self.extensions = [] totalExtLength = p.get(2) - while not p.atLengthCheck(): - ext = TLSExtension().parse(p) + p2 = Parser(p.getFixBytes(totalExtLength)) + while p2.getRemainingLength() > 0: + ext = TLSExtension().parse(p2) self.extensions += [ext] p.stopLengthCheck() return self diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 86a47acc..27f3c217 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -681,9 +681,8 @@ def test_parse_with_short_message_trunc_extensions_list(self): )) client_hello = ClientHello() - # XXX for TLS1.0 and later this is invalid message - #with self.assertRaises(SyntaxError): - client_hello.parse(p) + with self.assertRaises(SyntaxError): + client_hello.parse(p) def test_parse_with_short_message_trunc_extension_header(self): p = Parser(bytearray( @@ -718,9 +717,8 @@ def test_parse_with_short_message_trunc_extensions_with_ext(self): )) client_hello = ClientHello() - # XXX does not check if extensions were not truncated - #with self.assertRaises(SyntaxError): - client_hello.parse(p) + with self.assertRaises(SyntaxError): + client_hello.parse(p) def test_parse_with_padded_message(self): p = Parser(bytearray( @@ -737,9 +735,8 @@ def test_parse_with_padded_message(self): )) client_hello = ClientHello() - # XXX does not check if there is extreneus data after extensions - #with self.assertRaises(SyntaxError): - client_hello.parse(p) + with self.assertRaises(SyntaxError): + client_hello.parse(p) class TestServerHello(unittest.TestCase): From 5de7fe8bc08e5f5cfa542fa3acdd0d2dcea40fa3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jul 2016 16:05:46 +0200 Subject: [PATCH 318/574] one more client hello test case --- unit_tests/test_tlslite_messages.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 27f3c217..55831b4f 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -738,6 +738,23 @@ def test_parse_with_padded_message(self): with self.assertRaises(SyntaxError): client_hello.parse(p) + def test_parse_with_short_message_trunc_extension(self): + p = Parser(bytearray( + #b'\x01' type of message + b'\x00\x00\x2c' + # length + b'\x03\x03' + # version + b'\x00'*32 + # client random + b'\x00' + # session id length + b'\x00\x00' # cipher suites length + b'\x00' # compression methods + b'\x00\x04' # extensions length + b'\xff\xab' # extension ID + b'\x00\x02' # extensions length + )) + + client_hello = ClientHello() + with self.assertRaises(SyntaxError): + client_hello.parse(p) class TestServerHello(unittest.TestCase): def test___init__(self): From 1577894ec1c5a09de07558806ae71412d6e6e84a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jul 2016 18:29:22 +0200 Subject: [PATCH 319/574] fix formatting in messages.py only whitespace changes --- tlslite/messages.py | 135 +++++++++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 58 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 1d9ae8ad..8394111a 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1,4 +1,4 @@ -# Authors: +# Authors: # Trevor Perrin # Google - handling CertificateRequest.certificate_types # Google (adapted by Sam Rushing and Marcelo Fernandez) - NPN support @@ -20,6 +20,7 @@ from .utils.tackwrapper import * from .extensions import * + class RecordHeader(object): """Generic interface to SSLv2 and SSLv3 (and later) record headers""" @@ -31,6 +32,7 @@ def __init__(self, ssl2): self.length = 0 self.ssl2 = ssl2 + class RecordHeader3(RecordHeader): """SSLv3 (and later) TLS record header""" @@ -75,12 +77,14 @@ def typeName(self): def __str__(self): return "SSLv3 record,version({0[0]}.{0[1]}),"\ "content type({1}),length({2})".format(self.version, - self.typeName, self.length) + self.typeName, + self.length) def __repr__(self): return "RecordHeader3(type={0}, version=({1[0]}.{1[1]}), length={2})".\ format(self.type, self.version, self.length) + class RecordHeader2(RecordHeader): """ SSLv2 record header @@ -148,6 +152,7 @@ def write(self): return writer.bytes + class Message(object): """Generic TLS message""" @@ -168,6 +173,7 @@ def write(self): """Return serialised object data""" return self.data + class Alert(object): def __init__(self): self.contentType = ContentType.alert @@ -195,7 +201,7 @@ def write(self): @property def levelName(self): matching = [x[0] for x in AlertLevel.__dict__.items() - if x[1] == self.level] + if x[1] == self.level] if len(matching) == 0: return "unknown({0})".format(self.level) else: @@ -204,7 +210,7 @@ def levelName(self): @property def descriptionName(self): matching = [x[0] for x in AlertDescription.__dict__.items() - if x[1] == self.description] + if x[1] == self.description] if len(matching) == 0: return "unknown({0})".format(self.description) else: @@ -212,23 +218,25 @@ def descriptionName(self): def __str__(self): return "Alert, level:{0}, description:{1}".format(self.levelName, - self.descriptionName) + self.descriptionName) def __repr__(self): return "Alert(level={0}, description={1})".format(self.level, - self.description) + self.description) + class HandshakeMsg(object): def __init__(self, handshakeType): self.contentType = ContentType.handshake self.handshakeType = handshakeType - + def postWrite(self, w): headerWriter = Writer() headerWriter.add(self.handshakeType, 1) headerWriter.add(len(w.bytes), 3) return headerWriter.bytes + w.bytes + class ClientHello(HandshakeMsg): """ Class for handling the ClientHello TLS message, supports both the SSLv2 @@ -257,7 +265,7 @@ class ClientHello(HandshakeMsg): def __init__(self, ssl2=False): HandshakeMsg.__init__(self, HandshakeType.client_hello) self.ssl2 = ssl2 - self.client_version = (0,0) + self.client_version = (0, 0) self.random = bytearray(32) self.session_id = bytearray(0) self.cipher_suites = [] # a list of 16-bit values @@ -272,15 +280,15 @@ def __str__(self): """ if self.session_id.count(bytearray(b'\x00')) == len(self.session_id)\ - and len(self.session_id) != 0: + and len(self.session_id) != 0: session = "bytearray(b'\\x00'*{0})".format(len(self.session_id)) else: session = repr(self.session_id) ret = "client_hello,version({0[0]}.{0[1]}),random(...),"\ - "session ID({1!s}),cipher suites({2!r}),"\ - "compression methods({3!r})".format( - self.client_version, session, - self.cipher_suites, self.compression_methods) + "session ID({1!s}),cipher suites({2!r}),"\ + "compression methods({3!r})".format( + self.client_version, session, + self.cipher_suites, self.compression_methods) if self.extensions is not None: ret += ",extensions({0!r})".format(self.extensions) @@ -294,10 +302,11 @@ def __repr__(self): @rtype: str """ return "ClientHello(ssl2={0}, client_version=({1[0]}.{1[1]}), "\ - "random={2!r}, session_id={3!r}, cipher_suites={4!r}, "\ - "compression_methods={5}, extensions={6})".format(\ - self.ssl2, self.client_version, self.random, self.session_id, - self.cipher_suites, self.compression_methods, self.extensions) + "random={2!r}, session_id={3!r}, cipher_suites={4!r}, "\ + "compression_methods={5}, extensions={6})".format( + self.ssl2, self.client_version, self.random, + self.session_id, self.cipher_suites, + self.compression_methods, self.extensions) def getExtension(self, extType): """ @@ -313,7 +322,7 @@ def getExtension(self, extType): exts = [ext for ext in self.extensions if ext.extType == extType] if len(exts) > 1: raise TLSInternalError( - "Multiple extensions of the same type present") + "Multiple extensions of the same type present") elif len(exts) == 1: return exts[0] else: @@ -460,16 +469,15 @@ def supports_npn(self, present): if present: npn_ext = self.getExtension(ExtensionType.supports_npn) if npn_ext is None: - ext = TLSExtension().create( - ExtensionType.supports_npn, - bytearray(0)) + ext = TLSExtension().create(ExtensionType.supports_npn, + bytearray(0)) self.addExtension(ext) else: return else: if self.extensions is None: return - #remove all extension of this type without changing reference + # remove all extension of this type without changing reference self.extensions[:] = [ext for ext in self.extensions if ext.extType != ExtensionType.supports_npn] @@ -650,6 +658,7 @@ def write(self): else: return self._write() + class ServerHello(HandshakeMsg): """server_hello message @@ -685,7 +694,7 @@ def __init__(self): """Initialise ServerHello object""" HandshakeMsg.__init__(self, HandshakeType.server_hello) - self.server_version = (0,0) + self.server_version = (0, 0) self.random = bytearray(32) self.session_id = bytearray(0) self.cipher_suite = 0 @@ -711,10 +720,10 @@ def __str__(self): def __repr__(self): return "ServerHello(server_version=({0[0]}, {0[1]}), random={1!r}, "\ "session_id={2!r}, cipher_suite={3}, compression_method={4}, "\ - "_tack_ext={5}, extensions={6!r})".format(\ - self.server_version, self.random, self.session_id, - self.cipher_suite, self.compression_method, self._tack_ext, - self.extensions) + "_tack_ext={5}, extensions={6!r})".format( + self.server_version, self.random, self.session_id, + self.cipher_suite, self.compression_method, self._tack_ext, + self.extensions) def getExtension(self, extType): """Return extension of a given type, None if extension of given type @@ -729,7 +738,7 @@ def getExtension(self, extType): exts = [ext for ext in self.extensions if ext.extType == extType] if len(exts) > 1: raise TLSInternalError( - "Multiple extensions of the same type present") + "Multiple extensions of the same type present") elif len(exts) == 1: return exts[0] else: @@ -822,8 +831,8 @@ def next_protos(self, val): if val is None: return else: - # convinience function, make sure the values are properly encoded - val = [ bytearray(x) for x in val ] + # convinience function, make sure the values are properly encoded + val = [bytearray(x) for x in val] npn_ext = self.getExtension(ExtensionType.supports_npn) @@ -851,7 +860,8 @@ def next_protos_advertised(self, val): self.next_protos = val def create(self, version, random, session_id, cipher_suite, - certificate_type=None, tackExt=None, next_protos_advertised=None, + certificate_type=None, tackExt=None, + next_protos_advertised=None, extensions=None): """Initialize the object for deserialisation""" self.extensions = extensions @@ -904,9 +914,10 @@ def write(self): w2.bytes += b w.add(len(w2.bytes), 2) - w.bytes += w2.bytes + w.bytes += w2.bytes return self.postWrite(w) + class ServerHello2(HandshakeMsg): """ SERVER-HELLO message from SSLv2 @@ -992,6 +1003,7 @@ def parse(self, parser): parser.stopLengthCheck() return self + class Certificate(HandshakeMsg): def __init__(self, certificateType): HandshakeMsg.__init__(self, HandshakeType.certificate) @@ -1030,11 +1042,11 @@ def write(self): certificate_list = self.certChain.x509List else: certificate_list = [] - #determine length + # determine length for cert in certificate_list: bytes = cert.writeBytes() chainLength += len(bytes)+3 - #add bytes + # add bytes w.add(chainLength, 3) for cert in certificate_list: bytes = cert.writeBytes() @@ -1043,6 +1055,7 @@ def write(self): raise AssertionError() return self.postWrite(w) + class CertificateRequest(HandshakeMsg): def __init__(self, version): HandshakeMsg.__init__(self, HandshakeType.certificate_request) @@ -1060,33 +1073,34 @@ def create(self, certificate_types, certificate_authorities, sig_algs=()): def parse(self, p): p.startLengthCheck(3) self.certificate_types = p.getVarList(1, 1) - if self.version >= (3,3): + if self.version >= (3, 3): self.supported_signature_algs = p.getVarTupleList(1, 2, 2) ca_list_length = p.get(2) index = 0 self.certificate_authorities = [] while index != ca_list_length: - ca_bytes = p.getVarBytes(2) - self.certificate_authorities.append(ca_bytes) - index += len(ca_bytes)+2 + ca_bytes = p.getVarBytes(2) + self.certificate_authorities.append(ca_bytes) + index += len(ca_bytes)+2 p.stopLengthCheck() return self def write(self): w = Writer() w.addVarSeq(self.certificate_types, 1, 1) - if self.version >= (3,3): + if self.version >= (3, 3): w.addVarTupleSeq(self.supported_signature_algs, 1, 2) caLength = 0 - #determine length + # determine length for ca_dn in self.certificate_authorities: caLength += len(ca_dn)+2 w.add(caLength, 2) - #add bytes + # add bytes for ca_dn in self.certificate_authorities: w.addVarSeq(ca_dn, 1, 2) return self.postWrite(w) + class ServerKeyExchange(HandshakeMsg): """ @@ -1156,21 +1170,20 @@ def __repr__(self): "".format(CipherSuite.ietfNames[self.cipherSuite], self.version) if self.srp_N != 0: - ret += ", srp_N={0}, srp_g={1}, srp_s={2!r}, srp_B={3}".format(\ - self.srp_N, self.srp_g, self.srp_s, self.srp_B) + ret += ", srp_N={0}, srp_g={1}, srp_s={2!r}, srp_B={3}".format( + self.srp_N, self.srp_g, self.srp_s, self.srp_B) if self.dh_p != 0: - ret += ", dh_p={0}, dh_g={1}, dh_Ys={2}".format(\ - self.dh_p, self.dh_g, self.dh_Ys) + ret += ", dh_p={0}, dh_g={1}, dh_Ys={2}".format( + self.dh_p, self.dh_g, self.dh_Ys) if self.signAlg != 0: - ret += ", hashAlg={0}, signAlg={1}".format(\ - self.hashAlg, self.signAlg) + ret += ", hashAlg={0}, signAlg={1}".format( + self.hashAlg, self.signAlg) if self.signature != bytearray(0): ret += ", signature={0!r}".format(self.signature) ret += ")" return ret - def createSRP(self, srp_N, srp_g, srp_s, srp_B): """Set SRP protocol parameters""" self.srp_N = srp_N @@ -1276,11 +1289,12 @@ def hash(self, clientRandom, serverRandom): if self.version >= (3, 3): hashAlg = HashAlgorithm.toRepr(self.hashAlg) if hashAlg is None: - raise AssertionError("Unknown hash algorithm: {0}".\ + raise AssertionError("Unknown hash algorithm: {0}". format(self.hashAlg)) return secureHash(bytesToHash, hashAlg) return MD5(bytesToHash) + SHA1(bytesToHash) + class ServerHelloDone(HandshakeMsg): def __init__(self): HandshakeMsg.__init__(self, HandshakeType.server_hello_done) @@ -1301,6 +1315,7 @@ def __repr__(self): """Human readable representation of object""" return "ServerHelloDone()" + class ClientKeyExchange(HandshakeMsg): """ @@ -1362,7 +1377,7 @@ def createRSA(self, encryptedPreMasterSecret): """ self.encryptedPreMasterSecret = encryptedPreMasterSecret return self - + def createDH(self, dh_Yc): """ Set the client FFDH key share @@ -1400,9 +1415,9 @@ def parse(self, parser): if self.cipherSuite in CipherSuite.srpAllSuites: self.srp_A = bytesToNumber(parser.getVarBytes(2)) elif self.cipherSuite in CipherSuite.certSuites: - if self.version in ((3,1), (3,2), (3,3)): + if self.version in ((3, 1), (3, 2), (3, 3)): self.encryptedPreMasterSecret = parser.getVarBytes(2) - elif self.version == (3,0): + elif self.version == (3, 0): self.encryptedPreMasterSecret = \ parser.getFixBytes(parser.getRemainingLength()) else: @@ -1426,9 +1441,9 @@ def write(self): if self.cipherSuite in CipherSuite.srpAllSuites: w.addVarSeq(numberToByteArray(self.srp_A), 1, 2) elif self.cipherSuite in CipherSuite.certSuites: - if self.version in ((3,1), (3,2), (3,3)): + if self.version in ((3, 1), (3, 2), (3, 3)): w.addVarSeq(self.encryptedPreMasterSecret, 1, 2) - elif self.version == (3,0): + elif self.version == (3, 0): w.addFixSeq(self.encryptedPreMasterSecret, 1) else: raise AssertionError() @@ -1440,6 +1455,7 @@ def write(self): raise AssertionError() return self.postWrite(w) + class ClientMasterKey(HandshakeMsg): """ Handling of SSLv2 CLIENT-MASTER-KEY message @@ -1502,6 +1518,7 @@ def parse(self, parser): parser.stopLengthCheck() return self + class CertificateVerify(HandshakeMsg): """Serializer for TLS handshake protocol Certificate Verify message""" @@ -1554,6 +1571,7 @@ def write(self): writer.addVarSeq(self.signature, 1, 2) return self.postWrite(writer) + class ChangeCipherSpec(object): def __init__(self): self.contentType = ContentType.change_cipher_spec @@ -1571,7 +1589,7 @@ def parse(self, p): def write(self): w = Writer() - w.add(self.type,1) + w.add(self.type, 1) return w.bytes @@ -1598,6 +1616,7 @@ def write(self, trial=False): w.addVarSeq(bytearray(paddingLen), 1, 1) return self.postWrite(w) + class Finished(HandshakeMsg): def __init__(self, version): HandshakeMsg.__init__(self, HandshakeType.finished) @@ -1610,9 +1629,9 @@ def create(self, verify_data): def parse(self, p): p.startLengthCheck(3) - if self.version == (3,0): + if self.version == (3, 0): self.verify_data = p.getFixBytes(36) - elif self.version in ((3,1), (3,2), (3,3)): + elif self.version in ((3, 1), (3, 2), (3, 3)): self.verify_data = p.getFixBytes(12) else: raise AssertionError() @@ -1683,7 +1702,7 @@ def __init__(self): def create(self, bytes): self.bytes = bytes return self - + def splitFirstByte(self): newMsg = ApplicationData().create(self.bytes[:1]) self.bytes = self.bytes[1:] From b312b1e0d8e7f01f15fc320e8007ddc9a02b9a3c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jul 2016 18:36:06 +0200 Subject: [PATCH 320/574] stylistic changes in messages.py use the `is not None` idiom for checking null-ness remove the unnecessary if block --- tlslite/messages.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 8394111a..bb7550ae 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -128,10 +128,7 @@ def write(self): """Serialise object to bytearray""" writer = Writer() - if not (self.padding or self.securityEscape): - shortHeader = True - else: - shortHeader = False + shortHeader = not (self.padding or self.securityEscape) if ((shortHeader and self.length >= 0x8000) or (not shortHeader and self.length >= 0x4000)): @@ -565,15 +562,15 @@ def create(self, version, random, session_id, cipher_suites, self.session_id = session_id self.cipher_suites = cipher_suites self.compression_methods = [0] - if not extensions is None: + if extensions is not None: self.extensions = extensions - if not certificate_types is None: + if certificate_types is not None: self.certificate_types = certificate_types - if not srpUsername is None: + if srpUsername is not None: self.srp_username = bytearray(srpUsername, "utf-8") self.tack = tack self.supports_npn = supports_npn - if not serverName is None: + if serverName is not None: self.server_name = bytearray(serverName, "utf-8") return self @@ -642,7 +639,7 @@ def _write(self): w.addVarSeq(self.cipher_suites, 2, 2) w.addVarSeq(self.compression_methods, 1, 1) - if not self.extensions is None: + if self.extensions is not None: w2 = Writer() for ext in self.extensions: w2.bytes += ext.write() @@ -773,7 +770,7 @@ def tackExt(self, val): """ self._tack_ext = val # makes sure that extensions are included in the on the wire encoding - if not val is None: + if val is not None: if self.extensions is None: self.extensions = [] @@ -902,7 +899,7 @@ def write(self): w.add(self.cipher_suite, 2) w.add(self.compression_method, 1) - if not self.extensions is None: + if self.extensions is not None: w2 = Writer() for ext in self.extensions: w2.bytes += ext.write() From b30fabf67990b8a95fb136e02e58abe51812c1b9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jul 2016 18:49:10 +0200 Subject: [PATCH 321/574] fixup fix formatting in messages.py --- tlslite/messages.py | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index bb7550ae..2cbc8682 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -22,7 +22,6 @@ class RecordHeader(object): - """Generic interface to SSLv2 and SSLv3 (and later) record headers""" def __init__(self, ssl2): @@ -34,7 +33,6 @@ def __init__(self, ssl2): class RecordHeader3(RecordHeader): - """SSLv3 (and later) TLS record header""" def __init__(self): @@ -151,7 +149,6 @@ def write(self): class Message(object): - """Generic TLS message""" def __init__(self, contentType, data): @@ -259,6 +256,7 @@ class ClientHello(HandshakeMsg): @ivar extensions: list of TLS extensions parsed from wire or to send, see L{TLSExtension} and child classes for exact examples """ + def __init__(self, ssl2=False): HandshakeMsg.__init__(self, HandshakeType.client_hello) self.ssl2 = ssl2 @@ -275,7 +273,6 @@ def __str__(self): @rtype: str """ - if self.session_id.count(bytearray(b'\x00')) == len(self.session_id)\ and len(self.session_id) != 0: session = "bytearray(b'\\x00'*{0})".format(len(self.session_id)) @@ -687,9 +684,9 @@ class ServerHello(HandshakeMsg): @ivar extensions: list of TLS extensions present in server_hello message, see L{TLSExtension} and child classes for exact examples """ + def __init__(self): """Initialise ServerHello object""" - HandshakeMsg.__init__(self, HandshakeType.server_hello) self.server_version = (0, 0) self.random = bytearray(32) @@ -754,8 +751,7 @@ def addExtension(self, ext): @property def tackExt(self): - """ Returns the TACK extension - """ + """Returns the TACK extension""" if self._tack_ext is None: ext = self.getExtension(ExtensionType.tack) if ext is None or not tackpyLoaded: @@ -766,8 +762,7 @@ def tackExt(self): @tackExt.setter def tackExt(self, val): - """ Set the TACK extension - """ + """Set the TACK extension""" self._tack_ext = val # makes sure that extensions are included in the on the wire encoding if val is not None: @@ -776,7 +771,8 @@ def tackExt(self, val): @property def certificate_type(self): - """Returns the certificate type selected by server + """ + Returns the certificate type selected by server @rtype: int """ @@ -789,7 +785,8 @@ def certificate_type(self): @certificate_type.setter def certificate_type(self, val): - """Sets the certificate type supported + """ + Sets the certificate type supported @type val: int @param val: type of certificate @@ -807,7 +804,8 @@ def certificate_type(self, val): @property def next_protos(self): - """Returns the advertised protocols in NPN extension + """ + Returns the advertised protocols in NPN extension @rtype: list of bytearrays """ @@ -820,7 +818,8 @@ def next_protos(self): @next_protos.setter def next_protos(self, val): - """Sets the advertised protocols in NPN extension + """ + Sets the advertised protocols in NPN extension @type val: list @param val: list of protocols to advertise as UTF-8 encoded names @@ -841,7 +840,8 @@ def next_protos(self, val): @property def next_protos_advertised(self): - """Returns the advertised protocols in NPN extension + """ + Returns the advertised protocols in NPN extension @rtype: list of bytearrays """ @@ -1099,7 +1099,6 @@ def write(self): class ServerKeyExchange(HandshakeMsg): - """ Handling TLS Handshake protocol Server Key Exchange messages @@ -1162,7 +1161,6 @@ def __init__(self, cipherSuite, version): self.signAlg = 0 def __repr__(self): - ret = "ServerKeyExchange(cipherSuite=CipherSuite.{0}, version={1}"\ "".format(CipherSuite.ietfNames[self.cipherSuite], self.version) @@ -1203,7 +1201,8 @@ def createECDH(self, curve_type, named_curve=None, point=None): self.ecdh_Ys = point def parse(self, parser): - """Deserialise message from L{Parser} + """ + Deserialise message from L{Parser} @type parser: L{Parser} @param parser: parser to read data from @@ -1237,7 +1236,8 @@ def parse(self, parser): return self def writeParams(self): - """Serialise the key exchange parameters + """ + Serialise the key exchange parameters @rtype: bytearray """ @@ -1314,7 +1314,6 @@ def __repr__(self): class ClientKeyExchange(HandshakeMsg): - """ Handling of TLS Handshake protocol ClientKeyExchange message @@ -1517,11 +1516,11 @@ def parse(self, parser): class CertificateVerify(HandshakeMsg): - """Serializer for TLS handshake protocol Certificate Verify message""" def __init__(self, version): - """Create message + """ + Create message @param version: TLS protocol version in use """ From 46051545d01265cb42327b88de5344115f6cfa88 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jul 2016 19:07:14 +0200 Subject: [PATCH 322/574] make docs consistent with PEP-257 --- tlslite/messages.py | 173 ++++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 85 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 2cbc8682..b569a6bf 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -22,10 +22,10 @@ class RecordHeader(object): - """Generic interface to SSLv2 and SSLv3 (and later) record headers""" + """Generic interface to SSLv2 and SSLv3 (and later) record headers.""" def __init__(self, ssl2): - """define instance variables""" + """Define instance variables.""" self.type = 0 self.version = (0, 0) self.length = 0 @@ -33,21 +33,21 @@ def __init__(self, ssl2): class RecordHeader3(RecordHeader): - """SSLv3 (and later) TLS record header""" + """SSLv3 (and later) TLS record header.""" def __init__(self): - """Define a SSLv3 style class""" + """Define a SSLv3 style class.""" super(RecordHeader3, self).__init__(ssl2=False) def create(self, version, type, length): - """Set object values for writing (serialisation)""" + """Set object values for writing (serialisation).""" self.type = type self.version = version self.length = length return self def write(self): - """Serialise object to bytearray""" + """Serialise object to bytearray.""" writer = Writer() writer.add(self.type, 1) writer.add(self.version[0], 1) @@ -56,7 +56,7 @@ def write(self): return writer.bytes def parse(self, parser): - """Deserialise object from Parser""" + """Deserialise object from Parser.""" self.type = parser.get(1) self.version = (parser.get(1), parser.get(1)) self.length = parser.get(2) @@ -85,7 +85,7 @@ def __repr__(self): class RecordHeader2(RecordHeader): """ - SSLv2 record header + SSLv2 record header. @type padding: int @ivar padding: number of bytes added at end of message to make it multiple @@ -95,13 +95,13 @@ class RecordHeader2(RecordHeader): """ def __init__(self): - """Define a SSLv2 style class""" + """Define a SSLv2 style class.""" super(RecordHeader2, self).__init__(ssl2=True) self.padding = 0 self.securityEscape = False def parse(self, parser): - """Deserialise object from Parser""" + """Deserialise object from Parser.""" firstByte = parser.get(1) secondByte = parser.get(1) if firstByte & 0x80: @@ -116,14 +116,14 @@ def parse(self, parser): return self def create(self, length, padding=0, securityEscape=False): - """Set object's values""" + """Set object's values.""" self.length = length self.padding = padding self.securityEscape = securityEscape return self def write(self): - """Serialise object to bytearray""" + """Serialise object to bytearray.""" writer = Writer() shortHeader = not (self.padding or self.securityEscape) @@ -149,11 +149,11 @@ def write(self): class Message(object): - """Generic TLS message""" + """Generic TLS message.""" def __init__(self, contentType, data): """ - Initialize object with specified contentType and data + Initialize object with specified contentType and data. @type contentType: int @param contentType: TLS record layer content type of associated data @@ -164,7 +164,7 @@ def __init__(self, contentType, data): self.data = data def write(self): - """Return serialised object data""" + """Return serialised object data.""" return self.data @@ -233,8 +233,7 @@ def postWrite(self, w): class ClientHello(HandshakeMsg): """ - Class for handling the ClientHello TLS message, supports both the SSLv2 - and SSLv3 style messages. + Class for handling the ClientHello SSLv2/SSLv3/TLS message. @type certificate_types: list @ivar certificate_types: list of supported certificate types (deprecated) @@ -269,7 +268,7 @@ def __init__(self, ssl2=False): def __str__(self): """ - Return human readable representation of Client Hello + Return human readable representation of Client Hello. @rtype: str """ @@ -291,7 +290,7 @@ def __str__(self): def __repr__(self): """ - Return machine readable representation of Client Hello + Return machine readable representation of Client Hello. @rtype: str """ @@ -304,7 +303,7 @@ def __repr__(self): def getExtension(self, extType): """ - Returns extension of given type if present, None otherwise + Return extension of given type if present, None otherwise. @rtype: L{tlslite.extensions.TLSExtension} @raise TLSInternalError: when there are multiple extensions of the @@ -324,7 +323,7 @@ def getExtension(self, extType): def addExtension(self, ext): """ - Adds extension to internal list of extensions + Add extension to internal list of extensions. @type ext: TLSExtension @param ext: extension object to add to list @@ -337,7 +336,7 @@ def addExtension(self, ext): @property def certificate_types(self): """ - Returns the list of certificate types supported. + Return the list of certificate types supported. @deprecated: use extensions field to get the extension for inspection """ @@ -352,6 +351,8 @@ def certificate_types(self): @certificate_types.setter def certificate_types(self, val): """ + Set list of supported certificate types. + Sets the list of supported types to list given in L{val} if the cert_type extension is present. Creates the extension and places it last in the list otherwise. @@ -371,7 +372,7 @@ def certificate_types(self, val): @property def srp_username(self): """ - Returns username for the SRP. + Return username for the SRP. @deprecated: use extensions field to get the extension for inspection """ @@ -385,7 +386,7 @@ def srp_username(self): @srp_username.setter def srp_username(self, name): """ - Sets the username for SRP. + Set the username for SRP. @type name: bytearray @param name: UTF-8 encoded username @@ -401,7 +402,7 @@ def srp_username(self, name): @property def tack(self): """ - Returns whatever the client supports TACK + Return whether the client supports TACK. @rtype: boolean @deprecated: use extensions field to get the extension for inspection @@ -416,7 +417,7 @@ def tack(self): @tack.setter def tack(self, present): """ - Creates or deletes the TACK extension. + Create or delete the TACK extension. @type present: boolean @param present: True will create extension while False will remove @@ -439,7 +440,7 @@ def tack(self, present): @property def supports_npn(self): """ - Returns whatever client supports NPN extension + Return whether client supports NPN extension. @rtype: boolean @deprecated: use extensions field to get the extension for inspection @@ -454,7 +455,7 @@ def supports_npn(self): @supports_npn.setter def supports_npn(self, present): """ - Creates or deletes the NPN extension + Create or delete the NPN extension. @type present: boolean @param present: selects whatever to create or remove the extension @@ -478,7 +479,7 @@ def supports_npn(self, present): @property def server_name(self): """ - Returns first host_name present in SNI extension + Return first host_name present in SNI extension. @rtype: bytearray @deprecated: use extensions field to get the extension for inspection @@ -495,7 +496,7 @@ def server_name(self): @server_name.setter def server_name(self, hostname): """ - Sets the first host_name present in SNI extension + Set the first host_name present in SNI extension. @type hostname: bytearray @param hostname: name of the host_name to set @@ -572,7 +573,7 @@ def create(self, version, random, session_id, cipher_suites, return self def parse(self, p): - """Deserialise object from on the wire data""" + """Deserialise object from on the wire data.""" if self.ssl2: self.client_version = (p.get(1), p.get(1)) cipherSpecsLength = p.get(2) @@ -606,7 +607,7 @@ def parse(self, p): return self def _writeSSL2(self): - """Serialise SSLv2 object to on the wire data""" + """Serialise SSLv2 object to on the wire data.""" writer = Writer() writer.add(self.handshakeType, 1) writer.add(self.client_version[0], 1) @@ -627,7 +628,7 @@ def _writeSSL2(self): return writer.bytes def _write(self): - """Serialise SSLv3 or TLS object to on the wire data""" + """Serialise SSLv3 or TLS object to on the wire data.""" w = Writer() w.add(self.client_version[0], 1) w.add(self.client_version[1], 1) @@ -646,7 +647,7 @@ def _write(self): return self.postWrite(w) def write(self): - """Serialise object to on the wire data""" + """Serialise object to on the wire data.""" if self.ssl2: return self._writeSSL2() else: @@ -654,7 +655,8 @@ def write(self): class ServerHello(HandshakeMsg): - """server_hello message + """ + Handling of Server Hello messages. @type server_version: tuple @ivar server_version: protocol version encoded as two int tuple @@ -686,7 +688,7 @@ class ServerHello(HandshakeMsg): """ def __init__(self): - """Initialise ServerHello object""" + """Initialise ServerHello object.""" HandshakeMsg.__init__(self, HandshakeType.server_hello) self.server_version = (0, 0) self.random = bytearray(32) @@ -720,8 +722,8 @@ def __repr__(self): self.extensions) def getExtension(self, extType): - """Return extension of a given type, None if extension of given type - is not present + """ + Return extension of a given type if present, None otherwise. @rtype: L{TLSExtension} @raise TLSInternalError: multiple extensions of the same type present @@ -740,7 +742,7 @@ def getExtension(self, extType): def addExtension(self, ext): """ - Add extension to internal list of extensions + Add extension to internal list of extensions. @type ext: TLSExtension @param ext: extension to add to list @@ -751,7 +753,7 @@ def addExtension(self, ext): @property def tackExt(self): - """Returns the TACK extension""" + """Return the TACK extension.""" if self._tack_ext is None: ext = self.getExtension(ExtensionType.tack) if ext is None or not tackpyLoaded: @@ -762,7 +764,7 @@ def tackExt(self): @tackExt.setter def tackExt(self, val): - """Set the TACK extension""" + """Set the TACK extension.""" self._tack_ext = val # makes sure that extensions are included in the on the wire encoding if val is not None: @@ -772,7 +774,7 @@ def tackExt(self, val): @property def certificate_type(self): """ - Returns the certificate type selected by server + Return the certificate type selected by server. @rtype: int """ @@ -786,7 +788,7 @@ def certificate_type(self): @certificate_type.setter def certificate_type(self, val): """ - Sets the certificate type supported + Set the certificate type supported. @type val: int @param val: type of certificate @@ -805,7 +807,7 @@ def certificate_type(self, val): @property def next_protos(self): """ - Returns the advertised protocols in NPN extension + Return the advertised protocols in NPN extension. @rtype: list of bytearrays """ @@ -819,7 +821,7 @@ def next_protos(self): @next_protos.setter def next_protos(self, val): """ - Sets the advertised protocols in NPN extension + Set the advertised protocols in NPN extension. @type val: list @param val: list of protocols to advertise as UTF-8 encoded names @@ -841,7 +843,7 @@ def next_protos(self, val): @property def next_protos_advertised(self): """ - Returns the advertised protocols in NPN extension + Return the advertised protocols in NPN extension. @rtype: list of bytearrays """ @@ -849,7 +851,8 @@ def next_protos_advertised(self): @next_protos_advertised.setter def next_protos_advertised(self, val): - """Sets the advertised protocols in NPN extension + """ + Set the advertised protocols in NPN extension. @type val: list @param val: list of protocols to advertise as UTF-8 encoded names @@ -860,7 +863,7 @@ def create(self, version, random, session_id, cipher_suite, certificate_type=None, tackExt=None, next_protos_advertised=None, extensions=None): - """Initialize the object for deserialisation""" + """Initialize the object for deserialisation.""" self.extensions = extensions self.server_version = version self.random = random @@ -917,7 +920,7 @@ def write(self): class ServerHello2(HandshakeMsg): """ - SERVER-HELLO message from SSLv2 + SERVER-HELLO message from SSLv2. @type session_id_hit: int @ivar session_id_hit: non zero if the client provided session ID was @@ -950,7 +953,7 @@ def __init__(self): def create(self, session_id_hit, certificate_type, server_version, certificate, ciphers, session_id): - """Initialize fields of the SERVER-HELLO message""" + """Initialize fields of the SERVER-HELLO message.""" self.session_id_hit = session_id_hit self.certificate_type = certificate_type self.server_version = server_version @@ -960,7 +963,7 @@ def create(self, session_id_hit, certificate_type, server_version, return self def write(self): - """Serialise object to on the wire data""" + """Serialise object to on the wire data.""" writer = Writer() writer.add(self.handshakeType, 1) writer.add(self.session_id_hit, 1) @@ -984,7 +987,7 @@ def write(self): return writer.bytes def parse(self, parser): - """Deserialise object from on the wire data""" + """Deserialise object from on the wire data.""" self.session_id_hit = parser.get(1) self.certificate_type = parser.get(1) self.server_version = (parser.get(1), parser.get(1)) @@ -1100,7 +1103,7 @@ def write(self): class ServerKeyExchange(HandshakeMsg): """ - Handling TLS Handshake protocol Server Key Exchange messages + Handling TLS Handshake protocol Server Key Exchange messages. @type cipherSuite: int @cvar cipherSuite: id of ciphersuite selected in Server Hello message @@ -1134,7 +1137,7 @@ class ServerKeyExchange(HandshakeMsg): def __init__(self, cipherSuite, version): """ - Initialise Server Key Exchange for reading or writing + Initialise Server Key Exchange for reading or writing. @type cipherSuite: int @param cipherSuite: id of ciphersuite selected by server @@ -1180,7 +1183,7 @@ def __repr__(self): return ret def createSRP(self, srp_N, srp_g, srp_s, srp_B): - """Set SRP protocol parameters""" + """Set SRP protocol parameters.""" self.srp_N = srp_N self.srp_g = srp_g self.srp_s = srp_s @@ -1188,21 +1191,21 @@ def createSRP(self, srp_N, srp_g, srp_s, srp_B): return self def createDH(self, dh_p, dh_g, dh_Ys): - """Set FFDH protocol parameters""" + """Set FFDH protocol parameters.""" self.dh_p = dh_p self.dh_g = dh_g self.dh_Ys = dh_Ys return self def createECDH(self, curve_type, named_curve=None, point=None): - """Set ECDH protocol parameters""" + """Set ECDH protocol parameters.""" self.curve_type = curve_type self.named_curve = named_curve self.ecdh_Ys = point def parse(self, parser): """ - Deserialise message from L{Parser} + Deserialise message from L{Parser}. @type parser: L{Parser} @param parser: parser to read data from @@ -1237,7 +1240,7 @@ def parse(self, parser): def writeParams(self): """ - Serialise the key exchange parameters + Serialise the key exchange parameters. @rtype: bytearray """ @@ -1262,7 +1265,7 @@ def writeParams(self): def write(self): """ - Serialise complete message + Serialise complete message. @rtype: bytearray """ @@ -1278,7 +1281,7 @@ def write(self): def hash(self, clientRandom, serverRandom): """ - Calculate hash of parameters to sign + Calculate hash of parameters to sign. @rtype: bytearray """ @@ -1309,13 +1312,13 @@ def write(self): return self.postWrite(w) def __repr__(self): - """Human readable representation of object""" + """Human readable representation of object.""" return "ServerHelloDone()" class ClientKeyExchange(HandshakeMsg): """ - Handling of TLS Handshake protocol ClientKeyExchange message + Handling of TLS Handshake protocol ClientKeyExchange message. @type cipherSuite: int @ivar cipherSuite: the cipher suite id used for the connection @@ -1334,7 +1337,7 @@ class ClientKeyExchange(HandshakeMsg): def __init__(self, cipherSuite, version=None): """ - Initialise ClientKeyExchange for reading or writing + Initialise ClientKeyExchange for reading or writing. @type cipherSuite: int @param cipherSuite: id of the ciphersuite selected by server @@ -1351,7 +1354,7 @@ def __init__(self, cipherSuite, version=None): def createSRP(self, srp_A): """ - Set the SRP client answer + Set the SRP client answer. returns self @@ -1364,7 +1367,7 @@ def createSRP(self, srp_A): def createRSA(self, encryptedPreMasterSecret): """ - Set the encrypted PreMaster Secret + Set the encrypted PreMaster Secret. returns self @@ -1376,7 +1379,7 @@ def createRSA(self, encryptedPreMasterSecret): def createDH(self, dh_Yc): """ - Set the client FFDH key share + Set the client FFDH key share. returns self @@ -1388,7 +1391,7 @@ def createDH(self, dh_Yc): def createECDH(self, ecdh_Yc): """ - Set the client ECDH key share + Set the client ECDH key share. returns self @@ -1400,7 +1403,7 @@ def createECDH(self, ecdh_Yc): def parse(self, parser): """ - Deserialise the message from L{Parser} + Deserialise the message from L{Parser}, returns self @@ -1429,7 +1432,7 @@ def parse(self, parser): def write(self): """ - Serialise the object + Serialise the object. @rtype: bytearray """ @@ -1454,7 +1457,7 @@ def write(self): class ClientMasterKey(HandshakeMsg): """ - Handling of SSLv2 CLIENT-MASTER-KEY message + Handling of SSLv2 CLIENT-MASTER-KEY message. @type cipher: int @ivar cipher: negotiated cipher @@ -1479,7 +1482,7 @@ def __init__(self): self.key_argument = bytearray(0) def create(self, cipher, clear_key, encrypted_key, key_argument): - """Set values of the CLIENT-MASTER-KEY object""" + """Set values of the CLIENT-MASTER-KEY object.""" self.cipher = cipher self.clear_key = clear_key self.encrypted_key = encrypted_key @@ -1487,7 +1490,7 @@ def create(self, cipher, clear_key, encrypted_key, key_argument): return self def write(self): - """Serialise the object to on the wire data""" + """Serialise the object to on the wire data.""" writer = Writer() writer.add(self.handshakeType, 1) writer.add(self.cipher, 3) @@ -1500,7 +1503,7 @@ def write(self): return writer.bytes def parse(self, parser): - """Deserialise object from on the wire data""" + """Deserialise object from on the wire data.""" self.cipher = parser.get(3) clear_key_length = parser.get(2) encrypted_key_length = parser.get(2) @@ -1516,11 +1519,11 @@ def parse(self, parser): class CertificateVerify(HandshakeMsg): - """Serializer for TLS handshake protocol Certificate Verify message""" + """Serializer for TLS handshake protocol Certificate Verify message.""" def __init__(self, version): """ - Create message + Create message. @param version: TLS protocol version in use """ @@ -1531,7 +1534,7 @@ def __init__(self, version): def create(self, signature, signatureAlgorithm=None): """ - Provide data for serialisation of message + Provide data for serialisation of message. @param signature: signature carried in the message @param signatureAlgorithm: signature algorithm used to make the @@ -1543,7 +1546,7 @@ def create(self, signature, signatureAlgorithm=None): def parse(self, parser): """ - Deserialize message from parser + Deserialize message from parser. @param parser: parser with data to read """ @@ -1556,7 +1559,7 @@ def parse(self, parser): def write(self): """ - Serialize the data to bytearray + Serialize the data to bytearray. @rtype: bytearray """ @@ -1641,24 +1644,24 @@ def write(self): class SSL2Finished(HandshakeMsg): - """Handling of the SSL2 FINISHED messages""" + """Handling of the SSL2 FINISHED messages.""" def __init__(self, msg_type): super(SSL2Finished, self).__init__(msg_type) self.verify_data = bytearray(0) def create(self, verify_data): - """Set the message payload""" + """Set the message payload.""" self.verify_data = verify_data return self def parse(self, parser): - """Deserialise the message from on the wire data""" + """Deserialise the message from on the wire data.""" self.verify_data = parser.getFixBytes(parser.getRemainingLength()) return self def write(self): - """Serialise the message to on the wire data""" + """Serialise the message to on the wire data.""" writer = Writer() writer.add(self.handshakeType, 1) writer.addFixSeq(self.verify_data, 1) @@ -1668,7 +1671,7 @@ def write(self): class ClientFinished(SSL2Finished): """ - Handling of SSLv2 CLIENT-FINISHED message + Handling of SSLv2 CLIENT-FINISHED message. @type verify_data: bytearray @ivar verify_data: payload of the message, should be the CONNECTION-ID @@ -1680,7 +1683,7 @@ def __init__(self): class ServerFinished(SSL2Finished): """ - Handling of SSLv2 SERVER-FINISHED message + Handling of SSLv2 SERVER-FINISHED message. @type verify_data: bytearray @ivar verify_data: payload of the message, should be SESSION-ID From 5373f1b17ddd7757be908ed2bee4e3867f58eb04 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jul 2016 19:13:32 +0200 Subject: [PATCH 323/574] deduplicate code between Client and Server Hello --- tlslite/messages.py | 102 +++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 64 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index b569a6bf..ebaedfce 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -231,7 +231,43 @@ def postWrite(self, w): return headerWriter.bytes + w.bytes -class ClientHello(HandshakeMsg): +class HelloMessage(HandshakeMsg): + """Class for sharing code between L{ClientHello} and L{ServerHello}""" + + def getExtension(self, extType): + """ + Return extension of given type if present, None otherwise. + + @rtype: L{tlslite.extensions.TLSExtension} + @raise TLSInternalError: when there are multiple extensions of the + same type + """ + if self.extensions is None: + return None + + exts = [ext for ext in self.extensions if ext.extType == extType] + if len(exts) > 1: + raise TLSInternalError( + "Multiple extensions of the same type present") + elif len(exts) == 1: + return exts[0] + else: + return None + + def addExtension(self, ext): + """ + Add extension to internal list of extensions. + + @type ext: TLSExtension + @param ext: extension object to add to list + """ + if self.extensions is None: + self.extensions = [] + + self.extensions.append(ext) + + +class ClientHello(HelloMessage): """ Class for handling the ClientHello SSLv2/SSLv3/TLS message. @@ -301,38 +337,6 @@ def __repr__(self): self.session_id, self.cipher_suites, self.compression_methods, self.extensions) - def getExtension(self, extType): - """ - Return extension of given type if present, None otherwise. - - @rtype: L{tlslite.extensions.TLSExtension} - @raise TLSInternalError: when there are multiple extensions of the - same type - """ - if self.extensions is None: - return None - - exts = [ext for ext in self.extensions if ext.extType == extType] - if len(exts) > 1: - raise TLSInternalError( - "Multiple extensions of the same type present") - elif len(exts) == 1: - return exts[0] - else: - return None - - def addExtension(self, ext): - """ - Add extension to internal list of extensions. - - @type ext: TLSExtension - @param ext: extension object to add to list - """ - if self.extensions is None: - self.extensions = [] - - self.extensions.append(ext) - @property def certificate_types(self): """ @@ -654,7 +658,7 @@ def write(self): return self._write() -class ServerHello(HandshakeMsg): +class ServerHello(HelloMessage): """ Handling of Server Hello messages. @@ -721,36 +725,6 @@ def __repr__(self): self.cipher_suite, self.compression_method, self._tack_ext, self.extensions) - def getExtension(self, extType): - """ - Return extension of a given type if present, None otherwise. - - @rtype: L{TLSExtension} - @raise TLSInternalError: multiple extensions of the same type present - """ - if self.extensions is None: - return None - - exts = [ext for ext in self.extensions if ext.extType == extType] - if len(exts) > 1: - raise TLSInternalError( - "Multiple extensions of the same type present") - elif len(exts) == 1: - return exts[0] - else: - return None - - def addExtension(self, ext): - """ - Add extension to internal list of extensions. - - @type ext: TLSExtension - @param ext: extension to add to list - """ - if self.extensions is None: - self.extensions = [] - self.extensions.append(ext) - @property def tackExt(self): """Return the TACK extension.""" From 91362fd0a81d3dad781dd7c3fc1838635577d78c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jul 2016 20:15:12 +0200 Subject: [PATCH 324/574] deduplicate ext parsing between Client and Server Hello --- tlslite/messages.py | 82 +++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index ebaedfce..f2101239 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -234,6 +234,11 @@ def postWrite(self, w): class HelloMessage(HandshakeMsg): """Class for sharing code between L{ClientHello} and L{ServerHello}""" + def __init__(self, *args, **kwargs): + """Initialize object.""" + super(HelloMessage, self).__init__(*args, **kwargs) + self.extensions = None + def getExtension(self, extType): """ Return extension of given type if present, None otherwise. @@ -266,6 +271,34 @@ def addExtension(self, ext): self.extensions.append(ext) + def _addExt(self, extType): + """Add en empty extension of given type, if not already present""" + ext = self.getExtension(extType) + if ext is None: + ext = TLSExtension(extType=extType).create(bytearray(0)) + self.addExtension(ext) + + def _removeExt(self, extType): + """Remove extension of given type""" + if self.extensions is not None: + self.extensions[:] = (i for i in self.extensions + if i.extType != extType) + + + def _addOrRemoveExt(self, extType, add): + """ + Remove or add an empty extension of given type. + + @type extType: int + @param extType: numeric id of extension to add or remove + @type add: boolean + @param add: whether to add (True) or remove (False) the extension + """ + if add: + self._addExt(extType) + else: + self._removeExt(extType) + class ClientHello(HelloMessage): """ @@ -293,14 +326,13 @@ class ClientHello(HelloMessage): """ def __init__(self, ssl2=False): - HandshakeMsg.__init__(self, HandshakeType.client_hello) + super(ClientHello, self).__init__(HandshakeType.client_hello) self.ssl2 = ssl2 self.client_version = (0, 0) self.random = bytearray(32) self.session_id = bytearray(0) self.cipher_suites = [] # a list of 16-bit values self.compression_methods = [] # a list of 8-bit values - self.extensions = None def __str__(self): """ @@ -411,12 +443,7 @@ def tack(self): @rtype: boolean @deprecated: use extensions field to get the extension for inspection """ - tack_ext = self.getExtension(ExtensionType.tack) - - if tack_ext is None: - return False - else: - return True + return self.getExtension(ExtensionType.tack) is not None @tack.setter def tack(self, present): @@ -427,19 +454,7 @@ def tack(self, present): @param present: True will create extension while False will remove extension from client hello """ - if present: - tack_ext = self.getExtension(ExtensionType.tack) - if tack_ext is None: - ext = TLSExtension().create(ExtensionType.tack, bytearray(0)) - self.addExtension(ext) - else: - return - else: - if self.extensions is None: - return - # remove all extensions of this type without changing reference - self.extensions[:] = [ext for ext in self.extensions if - ext.extType != ExtensionType.tack] + self._addOrRemoveExt(ExtensionType.tack, present) @property def supports_npn(self): @@ -449,12 +464,7 @@ def supports_npn(self): @rtype: boolean @deprecated: use extensions field to get the extension for inspection """ - npn_ext = self.getExtension(ExtensionType.supports_npn) - - if npn_ext is None: - return False - else: - return True + return self.getExtension(ExtensionType.supports_npn) is not None @supports_npn.setter def supports_npn(self, present): @@ -465,20 +475,7 @@ def supports_npn(self, present): @param present: selects whatever to create or remove the extension from list of supported ones """ - if present: - npn_ext = self.getExtension(ExtensionType.supports_npn) - if npn_ext is None: - ext = TLSExtension().create(ExtensionType.supports_npn, - bytearray(0)) - self.addExtension(ext) - else: - return - else: - if self.extensions is None: - return - # remove all extension of this type without changing reference - self.extensions[:] = [ext for ext in self.extensions if - ext.extType != ExtensionType.supports_npn] + self._addOrRemoveExt(ExtensionType.supports_npn, present) @property def server_name(self): @@ -693,14 +690,13 @@ class ServerHello(HelloMessage): def __init__(self): """Initialise ServerHello object.""" - HandshakeMsg.__init__(self, HandshakeType.server_hello) + super(ServerHello, self).__init__(HandshakeType.server_hello) self.server_version = (0, 0) self.random = bytearray(32) self.session_id = bytearray(0) self.cipher_suite = 0 self.compression_method = 0 self._tack_ext = None - self.extensions = None def __str__(self): base = "server_hello,length({0}),version({1[0]}.{1[1]}),random(...),"\ From 66fdb337c29e49b364aa07fca5a92bbace43a4f9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 29 Jul 2016 20:15:52 +0200 Subject: [PATCH 325/574] simplify Alert string formatting --- tlslite/messages.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index f2101239..1dd22b13 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -192,23 +192,22 @@ def write(self): w.add(self.description, 1) return w.bytes + @staticmethod + def _noneAsUnknown(text, number): + """if text is None or empty, format number as 'unknown(number)'""" + if not text: + text = "unknown({0})".format(number) + return text + @property def levelName(self): - matching = [x[0] for x in AlertLevel.__dict__.items() - if x[1] == self.level] - if len(matching) == 0: - return "unknown({0})".format(self.level) - else: - return str(matching[0]) + return self._noneAsUnknown(AlertLevel.toRepr(self.level), + self.level) @property def descriptionName(self): - matching = [x[0] for x in AlertDescription.__dict__.items() - if x[1] == self.description] - if len(matching) == 0: - return "unknown({0})".format(self.description) - else: - return str(matching[0]) + return self._noneAsUnknown(AlertDescription.toRepr(self.description), + self.description) def __str__(self): return "Alert, level:{0}, description:{1}".format(self.levelName, From 67da052fcbe3486caa35752ca58dfe0303ae3e74 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 2 Aug 2016 16:47:27 +0200 Subject: [PATCH 326/574] add info that hypothesis depends on enum34 on python2 --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7bdf7cfd..1ff5f608 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,6 +39,7 @@ The python module dependencies are as follows: * diff_cover * coverage * hypothesis +* enum34 (for Python2, in case of new hypothesis package) On Fedora they can be installed using: From 3be34f9d9c9d073aa94999adce5ddebcaa954ebc Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 13:14:26 +0200 Subject: [PATCH 327/574] line formatting make code fit 80 columns --- tlslite/utils/rijndael.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/tlslite/utils/rijndael.py b/tlslite/utils/rijndael.py index a1d720a2..676510f7 100644 --- a/tlslite/utils/rijndael.py +++ b/tlslite/utils/rijndael.py @@ -41,7 +41,9 @@ [[0, 0], [1, 7], [3, 5], [4, 4]]] # [keysize][block_size] -num_rounds = {16: {16: 10, 24: 12, 32: 14}, 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}} +num_rounds = {16: {16: 10, 24: 12, 32: 14}, + 24: {16: 12, 24: 12, 32: 14}, + 32: {16: 14, 24: 14, 32: 14}} A = [[1, 1, 1, 1, 1, 0, 0, 0], [0, 1, 1, 1, 1, 1, 0, 0], @@ -123,7 +125,8 @@ def mul(a, b): pivot = AA[i][i] for j in range(8): if AA[i][j] != 0: - AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255] + AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - + log[pivot & 0xFF]) % 255] for t in range(4): if i != t: for j in range(i+1, 8): @@ -277,7 +280,8 @@ def __init__(self, key, block_size = 16): def encrypt(self, plaintext): if len(plaintext) != self.block_size: - raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext))) + raise ValueError('wrong block length, expected {0} got {1}' + .format(self.block_size, len(plaintext))) Ke = self.Ke BC = self.block_size // 4 @@ -312,15 +316,16 @@ def encrypt(self, plaintext): result = [] for i in range(BC): tt = Ke[ROUNDS][i] - result.append((S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) - result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) - result.append((S[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) - result.append((S[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF) + result.append((S[(t[ i ] >> 24) & 0xFF] ^ (tt>>24)) & 0xFF) + result.append((S[(t[(i+s1) % BC] >> 16) & 0xFF] ^ (tt>>16)) & 0xFF) + result.append((S[(t[(i+s2) % BC] >> 8) & 0xFF] ^ (tt>> 8)) & 0xFF) + result.append((S[ t[(i+s3) % BC] & 0xFF] ^ tt ) & 0xFF) return bytearray(result) def decrypt(self, ciphertext): if len(ciphertext) != self.block_size: - raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext))) + raise ValueError('wrong block length, expected {0} got {1}' + .format(self.block_size, len(plaintext))) Kd = self.Kd BC = self.block_size // 4 @@ -355,10 +360,10 @@ def decrypt(self, ciphertext): result = [] for i in range(BC): tt = Kd[ROUNDS][i] - result.append((Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) - result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) - result.append((Si[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) - result.append((Si[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF) + result.append((Si[(t[ i ] >> 24) & 0xFF] ^ (tt>>24)) &0xFF) + result.append((Si[(t[(i+s1) % BC] >> 16) & 0xFF] ^ (tt>>16)) &0xFF) + result.append((Si[(t[(i+s2) % BC] >> 8) & 0xFF] ^ (tt>> 8)) &0xFF) + result.append((Si[ t[(i+s3) % BC] & 0xFF] ^ tt ) &0xFF) return bytearray(result) def encrypt(key, block): From 79a6021b02cf38bcd7d4f0b57ae3b8977eb465ca Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 13:15:22 +0200 Subject: [PATCH 328/574] sanity check for rijndael implementation --- unit_tests/test_tlslite_utils_rijndael.py | 51 +++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 unit_tests/test_tlslite_utils_rijndael.py diff --git a/unit_tests/test_tlslite_utils_rijndael.py b/unit_tests/test_tlslite_utils_rijndael.py new file mode 100644 index 00000000..aa297f39 --- /dev/null +++ b/unit_tests/test_tlslite_utils_rijndael.py @@ -0,0 +1,51 @@ +# Copyright (c) 2016, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +from __future__ import division +try: + import unittest2 as unittest +except ImportError: + import unittest + +import tlslite.utils.rijndael as rijndael + +class TestConstants(unittest.TestCase): + pass + +class TestSelfDecryptEncrypt(unittest.TestCase): + def enc_dec(self, k_len, b_len): + plaintext = bytearray(b'b' * b_len) + cipher = rijndael.rijndael(bytearray(b'a' * k_len), b_len) + self.assertEqual(plaintext, + cipher.decrypt(cipher.encrypt(plaintext))) + + def test_16_16(self): + self.enc_dec(16, 16) + + def test_16_24(self): + self.enc_dec(16, 24) + + def test_16_32(self): + self.enc_dec(16, 32) + + def test_24_16(self): + self.enc_dec(24, 16) + + def test_24_24(self): + self.enc_dec(24, 24) + + def test_24_32(self): + self.enc_dec(24, 32) + + def test_32_16(self): + self.enc_dec(32, 16) + + def test_32_24(self): + self.enc_dec(32, 24) + + def test_32_32(self): + self.enc_dec(32, 32) + From cb7b83d6f9df0d16a5d602bd0fb3650e888dc564 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 13:16:39 +0200 Subject: [PATCH 329/574] remove unused import --- tlslite/utils/rijndael.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tlslite/utils/rijndael.py b/tlslite/utils/rijndael.py index 676510f7..88371d3d 100644 --- a/tlslite/utils/rijndael.py +++ b/tlslite/utils/rijndael.py @@ -34,7 +34,6 @@ # deleting all the comments and renaming all the variables import copy -import string shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]], [[0, 0], [1, 5], [2, 4], [3, 3]], From 01dbfbcc821e914b37761d3b3dedb89eff32898a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 13:19:01 +0200 Subject: [PATCH 330/574] use faster array copy mechanism there's no need to use external module to just copy an array, especially if built in methods are faster --- tlslite/utils/rijndael.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tlslite/utils/rijndael.py b/tlslite/utils/rijndael.py index 88371d3d..3627eaed 100644 --- a/tlslite/utils/rijndael.py +++ b/tlslite/utils/rijndael.py @@ -33,8 +33,6 @@ # code, in which case it can be made public domain by # deleting all the comments and renaming all the variables -import copy - shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]], [[0, 0], [1, 5], [2, 4], [3, 3]], [[0, 0], [1, 7], [3, 5], [4, 4]]] @@ -310,7 +308,7 @@ def encrypt(self, plaintext): T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^ T3[(t[(i + s2) % BC] >> 8) & 0xFF] ^ T4[ t[(i + s3) % BC] & 0xFF] ) ^ Ke[r][i] - t = copy.copy(a) + t = a[:] # last round is special result = [] for i in range(BC): @@ -354,7 +352,7 @@ def decrypt(self, ciphertext): T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^ T7[(t[(i + s2) % BC] >> 8) & 0xFF] ^ T8[ t[(i + s3) % BC] & 0xFF] ) ^ Kd[r][i] - t = copy.copy(a) + t = a[:] # last round is special result = [] for i in range(BC): From 5fc321dabfd0e276dc91d0c93d0381d0e0a2ad7d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 13:32:58 +0200 Subject: [PATCH 331/574] test case for AES constants --- unit_tests/test_tlslite_utils_rijndael.py | 204 +++++++++++++++++++++- 1 file changed, 203 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_utils_rijndael.py b/unit_tests/test_tlslite_utils_rijndael.py index aa297f39..89f69c81 100644 --- a/unit_tests/test_tlslite_utils_rijndael.py +++ b/unit_tests/test_tlslite_utils_rijndael.py @@ -13,7 +13,209 @@ import tlslite.utils.rijndael as rijndael class TestConstants(unittest.TestCase): - pass + def setUp(self): + A = [[1, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 1, 1, 1, 1, 1], + [1, 0, 0, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 0, 1, 1], + [1, 1, 1, 1, 0, 0, 0, 1]] + + # produce log and alog tables, needed for multiplying in the + # field GF(2^m) (generator = 3) + alog = [1] + for i in range(255): + j = (alog[-1] << 1) ^ alog[-1] + if j & 0x100 != 0: + j ^= 0x11B + alog.append(j) + + log = [0] * 256 + for i in range(1, 255): + log[alog[i]] = i + + # multiply two elements of GF(2^m) + def mul(a, b): + if a == 0 or b == 0: + return 0 + return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255] + + # substitution box based on F^{-1}(x) + box = [[0] * 8 for i in range(256)] + box[1][7] = 1 + for i in range(2, 256): + j = alog[255 - log[i]] + for t in range(8): + box[i][t] = (j >> (7 - t)) & 0x01 + + B = [0, 1, 1, 0, 0, 0, 1, 1] + + # affine transform: box[i] <- B + A*box[i] + cox = [[0] * 8 for i in range(256)] + for i in range(256): + for t in range(8): + cox[i][t] = B[t] + for j in range(8): + cox[i][t] ^= A[t][j] * box[i][j] + + # S-boxes and inverse S-boxes + S = [0] * 256 + Si = [0] * 256 + for i in range(256): + S[i] = cox[i][0] << 7 + for t in range(1, 8): + S[i] ^= cox[i][t] << (7-t) + Si[S[i] & 0xFF] = i + + # T-boxes + G = [[2, 1, 1, 3], + [3, 2, 1, 1], + [1, 3, 2, 1], + [1, 1, 3, 2]] + + AA = [[0] * 8 for i in range(4)] + + for i in range(4): + for j in range(4): + AA[i][j] = G[i][j] + AA[i][i+4] = 1 + + for i in range(4): + pivot = AA[i][i] + if pivot == 0: + t = i + 1 + while AA[t][i] == 0 and t < 4: + t += 1 + assert t != 4, 'G matrix must be invertible' + for j in range(8): + AA[i][j], AA[t][j] = AA[t][j], AA[i][j] + pivot = AA[i][i] + for j in range(8): + if AA[i][j] != 0: + AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - + log[pivot & 0xFF]) % 255] + for t in range(4): + if i != t: + for j in range(i+1, 8): + AA[t][j] ^= mul(AA[i][j], AA[t][i]) + AA[t][i] = 0 + + iG = [[0] * 4 for i in range(4)] + + for i in range(4): + for j in range(4): + iG[i][j] = AA[i][j + 4] + + def mul4(a, bs): + if a == 0: + return 0 + r = 0 + for b in bs: + r <<= 8 + if b != 0: + r = r | mul(a, b) + return r + + T1 = [] + T2 = [] + T3 = [] + T4 = [] + T5 = [] + T6 = [] + T7 = [] + T8 = [] + U1 = [] + U2 = [] + U3 = [] + U4 = [] + + for t in range(256): + s = S[t] + T1.append(mul4(s, G[0])) + T2.append(mul4(s, G[1])) + T3.append(mul4(s, G[2])) + T4.append(mul4(s, G[3])) + + s = Si[t] + T5.append(mul4(s, iG[0])) + T6.append(mul4(s, iG[1])) + T7.append(mul4(s, iG[2])) + T8.append(mul4(s, iG[3])) + + U1.append(mul4(t, iG[0])) + U2.append(mul4(t, iG[1])) + U3.append(mul4(t, iG[2])) + U4.append(mul4(t, iG[3])) + + # round constants + rcon = [1] + r = 1 + for t in range(1, 30): + r = mul(2, r) + rcon.append(r) + + self.S = S + self.Si = Si + self.T1 = T1 + self.T2 = T2 + self.T3 = T3 + self.T4 = T4 + self.T5 = T5 + self.T6 = T6 + self.T7 = T7 + self.T8 = T8 + self.U1 = U1 + self.U2 = U2 + self.U3 = U3 + self.U4 = U4 + self.rcon = rcon + + def test_S_box(self): + self.assertEqual(rijndael.S, self.S) + + def test_Si_box(self): + self.assertEqual(rijndael.Si, self.Si) + + def test_T1(self): + self.assertEqual(rijndael.T1, self.T1) + + def test_T2(self): + self.assertEqual(rijndael.T2, self.T2) + + def test_T3(self): + self.assertEqual(rijndael.T3, self.T3) + + def test_T4(self): + self.assertEqual(rijndael.T4, self.T4) + + def test_T5(self): + self.assertEqual(rijndael.T5, self.T5) + + def test_T6(self): + self.assertEqual(rijndael.T6, self.T6) + + def test_T7(self): + self.assertEqual(rijndael.T7, self.T7) + + def test_T8(self): + self.assertEqual(rijndael.T8, self.T8) + + def test_U1(self): + self.assertEqual(rijndael.U1, self.U1) + + def test_U2(self): + self.assertEqual(rijndael.U2, self.U2) + + def test_U3(self): + self.assertEqual(rijndael.U3, self.U3) + + def test_U4(self): + self.assertEqual(rijndael.U4, self.U4) + + def test_rcon(self): + self.assertEqual(rijndael.rcon, self.rcon) class TestSelfDecryptEncrypt(unittest.TestCase): def enc_dec(self, k_len, b_len): From 667b1ebee8bd34a4d813736eb6cb81084ec95ff7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 13:45:43 +0200 Subject: [PATCH 332/574] make the constants immutable --- tlslite/utils/rijndael.py | 16 ++++++++++++ unit_tests/test_tlslite_utils_rijndael.py | 30 +++++++++++------------ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/tlslite/utils/rijndael.py b/tlslite/utils/rijndael.py index 3627eaed..f2e926e2 100644 --- a/tlslite/utils/rijndael.py +++ b/tlslite/utils/rijndael.py @@ -202,6 +202,22 @@ def mul4(a, bs): del cox del iG +S = tuple(S) +Si = tuple(Si) +T1 = tuple(T1) +T2 = tuple(T2) +T3 = tuple(T3) +T4 = tuple(T4) +T5 = tuple(T5) +T6 = tuple(T6) +T7 = tuple(T7) +T8 = tuple(T8) +U1 = tuple(U1) +U2 = tuple(U2) +U3 = tuple(U3) +U4 = tuple(U4) +rcon = tuple(rcon) + class rijndael: def __init__(self, key, block_size = 16): if block_size != 16 and block_size != 24 and block_size != 32: diff --git a/unit_tests/test_tlslite_utils_rijndael.py b/unit_tests/test_tlslite_utils_rijndael.py index 89f69c81..67087a7c 100644 --- a/unit_tests/test_tlslite_utils_rijndael.py +++ b/unit_tests/test_tlslite_utils_rijndael.py @@ -156,21 +156,21 @@ def mul4(a, bs): r = mul(2, r) rcon.append(r) - self.S = S - self.Si = Si - self.T1 = T1 - self.T2 = T2 - self.T3 = T3 - self.T4 = T4 - self.T5 = T5 - self.T6 = T6 - self.T7 = T7 - self.T8 = T8 - self.U1 = U1 - self.U2 = U2 - self.U3 = U3 - self.U4 = U4 - self.rcon = rcon + self.S = tuple(S) + self.Si = tuple(Si) + self.T1 = tuple(T1) + self.T2 = tuple(T2) + self.T3 = tuple(T3) + self.T4 = tuple(T4) + self.T5 = tuple(T5) + self.T6 = tuple(T6) + self.T7 = tuple(T7) + self.T8 = tuple(T8) + self.U1 = tuple(U1) + self.U2 = tuple(U2) + self.U3 = tuple(U3) + self.U4 = tuple(U4) + self.rcon = tuple(rcon) def test_S_box(self): self.assertEqual(rijndael.S, self.S) From d2a12d80694bd64cd89e81b32a078dc723b845c8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 14:17:31 +0200 Subject: [PATCH 333/574] precompute S, T and U-boxes for aes --- tlslite/utils/rijndael.py | 1027 ++++++++++++++++++++++++++++++------- 1 file changed, 852 insertions(+), 175 deletions(-) diff --git a/tlslite/utils/rijndael.py b/tlslite/utils/rijndael.py index f2e926e2..821e1c76 100644 --- a/tlslite/utils/rijndael.py +++ b/tlslite/utils/rijndael.py @@ -42,181 +42,858 @@ 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}} -A = [[1, 1, 1, 1, 1, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 0, 0], - [0, 0, 1, 1, 1, 1, 1, 0], - [0, 0, 0, 1, 1, 1, 1, 1], - [1, 0, 0, 0, 1, 1, 1, 1], - [1, 1, 0, 0, 0, 1, 1, 1], - [1, 1, 1, 0, 0, 0, 1, 1], - [1, 1, 1, 1, 0, 0, 0, 1]] - -# produce log and alog tables, needed for multiplying in the -# field GF(2^m) (generator = 3) -alog = [1] -for i in range(255): - j = (alog[-1] << 1) ^ alog[-1] - if j & 0x100 != 0: - j ^= 0x11B - alog.append(j) - -log = [0] * 256 -for i in range(1, 255): - log[alog[i]] = i - -# multiply two elements of GF(2^m) -def mul(a, b): - if a == 0 or b == 0: - return 0 - return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255] - -# substitution box based on F^{-1}(x) -box = [[0] * 8 for i in range(256)] -box[1][7] = 1 -for i in range(2, 256): - j = alog[255 - log[i]] - for t in range(8): - box[i][t] = (j >> (7 - t)) & 0x01 - -B = [0, 1, 1, 0, 0, 0, 1, 1] - -# affine transform: box[i] <- B + A*box[i] -cox = [[0] * 8 for i in range(256)] -for i in range(256): - for t in range(8): - cox[i][t] = B[t] - for j in range(8): - cox[i][t] ^= A[t][j] * box[i][j] - -# S-boxes and inverse S-boxes -S = [0] * 256 -Si = [0] * 256 -for i in range(256): - S[i] = cox[i][0] << 7 - for t in range(1, 8): - S[i] ^= cox[i][t] << (7-t) - Si[S[i] & 0xFF] = i - -# T-boxes -G = [[2, 1, 1, 3], - [3, 2, 1, 1], - [1, 3, 2, 1], - [1, 1, 3, 2]] - -AA = [[0] * 8 for i in range(4)] - -for i in range(4): - for j in range(4): - AA[i][j] = G[i][j] - AA[i][i+4] = 1 - -for i in range(4): - pivot = AA[i][i] - if pivot == 0: - t = i + 1 - while AA[t][i] == 0 and t < 4: - t += 1 - assert t != 4, 'G matrix must be invertible' - for j in range(8): - AA[i][j], AA[t][j] = AA[t][j], AA[i][j] - pivot = AA[i][i] - for j in range(8): - if AA[i][j] != 0: - AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - - log[pivot & 0xFF]) % 255] - for t in range(4): - if i != t: - for j in range(i+1, 8): - AA[t][j] ^= mul(AA[i][j], AA[t][i]) - AA[t][i] = 0 - -iG = [[0] * 4 for i in range(4)] - -for i in range(4): - for j in range(4): - iG[i][j] = AA[i][j + 4] - -def mul4(a, bs): - if a == 0: - return 0 - r = 0 - for b in bs: - r <<= 8 - if b != 0: - r = r | mul(a, b) - return r - -T1 = [] -T2 = [] -T3 = [] -T4 = [] -T5 = [] -T6 = [] -T7 = [] -T8 = [] -U1 = [] -U2 = [] -U3 = [] -U4 = [] - -for t in range(256): - s = S[t] - T1.append(mul4(s, G[0])) - T2.append(mul4(s, G[1])) - T3.append(mul4(s, G[2])) - T4.append(mul4(s, G[3])) - - s = Si[t] - T5.append(mul4(s, iG[0])) - T6.append(mul4(s, iG[1])) - T7.append(mul4(s, iG[2])) - T8.append(mul4(s, iG[3])) - - U1.append(mul4(t, iG[0])) - U2.append(mul4(t, iG[1])) - U3.append(mul4(t, iG[2])) - U4.append(mul4(t, iG[3])) - -# round constants -rcon = [1] -r = 1 -for t in range(1, 30): - r = mul(2, r) - rcon.append(r) - -del A -del AA -del pivot -del B -del G -del box -del log -del alog -del i -del j -del r -del s -del t -del mul -del mul4 -del cox -del iG - -S = tuple(S) -Si = tuple(Si) -T1 = tuple(T1) -T2 = tuple(T2) -T3 = tuple(T3) -T4 = tuple(T4) -T5 = tuple(T5) -T6 = tuple(T6) -T7 = tuple(T7) -T8 = tuple(T8) -U1 = tuple(U1) -U2 = tuple(U2) -U3 = tuple(U3) -U4 = tuple(U4) -rcon = tuple(rcon) +# S box +S = (99, 124, 119, 123, 242, 107, 111, 197, + 48, 1, 103, 43, 254, 215, 171, 118, + 202, 130, 201, 125, 250, 89, 71, 240, + 173, 212, 162, 175, 156, 164, 114, 192, + 183, 253, 147, 38, 54, 63, 247, 204, + 52, 165, 229, 241, 113, 216, 49, 21, + 4, 199, 35, 195, 24, 150, 5, 154, + 7, 18, 128, 226, 235, 39, 178, 117, + 9, 131, 44, 26, 27, 110, 90, 160, + 82, 59, 214, 179, 41, 227, 47, 132, + 83, 209, 0, 237, 32, 252, 177, 91, + 106, 203, 190, 57, 74, 76, 88, 207, + 208, 239, 170, 251, 67, 77, 51, 133, + 69, 249, 2, 127, 80, 60, 159, 168, + 81, 163, 64, 143, 146, 157, 56, 245, + 188, 182, 218, 33, 16, 255, 243, 210, + 205, 12, 19, 236, 95, 151, 68, 23, + 196, 167, 126, 61, 100, 93, 25, 115, + 96, 129, 79, 220, 34, 42, 144, 136, + 70, 238, 184, 20, 222, 94, 11, 219, + 224, 50, 58, 10, 73, 6, 36, 92, + 194, 211, 172, 98, 145, 149, 228, 121, + 231, 200, 55, 109, 141, 213, 78, 169, + 108, 86, 244, 234, 101, 122, 174, 8, + 186, 120, 37, 46, 28, 166, 180, 198, + 232, 221, 116, 31, 75, 189, 139, 138, + 112, 62, 181, 102, 72, 3, 246, 14, + 97, 53, 87, 185, 134, 193, 29, 158, + 225, 248, 152, 17, 105, 217, 142, 148, + 155, 30, 135, 233, 206, 85, 40, 223, + 140, 161, 137, 13, 191, 230, 66, 104, + 65, 153, 45, 15, 176, 84, 187, 22) + +# inverse of S box +Si = (82, 9, 106, 213, 48, 54, 165, 56, + 191, 64, 163, 158, 129, 243, 215, 251, + 124, 227, 57, 130, 155, 47, 255, 135, + 52, 142, 67, 68, 196, 222, 233, 203, + 84, 123, 148, 50, 166, 194, 35, 61, + 238, 76, 149, 11, 66, 250, 195, 78, + 8, 46, 161, 102, 40, 217, 36, 178, + 118, 91, 162, 73, 109, 139, 209, 37, + 114, 248, 246, 100, 134, 104, 152, 22, + 212, 164, 92, 204, 93, 101, 182, 146, + 108, 112, 72, 80, 253, 237, 185, 218, + 94, 21, 70, 87, 167, 141, 157, 132, + 144, 216, 171, 0, 140, 188, 211, 10, + 247, 228, 88, 5, 184, 179, 69, 6, + 208, 44, 30, 143, 202, 63, 15, 2, + 193, 175, 189, 3, 1, 19, 138, 107, + 58, 145, 17, 65, 79, 103, 220, 234, + 151, 242, 207, 206, 240, 180, 230, 115, + 150, 172, 116, 34, 231, 173, 53, 133, + 226, 249, 55, 232, 28, 117, 223, 110, + 71, 241, 26, 113, 29, 41, 197, 137, + 111, 183, 98, 14, 170, 24, 190, 27, + 252, 86, 62, 75, 198, 210, 121, 32, + 154, 219, 192, 254, 120, 205, 90, 244, + 31, 221, 168, 51, 136, 7, 199, 49, + 177, 18, 16, 89, 39, 128, 236, 95, + 96, 81, 127, 169, 25, 181, 74, 13, + 45, 229, 122, 159, 147, 201, 156, 239, + 160, 224, 59, 77, 174, 42, 245, 176, + 200, 235, 187, 60, 131, 83, 153, 97, + 23, 43, 4, 126, 186, 119, 214, 38, + 225, 105, 20, 99, 85, 33, 12, 125) + +T1 = (0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, + 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, + 0x60303050, 0x2010103, 0xce6767a9, 0x562b2b7d, + 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, + 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, + 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, + 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, + 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, + 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, + 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, + 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, + 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, + 0x804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, + 0x30181828, 0x379696a1, 0xa05050f, 0x2f9a9ab5, + 0xe070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, + 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, + 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, + 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, + 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, + 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, + 0xa65353f5, 0xb9d1d168, 0x0, 0xc1eded2c, + 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, + 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, + 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, + 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, + 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, + 0x8a4545cf, 0xe9f9f910, 0x4020206, 0xfe7f7f81, + 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, + 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x58f8f8a, + 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, + 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, + 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, + 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, + 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, + 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, + 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, + 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, + 0x44222266, 0x542a2a7e, 0x3b9090ab, 0xb888883, + 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, + 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, + 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, + 0x924949db, 0xc06060a, 0x4824246c, 0xb85c5ce4, + 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, + 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, + 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, + 0x18d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, + 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, + 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, + 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, + 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, + 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, + 0x964b4bdd, 0x61bdbddc, 0xd8b8b86, 0xf8a8a85, + 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, + 0x904848d8, 0x6030305, 0xf7f6f601, 0x1c0e0e12, + 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, + 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, + 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, + 0xd26969bb, 0xa9d9d970, 0x78e8e89, 0x339494a7, + 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, + 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, + 0x38c8c8f, 0x59a1a1f8, 0x9898980, 0x1a0d0d17, + 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, + 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, + 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a) + +T2 = (0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, + 0xdfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, + 0x50603030, 0x3020101, 0xa9ce6767, 0x7d562b2b, + 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, + 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, + 0x15effafa, 0xebb25959, 0xc98e4747, 0xbfbf0f0, + 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, + 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, + 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, + 0x5a6c3636, 0x417e3f3f, 0x2f5f7f7, 0x4f83cccc, + 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x8f9f1f1, + 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, + 0xc080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, + 0x28301818, 0xa1379696, 0xf0a0505, 0xb52f9a9a, + 0x90e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, + 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, + 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, + 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, + 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, + 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, + 0xf5a65353, 0x68b9d1d1, 0x0, 0x2cc1eded, + 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, + 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, + 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, + 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, + 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, + 0xcf8a4545, 0x10e9f9f9, 0x6040202, 0x81fe7f7f, + 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, + 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, + 0xad3f9292, 0xbc219d9d, 0x48703838, 0x4f1f5f5, + 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, + 0x30201010, 0x1ae5ffff, 0xefdf3f3, 0x6dbfd2d2, + 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, + 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, + 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, + 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, + 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, + 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, + 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, + 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, + 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, + 0xdb924949, 0xa0c0606, 0x6c482424, 0xe4b85c5c, + 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, + 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, + 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, + 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, + 0xb4d86c6c, 0xfaac5656, 0x7f3f4f4, 0x25cfeaea, + 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, + 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, + 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, + 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, + 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, + 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, + 0xd8904848, 0x5060303, 0x1f7f6f6, 0x121c0e0e, + 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, + 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, + 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, + 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, + 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, + 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, + 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, + 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, + 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, + 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616) + +T3 = (0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, + 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, + 0x30506030, 0x1030201, 0x67a9ce67, 0x2b7d562b, + 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, + 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, + 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, + 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, + 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, + 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, + 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, + 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, + 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, + 0x40c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, + 0x18283018, 0x96a13796, 0x50f0a05, 0x9ab52f9a, + 0x7090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, + 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, + 0x91b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, + 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, + 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, + 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, + 0x53f5a653, 0xd168b9d1, 0x0, 0xed2cc1ed, + 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, + 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, + 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, + 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, + 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, + 0x45cf8a45, 0xf910e9f9, 0x2060402, 0x7f81fe7f, + 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, + 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, + 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, + 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, + 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, + 0xcd4c81cd, 0xc14180c, 0x13352613, 0xec2fc3ec, + 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, + 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, + 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, + 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, + 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, + 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, + 0xde79a7de, 0x5ee2bc5e, 0xb1d160b, 0xdb76addb, + 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0xa1e140a, + 0x49db9249, 0x60a0c06, 0x246c4824, 0x5ce4b85c, + 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, + 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, + 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, + 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, + 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, + 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x8181008, + 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, + 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, + 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, + 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, + 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, + 0x48d89048, 0x3050603, 0xf601f7f6, 0xe121c0e, + 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, + 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, + 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, + 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, + 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, + 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, + 0x8c8f038c, 0xa1f859a1, 0x89800989, 0xd171a0d, + 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, + 0x41c38241, 0x99b02999, 0x2d775a2d, 0xf111e0f, + 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16) + +T4 = (0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, + 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, + 0x30305060, 0x1010302, 0x6767a9ce, 0x2b2b7d56, + 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, + 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, + 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, + 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, + 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, + 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, + 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, + 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, + 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, + 0x4040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, + 0x18182830, 0x9696a137, 0x5050f0a, 0x9a9ab52f, + 0x707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, + 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, + 0x9091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, + 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, + 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, + 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, + 0x5353f5a6, 0xd1d168b9, 0x0, 0xeded2cc1, + 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, + 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, + 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, + 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, + 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, + 0x4545cf8a, 0xf9f910e9, 0x2020604, 0x7f7f81fe, + 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, + 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, + 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, + 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, + 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, + 0xcdcd4c81, 0xc0c1418, 0x13133526, 0xecec2fc3, + 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, + 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, + 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, + 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, + 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, + 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, + 0xdede79a7, 0x5e5ee2bc, 0xb0b1d16, 0xdbdb76ad, + 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0xa0a1e14, + 0x4949db92, 0x6060a0c, 0x24246c48, 0x5c5ce4b8, + 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, + 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, + 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, + 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, + 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, + 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x8081810, + 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, + 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, + 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, + 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, + 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, + 0x4848d890, 0x3030506, 0xf6f601f7, 0xe0e121c, + 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, + 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, + 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, + 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, + 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, + 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, + 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0xd0d171a, + 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, + 0x4141c382, 0x9999b029, 0x2d2d775a, 0xf0f111e, + 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c) + +T5 = (0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, + 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, + 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, + 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, + 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, + 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, + 0x38f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, + 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, + 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, + 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, + 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, + 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, + 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, + 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, + 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, + 0x302887f2, 0x23bfa5b2, 0x2036aba, 0xed16825c, + 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, + 0x65daf4cd, 0x605bed5, 0xd134621f, 0xc4a6fe8a, + 0x342e539d, 0xa2f355a0, 0x58ae132, 0xa4f6eb75, + 0xb83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, + 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, + 0x91548db5, 0x71c45d05, 0x406d46f, 0x605015ff, + 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, + 0xb0e842bd, 0x7898b88, 0xe7195b38, 0x79c8eedb, + 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x0, + 0x9808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, + 0xfd0efffb, 0xf853856, 0x3daed51e, 0x362d3927, + 0xa0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, + 0xc0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, + 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, + 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, + 0xe090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, + 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, + 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, + 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, + 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, + 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, + 0x1d9e2f4b, 0xdcb230f3, 0xd8652ec, 0x77c1e3d0, + 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, + 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, + 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, + 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, + 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, + 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, + 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, + 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, + 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, + 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, + 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, + 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, + 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, + 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, + 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, + 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, + 0x9d5eea04, 0x18c355d, 0xfa877473, 0xfb0b412e, + 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, + 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, + 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, + 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, + 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, + 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, + 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, + 0x39a80171, 0x80cb3de, 0xd8b4e49c, 0x6456c190, + 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742) + +T6 = (0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, + 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, + 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, + 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, + 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, + 0x2c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, + 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, + 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, + 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, + 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, + 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, + 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, + 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, + 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, + 0x7b2eb28, 0x32fb5c2, 0x9a86c57b, 0xa5d33708, + 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, + 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, + 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, + 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, + 0x390b83ec, 0xaa4060ef, 0x65e719f, 0x51bd6e10, + 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, + 0xb591548d, 0x571c45d, 0x6f0406d4, 0xff605015, + 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, + 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, + 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x0, + 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, + 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, + 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, + 0xb10c0a67, 0xf9357e7, 0xd2b4ee96, 0x9e1b9b91, + 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, + 0xae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, + 0xb0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, + 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, + 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, + 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, + 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, + 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, + 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, + 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, + 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, + 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, + 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, + 0xe42c3a9d, 0xd507892, 0x9b6a5fcc, 0x62547e46, + 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, + 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, + 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, + 0x9cd2678, 0xf46e5918, 0x1ec9ab7, 0xa8834f9a, + 0x65e6956e, 0x7eaaffe6, 0x821bccf, 0xe6ef15e8, + 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, + 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, + 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, + 0x4af10498, 0xf741ecda, 0xe7fcd50, 0x2f1791f6, + 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, + 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, + 0x49d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, + 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, + 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, + 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, + 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, + 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, + 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, + 0x72161dc3, 0xcbce225, 0x8b283c49, 0x41ff0d95, + 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, + 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857) + +T7 = (0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, + 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x3934be3, + 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, + 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, + 0x5a49deb1, 0x1b6725ba, 0xe9845ea, 0xc0e15dfe, + 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, + 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, + 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, + 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, + 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, + 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, + 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, + 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, + 0xd323ab73, 0x2e2724b, 0x8f57e31f, 0xab2a6655, + 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x8a5d337, + 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, + 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, + 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, + 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, + 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, + 0x8af93e21, 0x63d96dd, 0x5aedd3e, 0xbd464de6, + 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, + 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, + 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, + 0xa47a17c, 0xfe97c42, 0x1ec9f884, 0x0, + 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, + 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, + 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, + 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, + 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, + 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, + 0xd0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, + 0x198557f1, 0x74caf75, 0xddbbee99, 0x60fda37f, + 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, + 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, + 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, + 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, + 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, + 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, + 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, + 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0xb3698d4, + 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, + 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, + 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, + 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, + 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, + 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, + 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, + 0x9bd9bae7, 0x36ce4a6f, 0x9d4ea9f, 0x7cd629b0, + 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, + 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, + 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, + 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x4dfe496, + 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, + 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, + 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, + 0x618c9ad7, 0xc7a37a1, 0x148e59f8, 0x3c89eb13, + 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, + 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, + 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, + 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, + 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, + 0x17139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, + 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8) + +T8 = (0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, + 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, + 0x30fa5520, 0x766df6ad, 0xcc769188, 0x24c25f5, + 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, + 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, + 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, + 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, + 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, + 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, + 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, + 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, + 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x82b94f9, + 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, + 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, + 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, + 0x2887f230, 0xbfa5b223, 0x36aba02, 0x16825ced, + 0xcf1c2b8a, 0x79b492a7, 0x7f2f0f3, 0x69e2a14e, + 0xdaf4cd65, 0x5bed506, 0x34621fd1, 0xa6fe8ac4, + 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, + 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, + 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, + 0x548db591, 0xc45d0571, 0x6d46f04, 0x5015ff60, + 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, + 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, + 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x0, + 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, + 0xefffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, + 0xfd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, + 0xa67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, + 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, + 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, + 0x90d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, + 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, + 0x1269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, + 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, + 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, + 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, + 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, + 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, + 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, + 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, + 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, + 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, + 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, + 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, + 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, + 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, + 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, + 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, + 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, + 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, + 0x4984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, + 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, + 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, + 0x5eea049d, 0x8c355d01, 0x877473fa, 0xb412efb, + 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, + 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, + 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, + 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, + 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, + 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, + 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0xd9541ff, + 0xa8017139, 0xcb3de08, 0xb4e49cd8, 0x56c19064, + 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0) + +U1 = (0x0, 0xe090d0b, 0x1c121a16, 0x121b171d, + 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, + 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, + 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, + 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, + 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, + 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, + 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, + 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, + 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, + 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, + 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, + 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, + 0x38f5fe7, 0xd8652ec, 0x1f9d45f1, 0x119448fa, + 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, + 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, + 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, + 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, + 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, + 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, + 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, + 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, + 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, + 0x58ae132, 0xb83ec39, 0x1998fb24, 0x1791f62f, + 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, + 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, + 0x605bed5, 0x80cb3de, 0x1a17a4c3, 0x141ea9c8, + 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, + 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, + 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, + 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, + 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, + 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, + 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, + 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, + 0x9808683, 0x7898b88, 0x15929c95, 0x1b9b919e, + 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, + 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, + 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, + 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, + 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, + 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, + 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, + 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, + 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, + 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, + 0xa0fd964, 0x406d46f, 0x161dc372, 0x1814ce79, + 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, + 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, + 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, + 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, + 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, + 0xc0a67b1, 0x2036aba, 0x10187da7, 0x1e1170ac, + 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, + 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, + 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, + 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, + 0xf853856, 0x18c355d, 0x13972240, 0x1d9e2f4b, + 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, + 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, + 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, + 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, + 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, + 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3) + +U2 = (0x0, 0xb0e090d, 0x161c121a, 0x1d121b17, + 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, + 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, + 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, + 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, + 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, + 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, + 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, + 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, + 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, + 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, + 0xf9357e7, 0x49d5eea, 0x198f45fd, 0x12814cf0, + 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, + 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, + 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, + 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, + 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, + 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, + 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, + 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, + 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, + 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, + 0x1e3daed5, 0x1533a7d8, 0x821bccf, 0x32fb5c2, + 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, + 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, + 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, + 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, + 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, + 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, + 0x11aef932, 0x1aa0f03f, 0x7b2eb28, 0xcbce225, + 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, + 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, + 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, + 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, + 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, + 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, + 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, + 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, + 0x1fd13462, 0x14df3d6f, 0x9cd2678, 0x2c32f75, + 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, + 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, + 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, + 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, + 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, + 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, + 0x10426385, 0x1b4c6a88, 0x65e719f, 0xd507892, + 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, + 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, + 0x1ec9ab7, 0xae293ba, 0x17f088ad, 0x1cfe81a0, + 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, + 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, + 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, + 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, + 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, + 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, + 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, + 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, + 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, + 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, + 0xe7fcd50, 0x571c45d, 0x1863df4a, 0x136dd647, + 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, + 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, + 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, + 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697) + +U3 = (0x0, 0xd0b0e09, 0x1a161c12, 0x171d121b, + 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, + 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, + 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, + 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, + 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, + 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, + 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, + 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, + 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, + 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, + 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, + 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, + 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, + 0x3934be3, 0xe9845ea, 0x198557f1, 0x148e59f8, + 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, + 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, + 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, + 0x5aedd3e, 0x8a5d337, 0x1fb8c12c, 0x12b3cf25, + 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, + 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, + 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, + 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, + 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, + 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, + 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, + 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, + 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, + 0x63d96dd, 0xb3698d4, 0x1c2b8acf, 0x112084c6, + 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, + 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, + 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, + 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, + 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, + 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, + 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, + 0xa47a17c, 0x74caf75, 0x1051bd6e, 0x1d5ab367, + 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, + 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, + 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, + 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, + 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, + 0x9d4ea9f, 0x4dfe496, 0x13c2f68d, 0x1ec9f884, + 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, + 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, + 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, + 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, + 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, + 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, + 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, + 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, + 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, + 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, + 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, + 0xfe97c42, 0x2e2724b, 0x15ff6050, 0x18f46e59, + 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, + 0xc7a37a1, 0x17139a8, 0x166c2bb3, 0x1b6725ba, + 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, + 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, + 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, + 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, + 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, + 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, + 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46) + +U4 = (0x0, 0x90d0b0e, 0x121a161c, 0x1b171d12, + 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, + 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, + 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, + 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, + 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, + 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, + 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, + 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, + 0x1f8f57e3, 0x16825ced, 0xd9541ff, 0x4984af1, + 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, + 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, + 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, + 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, + 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, + 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, + 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, + 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, + 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, + 0x1a3182e5, 0x133c89eb, 0x82b94f9, 0x1269ff7, + 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, + 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, + 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, + 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, + 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, + 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, + 0x5bed506, 0xcb3de08, 0x17a4c31a, 0x1ea9c814, + 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, + 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, + 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, + 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, + 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, + 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, + 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, + 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, + 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, + 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, + 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, + 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, + 0x105633e9, 0x195b38e7, 0x24c25f5, 0xb412efb, + 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, + 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, + 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, + 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, + 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, + 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, + 0xfd9640a, 0x6d46f04, 0x1dc37216, 0x14ce7918, + 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, + 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, + 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, + 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, + 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, + 0xa67b10c, 0x36aba02, 0x187da710, 0x1170ac1e, + 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, + 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, + 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, + 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, + 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, + 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, + 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, + 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, + 0x15e8e6ef, 0x1ce5ede1, 0x7f2f0f3, 0xefffbfd, + 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, + 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d) + +rcon = (0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, + 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, + 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, + 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91) class rijndael: def __init__(self, key, block_size = 16): From 0b20605ef8b34a668ceb5a477bf07cd8b3289153 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 4 Aug 2016 15:43:11 +0200 Subject: [PATCH 334/574] more test coverage for codec.Writer --- unit_tests/test_tlslite_utils_codec.py | 119 +++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/unit_tests/test_tlslite_utils_codec.py b/unit_tests/test_tlslite_utils_codec.py index c0215eeb..bc8c89d4 100644 --- a/unit_tests/test_tlslite_utils_codec.py +++ b/unit_tests/test_tlslite_utils_codec.py @@ -222,18 +222,93 @@ def test_add_with_multibyte_data(self): self.assertEqual(bytearray(b'\x02\x00'), w.bytes) + def test_add_with_three_byte_data(self): + w = Writer() + w.add(0xbacc01, 3) + + self.assertEqual(bytearray(b'\xba\xcc\x01'), w.bytes) + + def test_add_with_three_overflowing_data(self): + w = Writer() + + with self.assertRaises(ValueError): + w.add(0x01000000, 3) + + def test_add_with_three_underflowing_data(self): + w = Writer() + + with self.assertRaises(ValueError): + w.add(-1, 3) + def test_add_with_overflowing_data(self): w = Writer() with self.assertRaises(ValueError): w.add(256, 1) + def test_add_with_underflowing_data(self): + w = Writer() + + with self.assertRaises(ValueError): + w.add(-1, 1) + + def test_add_with_four_byte_data(self): + w = Writer() + w.add(0x01020304, 4) + + self.assertEqual(bytearray(b'\x01\x02\x03\x04'), w.bytes) + + def test_add_with_five_bytes_data(self): + w = Writer() + w.add(0x02, 5) + + self.assertEqual(bytearray(b'\x00\x00\x00\x00\x02'), w.bytes) + + def test_add_with_six_bytes_data(self): + w = Writer() + w.add(0x010203040506, 6) + + self.assertEqual(bytearray(b'\x01\x02\x03\x04\x05\x06'), w.bytes) + + def test_add_with_five_overflowing_bytes(self): + w = Writer() + + with self.assertRaises(ValueError): + w.add(0x010000000000, 5) + + def test_add_with_five_underflowing_bytes(self): + w = Writer() + + with self.assertRaises(ValueError): + w.add(-1, 5) + + def test_add_with_four_bytes_overflowing_data(self): + w = Writer() + + with self.assertRaises(ValueError): + w.add(0x0100000000, 4) + + def test_add_five_twice(self): + w = Writer() + w.add(0x0102030405, 5) + w.add(0x1112131415, 5) + + self.assertEqual(bytearray(b'\x01\x02\x03\x04\x05' + b'\x11\x12\x13\x14\x15'), + w.bytes) + def test_addFixSeq(self): w = Writer() w.addFixSeq([16,17,18], 2) self.assertEqual(bytearray(b'\x00\x10\x00\x11\x00\x12'), w.bytes) + def test_addFixSeq_with_overflowing_data(self): + w = Writer() + + with self.assertRaises(ValueError): + w.addFixSeq([16, 17, 256], 1) + def test_addVarSeq(self): w = Writer() w.addVarSeq([16, 17, 18], 2, 2) @@ -244,6 +319,44 @@ def test_addVarSeq(self): b'\x00\x11' + b'\x00\x12'), w.bytes) + def test_addVarSeq_single_byte_data(self): + w = Writer() + w.addVarSeq([0xaa, 0xbb, 0xcc], 1, 2) + + self.assertEqual(bytearray( + b'\x00\x03' + + b'\xaa' + + b'\xbb' + + b'\xcc'), w.bytes) + + def test_addVarSeq_triple_byte_data(self): + w = Writer() + w.addVarSeq([0xaa, 0xbb, 0xcc], 3, 2) + + self.assertEqual(bytearray( + b'\x00\x09' + + b'\x00\x00\xaa' + + b'\x00\x00\xbb' + + b'\x00\x00\xcc'), w.bytes) + + def test_addVarSeq_with_overflowing_data(self): + w = Writer() + + with self.assertRaises(ValueError): + w.addVarSeq([16, 17, 0x10000], 2, 2) + + def test_addVarSeq_with_one_byte_overflowing_data(self): + w = Writer() + + with self.assertRaises(ValueError): + w.addVarSeq([16, 17, 0x100], 1, 2) + + def test_addVarSeq_with_three_byte_overflowing_data(self): + w = Writer() + + with self.assertRaises(ValueError): + w.addVarSeq([16, 17, 0x1000000], 3, 2) + def test_bytes(self): w = Writer() w.bytes += bytearray(b'\xbe\xef') @@ -283,5 +396,11 @@ def test_addVarTupleSeq_with_invalid_sized_tuples(self): with self.assertRaises(ValueError): w.addVarTupleSeq([(1, 2), (2, 3, 9)], 1, 2) + def test_addVarTupleSeq_with_overflowing_data(self): + w = Writer() + + with self.assertRaises(ValueError): + w.addVarTupleSeq([(1, 2), (2, 256)], 1, 2) + if __name__ == '__main__': unittest.main() From ff9bd77d7a751f2440a6a7f419f6572a6ae35a82 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 23:24:28 +0200 Subject: [PATCH 335/574] document codec.Writer --- tlslite/utils/codec.py | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tlslite/utils/codec.py b/tlslite/utils/codec.py index 03fe60fe..3eb0fb2b 100644 --- a/tlslite/utils/codec.py +++ b/tlslite/utils/codec.py @@ -6,10 +6,25 @@ from .compat import * class Writer(object): + """Serialisation helper for complex byte-based structures.""" + def __init__(self): + """Initialise the serializer with no data.""" self.bytes = bytearray(0) def add(self, x, length): + """ + Add a single positive integer value x, encode it in length bytes + + Encode positive integer x in big-endian format using length bytes, + add to the internal buffer. + + @type x: int + @param x: value to encode + + @type length: int + @param length: number of bytes to use for encoding the value + """ self.bytes += bytearray(length) newIndex = len(self.bytes) - 1 for count in range(length): @@ -20,10 +35,38 @@ def add(self, x, length): raise ValueError("Can't represent value in specified length") def addFixSeq(self, seq, length): + """ + Add a list of items, encode every item in length bytes + + Uses the unbounded iterable seq to produce items, each of + which is then encoded to length bytes + + @type seq: iterable of int + @param seq: list of positive integers to encode + + @type length: int + @param length: number of bytes to which encode every element + """ for e in seq: self.add(e, length) def addVarSeq(self, seq, length, lengthLength): + """ + Add a bounded list of same-sized values + + Create a list of specific length with all items being of the same + size + + @type seq: list of int + @param seq: list of positive integers to encode + + @type length: int + @param length: amount of bytes in which to encode every item + + @type lengthLength: int + @param lengthLength: amount of bytes in which to encode the overall + length of the array + """ self.add(len(seq)*length, lengthLength) for e in seq: self.add(e, length) From 6208b5c39dacb4902cb59f7a8b0d8739ba8dee50 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 23:18:46 +0200 Subject: [PATCH 336/574] actually use the range object in Writer.add() --- tlslite/utils/codec.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tlslite/utils/codec.py b/tlslite/utils/codec.py index 3eb0fb2b..4a1be476 100644 --- a/tlslite/utils/codec.py +++ b/tlslite/utils/codec.py @@ -27,10 +27,9 @@ def add(self, x, length): """ self.bytes += bytearray(length) newIndex = len(self.bytes) - 1 - for count in range(length): - self.bytes[newIndex] = x & 0xFF + for i in range(newIndex, newIndex - length, -1): + self.bytes[i] = x & 0xFF x >>= 8 - newIndex -= 1 if x != 0: raise ValueError("Can't represent value in specified length") From 64d9d36027b6cdc12d46ea3bdd989ed007ab96d7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 4 Aug 2016 19:27:24 +0200 Subject: [PATCH 337/574] 4 times faster version of add() add() method is called thousands of times when assembling large Client Hello messages, make the method much faster for very tight loops, using dedicated add* methods is even faster (another 50% increase) leave the old code as a fallback in python2, python3 int.to_bytes is just as fast as struct.pack --- tlslite/utils/codec.py | 116 ++++++++++++++++++++++++++++++++++------- 1 file changed, 96 insertions(+), 20 deletions(-) diff --git a/tlslite/utils/codec.py b/tlslite/utils/codec.py index 4a1be476..ad06ab67 100644 --- a/tlslite/utils/codec.py +++ b/tlslite/utils/codec.py @@ -3,6 +3,9 @@ """Classes for reading/writing binary data (such as TLS records).""" +import sys +import struct +from struct import pack from .compat import * class Writer(object): @@ -12,26 +15,99 @@ def __init__(self): """Initialise the serializer with no data.""" self.bytes = bytearray(0) - def add(self, x, length): - """ - Add a single positive integer value x, encode it in length bytes - - Encode positive integer x in big-endian format using length bytes, - add to the internal buffer. - - @type x: int - @param x: value to encode - - @type length: int - @param length: number of bytes to use for encoding the value - """ - self.bytes += bytearray(length) - newIndex = len(self.bytes) - 1 - for i in range(newIndex, newIndex - length, -1): - self.bytes[i] = x & 0xFF - x >>= 8 - if x != 0: - raise ValueError("Can't represent value in specified length") + def addOne(self, val): + """Add a single-byte wide element to buffer, see add().""" + self.bytes.append(val) + + if sys.version_info < (2, 7): + # struct.pack on Python2.6 does not raise exception if the value + # is larger than can fit inside the specified size + def addTwo(self, val): + """Add a double-byte wide element to buffer, see add().""" + if not 0 <= val <= 0xffff: + raise ValueError("Can't represent value in specified length") + self.bytes += pack('>H', val) + + def addThree(self, val): + """Add a thee-byte wide element to buffer, see add().""" + if not 0 <= val <= 0xffffff: + raise ValueError("Can't represent value in specified length") + self.bytes += pack('>BH', val >> 16, val & 0xffff) + + def addFour(self, val): + """Add a four-byte wide element to buffer, see add().""" + if not 0 <= val <= 0xffffffff: + raise ValueError("Can't represent value in specified length") + self.bytes += pack('>I', val) + else: + def addTwo(self, val): + """Add a double-byte wide element to buffer, see add().""" + try: + self.bytes += pack('>H', val) + except struct.error: + raise ValueError("Can't represent value in specified length") + + def addThree(self, val): + """Add a thee-byte wide element to buffer, see add().""" + try: + self.bytes += pack('>BH', val >> 16, val & 0xffff) + except struct.error: + raise ValueError("Can't represent value in specified length") + + def addFour(self, val): + """Add a four-byte wide element to buffer, see add().""" + try: + self.bytes += pack('>I', val) + except struct.error: + raise ValueError("Can't represent value in specified length") + + if sys.version_info >= (3, 0): + # the method is called thousands of times, so it's better to extern + # the version info check + def add(self, x, length): + """ + Add a single positive integer value x, encode it in length bytes + + Encode positive integer x in big-endian format using length bytes, + add to the internal buffer. + + @type x: int + @param x: value to encode + + @type length: int + @param length: number of bytes to use for encoding the value + """ + try: + self.bytes += x.to_bytes(length, 'big') + except OverflowError: + raise ValueError("Can't represent value in specified length") + else: + _addMethods = {1: addOne, 2: addTwo, 3: addThree, 4: addFour} + + def add(self, x, length): + """ + Add a single positive integer value x, encode it in length bytes + + Encode positive iteger x in big-endian format using length bytes, + add to the internal buffer. + + @type x: int + @param x: value to encode + + @type length: int + @param length: number of bytes to use for encoding the value + """ + try: + self._addMethods[length](self, x) + except KeyError: + self.bytes += bytearray(length) + newIndex = len(self.bytes) - 1 + for i in range(newIndex, newIndex - length, -1): + self.bytes[i] = x & 0xFF + x >>= 8 + if x != 0: + raise ValueError("Can't represent value in specified " + "length") def addFixSeq(self, seq, length): """ From c3d838444dee56a1a86d9a411fbbf6b5e58f4bbb Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 4 Aug 2016 19:57:10 +0200 Subject: [PATCH 338/574] don't use addFixSeq() unless necessary since addFixSeq uses add() internally, it's slow, use w.bytes concatenation if possible --- tlslite/extensions.py | 7 ++++--- tlslite/messages.py | 10 +++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 0f0f1ff8..f6b9354c 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -159,9 +159,10 @@ def write(self): assert self.extType is not None w = Writer() - w.add(self.extType, 2) - w.add(len(self.extData), 2) - w.addFixSeq(self.extData, 1) + w.addTwo(self.extType) + data = self.extData + w.addTwo(len(data)) + w.bytes += data return w.bytes @staticmethod diff --git a/tlslite/messages.py b/tlslite/messages.py index f98642a1..7ace1b1e 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -633,7 +633,7 @@ def _write(self): w = Writer() w.add(self.client_version[0], 1) w.add(self.client_version[1], 1) - w.addFixSeq(self.random, 1) + w.bytes += self.random w.addVarSeq(self.session_id, 1, 1) w.addVarSeq(self.cipher_suites, 2, 2) w.addVarSeq(self.compression_methods, 1, 1) @@ -867,7 +867,7 @@ def write(self): w = Writer() w.add(self.server_version[0], 1) w.add(self.server_version[1], 1) - w.addFixSeq(self.random, 1) + w.bytes += self.random w.addVarSeq(self.session_id, 1, 1) w.add(self.cipher_suite, 2) w.add(self.compression_method, 1) @@ -1413,7 +1413,7 @@ def write(self): if self.version in ((3, 1), (3, 2), (3, 3)): w.addVarSeq(self.encryptedPreMasterSecret, 1, 2) elif self.version == (3, 0): - w.addFixSeq(self.encryptedPreMasterSecret, 1) + w.bytes += self.encryptedPreMasterSecret else: raise AssertionError() elif self.cipherSuite in CipherSuite.dhAllSuites: @@ -1609,7 +1609,7 @@ def parse(self, p): def write(self): w = Writer() - w.addFixSeq(self.verify_data, 1) + w.bytes += self.verify_data return self.postWrite(w) @@ -1634,7 +1634,7 @@ def write(self): """Serialise the message to on the wire data.""" writer = Writer() writer.add(self.handshakeType, 1) - writer.addFixSeq(self.verify_data, 1) + writer.bytes += self.verify_data # does not use postWrite() as it's a SSLv2 message return writer.bytes From 53ce9adebd247019c6d64806cc32492a345825e0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 4 Aug 2016 20:14:14 +0200 Subject: [PATCH 339/574] speed up Writer.addVarSeq() addVarSeq() is used very often (e.g. for ciphersuites), speed up the most common uses of it --- tlslite/utils/codec.py | 78 +++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/tlslite/utils/codec.py b/tlslite/utils/codec.py index ad06ab67..1b6ff26e 100644 --- a/tlslite/utils/codec.py +++ b/tlslite/utils/codec.py @@ -125,26 +125,72 @@ def addFixSeq(self, seq, length): for e in seq: self.add(e, length) - def addVarSeq(self, seq, length, lengthLength): - """ - Add a bounded list of same-sized values + if sys.version_info < (2, 7): + # struct.pack on Python2.6 does not raise exception if the value + # is larger than can fit inside the specified size + def _addVarSeqTwo(self, seq): + """Helper method for addVarSeq""" + if not all(0 <= i <= 0xffff for i in seq): + raise ValueError("Can't represent value in specified " + "length") + self.bytes += pack('>' + 'H' * len(seq), *seq) + + def addVarSeq(self, seq, length, lengthLength): + """ + Add a bounded list of same-sized values - Create a list of specific length with all items being of the same - size + Create a list of specific length with all items being of the same + size - @type seq: list of int - @param seq: list of positive integers to encode + @type seq: list of int + @param seq: list of positive integers to encode - @type length: int - @param length: amount of bytes in which to encode every item + @type length: int + @param length: amount of bytes in which to encode every item - @type lengthLength: int - @param lengthLength: amount of bytes in which to encode the overall - length of the array - """ - self.add(len(seq)*length, lengthLength) - for e in seq: - self.add(e, length) + @type lengthLength: int + @param lengthLength: amount of bytes in which to encode the overall + length of the array + """ + self.add(len(seq)*length, lengthLength) + if length == 1: + self.bytes.extend(seq) + elif length == 2: + self._addVarSeqTwo(seq) + else: + for i in seq: + self.add(i, length) + else: + def addVarSeq(self, seq, length, lengthLength): + """ + Add a bounded list of same-sized values + + Create a list of specific length with all items being of the same + size + + @type seq: list of int + @param seq: list of positive integers to encode + + @type length: int + @param length: amount of bytes in which to encode every item + + @type lengthLength: int + @param lengthLength: amount of bytes in which to encode the overall + length of the array + """ + seqLen = len(seq) + self.add(seqLen*length, lengthLength) + if length == 1: + self.bytes.extend(seq) + elif length == 2: + try: + self.bytes += pack('>' + 'H' * seqLen, *seq) + except struct.error: + raise ValueError("Can't represent value in specified " + "length") + else: + for i in seq: + self.add(i, length) def addVarTupleSeq(self, seq, length, lengthLength): """ From a4ba423e09f23f4755f5620e7858c7c7983f063e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 12:07:01 +0200 Subject: [PATCH 340/574] add waiver for code duplication in codec.Writer The reason for existance of addTwo() and addFour() is to optimise the methods as much as possible, it unfortunately means that there is some code duplication between them. For tight loops another if statement may increase their execution time by 10 or even 20% --- .codeclimate.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index 713a2970..3c06f9ed 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -5,6 +5,10 @@ engines: enabled: true duplication: enabled: true + exclude_fingerprints: + # duplication in tlslite/utils/codec.py Writer.addTwo() .addFour() + - 2e783666ce368f4223c1e7f5b162e2d9 + - 2c398389f33ea2572edefc5370ed49c0 config: languages: - python From b3b307be28adaa1ca1d5c9982fd3ff044fe9371c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 23:50:18 +0200 Subject: [PATCH 341/574] remove unused import --- tlslite/utils/codec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/utils/codec.py b/tlslite/utils/codec.py index 1b6ff26e..c939af6e 100644 --- a/tlslite/utils/codec.py +++ b/tlslite/utils/codec.py @@ -6,7 +6,7 @@ import sys import struct from struct import pack -from .compat import * + class Writer(object): """Serialisation helper for complex byte-based structures.""" From c60edcdaf8b144a33a83ff3fa86a82adca150bc2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 23:22:02 +0200 Subject: [PATCH 342/574] more addVarTupleSeq test coverage --- unit_tests/test_tlslite_utils_codec.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/unit_tests/test_tlslite_utils_codec.py b/unit_tests/test_tlslite_utils_codec.py index bc8c89d4..2ac7d0b3 100644 --- a/unit_tests/test_tlslite_utils_codec.py +++ b/unit_tests/test_tlslite_utils_codec.py @@ -402,5 +402,15 @@ def test_addVarTupleSeq_with_overflowing_data(self): with self.assertRaises(ValueError): w.addVarTupleSeq([(1, 2), (2, 256)], 1, 2) + def test_addVarTupleSeq_with_double_byte_invalid_sized_tuples(self): + w = Writer() + with self.assertRaises(ValueError): + w.addVarTupleSeq([(1, 2), (2, 3, 4)], 2, 2) + + def test_addVarTupleSeq_with_double_byte_overflowing_data(self): + w = Writer() + with self.assertRaises(ValueError): + w.addVarTupleSeq([(1, 2), (3, 0x10000)], 2, 2) + if __name__ == '__main__': unittest.main() From b7621e98aadb6126db81f978209e690f983b9a0e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 6 Aug 2016 00:02:25 +0200 Subject: [PATCH 343/574] speed up addVarTupleSeq() move the length check outside the tight loop, and handle the special case of length == 1 (the only case actually used in tlslite-ng now) with a fastest implementation --- tlslite/utils/codec.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tlslite/utils/codec.py b/tlslite/utils/codec.py index c939af6e..a74fcb45 100644 --- a/tlslite/utils/codec.py +++ b/tlslite/utils/codec.py @@ -209,17 +209,24 @@ def addVarTupleSeq(self, seq, length, lengthLength): @type lengthLength: int @param lengthLength: length in bytes of overall length field """ - if len(seq) == 0: + if not seq: self.add(0, lengthLength) else: - tupleSize = len(seq[0]) - tupleLength = tupleSize*length - self.add(len(seq)*tupleLength, lengthLength) - for elemTuple in seq: - if len(elemTuple) != tupleSize: - raise ValueError("Tuples of different sizes") - for elem in elemTuple: - self.add(elem, length) + startPos = len(self.bytes) + dataLength = len(seq) * len(seq[0]) * length + self.add(dataLength, lengthLength) + # since at the time of writing, all the calls encode single byte + # elements, and it's very easy to speed up that case, give it + # special case + if length == 1: + for elemTuple in seq: + self.bytes.extend(elemTuple) + else: + for elemTuple in seq: + self.addFixSeq(elemTuple, length) + if startPos + dataLength + lengthLength != len(self.bytes): + raise ValueError("Tuples of different lengths") + class Parser(object): def __init__(self, bytes): From 8cb1b543a1012ce753a2a2b7a04a436c2d0306ed Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 Aug 2016 23:55:47 +0200 Subject: [PATCH 344/574] don't warn about "B" cyclomatic complexity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit from https://docs.codeclimate.com/docs/radon : The Radon engine can be configured to only report issues over (and including) a configured radon Grade threshold and from http://radon.readthedocs.io/en/latest/commandline.html : score 6 - 10 — rank B — Risk low - well structured and stable block so such complexity should not cause a failing CI test in github --- .codeclimate.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index 3c06f9ed..e71671ad 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,6 +1,8 @@ engines: radon: enabled: true + config: + threshold: "C" pep8: enabled: true duplication: From 87e72a1bdf195629d6f098dca264ff2a75953f0f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Aug 2016 00:00:56 +0200 Subject: [PATCH 345/574] document current location for box generation algorithms --- tlslite/utils/rijndael.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tlslite/utils/rijndael.py b/tlslite/utils/rijndael.py index 821e1c76..b4384ebf 100644 --- a/tlslite/utils/rijndael.py +++ b/tlslite/utils/rijndael.py @@ -42,6 +42,9 @@ 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}} +# see unit_tests/test_tlslite_utils_rijndael.py for algorithm used to +# calculate S, Si, T, U and rcon arrays + # S box S = (99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, From f9950c8c9df065643b07214ae85a201a87163764 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 8 Aug 2016 17:37:10 +0200 Subject: [PATCH 346/574] update the version to 0.6.0-beta1 include documentation updates to the newly supported features --- README | 9 +++++---- README.md | 31 +++++++++++++++++++++++++------ setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/README b/README index 75f216fb..7f70d0d5 100644 --- a/README +++ b/README @@ -8,9 +8,9 @@ Functionality implemented include: - all above mentioned protocols, including support for client certificates (RFC 6101, RFC 2246, RFC 4346, RFC 5246) - RC4, 3DES-CBC, AES-CBC, AES-GCM and ChaCha20 ciphers (RFC 5246, RFC 6347, - RFC 4492, RFC 5288, RFC 5289, RFC 7539) - - MD5, SHA1, SHA256 and SHA384 HMACs as well as AEAD mode of operation in GCM - or Poly1305 authenticator + RFC 4492, RFC 5288, RFC 5289, RFC 7539, RFC 7905) + - MD5, SHA1, SHA256 and SHA384 HMACs as well as AEAD mode of operation with + GCM or Poly1305 authenticator - RSA, DHE_RSA and ECDHE_RSA key exchange - full set of signature hashes (md5, sha1, sha224, sha256, sha384 and sha512) for ServerKeyExchange and CertfificateVerify in TLS v1.2 @@ -22,10 +22,11 @@ Functionality implemented include: - NULL encryption ciphersuites - FALLBACK_SCSV (RFC 7507) - encrypt-then-MAC mode of operation for CBC ciphersuites (RFC 7366) - - client certificates - TACK certificate pinning - SRP_SHA_RSA and SRP_SHA ciphersuites (RFC 5054) - Extended Master Secret calculation for TLS connections (RFC 7627) + - padding extension (RFC 7685) + - Keying material exporter (RFC 5705) tlslite-ng aims to be a drop-in replacement for tlslite while providing more diff --git a/README.md b/README.md index f62f5604..4fac60ce 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.6.0-alpha5 2016-06-09 +tlslite-ng version 0.6.0-beta1 2016-08-08 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -49,11 +49,14 @@ Implemented features of TLS include: * SSLv3, TLSv1.0, TLSv1.1 and TLSv1.2 * ciphersuites with DHE, ECDHE, RSA and SRP key exchange together with - AES (including GCM variant), 3DES, RC4 and (the experimental) ChaCha20 - symmetric ciphers. + AES (including GCM variant), 3DES, RC4 and ChaCha20 (both the official + standard and the IETF draft) symmetric ciphers. * Secure Renegotiation * Encrypt Then MAC extension * TLS_FALLBACK_SCSV +* Extended master secret +* padding extension +* keying material exporter * (experimental) TACK extension 2 Licenses/Acknowledgements @@ -583,6 +586,22 @@ encrypt-then-MAC mode for CBC ciphers. 0.6.0 - WIP +* make the Client Hello parser more strict, it will now abort if the + extensions extend past the length of extension field +* make the decoder honour the 2^14 byte protocol limit on plaintext per record +* fix sending correct alerts on receiving malformed or invalid messages in + handshake +* proper signalling for Secure Renegotiation (renegotiation remains unsupported + but server now indicates that the extension was understood and will abort + if receiving a renegotiated hello) +* stop server from leaking lengths of headers in HTTP responses when using + standard library modules +* HMAC-based Extract-and-Expand Key Derivation Function (HKDF) implementation + from RFC 5869 (Tomas Foukal) +* added protection against + [RSA-CRT key leaks](https://people.redhat.com/~fweimer/rsa-crt-leaks.pdf) + (Tomas Foukal) +* Keying material exporter from RFC 5705 * Session Hash a.k.a. Extended Master Secret extension from RFC 7627 * make the library work on systems working in FIPS mode * support for the padding extension from RFC 7685 (Karel Srot) @@ -594,9 +613,9 @@ encrypt-then-MAC mode for CBC ciphers. messages in TLS 1.2 * mark library as compatible with Python 3.5 (it was previously, but now it is verified with Continous Integration) -* small cleanups and more documentation -* add support for ChaCha20 and Poly1305 -* add TLS_DHE_RSA_WITH_CHACHA20_POLY1305 ciphersuite +* cleanups (style fixes, deduplication of code) and more documentation +* add support for ChaCha20 and Poly1305 (both the IETF draft and released + standard) with both ECDHE_RSA and DHE_RSA key exchange * expose padding and MAC-ing functions and blockSize property in RecordLayer 0.5.1 - 2015-11-05 diff --git a/setup.py b/setup.py index 17d7d9a2..80444c4c 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.6.0-alpha5", + version="0.6.0-beta1", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index a10d4529..7eec09cd 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.6.0-alpha5 +@version: 0.6.0-beta1 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 1aed6ad4..be0b3cc4 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.6.0-alpha5" +__version__ = "0.6.0-beta1" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 76cbe31571193ce01eeac02017f8afcefcd21f1e Mon Sep 17 00:00:00 2001 From: mildass Date: Wed, 13 Jul 2016 20:13:40 +0200 Subject: [PATCH 347/574] Moves ADH from tlsconnection.py to keyexchange.py Adds ADHKeyExchange class, where is ADH from tlsconnection.py, from which DHE_RSAKeyExchange inherit. --- tlslite/keyexchange.py | 51 ++++++++++++++++++++++++++++------------ tlslite/tlsconnection.py | 47 +++++++++++++----------------------- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index cb844284..c1d29829 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -24,7 +24,7 @@ class KeyExchange(object): NOT stable, will get moved from this file """ - def __init__(self, cipherSuite, clientHello, serverHello, privateKey): + def __init__(self, cipherSuite, clientHello, serverHello, privateKey=None): """Initialize KeyExchange. privateKey is the signing private key""" self.cipherSuite = cipherSuite self.clientHello = clientHello @@ -228,30 +228,31 @@ def makeClientKeyExchange(self): clientKeyExchange.createRSA(self.encPremasterSecret) return clientKeyExchange -# the DHE_RSA part comes from IETF ciphersuite names, we want to keep it -#pylint: disable = invalid-name -class DHE_RSAKeyExchange(KeyExchange): + +class ADHKeyExchange(KeyExchange): """ - Handling of ephemeral Diffe-Hellman Key exchange + Handling of anonymous Diffe-Hellman Key exchange - NOT stable API, do NOT use + FFDHE without signing serverKeyExchange useful for anonymous DH """ - def __init__(self, cipherSuite, clientHello, serverHello, privateKey): - super(DHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, - serverHello, privateKey) + def __init__(self, cipherSuite, clientHello, serverHello): + super(ADHKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello) #pylint: enable = invalid-name self.dh_Xs = None self.dh_Yc = None # 2048-bit MODP Group (RFC 3526, Section 3) + # TODO make configurable dh_g, dh_p = goodGroupParameters[2] # RFC 3526, Section 8. strength = 160 - - def makeServerKeyExchange(self, sigHash=None): - """Prepare server side of key exchange with selected parameters""" + def makeServerKeyExchange(self): + """ + Prepare server side of anonymous key exchange with selected parameters + """ # 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)) @@ -260,7 +261,7 @@ def makeServerKeyExchange(self, sigHash=None): version = self.serverHello.server_version serverKeyExchange = ServerKeyExchange(self.cipherSuite, version) serverKeyExchange.createDH(self.dh_p, self.dh_g, dh_Ys) - self.signServerKeyExchange(serverKeyExchange, sigHash) + # No sign for anonymous ServerKeyExchange. return serverKeyExchange def processClientKeyExchange(self, clientKeyExchange): @@ -292,10 +293,31 @@ def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): def makeClientKeyExchange(self): """Create client key share for the key exchange""" - cke = super(DHE_RSAKeyExchange, self).makeClientKeyExchange() + cke = super(ADHKeyExchange, self).makeClientKeyExchange() cke.createDH(self.dh_Yc) return cke +# the DHE_RSA part comes from IETF ciphersuite names, we want to keep it +#pylint: disable = invalid-name +class DHE_RSAKeyExchange(ADHKeyExchange): + """ + Handling of ephemeral Diffe-Hellman Key exchange + + NOT stable API, do NOT use + """ + + def __init__(self, cipherSuite, clientHello, serverHello, privateKey): + super(DHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello) +#pylint: enable = invalid-name + self.privateKey = privateKey + + def makeServerKeyExchange(self, sigHash=None): + """Prepare server side of key exchange with selected parameters""" + ske = super(DHE_RSAKeyExchange, self).makeServerKeyExchange() + self.signServerKeyExchange(ske, sigHash) + return ske + # The ECDHE_RSA part comes from the IETF names of ciphersuites, so we want to # keep it #pylint: disable = invalid-name @@ -475,4 +497,3 @@ def makeClientKeyExchange(self): cke = super(SRPKeyExchange, self).makeClientKeyExchange() cke.createSRP(self.A) return cke - diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 73c4b4e5..4e0c8c06 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -28,7 +28,7 @@ from .handshakesettings import HandshakeSettings from .utils.tackwrapper import * from .keyexchange import KeyExchange, RSAKeyExchange, DHE_RSAKeyExchange, \ - ECDHE_RSAKeyExchange, SRPKeyExchange + ECDHE_RSAKeyExchange, SRPKeyExchange, ADHKeyExchange from .handshakehelpers import HandshakeHelpers class TLSConnection(TLSRecordLayer): @@ -1300,8 +1300,10 @@ def _handshakeServerAsyncHelper(self, verifierDB, # Perform anonymous Diffie Hellman key exchange elif cipherSuite in CipherSuite.anonSuites: - for result in self._serverAnonKeyExchange(clientHello, serverHello, - cipherSuite, settings): + keyExchange = ADHKeyExchange(cipherSuite, clientHello, + serverHello) + for result in self._serverAnonKeyExchange(serverHello, keyExchange, + cipherSuite): if result in (0,1): yield result else: break premasterSecret = result @@ -1737,48 +1739,31 @@ def _serverCertKeyExchange(self, clientHello, serverHello, yield (premasterSecret, clientCertChain) - def _serverAnonKeyExchange(self, clientHello, serverHello, cipherSuite, - settings): - # Calculate DH p, g, Xs, Ys - # TODO make configurable - dh_g, dh_p = goodGroupParameters[2] - dh_Xs = bytesToNumber(getRandomBytes(32)) - dh_Ys = powMod(dh_g, dh_Xs, dh_p) + def _serverAnonKeyExchange(self, serverHello, keyExchange, cipherSuite): - #Create ServerKeyExchange - serverKeyExchange = ServerKeyExchange(cipherSuite, self.version) - serverKeyExchange.createDH(dh_p, dh_g, dh_Ys) - - #Send ServerHello[, Certificate], ServerKeyExchange, - #ServerHelloDone + # Create ServerKeyExchange + serverKeyExchange = keyExchange.makeServerKeyExchange() + + # Send ServerHello[, Certificate], ServerKeyExchange, + # ServerHelloDone msgs = [] msgs.append(serverHello) msgs.append(serverKeyExchange) msgs.append(ServerHelloDone()) for result in self._sendMsgs(msgs): yield result - - #Get and check ClientKeyExchange + + # Get and check ClientKeyExchange for result in self._getMsg(ContentType.handshake, HandshakeType.client_key_exchange, cipherSuite): if result in (0,1): - yield result + yield result else: break - clientKeyExchange = result - dh_Yc = clientKeyExchange.dh_Yc - - if dh_Yc % dh_p == 0: - for result in self._sendError(AlertDescription.illegal_parameter, - "Suspicious dh_Yc value"): - yield result - assert(False) # Just to ensure we don't fall through somehow + cke = result + premasterSecret = keyExchange.processClientKeyExchange(cke) - #Calculate premaster secre - S = powMod(dh_Yc,dh_Xs,dh_p) - premasterSecret = numberToByteArray(S) - yield premasterSecret From 756158c2949c0abc96f1daccc68ab8600cfadc9a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 24 Aug 2016 17:52:18 +0200 Subject: [PATCH 348/574] python 2.7.3 compatibility The python on Debian 7 (Wheezy) is closer to 2.6 than to 2.7, for example struct.pack requires str() and will not accept bytearray(). make the compat26Str() handle this old python correctly --- tlslite/utils/compat.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tlslite/utils/compat.py b/tlslite/utils/compat.py index 2e17350a..09a20dd6 100644 --- a/tlslite/utils/compat.py +++ b/tlslite/utils/compat.py @@ -55,7 +55,8 @@ def compatLong(num): else: # Python 2.6 requires strings instead of bytearrays in a couple places, # so we define this function so it does the conversion if needed. - if sys.version_info < (2,7): + # same thing with very old 2.7 versions + if sys.version_info < (2, 7) or sys.version_info < (2, 7, 4): def compat26Str(x): return str(x) else: def compat26Str(x): return x From cc312c932be637219bd36449d8b8b37850787438 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 9 Aug 2016 19:29:04 +0200 Subject: [PATCH 349/574] fix the write buffering in Python3 too few commits ago we have fixed the buffering issue on Python2, but the SocketIO is also unbuffered, causing the headers to be sent separate from the body of the HTTP response in default server on Python 3, so fix that --- tlslite/tlsrecordlayer.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 14ece5e7..828e3058 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -11,6 +11,9 @@ """Helper class for TLSConnection.""" from __future__ import generators +import io +import socket + from .utils.compat import * from .utils.cryptomath import * from .utils.codec import Parser @@ -23,9 +26,6 @@ from .handshakehashes import HandshakeHashes from .bufferedsocket import BufferedSocket -import socket -import traceback - class TLSRecordLayer(object): """ This class handles data transmission for a TLS connection. @@ -460,6 +460,10 @@ def recv_into(self, b): b[:len(data)] = data return len(data) + # while the SocketIO and _fileobject in socket is private we really need + # to use it as it's what the real socket does internally + + # pylint: disable=no-member,protected-access def makefile(self, mode='r', bufsize=-1): """Create a file object for the TLS connection (socket emulation). @@ -479,14 +483,17 @@ def makefile(self, mode='r', bufsize=-1): # for writes, we MUST buffer otherwise the lengths of headers leak # through record layer boundaries - if 'w' in mode and bufsize == 0: + if 'w' in mode and bufsize <= 0: bufsize = 2**14 if sys.version_info < (3,): return socket._fileobject(self, mode, bufsize, close=True) else: - # XXX need to wrap this further if buffering is requested - return socket.SocketIO(self, mode) + if 'w' in mode: + return io.BufferedWriter(socket.SocketIO(self, mode), bufsize) + else: + return socket.SocketIO(self, mode) + # pylint: enable=no-member,protected-access def getsockname(self): """Return the socket's own address (socket emulation).""" From 75c3a1e1695fb9d4658fb5ac996ab484cb796bf3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 24 Aug 2016 19:52:47 +0200 Subject: [PATCH 350/574] workaround the formatExceptionTrace on Python3 on Python3 the sys module does not have a exc_type so don't use it --- tlslite/utils/compat.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tlslite/utils/compat.py b/tlslite/utils/compat.py index 2e17350a..dffc33f8 100644 --- a/tlslite/utils/compat.py +++ b/tlslite/utils/compat.py @@ -7,6 +7,7 @@ import os import math import binascii +import traceback import ecdsa if sys.version_info >= (3,0): @@ -52,6 +53,10 @@ def readStdinBinary(): def compatLong(num): return int(num) + def formatExceptionTrace(e): + """Return exception information formatted as string""" + return str(e) + else: # Python 2.6 requires strings instead of bytearrays in a couple places, # so we define this function so it does the conversion if needed. @@ -86,11 +91,17 @@ def b2a_base64(b): def compatLong(num): return long(num) - -import traceback -def formatExceptionTrace(e): - newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) - return newStr + + # pylint on Python3 goes nuts for the sys dereferences... + + #pylint: disable=no-member + def formatExceptionTrace(e): + """Return exception information formatted as string""" + newStr = "".join(traceback.format_exception(sys.exc_type, + sys.exc_value, + sys.exc_traceback)) + return newStr + #pylint: enable=no-member try: # Fedora and Red Hat Enterprise Linux versions have small curves removed From 632099d8fea903c7bf98a35281ad096721ca7bcb Mon Sep 17 00:00:00 2001 From: mildass Date: Sat, 13 Aug 2016 11:15:48 +0200 Subject: [PATCH 351/574] Adds AECDH key exchange class to keyexchange.py, which is used in tlsconnection.py --- tlslite/keyexchange.py | 55 ++++++++++++++++++++++++++++------------ tlslite/tlsconnection.py | 21 ++++++++++----- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index c1d29829..e5a453a9 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -175,6 +175,7 @@ def makeCertificateVerify(version, handshakeHashes, validSigAlgs, return certificateVerify + class RSAKeyExchange(KeyExchange): """ Handling of RSA key exchange @@ -231,7 +232,7 @@ def makeClientKeyExchange(self): class ADHKeyExchange(KeyExchange): """ - Handling of anonymous Diffe-Hellman Key exchange + Handling of anonymous Diffie-Hellman Key exchange FFDHE without signing serverKeyExchange useful for anonymous DH """ @@ -249,6 +250,7 @@ def __init__(self, cipherSuite, clientHello, serverHello): # RFC 3526, Section 8. strength = 160 + def makeServerKeyExchange(self): """ Prepare server side of anonymous key exchange with selected parameters @@ -297,6 +299,7 @@ def makeClientKeyExchange(self): cke.createDH(self.dh_Yc) return cke + # the DHE_RSA part comes from IETF ciphersuite names, we want to keep it #pylint: disable = invalid-name class DHE_RSAKeyExchange(ADHKeyExchange): @@ -318,24 +321,23 @@ def makeServerKeyExchange(self, sigHash=None): self.signServerKeyExchange(ske, sigHash) return ske -# The ECDHE_RSA part comes from the IETF names of ciphersuites, so we want to -# keep it -#pylint: disable = invalid-name -class ECDHE_RSAKeyExchange(KeyExchange): - """Helper class for conducting ECDHE key exchange""" - def __init__(self, cipherSuite, clientHello, serverHello, privateKey, - acceptedCurves): - super(ECDHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, - serverHello, privateKey) -#pylint: enable = invalid-name +class AECDHKeyExchange(KeyExchange): + """ + Handling of anonymous Eliptic curve Diffie-Hellman Key exchange + + ECDHE without signing serverKeyExchange useful for anonymous ECDH + """ + def __init__(self, cipherSuite, clientHello, serverHello, acceptedCurves): + super(AECDHKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello) self.ecdhXs = None self.acceptedCurves = acceptedCurves self.group_id = None self.ecdhYc = None def makeServerKeyExchange(self, sigHash=None): - """Create ECDHE version of Server Key Exchange""" + """Create AECDHE version of Server Key Exchange""" #Get client supported groups client_curves = self.clientHello.getExtension(\ ExtensionType.supported_groups) @@ -346,8 +348,7 @@ def makeServerKeyExchange(self, sigHash=None): #Pick first client preferred group we support self.group_id = next((x for x in client_curves \ - if x in self.acceptedCurves), - None) + if x in self.acceptedCurves), None) if self.group_id is None: raise TLSInsufficientSecurity("No mutual groups") generator = getCurveByName(GroupName.toRepr(self.group_id)).generator @@ -360,7 +361,7 @@ def makeServerKeyExchange(self, sigHash=None): serverKeyExchange.createECDH(ECCurveType.named_curve, named_curve=self.group_id, point=ecdhYs) - self.signServerKeyExchange(serverKeyExchange, sigHash) + # No sign for anonymous ServerKeyExchange return serverKeyExchange def processClientKeyExchange(self, clientKeyExchange): @@ -398,10 +399,32 @@ def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): def makeClientKeyExchange(self): """Make client key exchange for ECDHE""" - cke = super(ECDHE_RSAKeyExchange, self).makeClientKeyExchange() + cke = super(AECDHKeyExchange, self).makeClientKeyExchange() cke.createECDH(self.ecdhYc) return cke + +# The ECDHE_RSA part comes from the IETF names of ciphersuites, so we want to +# keep it +#pylint: disable = invalid-name +class ECDHE_RSAKeyExchange(AECDHKeyExchange): + """Helper class for conducting ECDHE key exchange""" + + def __init__(self, cipherSuite, clientHello, serverHello, privateKey, + acceptedCurves): + super(ECDHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello, + acceptedCurves) +#pylint: enable = invalid-name + self.privateKey = privateKey + + def makeServerKeyExchange(self, sigHash=None): + """Create ECDHE version of Server Key Exchange""" + ske = super(ECDHE_RSAKeyExchange, self).makeServerKeyExchange() + self.signServerKeyExchange(ske, sigHash) + return ske + + class SRPKeyExchange(KeyExchange): """Helper class for conducting SRP key exchange""" diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 4e0c8c06..331573a2 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -28,7 +28,7 @@ from .handshakesettings import HandshakeSettings from .utils.tackwrapper import * from .keyexchange import KeyExchange, RSAKeyExchange, DHE_RSAKeyExchange, \ - ECDHE_RSAKeyExchange, SRPKeyExchange, ADHKeyExchange + ECDHE_RSAKeyExchange, SRPKeyExchange, ADHKeyExchange, AECDHKeyExchange from .handshakehelpers import HandshakeHelpers class TLSConnection(TLSRecordLayer): @@ -1175,7 +1175,7 @@ def _handshakeServerAsyncHelper(self, verifierDB, return # Handshake was resumed, we're done else: break (clientHello, cipherSuite) = result - + #If not a resumption... # Create the ServerHello message @@ -1299,15 +1299,21 @@ def _handshakeServerAsyncHelper(self, verifierDB, (premasterSecret, clientCertChain) = result # Perform anonymous Diffie Hellman key exchange - elif cipherSuite in CipherSuite.anonSuites: - keyExchange = ADHKeyExchange(cipherSuite, clientHello, - serverHello) + elif (cipherSuite in CipherSuite.anonSuites or + cipherSuite in CipherSuite.ecdhAnonSuites): + if cipherSuite in CipherSuite.anonSuites: + keyExchange = ADHKeyExchange(cipherSuite, clientHello, + serverHello) + else: + acceptedCurves = self._curveNamesToList(settings) + keyExchange = AECDHKeyExchange(cipherSuite, clientHello, + serverHello, acceptedCurves) for result in self._serverAnonKeyExchange(serverHello, keyExchange, cipherSuite): if result in (0,1): yield result else: break premasterSecret = result - + else: assert(False) @@ -1411,12 +1417,13 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, cipherSuites += CipherSuite.getCertSuites(settings, self.version) elif anon: cipherSuites += CipherSuite.getAnonSuites(settings, self.version) + cipherSuites += CipherSuite.getEcdhAnonSuites(settings, + self.version) else: assert(False) cipherSuites = CipherSuite.filterForVersion(cipherSuites, minVersion=self.version, maxVersion=self.version) - #If resumption was requested and we have a session cache... if clientHello.session_id and sessionCache: session = None From 921fc49272d1233e51359d6fdd50de44204bf5de Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 25 Aug 2016 15:01:04 +0200 Subject: [PATCH 352/574] fix example in README.md the example uses incorrect syntax for SRP ciphersuites, use the current one --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4fac60ce..3601aba2 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ tls.py server -v verifierDB localhost:4443 Then try connecting to the server with: ``` -tls.py client localhost:4443 alice abra123cadabra +tls.py client -u alice -p abra123cadabra localhost:4443 ``` HTTPS From 95410a47221e0804ce68a096807d83906bfb153d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 25 Aug 2016 18:25:13 +0200 Subject: [PATCH 353/574] make SRP with database file work make the internal interfaces use bytearrays to pass usernames and passwords around, retain public API that accepts str add test cases so that file-based database continues to work fix verifierdb to work in python3 with files --- tests/tlstest.py | 33 ++++++++++++++++++- tlslite/basedb.py | 2 +- tlslite/keyexchange.py | 9 ++++-- tlslite/messages.py | 4 ++- tlslite/tlsconnection.py | 13 +++++--- tlslite/utils/compat.py | 4 ++- tlslite/verifierdb.py | 24 ++++++++------ unit_tests/test_tlslite_keyexchange.py | 45 +++++++++++++++++++------- unit_tests/test_tlslite_messages.py | 8 ++++- 9 files changed, 109 insertions(+), 33 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index d7ded655..e4f225d1 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -17,6 +17,7 @@ import socket import time import getopt +from tempfile import mkstemp try: from BaseHTTPServer import HTTPServer from SimpleHTTPServer import SimpleHTTPRequestHandler @@ -203,6 +204,15 @@ def connect(): test_no += 1 + print("Test {0} - good SRP (db)".format(test_no)) + synchro.recv(1) + connection = connect() + connection.handshakeClientSRP("test", "password") + testConnClient(connection) + connection.close() + + test_no += 1 + print("Test {0} - SRP faults".format(test_no)) for fault in Fault.clientSrpFaults + Fault.genericFaults: synchro.recv(1) @@ -839,7 +849,7 @@ def connect(): verifierDB = VerifierDB() verifierDB.create() entry = VerifierDB.makeVerifier("test", "password", 1536) - verifierDB["test"] = entry + verifierDB[b"test"] = entry synchro.send(b'R') connection = connect() @@ -849,6 +859,27 @@ def connect(): test_no += 1 + print("Test {0} - good SRP (db)".format(test_no)) + try: + (db_file, db_name) = mkstemp() + os.close(db_file) + # this is race'y but the interface dbm interface is stupid like that... + os.remove(db_name) + verifierDB = VerifierDB(db_name) + verifierDB.create() + entry = VerifierDB.makeVerifier("test", "password", 1536) + verifierDB[b"test"] = entry + + synchro.send(b'R') + connection = connect() + connection.handshakeServer(verifierDB=verifierDB) + testConnServer(connection) + connection.close() + finally: + os.remove(db_name) + + test_no += 1 + print("Test {0} - SRP faults".format(test_no)) for fault in Fault.clientSrpFaults + Fault.genericFaults: synchro.send(b'R') diff --git a/tlslite/basedb.py b/tlslite/basedb.py index e6b79442..7ec45672 100644 --- a/tlslite/basedb.py +++ b/tlslite/basedb.py @@ -104,7 +104,7 @@ def __contains__(self, username): self.lock.acquire() try: - return self.db.has_key(username) + return username in self.db finally: self.lock.release() diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index cb844284..b257c08b 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -397,10 +397,14 @@ def __init__(self, cipherSuite, clientHello, serverHello, privateKey, self.srpUsername = srpUsername self.password = password self.settings = settings + if srpUsername is not None and not isinstance(srpUsername, bytearray): + raise TypeError("srpUsername must be a bytearray object") + if password is not None and not isinstance(password, bytearray): + raise TypeError("password must be a bytearray object") def makeServerKeyExchange(self, sigHash=None): """Create SRP version of Server Key Exchange""" - srpUsername = self.clientHello.srp_username.decode("utf-8") + srpUsername = bytes(self.clientHello.srp_username) #Get parameters from username try: entry = self.verifierDB[srpUsername] @@ -458,8 +462,7 @@ def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): self.A = powMod(g, a, N) #Calculate client's static DH values (x, v) - x = makeX(s, bytearray(self.srpUsername, "utf-8"), - bytearray(self.password, "utf-8")) + x = makeX(s, self.srpUsername, self.password) v = powMod(g, x, N) #Calculate u diff --git a/tlslite/messages.py b/tlslite/messages.py index e3f5bfa3..3c24b19a 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -565,7 +565,9 @@ def create(self, version, random, session_id, cipher_suites, if certificate_types is not None: self.certificate_types = certificate_types if srpUsername is not None: - self.srp_username = bytearray(srpUsername, "utf-8") + if not isinstance(srpUsername, bytearray): + raise TypeError("srpUsername must be a bytearray object") + self.srp_username = srpUsername self.tack = tack if supports_npn is not None: self.supports_npn = supports_npn diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 73c4b4e5..94cf81ed 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -189,10 +189,10 @@ def handshakeClientSRP(self, username, password, session=None, If an exception is raised, the connection will have been automatically closed (if it was ever open). - @type username: str + @type username: bytearray @param username: The SRP username. - @type password: str + @type password: bytearray @param password: The SRP password. @type session: L{tlslite.session.Session} @@ -237,6 +237,11 @@ def handshakeClientSRP(self, username, password, session=None, @raise tlslite.errors.TLSAuthenticationError: If the checker doesn't like the other party's authentication credentials. """ + # TODO add deprecation warning + if isinstance(username, str): + username = bytearray(username, 'utf-8') + if isinstance(password, str): + password = bytearray(password, 'utf-8') handshaker = self._handshakeClientAsync(srpParams=(username, password), session=session, settings=settings, checker=checker, reqTack=reqTack, serverName=serverName) @@ -435,9 +440,9 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, #Add Faults to parameters if srpUsername and self.fault == Fault.badUsername: - srpUsername += "GARBAGE" + srpUsername += bytearray(b"GARBAGE") if password and self.fault == Fault.badPassword: - password += "GARBAGE" + password += bytearray(b"GARBAGE") #Tentatively set the version to the client's minimum version. #We'll use this for the ClientHello, and if an error occurs diff --git a/tlslite/utils/compat.py b/tlslite/utils/compat.py index 09a20dd6..81e09806 100644 --- a/tlslite/utils/compat.py +++ b/tlslite/utils/compat.py @@ -35,7 +35,9 @@ def a2b_hex(s): def a2b_base64(s): try: - b = bytearray(binascii.a2b_base64(bytearray(s, "ascii"))) + if isinstance(s, str): + s = bytearray(s, "ascii") + b = bytearray(binascii.a2b_base64(s)) except Exception as e: raise SyntaxError("base64 error: %s" % e) return b diff --git a/tlslite/verifierdb.py b/tlslite/verifierdb.py index 43845415..46c46fd8 100644 --- a/tlslite/verifierdb.py +++ b/tlslite/verifierdb.py @@ -26,10 +26,10 @@ def __init__(self, filename=None): this with a call to open(). To create a new on-disk database, follow this with a call to create(). """ - BaseDB.__init__(self, filename, "verifier") + BaseDB.__init__(self, filename, b"verifier") def _getItem(self, username, valueStr): - (N, g, salt, verifier) = valueStr.split(" ") + (N, g, salt, verifier) = valueStr.split(b" ") N = bytesToNumber(a2b_base64(N)) g = bytesToNumber(a2b_base64(g)) salt = a2b_base64(salt) @@ -56,11 +56,11 @@ def _setItem(self, username, value): if len(username)>=256: raise ValueError("username too long") N, g, salt, verifier = value - N = b2a_base64(numberToByteArray(N)) - g = b2a_base64(numberToByteArray(g)) - salt = b2a_base64(salt) - verifier = b2a_base64(numberToByteArray(verifier)) - valueStr = " ".join( (N, g, salt, verifier) ) + N = b2a_base64(numberToByteArray(N)).encode("ascii") + g = b2a_base64(numberToByteArray(g)).encode("ascii") + salt = b2a_base64(salt).encode("ascii") + verifier = b2a_base64(numberToByteArray(verifier)).encode("ascii") + valueStr = b" ".join((N, g, salt, verifier)) return valueStr def _checkItem(self, value, username, param): @@ -89,7 +89,13 @@ def makeVerifier(username, password, bits): @rtype: tuple @return: A tuple which may be stored in a VerifierDB. """ - usernameBytes = bytearray(username, "utf-8") - passwordBytes = bytearray(password, "utf-8") + if isinstance(username, str): + usernameBytes = bytearray(username, "utf-8") + else: + usernameBytes = bytearray(username) + if isinstance(password, str): + passwordBytes = bytearray(password, "utf-8") + else: + passwordBytes = bytearray(password) return mathtls.makeVerifier(usernameBytes, passwordBytes, bits) makeVerifier = staticmethod(makeVerifier) diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index bba3e75b..8f1d9fc2 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -608,7 +608,8 @@ def setUp(self): bytearray(32), bytearray(0), [], - srpUsername='user') + srpUsername=bytearray(b'user') + ) self.server_hello = ServerHello().create((3, 3), bytearray(32), bytearray(0), @@ -617,7 +618,7 @@ def setUp(self): verifierDB = VerifierDB() verifierDB.create() entry = verifierDB.makeVerifier('user', 'password', 2048) - verifierDB['user'] = entry + verifierDB[b'user'] = entry self.keyExchange = SRPKeyExchange(self.cipher_suite, self.client_hello, @@ -694,6 +695,26 @@ def test_SRP_key_exchange_without_signature(self): self.assertEqual(cln_premaster, srv_premaster) + def test_SRP_init_with_invalid_name(self): + with self.assertRaises(TypeError): + SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, None, + srpUsername='user', + password=bytearray(b'password'), + settings=HandshakeSettings()) + + def test_SRP_init_with_invalid_password(self): + with self.assertRaises(TypeError): + SRPKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, None, + srpUsername=bytearray(b'user'), + password='password', + settings=HandshakeSettings()) + def test_SRP_with_invalid_name(self): self.client_hello.srp_username = bytearray(b'test') @@ -717,8 +738,8 @@ def test_SRP_key_exchange_with_client(self): self.client_hello, self.server_hello, None, None, - srpUsername='user', - password='password', + srpUsername=bytearray(b'user'), + password=bytearray(b'password'), settings=HandshakeSettings()) client_premaster = client_keyExchange.processServerKeyExchange(\ @@ -740,8 +761,8 @@ def test_client_SRP_key_exchange_with_unknown_params(self): self.client_hello, self.server_hello, None, None, - srpUsername='user', - password='password') + srpUsername=bytearray(b'user'), + password=bytearray(b'password')) with self.assertRaises(TLSInsufficientSecurity): client_keyExchange.processServerKeyExchange(None, keyExchange) @@ -754,8 +775,8 @@ def test_client_SRP_key_exchange_with_too_small_params(self): self.client_hello, self.server_hello, None, None, - srpUsername='user', - password='password', + srpUsername=bytearray(b'user'), + password=bytearray(b'password'), settings=settings) with self.assertRaises(TLSInsufficientSecurity): client_keyExchange.processServerKeyExchange(None, keyExchange) @@ -770,8 +791,8 @@ def test_client_SRP_key_exchange_with_too_big_params(self): self.client_hello, self.server_hello, None, None, - srpUsername='user', - password='password', + srpUsername=bytearray(b'user'), + password=bytearray(b'password'), settings=settings) with self.assertRaises(TLSInsufficientSecurity): client_keyExchange.processServerKeyExchange(None, keyExchange) @@ -785,8 +806,8 @@ def test_client_SRP_key_exchange_with_invalid_params(self): self.client_hello, self.server_hello, None, None, - srpUsername='user', - password='password', + srpUsername=bytearray(b'user'), + password=bytearray(b'password'), settings=settings) with self.assertRaises(TLSIllegalParameterException): client_keyExchange.processServerKeyExchange(None, keyExchange) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index d4bba4c6..ef811033 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -324,10 +324,16 @@ def test_write_with_certificate_types(self): b'\x01' # type - OpenPGP )), list(client_hello.write())) + def test_invalid_srp_username(self): + with self.assertRaises(TypeError): + ClientHello().create((3, 1), + bytearray(b'\x00'*32), bytearray(0), + [], srpUsername='test') + def test_write_with_srp_username(self): client_hello = ClientHello().create((3,1), bytearray(b'\x00'*31 + b'\xff'), bytearray(0), - [], srpUsername="example-test") + [], srpUsername=bytearray(b"example-test")) self.assertEqual(list(bytearray( b'\x01' + # type of message - client_hello From ddb79291f0b21ce9bc12e41237cada14c04cd9c4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 26 Aug 2016 12:24:06 +0200 Subject: [PATCH 354/574] update README files after latest merges --- README | 2 +- README.md | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README b/README index 7f70d0d5..7cafd474 100644 --- a/README +++ b/README @@ -18,7 +18,7 @@ Functionality implemented include: for ECDHE_RSA key exchange (support for last two depends on the version of ecdsa library used) - anonymous DHE key exchange - - anonymous ECDH key exchange in client + - anonymous ECDH key exchange - NULL encryption ciphersuites - FALLBACK_SCSV (RFC 7507) - encrypt-then-MAC mode of operation for CBC ciphersuites (RFC 7366) diff --git a/README.md b/README.md index 4fac60ce..4665b6a4 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ tlslite-ng aims to be a drop in replacement for the original TLS Lite. Implemented features of TLS include: * SSLv3, TLSv1.0, TLSv1.1 and TLSv1.2 -* ciphersuites with DHE, ECDHE, RSA and SRP key exchange together with - AES (including GCM variant), 3DES, RC4 and ChaCha20 (both the official - standard and the IETF draft) symmetric ciphers. +* ciphersuites with DHE, ADH, ECDHE, AECDH, RSA and SRP key exchange together + with AES (including GCM variant), 3DES, RC4 and ChaCha20 (both the official + standard and the IETF draft) symmetric ciphers and NULL encryption. * Secure Renegotiation * Encrypt Then MAC extension * TLS_FALLBACK_SCSV @@ -586,6 +586,8 @@ encrypt-then-MAC mode for CBC ciphers. 0.6.0 - WIP +* fixed compatibility with Python 2.7.3 +* AECDH support on server side (Milan Lysonek) * make the Client Hello parser more strict, it will now abort if the extensions extend past the length of extension field * make the decoder honour the 2^14 byte protocol limit on plaintext per record From 331849465dff8ecd5eb4d0576618b3357dcf9b4e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 26 Aug 2016 17:28:33 +0200 Subject: [PATCH 355/574] add ALPN alert --- tlslite/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index dde9f396..e28eb8ae 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -283,6 +283,7 @@ class AlertDescription(TLSEnum): unsupported_extension = 110 # RFC 5246 unrecognized_name = 112 # RFC 6066 unknown_psk_identity = 115 + no_application_protocol = 120 # RFC 7301 class CipherSuite: From 45fc21114a45ee1f83bfc89e38044b1eef4d14c7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 26 Aug 2016 17:28:56 +0200 Subject: [PATCH 356/574] add ALPN extension --- tlslite/constants.py | 1 + tlslite/extensions.py | 88 ++++++++++++++++++++++ unit_tests/test_tlslite_extensions.py | 104 +++++++++++++++++++++++++- 3 files changed, 192 insertions(+), 1 deletion(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index e28eb8ae..002b0308 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -118,6 +118,7 @@ class ExtensionType: # RFC 6066 / 4366 ec_point_formats = 11 # RFC 4492 srp = 12 # RFC 5054 signature_algorithms = 13 # RFC 5246 + alpn = 16 # RFC 7301 client_hello_padding = 21 # RFC 7685 encrypt_then_mac = 22 # RFC 7366 extended_master_secret = 23 # RFC 7627 diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 0a0e5456..e1b95c44 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -1161,6 +1161,93 @@ def parse(self, parser): return self + +class ALPNExtension(TLSExtension): + """ + Handling of Application Layer Protocol Negotiation extension from RFC 7301. + + @type protocol_names: list of bytearrays + @ivar protocol_names: list of protocol names acceptable or selected by peer + + @type extType: int + @ivar extType: numberic type of ALPNExtension, i.e. 16 + + @type extData: bytearray + @ivar extData: raw encoding of the extension data + """ + + def __init__(self): + """ + Create instance of NPNExtension + + See also: L{create} and L{parse} + """ + super(ALPNExtension, self).__init__(extType=ExtensionType.alpn) + + self.protocol_names = None + + def __repr__(self): + """ + Create programmer-readable representation of object + + @rtype: str + """ + return "ALPNExtension(protocol_names={0!r})".format(self.protocol_names) + + @property + def extData(self): + """ + Return encoded payload of the extension + + @rtype: bytearray + """ + if self.protocol_names is None: + return bytearray(0) + + writer = Writer() + for prot in self.protocol_names: + writer.add(len(prot), 1) + writer.bytes += prot + + writer2 = Writer() + writer2.add(len(writer.bytes), 2) + writer2.bytes += writer.bytes + + return writer2.bytes + + def create(self, protocol_names=None): + """ + Create an instance of ALPNExtension with specified protocols + + @type protocols: list of bytearray + @param protocols: list of protocol names that are to be sent + """ + self.protocol_names = protocol_names + return self + + def parse(self, parser): + """ + Parse the extension from on the wire format + + @type parser: L{tlslite.util.codec.Parser} + @param parser: data to be parsed as extension + + @raise SyntaxError: when the encoding of the extension is self + inconsistent + + @rtype: L{ALPNExtension} + """ + self.protocol_names = [] + parser.startLengthCheck(2) + while not parser.atLengthCheck(): + name_len = parser.get(1) + self.protocol_names.append(parser.getFixBytes(name_len)) + parser.stopLengthCheck() + if parser.getRemainingLength() != 0: + raise SyntaxError("Trailing data after protocol_name_list") + return self + + TLSExtension._universalExtensions = \ { ExtensionType.server_name: SNIExtension, @@ -1169,6 +1256,7 @@ def parse(self, parser): ExtensionType.ec_point_formats: ECPointFormatsExtension, ExtensionType.srp: SRPExtension, ExtensionType.signature_algorithms: SignatureAlgorithmsExtension, + ExtensionType.alpn: ALPNExtension, ExtensionType.supports_npn: NPNExtension, ExtensionType.client_hello_padding: PaddingExtension, ExtensionType.renegotiation_info: RenegotiationInfoExtension} diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 782e2967..d4773cd4 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -12,7 +12,7 @@ SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\ TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\ SignatureAlgorithmsExtension, PaddingExtension, VarListExtension, \ - RenegotiationInfoExtension + RenegotiationInfoExtension, ALPNExtension from tlslite.utils.codec import Parser from tlslite.constants import NameType, ExtensionType, GroupName,\ ECPointFormat, HashAlgorithm, SignatureAlgorithm @@ -1485,5 +1485,107 @@ def test_parse_with_data(self): self.assertEqual(ext.renegotiated_connection, bytearray(b'abc')) +class TestAPLNExtension(unittest.TestCase): + def setUp(self): + self.ext = ALPNExtension() + + def test___init__(self): + self.assertIsNotNone(self.ext) + self.assertEqual(self.ext.extType, 16) + self.assertEqual(self.ext.extData, bytearray()) + self.assertIsNone(self.ext.protocol_names) + + def test___repr__(self): + self.assertEqual("ALPNExtension(protocol_names=None)", + repr(self.ext)) + + def test_create(self): + self.ext.create([bytearray(b'http/1.1'), + bytearray(b'spdy/1')]) + self.assertEqual(self.ext.protocol_names, + [bytearray(b'http/1.1'), + bytearray(b'spdy/1')]) + + def test___repr___with_values(self): + self.ext.create([bytearray(b'http/1.1'), + bytearray(b'spdy/1')]) + + self.assertEqual("ALPNExtension(protocol_names=" + "[bytearray(b'http/1.1'), bytearray(b'spdy/1')])", + repr(self.ext)) + + def test_extData_with_empty_array(self): + self.ext.create([]) + + self.assertEqual(self.ext.extData, bytearray(b'\x00\x00')) + + def test_extData_with_empty_names(self): + self.ext.create([bytearray(), bytearray()]) + + self.assertEqual(self.ext.extData, bytearray(b'\x00\x02\x00\x00')) + + def test_extData_with_names(self): + self.ext.create([bytearray(b'http/1.1'), bytearray(b'spdy/1')]) + + self.assertEqual(self.ext.extData, + bytearray(b'\x00\x10' + b'\x08http/1.1' + b'\x06spdy/1')) + + def test_parse_with_empty_data(self): + parser = Parser(bytearray(b'')) + + with self.assertRaises(SyntaxError): + self.ext.parse(parser) + + def test_parse_with_empty_array(self): + parser = Parser(bytearray(b'\x00\x00')) + + self.ext.parse(parser) + + self.assertEqual(self.ext.protocol_names, []) + + def test_parse_with_too_little_data(self): + parser = Parser(bytearray(b'\x00\x10' + b'\x08http/1.1')) + + with self.assertRaises(SyntaxError): + self.ext.parse(parser) + + def test_parse_with_too_much_data(self): + parser = Parser(bytearray(b'\x00\x10' + b'\x08http/1.1' + b'\x06spdy/1' + b'\x06spdy/2')) + + with self.assertRaises(SyntaxError): + self.ext.parse(parser) + + def test_parse_with_values(self): + parser = Parser(bytearray(b'\x00\x10' + b'\x08http/1.1' + b'\x06spdy/1')) + + ext = self.ext.parse(parser) + + self.assertIs(ext, self.ext) + + self.assertEqual(ext.protocol_names, [bytearray(b'http/1.1'), + bytearray(b'spdy/1')]) + + def test_parse_from_TLSExtension(self): + ext = TLSExtension() + + parser = Parser(bytearray(b'\x00\x10\x00\x12' + b'\x00\x10' + b'\x08http/1.1' + b'\x06spdy/1')) + + ext2 = ext.parse(parser) + self.assertIsInstance(ext2, ALPNExtension) + self.assertEqual(ext2.protocol_names, [bytearray(b'http/1.1'), + bytearray(b'spdy/1')]) + + if __name__ == '__main__': unittest.main() From 9a0bba69e4fc0f8fbf0a563fcce5303ed9296898 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 26 Aug 2016 19:28:53 +0200 Subject: [PATCH 357/574] client side support for ALPN --- scripts/tls.py | 23 +++++++++++--- tlslite/session.py | 10 +++++- tlslite/tlsconnection.py | 68 +++++++++++++++++++++++++++++++++------- 3 files changed, 84 insertions(+), 17 deletions(-) diff --git a/scripts/tls.py b/scripts/tls.py index d66c7077..a1d6166a 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -76,11 +76,13 @@ def printUsage(s=None): [--reqcert] HOST:PORT client - [-k KEY] [-c CERT] [-u USER] [-p PASS] [-l LABEL] [-L LENGTH] + [-k KEY] [-c CERT] [-u USER] [-p PASS] [-l LABEL] [-L LENGTH] [-a ALPN] HOST:PORT LABEL - TLS exporter label LENGTH - amount of info to export using TLS exporter + ALPN - name of protocol for ALPN negotiation, can be present multiple times + in client to specify multiple protocols supported """) sys.exit(-1) @@ -109,6 +111,7 @@ def handleArgs(argv, argString, flagsList=[]): directory = None expLabel = None expLength = 20 + alpn = [] for opt, arg in opts: if opt == "-k": @@ -142,9 +145,14 @@ def handleArgs(argv, argString, flagsList=[]): expLabel = arg elif opt == "-L": expLength = int(arg) + elif opt == "-a": + alpn.append(bytearray(arg, 'utf-8')) else: assert(False) - + + # when no names provided, don't return array + if not alpn: + alpn = None if not argv: printError("Missing address") if len(argv)>1: @@ -178,6 +186,8 @@ def handleArgs(argv, argString, flagsList=[]): retList.append(expLabel) if "L" in argString: retList.append(expLength) + if "a" in argString: + retList.append(alpn) return retList @@ -216,6 +226,9 @@ def printGoodConnection(connection, seconds): emptyStr = "\n (via TACK Certificate)" print(" TACK: %s" % emptyStr) print(str(connection.session.tackExt)) + if connection.session.appProto: + print(" Application Layer Protocol negotiated: {0}".format( + connection.session.appProto.decode('utf-8'))) print(" Next-Protocol Negotiated: %s" % connection.next_proto) print(" Encrypt-then-MAC: {0}".format(connection.encryptThenMAC)) print(" Extended Master Secret: {0}".format( @@ -233,8 +246,8 @@ def printExporter(connection, expLabel, expLength): def clientCmd(argv): (address, privateKey, certChain, username, password, expLabel, - expLength) = \ - handleArgs(argv, "kcuplL") + expLength, alpn) = \ + handleArgs(argv, "kcuplLa") if (certChain and not privateKey) or (not certChain and privateKey): raise SyntaxError("Must specify CERT and KEY together") @@ -261,7 +274,7 @@ def clientCmd(argv): settings=settings, serverName=address[0]) else: connection.handshakeClientCert(certChain, privateKey, - settings=settings, serverName=address[0]) + settings=settings, serverName=address[0], alpn=alpn) stop = time.clock() print("Handshake success") except TLSLocalAlert as a: diff --git a/tlslite/session.py b/tlslite/session.py index c31e9dfb..19ff985c 100644 --- a/tlslite/session.py +++ b/tlslite/session.py @@ -46,6 +46,10 @@ class Session(object): @type encryptThenMAC: bool @ivar encryptThenMAC: True if connection uses CBC cipher in encrypt-then-MAC mode + + @type appProto: bytearray + @ivar appProto: name of the negotiated application level protocol, None + if not negotiated """ def __init__(self): @@ -61,11 +65,13 @@ def __init__(self): self.resumable = False self.encryptThenMAC = False self.extendedMasterSecret = False + self.appProto = bytearray(0) def create(self, masterSecret, sessionID, cipherSuite, srpUsername, clientCertChain, serverCertChain, tackExt, tackInHelloExt, serverName, resumable=True, - encryptThenMAC=False, extendedMasterSecret=False): + encryptThenMAC=False, extendedMasterSecret=False, + appProto=bytearray(0)): self.masterSecret = masterSecret self.sessionID = sessionID self.cipherSuite = cipherSuite @@ -78,6 +84,7 @@ def create(self, masterSecret, sessionID, cipherSuite, self.resumable = resumable self.encryptThenMAC = encryptThenMAC self.extendedMasterSecret = extendedMasterSecret + self.appProto = appProto def _clone(self): other = Session() @@ -93,6 +100,7 @@ def _clone(self): other.resumable = self.resumable other.encryptThenMAC = self.encryptThenMAC other.extendedMasterSecret = self.extendedMasterSecret + other.appProto = self.appProto return other def valid(self): diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 331573a2..e879e9cb 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -255,7 +255,7 @@ def handshakeClientSRP(self, username, password, session=None, def handshakeClientCert(self, certChain=None, privateKey=None, session=None, settings=None, checker=None, nextProtos=None, reqTack=True, serverName=None, - async=False): + async=False, alpn=None): """Perform a certificate-based handshake in the role of client. This function performs an SSL or TLS handshake. The server @@ -320,6 +320,11 @@ def handshakeClientCert(self, certChain=None, privateKey=None, waiting to write to the socket, or will raise StopIteration if the handshake operation is completed. + @type alpn: list of bytearrays + @param alpn: protocol names to advertise to server as supported by + client in the Application Layer Protocol Negotiation extension. + Example items in the array include b'http/1.1' or b'h2'. + @rtype: None or an iterable @return: If 'async' is True, a generator object will be returned. @@ -337,7 +342,8 @@ def handshakeClientCert(self, certChain=None, privateKey=None, checker=checker, serverName=serverName, nextProtos=nextProtos, - reqTack=reqTack) + reqTack=reqTack, + alpn=alpn) # The handshaker is a Python Generator which executes the handshake. # It allows the handshake to be run in a "piecewise", asynchronous # fashion, returning 1 when it is waiting to able to write, 0 when @@ -353,7 +359,8 @@ def handshakeClientCert(self, certChain=None, privateKey=None, def _handshakeClientAsync(self, srpParams=(), certParams=(), anonParams=(), session=None, settings=None, checker=None, - nextProtos=None, serverName=None, reqTack=True): + nextProtos=None, serverName=None, reqTack=True, + alpn=None): handshaker = self._handshakeClientAsyncHelper(srpParams=srpParams, certParams=certParams, @@ -362,14 +369,16 @@ def _handshakeClientAsync(self, srpParams=(), certParams=(), anonParams=(), settings=settings, serverName=serverName, nextProtos=nextProtos, - reqTack=reqTack) + reqTack=reqTack, + alpn=alpn) for result in self._handshakeWrapperAsync(handshaker, checker): yield result def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, - session, settings, serverName, nextProtos, reqTack): - + session, settings, serverName, nextProtos, + reqTack, alpn): + self._handshakeStart(client=True) #Unpack parameters @@ -408,7 +417,9 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, if nextProtos is not None: if len(nextProtos) == 0: raise ValueError("Caller passed no nextProtos") - + if alpn is not None and not alpn: + raise ValueError("Caller passed empty alpn list") + # Validates the settings and filters out any unsupported ciphers # or crypto libraries that were requested if not settings: @@ -451,7 +462,7 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, for result in self._clientSendClientHello(settings, session, srpUsername, srpParams, certParams, anonParams, serverName, nextProtos, - reqTack): + reqTack, alpn): if result in (0,1): yield result else: break clientHello = result @@ -485,6 +496,11 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, self._handshakeDone(resumed=True) self._serverRandom = serverHello.random self._clientRandom = clientHello.random + # alpn protocol is independent of resumption and renegotiation + # and needs to be negotiated every time + alpnExt = serverHello.getExtension(ExtensionType.alpn) + if alpnExt: + session.appProto = alpnExt.protocol_names[0] return #If the server selected an SRP ciphersuite, the client finishes @@ -547,6 +563,12 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, else: break masterSecret = result + # check if an application layer protocol was negotiated + alpnProto = None + alpnExt = serverHello.getExtension(ExtensionType.alpn) + if alpnExt: + alpnProto = alpnExt.protocol_names[0] + # Create the session object which is used for resumptions self.session = Session() self.session.create(masterSecret, serverHello.session_id, cipherSuite, @@ -554,15 +576,16 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, tackExt, (serverHello.tackExt is not None), serverName, encryptThenMAC=self._recordLayer.encryptThenMAC, - extendedMasterSecret=self.extendedMasterSecret) + extendedMasterSecret=self.extendedMasterSecret, + appProto=alpnProto) self._handshakeDone(resumed=False) self._serverRandom = serverHello.random self._clientRandom = clientHello.random def _clientSendClientHello(self, settings, session, srpUsername, - srpParams, certParams, anonParams, - serverName, nextProtos, reqTack): + srpParams, certParams, anonParams, + serverName, nextProtos, reqTack, alpn): #Initialize acceptable ciphersuites cipherSuites = [CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV] if srpParams: @@ -610,6 +633,9 @@ def _clientSendClientHello(self, settings, session, srpUsername, assert len(sigList) > 0 extensions.append(SignatureAlgorithmsExtension().\ create(sigList)) + # if we know any protocols for ALPN, advertise them + if alpn: + extensions.append(ALPNExtension().create(alpn)) # don't send empty list of extensions or extensions in SSLv3 if not extensions or settings.maxVersion == (3, 0): extensions = None @@ -715,6 +741,26 @@ def _clientGetServerHello(self, settings, clientHello): AlertDescription.insufficient_security, "Negotiation of Extended master Secret failed"): yield result + alpnExt = serverHello.getExtension(ExtensionType.alpn) + if alpnExt: + if not alpnExt.protocol_names or \ + len(alpnExt.protocol_names) != 1: + for result in self._sendError( + AlertDescription.illegal_parameter, + "Server responded with invalid ALPN extension"): + yield result + clntAlpnExt = clientHello.getExtension(ExtensionType.alpn) + if not clntAlpnExt: + for result in self._sendError( + AlertDescription.unsupported_extension, + "Server sent ALPN extension without one in " + "client hello"): + yield result + if alpnExt.protocol_names[0] not in clntAlpnExt.protocol_names: + for result in self._sendError( + AlertDescription.illegal_parameter, + "Server selected ALPN protocol we did not advertise"): + yield result yield serverHello def _clientSelectNextProto(self, nextProtos, serverHello): From ff74a8a9268b538fbdedd97d12a32761e7123d63 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 26 Aug 2016 20:24:24 +0200 Subject: [PATCH 358/574] add support for server side ALPN --- scripts/tls.py | 1 + tlslite/tlsconnection.py | 74 ++++++++++++++++++++++++++++++++++------ 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/scripts/tls.py b/scripts/tls.py index a1d6166a..e0653ca2 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -355,6 +355,7 @@ def handshake(self, connection): sessionCache=sessionCache, settings=settings, nextProtos=[b"http/1.1"], + alpn=[bytearray(b'http/1.1')], reqCert=reqCert) # As an example (does not work here): #nextProtos=[b"spdy/3", b"spdy/2", b"http/1.1"]) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index e879e9cb..422e007d 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1065,7 +1065,7 @@ def handshakeServer(self, verifierDB=None, sessionCache=None, settings=None, checker=None, reqCAs = None, tacks=None, activationFlags=0, - nextProtos=None, anon=False): + nextProtos=None, anon=False, alpn=None): """Perform a handshake in the role of server. This function performs an SSL or TLS handshake. Depending on @@ -1134,6 +1134,11 @@ def handshakeServer(self, verifierDB=None, clients through the Next-Protocol Negotiation Extension, if they support it. + @type alpn: list of bytearrays + @param alpn: names of application layer protocols supported. + Note that it will be used instead of NPN if both were advertised by + client. + @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @@ -1145,7 +1150,7 @@ def handshakeServer(self, verifierDB=None, certChain, privateKey, reqCert, sessionCache, settings, checker, reqCAs, tacks=tacks, activationFlags=activationFlags, - nextProtos=nextProtos, anon=anon): + nextProtos=nextProtos, anon=anon, alpn=alpn): pass @@ -1154,7 +1159,7 @@ def handshakeServerAsync(self, verifierDB=None, sessionCache=None, settings=None, checker=None, reqCAs=None, tacks=None, activationFlags=0, - nextProtos=None, anon=False + nextProtos=None, anon=False, alpn=None ): """Start a server handshake operation on the TLS connection. @@ -1173,7 +1178,7 @@ def handshakeServerAsync(self, verifierDB=None, sessionCache=sessionCache, settings=settings, reqCAs=reqCAs, tacks=tacks, activationFlags=activationFlags, - nextProtos=nextProtos, anon=anon) + nextProtos=nextProtos, anon=anon, alpn=alpn) for result in self._handshakeWrapperAsync(handshaker, checker): yield result @@ -1182,7 +1187,7 @@ def _handshakeServerAsyncHelper(self, verifierDB, certChain, privateKey, reqCert, sessionCache, settings, reqCAs, tacks, activationFlags, - nextProtos, anon): + nextProtos, anon, alpn): self._handshakeStart(client=False) @@ -1203,6 +1208,8 @@ def _handshakeServerAsyncHelper(self, verifierDB, raise ValueError("tackpy is not loaded") if not settings or not settings.useExperimentalTackExtension: raise ValueError("useExperimentalTackExtension not enabled") + if alpn is not None and not alpn: + raise ValueError("Empty list of ALPN protocols") if not settings: settings = HandshakeSettings() @@ -1212,9 +1219,9 @@ def _handshakeServerAsyncHelper(self, verifierDB, # ****************************** # Handle ClientHello and resumption - for result in self._serverGetClientHello(settings, certChain,\ - verifierDB, sessionCache, - anon): + for result in self._serverGetClientHello(settings, certChain, + verifierDB, sessionCache, + anon, alpn): if result in (0,1): yield result elif result == None: self._handshakeDone(resumed=True) @@ -1233,6 +1240,11 @@ def _handshakeServerAsyncHelper(self, verifierDB, if not clientHello.supports_npn: nextProtos = None + alpnExt = clientHello.getExtension(ExtensionType.alpn) + if alpnExt and alpn: + # if there's ALPN, don't do NPN + nextProtos = None + # If not doing a certificate-based suite, discard the TACK if not cipherSuite in CipherSuite.certAllSuites: tacks = None @@ -1266,6 +1278,19 @@ def _handshakeServerAsyncHelper(self, verifierDB, "Failed to negotiate Extended Master Secret"): yield result + selectedALPN = None + if alpnExt and alpn: + for protoName in alpnExt.protocol_names: + if protoName in alpn: + selectedALPN = protoName + ext = ALPNExtension().create([protoName]) + extensions.append(ext) + break + else: + for result in self._sendError( + AlertDescription.no_application_protocol, + "No mutually supported application layer protocols"): + yield result # notify client that we understood its renegotiation info extension # or SCSV secureRenego = False @@ -1388,7 +1413,8 @@ def _handshakeServerAsyncHelper(self, verifierDB, srpUsername, clientCertChain, serverCertChain, tackExt, (serverHello.tackExt is not None), serverName, - encryptThenMAC=self._recordLayer.encryptThenMAC) + encryptThenMAC=self._recordLayer.encryptThenMAC, + appProto=selectedALPN) #Add the session object to the session cache if sessionCache and sessionID: @@ -1400,7 +1426,7 @@ def _handshakeServerAsyncHelper(self, verifierDB, def _serverGetClientHello(self, settings, certChain, verifierDB, - sessionCache, anon): + sessionCache, anon, alpn): #Tentatively set version to most-desirable version, so if an error #occurs parsing the ClientHello, this is what we'll use for the #error alert @@ -1421,6 +1447,16 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, "Too old version: %s" % str(clientHello.client_version)): yield result + # Sanity check the ALPN extension + alpnExt = clientHello.getExtension(ExtensionType.alpn) + if alpnExt: + for protocolName in alpnExt.protocol_names: + if not protocolName: + for result in self._sendError( + AlertDescription.illegal_parameter, + "Client sent empty name in ALPN extension"): + yield result + #If client's version is too high, propose my highest version elif clientHello.client_version > settings.maxVersion: self.version = settings.maxVersion @@ -1545,6 +1581,23 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, if secureRenego: extensions.append(RenegotiationInfoExtension() .create(bytearray(0))) + selectedALPN = None + if alpn: + alpnExt = clientHello.getExtension(ExtensionType.alpn) + if alpnExt: + for protocolName in alpnExt.protocol_names: + if protocolName in alpn: + ext = ALPNExtension().create([protocolName]) + extensions.append(ext) + selectedALPN = protocolName + break + else: + for result in self._sendError( + AlertDescription.no_application_protocol, + "No commonly supported application layer" + "protocol supported"): + yield result + # don't send empty extensions if not extensions: extensions = None @@ -1575,6 +1628,7 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, self.session = session self._clientRandom = clientHello.random self._serverRandom = serverHello.random + self.session.appProto = selectedALPN yield None # Handshake done! #Calculate the first cipher suite intersection. From 04b018675b4caec6834946947beb673a39cc9a94 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 30 Aug 2016 12:34:23 +0200 Subject: [PATCH 359/574] fix typo in ALPNExtension comment --- tlslite/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index e1b95c44..23a2c192 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -1178,7 +1178,7 @@ class ALPNExtension(TLSExtension): def __init__(self): """ - Create instance of NPNExtension + Create instance of ALPNExtension See also: L{create} and L{parse} """ From 6f2d849a34b2ba1b44ebe28dd8224b17fdec9d63 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 7 Sep 2016 15:01:42 +0200 Subject: [PATCH 360/574] tag version 0.6.0 --- README | 2 ++ README.md | 7 +++++-- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README b/README index 7cafd474..6ee377a9 100644 --- a/README +++ b/README @@ -27,6 +27,8 @@ Functionality implemented include: - Extended Master Secret calculation for TLS connections (RFC 7627) - padding extension (RFC 7685) - Keying material exporter (RFC 5705) + - Next Protocol Negotiation + - Application-Layer Protocol Negotiation Extension (RFC 7301) tlslite-ng aims to be a drop-in replacement for tlslite while providing more diff --git a/README.md b/README.md index 87d3cf4f..ddbe7d89 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.6.0-beta1 2016-08-08 +tlslite-ng version 0.6.0 2016-09-07 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -584,8 +584,11 @@ encrypt-then-MAC mode for CBC ciphers. 12 History =========== -0.6.0 - WIP +0.6.0 - 2016-09-07 +* added support for ALPN from RFC 7301 +* fixed handling of SRP databases +* fixed compatibility issues with Python 3 * fixed compatibility with Python 2.7.3 * AECDH support on server side (Milan Lysonek) * make the Client Hello parser more strict, it will now abort if the diff --git a/setup.py b/setup.py index 80444c4c..24d79bf4 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.6.0-beta1", + version="0.6.0", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 7eec09cd..66db5032 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.6.0-beta1 +@version: 0.6.0 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index be0b3cc4..870fad85 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.6.0-beta1" +__version__ = "0.6.0" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 8d1999782bc4b354a06f2ad3634a2daa7e6836e8 Mon Sep 17 00:00:00 2001 From: almond29 Date: Wed, 14 Sep 2016 19:33:53 +0200 Subject: [PATCH 361/574] RSA PSS implementation and test cases --- tlslite/errors.py | 27 + tlslite/utils/rsakey.py | 166 +++- unit_tests/test_tlslite_utils_rsakey.py | 1177 ++++++++++++++++++++++- 3 files changed, 1368 insertions(+), 2 deletions(-) diff --git a/tlslite/errors.py b/tlslite/errors.py index 473c43ac..cddc9e85 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -24,6 +24,9 @@ class BaseTLSException(Exception): pass +class EncryptionError(BaseTLSException): + """Base class for exceptions thrown while encrypting""" + class TLSError(BaseTLSException): """Base class for all TLS Lite exceptions.""" @@ -230,3 +233,27 @@ class TLSUnknownPSKIdentity(TLSProtocolException): pass +class MaskTooLongError(EncryptionError): + """The maskLen passed into function is too high""" + + pass + +class MessageTooLongError(EncryptionError): + """The message passed into function is too long""" + + pass + +class EncodingError(EncryptionError): + """An error appeared while encoding""" + + pass + +class InvalidSignature(EncryptionError): + """Verification function found invalid signature""" + + pass + +class UnknownRSAType(EncryptionError): + """Unknown RSA algorithm type passed""" + + pass \ No newline at end of file diff --git a/tlslite/utils/rsakey.py b/tlslite/utils/rsakey.py index a09d56ba..3f06d314 100644 --- a/tlslite/utils/rsakey.py +++ b/tlslite/utils/rsakey.py @@ -4,6 +4,10 @@ """Abstract class for RSA.""" from .cryptomath import * +from .poly1305 import Poly1305 +from . import tlshashlib as hashlib +from ..errors import MaskTooLongError, MessageTooLongError, EncodingError, \ + InvalidSignature, UnknownRSAType class RSAKey(object): @@ -87,6 +91,167 @@ def hashAndVerify(self, sigBytes, bytes): result2 = self.verify(sigBytes, prefixedHashBytes2) return (result1 or result2) + def MGF1(self, mgfSeed, maskLen, hAlg): + """Generate mask from passed-in seed. + + This generates mask based on passed-in seed and output maskLen. + + @type mgfSeed: L{bytearray} + @param mgfSeed: Seed from which mask will be generated. + + @type maskLen: int + @param maskLen: Wished length of the mask, in octets + + @rtype: L{bytearray} + @return: Mask + """ + hashLen = getattr(hashlib, hAlg)().digest_size + if maskLen > (2 ** 32) * hashLen: + raise MaskTooLongError("Incorrect parameter maskLen") + T = bytearray() + end = (Poly1305.divceil(maskLen, hashLen)) + for x in range(0, end): + C = numberToByteArray(x, 4) + T += secureHash(mgfSeed + C, hAlg) + return T[:maskLen] + + def EMSA_PSS_encode(self, M, emBits, hAlg, sLen=0): + """Encode the passed in message + + This encodes the message using selected hash algorithm + + @type M: bytearray + @param M: Message to be encoded + + @type emBits: int + @param emBits: maximal length of returned EM + + @type hAlg: str + @param hAlg: hash algorithm to be used + + @type sLen: int + @param sLen: length of salt""" + hashLen = getattr(hashlib, hAlg)().digest_size + mHash = secureHash(M, hAlg) + emLen = Poly1305.divceil(emBits, 8) + if emLen < hashLen + sLen + 2: + raise EncodingError("The ending limit too short for selected hash and salt length") + salt = getRandomBytes(sLen) + M2 = bytearray(8) + mHash + salt + H = secureHash(M2, hAlg) + PS = bytearray(emLen - sLen - hashLen - 2) + DB = PS + bytearray(b'\x01') + salt + dbMask = self.MGF1(H, emLen - hashLen - 1, hAlg) + maskedDB = bytearray(i ^ j for i, j in zip(DB, dbMask)) + mLen = emLen*8 - emBits + mask = (1 << 8 - mLen) - 1 + maskedDB[0] &= mask + EM = maskedDB + H + bytearray(b'\xbc') + return EM + + def RSASSA_PSS_sign(self, M, hAlg, sLen=0): + """"Sign the passed in message + + This signs the message using selected hash algorithm + + @type M: bytearray + @param M: Message to be signed + + @type hAlg: str + @param hAlg: hash algorithm to be used + + @type sLen: int + @param sLen: length of salt""" + EM = self.EMSA_PSS_encode(M, numBits(self.n) - 1, hAlg, sLen) + m = bytesToNumber(EM) + if m >= self.n: + raise MessageTooLongError("Encode output too long") + s = self._rawPrivateKeyOp(m) + S = numberToByteArray(s, numBytes(self.n)) + return S + + def EMSA_PSS_verify(self, M, EM, emBits, hAlg, sLen=0): + """Verify signature in passed in encoded message + + This verifies the signature in encoded message + + @type M: bytearray + @param M: Original not signed message + + @type EM: bytearray + @param EM: Encoded message + + @type emBits: int + @param emBits: Length of the encoded message in bits + + @type hAlg: str + @param hAlg: hash algorithm to be used + + @type sLen: int + @param sLen: Length of salt + """ + hashLen = getattr(hashlib, hAlg)().digest_size + mHash = secureHash(M, hAlg) + emLen = Poly1305.divceil(emBits, 8) + if emLen < hashLen + sLen + 2: + raise InvalidSignature("Invalid signature") + if EM[-1] != 0xbc: + raise InvalidSignature("Invalid signature") + maskedDB = EM[0:emLen - hashLen - 1] + H = EM[emLen - hashLen - 1:emLen - hashLen - 1 + hashLen] + DBHelpMask = 1 << 8 - (8*emLen - emBits) + DBHelpMask -= 1 + DBHelpMask = (~DBHelpMask) & 0xff + if maskedDB[0] & DBHelpMask != 0: + raise InvalidSignature("Invalid signature") + dbMask = self.MGF1(H, emLen - hashLen - 1, hAlg) + DB = bytearray(i ^ j for i, j in zip(maskedDB, dbMask)) + mLen = emLen*8 - emBits + mask = (1 << 8 - mLen) - 1 + DB[0] &= mask + if any(x != 0 for x in DB[0:emLen - hashLen - sLen - 2 - 1]): + raise InvalidSignature("Invalid signature") + if DB[emLen - hashLen - sLen - 2] != 0x01: + raise InvalidSignature("Invalid signature") + if sLen != 0: + salt = DB[-sLen:] + else: + salt = bytearray() + newM = bytearray(8) + mHash + salt + newH = secureHash(newM, hAlg) + if H == newH: + return True + else: + raise InvalidSignature("Invalid signature") + + def RSASSA_PSS_verify(self, M, S, hAlg, sLen=0): + """Verify the signature in passed in message + + This verifies the signature in the signed message + + @type M: bytearray + @param M: Original message + + @type S: bytearray + @param S: Signed message + + @type hAlg: str + @param hAlg: Hash algorithm to be used + + @type sLen: int + @param sLen: Length of salt + """ + if len(bytearray(S)) != len(numberToByteArray(self.n)): + raise InvalidSignature + s = bytesToNumber(S) + m = self._rawPublicKeyOp(s) + EM = numberToByteArray(m, Poly1305.divceil(numBits(self.n) - 1, 8)) + result = self.EMSA_PSS_verify(M, EM, numBits(self.n) - 1, hAlg, sLen) + if result: + return True + else: + raise InvalidSignature("Invalid signature") + def sign(self, bytes): """Sign the passed-in bytes. @@ -111,7 +276,6 @@ def sign(self, bytes): def verify(self, sigBytes, bytes): """Verify the passed-in bytes with the signature. - This verifies a PKCS1 signature on the passed-in data. @type sigBytes: L{bytearray} of unsigned bytes diff --git a/unit_tests/test_tlslite_utils_rsakey.py b/unit_tests/test_tlslite_utils_rsakey.py index 3711d89a..9225a536 100644 --- a/unit_tests/test_tlslite_utils_rsakey.py +++ b/unit_tests/test_tlslite_utils_rsakey.py @@ -11,6 +11,1182 @@ from tlslite.utils.rsakey import RSAKey from tlslite.utils.python_rsakey import Python_RSAKey +from tlslite.utils.cryptomath import * +from tlslite.errors import * +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call + +class TestRSAPSS_components(unittest.TestCase): + + def setUp(self): + self.rsa = Python_RSAKey() + + def test_encodingError(self): + with self.assertRaises(EncodingError): + self.assertEqual(self.rsa.EMSA_PSS_encode( + bytearray(b'\xc7\xf5\'\x0f\xcar_\x9b\xd1\x9fQ\x9a\x8d|\xca<' + + b'\xc5\xc0y\x02@)\xf3\xba\xe5\x10\xf9\xb0!@\xfe#' + + b'\x89\x08\xe4\xf6\xc1\x8f\x07\xa8\x9ch|\x86\x84f' + + b'\x9b\x1f\x1d\xb2\xba\xf9%\x1a<\x82\x9f\xac\xcbI0' + + b'\x84\xe1n\xc9\xe2\x8dX\x86\x80t\xa5\xd6"\x16g' + + b'\xddnR\x8d\x16\xfe,\x9f=\xb4\xcf\xaflM\xce\x8c' + + b'\x849\xaf8\xce\xaa\xaa\x9c\xe2\xec\xae{\xc8\xf4' + + b'\xa5\xa5^;\xf9m\xf9\xcdW\\O\x9c\xb3\'\x95\x1b' + + b'\x8c\xdf\xe4\x08qh'), 10, 'sha1', 10), + bytearray(b'eA=!Fq4\xce\xef5?\xf4\xec\xd8\xa6FPX\xdc~(\xe3' + + b'\x92\x17z\xa5-\xcfV\xd4)\x99\x8fJ\xb2\x08\xa2@' + + b'\xe3')) + + def test_MGF1_2(self): + self.assertEqual(self.rsa.MGF1(bytearray(b'\xad\x8f\xd1\xf7\xf9' + + b'\x7fgrRS\xce}\x18\x985' + + b'\xb3'), 40, 'sha1'), + bytearray( + b'\xb80\x12s\xbb\xd9j\xce&U\x08\x14\xb2\x070' + + b'\xc7\xc8\xa8\xa0\xc1\xc3\xf3\xd41\xad\xbe\xe8' + + b'\x1dN\x94\xf6sx\x02\xed\xfb\x0b\x0b\x85\xc5')) + + def test_EMSA_PSS_encode(self): + def m(leght): + return bytearray(b'\x11"3DUT2\x16x\x90') + with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): + self.assertEqual(self.rsa.EMSA_PSS_encode( + bytearray(b'\xc7\xf5\'\x0f\xcar_\x9b\xd1\x9fQ\x9a\x8d|\xca<' + + b'\xc5\xc0y\x02@)\xf3\xba\xe5\x10\xf9\xb0!@\xfe#' + + b'\x89\x08\xe4\xf6\xc1\x8f\x07\xa8\x9ch|\x86\x84f' + + b'\x9b\x1f\x1d\xb2\xba\xf9%\x1a<\x82\x9f\xac\xcbI0' + + b'\x84\xe1n\xc9\xe2\x8dX\x86\x80t\xa5\xd6"\x16g' + + b'\xddnR\x8d\x16\xfe,\x9f=\xb4\xcf\xaflM\xce\x8c' + + b'\x849\xaf8\xce\xaa\xaa\x9c\xe2\xec\xae{\xc8\xf4' + + b'\xa5\xa5^;\xf9m\xf9\xcdW\\O\x9c\xb3\'\x95\x1b' + + b'\x8c\xdf\xe4\x08qh'), 1023, 'sha1', 10), + bytearray(b'eA=!Fq4\xce\xef5?\xf4\xec\xd8\xa6FPX\xdc~(\xe3' + + b'\x92\x17z\xa5-\xcfV\xd4)\x99\x8fJ\xb2\x08\xa2\xeb\t{\x8c\x13\xa3' + + b'\xfbm\xe7\xd2\r\xbc!\x91\x07\xaf \xf9\xd3V@' + + b'\xb0z,\xb8q\xec$A\x0c\x13\xda\xb7/\xa4U\xa3' + + b'\xbb7A\x92\x9b\'\xcf\x9f,\xad\x18\x1a\xf9_\x87' + + b'\xf5\x0e\x02\x08\x04\x88\xa3uDnrI\xff\xd0\xa7~_' + + b'\xdc>"}%i\xc4\x1a"w\xfdW\x14\x91\xae\x1b\x1f' + + b'\xcd\xd9L\xf7(w-]f\xc7\xdc\x82\n\x00[4\xf1f\xf6' + + b'\x141\x0e\x12\x13\xf8\x96fc\xc1\x15\xca\x95W' + + b'\xd8i\x0f@\x94\x01%\x14(Z\x88\xe1\x00\xf4\xf7' + + b'\x81\xfd,\x899x\x9b"\xa4\x01\xef@4@\x9cY\xe2' + + b'\x91\x89;{\x8dh\x80Ei\x1b\xf1\x7f1\x93\xe1\xa1j' + + b'\xb0\xf1Z\x0c>\xcc\xbeX&$\xfd\x96\xd3\x1e\x92' + + b'\xf5\x9b\xbd\x1a\xaa\t\x85>\x13\xb5\xf1s\xa7YN' + + b'\x1f\xdb\xa1*\xcc\x93\xa2\xbf\xfd\xe0\xda>0') + self.assertTrue(self.rsa.RSASSA_PSS_verify( + self.message, signed, 'sha1', 0)) + +class TestRSAPSS_mod1024(unittest.TestCase): + # Test cases from http://csrc.nist.gov/groups/STM/cavp/ + # file SigVerPSS_186-3.rsp + + n = int("be499b5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b9bfeb7aa72db126" + "4112eb3fd431d31a2a7e50941566929494a0e891ed5613918b4b51b0d1fb97783" + "b26acf7d0f384cfb35f4d2824f5dd380623a26bf180b63961c619dcdb20cae406" + "f22f6e276c80a37259490cfeb72c1a71a84f1846d330877ba3e3101ec9c7b", + 16) + e = int("00000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000011", + 16) + d = int("0d0f17362bdad181db4e1fe03e8de1a3208989914e14bf269558826bfa20faf4b" + "68dba6bb989a01f03a21c44665dc5f648cb5b59b954eb1077a80263bd22cdfb88" + "d39164b7404f4f1106ee01cf60b77695748d8fdaf9fd428963fe75144010b1934" + "c8e26a88239672cf49b3422a07c4d834ba208d570fe408e7095c90547e68d", + 16) + p = int("e7a80c5d211c06acb900939495f26d365fc2b4825b75e356f89003eaa5931e6be" + "5c3f7e6a633ad59db6289d06c354c235e739a1e3f3d39fb40d1ffb9cb44288f", + 16) + q = int("d248aa248000f720258742da67b711940c8f76e1ecd52b67a6ffe1e49354d66ff" + "84fa601804743f5838da2ed4693a5a28658d6528cc1803bf6c8dc73c5230b55", + 16) + dP = d % (p - 1) + dQ = d % (q - 1) + qInv = invMod(q, p) + message = bytearray(b'\xc7\xf5\x27\x0f\xca\x72\x5f\x9b\xd1\x9f\x51' + + b'\x9a\x8d\x7c\xca\x3c\xc5\xc0\x79\x02\x40\x29' + + b'\xf3\xba\xe5\x10\xf9\xb0\x21\x40\xfe\x23\x89' + + b'\x08\xe4\xf6\xc1\x8f\x07\xa8\x9c\x68\x7c\x86' + + b'\x84\x66\x9b\x1f\x1d\xb2\xba\xf9\x25\x1a\x3c' + + b'\x82\x9f\xac\xcb\x49\x30\x84\xe1\x6e\xc9\xe2' + + b'\x8d\x58\x86\x80\x74\xa5\xd6\x22\x16\x67\xdd' + + b'\x6e\x52\x8d\x16\xfe\x2c\x9f\x3d\xb4\xcf\xaf' + + b'\x6c\x4d\xce\x8c\x84\x39\xaf\x38\xce\xaa\xaa' + + b'\x9c\xe2\xec\xae\x7b\xc8\xf4\xa5\xa5\x5e\x3b' + + b'\xf9\x6d\xf9\xcd\x57\x5c\x4f\x9c\xb3\x27\x95' + + b'\x1b\x8c\xdf\xe4\x08\x71\x68') + salt = bytearray(b'\x11\x22\x33\x44\x55\x54\x32\x16\x78\x90') + + def setUp(self): + self.rsa = Python_RSAKey(self.n, self.e, self.d, self.p, self.q, + self.dP, self.dQ, self.qInv) + + def test_RSAPSS_sha1(self): + intendedS = bytearray(b'\x96\xc3\xf6\x92\x70\x1d\x14\xeb\xbe\xf9' + + b'\x22\xa5\xc2\x25\x7f\x71\x3d\x20\xa9\x2c' + + b'\x69\x38\x74\xe0\x35\xb5\xb0\x65\x15\x92' + + b'\xab\x1b\x96\x43\xd3\x81\xd6\xb4\xa9\x70' + + b'\xda\xd7\xe2\x38\x00\xe4\x9d\x1a\x66\x57' + + b'\xc3\x33\x35\x8e\x9b\xfa\x5c\x71\x34\x93' + + b'\x53\x3b\x90\xb0\x23\x4a\x0d\x0d\xcf\x42' + + b'\xd0\xa6\x6b\x48\x03\xe4\xdb\x78\x06\x19' + + b'\xcc\xab\x6b\xa5\xbb\x27\xd0\x43\xf3\x2d' + + b'\x8e\x60\x1e\x2f\x12\xee\x08\xae\xce\x5c' + + b'\x47\xcc\x2e\x02\x89\xcd\xbf\x25\xc9\x77' + + b'\xcf\x1b\xea\xdc\x04\x74\x21\x50\xbe\xea' + + b'\xd6\x96\x2d\xdd\xa9\xe9\x1e\x17') + def m(leght): + return self.salt + with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): + signed = self.rsa.RSASSA_PSS_sign(self.message, 'sha1', 10) + self.assertEqual(signed, intendedS) + + def test_RSAPSS_sha224(self): + intendedS = bytearray(b'\x47\xbd\x25\xf8\x19\xbe\x0f\x7e\xe8\x48\xa3' + + b'\x3c\x19\x54\xb5\xbb\xc5\xb0\x0f\xf1\x04\xa2' + + b'\xab\x98\xf4\x8c\x38\xe0\x17\x6a\x74\xd7\x07' + + b'\xb4\x4c\x36\xdf\x8d\x8c\x12\xda\x49\xec\xec' + + b'\x7b\xdc\xc3\x51\x45\x39\xdb\x2b\xd8\xe0\x64' + + b'\xca\x62\x89\xaf\xd0\x72\xfd\x86\xc9\xf4\x2e' + + b'\x56\x58\xb4\x35\x5b\x34\x19\x30\x4e\x0a\xe9' + + b'\x28\x57\x12\x8a\x3c\x5e\xbc\x9b\xa6\x01\x38' + + b'\xaf\x67\x44\xec\xf7\x52\x1a\xa1\x11\x94\xac' + + b'\x95\x20\x6c\xf7\xa8\x0b\xe9\xca\x5f\x4e\x58' + + b'\x49\xae\x67\xf0\x73\xdb\x7b\x69\x2f\xd9\x39' + + b'\xcb\x31\xed\x6b\xf5\xe0\x66') + def m(leght): + return self.salt + with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): + signed = self.rsa.RSASSA_PSS_sign(self.message, 'sha224', 10) + self.assertEqual(signed, intendedS) + + + def test_RSAPSS_sha256(self): + intendedS = bytearray(b'\x11\xe1\x69\xf2\xfd\x40\xb0\x76\x41\xb9\x76' + + b'\x8a\x2a\xb1\x99\x65\xfb\x6c\x27\xf1\x0f\xcf' + + b'\x03\x23\xfc\xc6\xd1\x2e\xb4\xf1\xc0\x6b\x33' + + b'\x0d\xda\xa1\xea\x50\x44\x07\xaf\xa2\x9d\xe9' + + b'\xeb\xe0\x37\x4f\xe9\xd1\xe7\xd0\xff\xbd\x5f' + + b'\xc1\xcf\x3a\x34\x46\xe4\x14\x54\x15\xd2\xab' + + b'\x24\xf7\x89\xb3\x46\x4c\x5c\x43\xa2\x56\xbb' + + b'\xc1\xd6\x92\xcf\x7f\x04\x80\x1d\xac\x5b\xb4' + + b'\x01\xa4\xa0\x3a\xb7\xd5\x72\x8a\x86\x0c\x19' + + b'\xe1\xa4\xdc\x79\x7c\xa5\x42\xc8\x20\x3c\xec' + + b'\x2e\x60\x1e\xb0\xc5\x1f\x56\x7f\x2e\xda\x02' + + b'\x2b\x0b\x9e\xbd\xde\xee\xfa') + def m(leght): + return self.salt + with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): + signed = self.rsa.RSASSA_PSS_sign(self.message, 'sha256', 10) + self.assertEqual(signed, intendedS) + + + def test_RSAPSS_sha384(self): + intendedS = bytearray(b'\xb2\x81\xad\x93\x4b\x27\x75\xc0\xcb\xa5\xfb' + + b'\x10\xaa\x57\x4d\x2e\xd8\x5c\x7f\x99\xb9\x42' + + b'\xb7\x8e\x49\x70\x24\x80\x06\x93\x62\xed\x39' + + b'\x4b\xad\xed\x55\xe5\x6c\xfc\xbe\x7b\x0b\x8d' + + b'\x22\x17\xa0\x5a\x60\xe1\xac\xd7\x25\xcb\x09' + + b'\x06\x0d\xfa\xc5\x85\xbc\x21\x32\xb9\x9b\x41' + + b'\xcd\xbd\x53\x0c\x69\xd1\x7c\xdb\xc8\x4b\xc6' + + b'\xb9\x83\x0f\xc7\xdc\x8e\x1b\x24\x12\xcf\xe0' + + b'\x6d\xcf\x8c\x1a\x0c\xc3\x45\x3f\x93\xf2\x5e' + + b'\xbf\x10\xcb\x0c\x90\x33\x4f\xac\x57\x3f\x44' + + b'\x91\x38\x61\x6e\x1a\x19\x4c\x67\xf4\x4e\xfa' + + b'\xc3\x4c\xc0\x7a\x52\x62\x67') + def m(leght): + return self.salt + with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): + signed = self.rsa.RSASSA_PSS_sign(self.message, 'sha384', 10) + self.assertEqual(signed, intendedS) + + + def test_RSAPSS_sha512(self): + intendedS = bytearray(b'\x8f\xfc\x38\xf9\xb8\x20\xef\x6b\x08\x0f\xd2' + + b'\xec\x7d\xe5\x62\x6c\x65\x8d\x79\x05\x6f\x3e' + + b'\xdf\x61\x0a\x29\x5b\x7b\x05\x46\xf7\x3e\x01' + + b'\xff\xdf\x4d\x00\x70\xeb\xf7\x9c\x33\xfd\x86' + + b'\xc2\xd6\x08\xbe\x94\x38\xb3\xd4\x20\xd0\x95' + + b'\x35\xb9\x7c\xd3\xd8\x46\xec\xaf\x8f\x65\x51' + + b'\xcd\xf9\x31\x97\xe9\xf8\xfb\x04\x80\x44\x47' + + b'\x3a\xb4\x1a\x80\x1e\x9f\x7f\xc9\x83\xc6\x2b' + + b'\x32\x43\x61\xda\xde\x9f\x71\xa6\x59\x52\xbd' + + b'\x35\xc5\x9f\xaa\xa4\xd6\xff\x46\x2f\x68\xa6' + + b'\xc4\xec\x0b\x42\x8a\xa4\x73\x36\xf2\x17\x8a' + + b'\xeb\x27\x61\x36\x56\x3b\x7d') + def m(leght): + return self.salt + with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): + signed = self.rsa.RSASSA_PSS_sign(self.message, 'sha512', 10) + self.assertEqual(signed, intendedS) + + def test_RSASSA_PSS_verify_sha1(self): + signed = bytearray(b'\x96\xc3\xf6\x92\x70\x1d\x14\xeb\xbe\xf9' + + b'\x22\xa5\xc2\x25\x7f\x71\x3d\x20\xa9\x2c' + + b'\x69\x38\x74\xe0\x35\xb5\xb0\x65\x15\x92' + + b'\xab\x1b\x96\x43\xd3\x81\xd6\xb4\xa9\x70' + + b'\xda\xd7\xe2\x38\x00\xe4\x9d\x1a\x66\x57' + + b'\xc3\x33\x35\x8e\x9b\xfa\x5c\x71\x34\x93' + + b'\x53\x3b\x90\xb0\x23\x4a\x0d\x0d\xcf\x42' + + b'\xd0\xa6\x6b\x48\x03\xe4\xdb\x78\x06\x19' + + b'\xcc\xab\x6b\xa5\xbb\x27\xd0\x43\xf3\x2d' + + b'\x8e\x60\x1e\x2f\x12\xee\x08\xae\xce\x5c' + + b'\x47\xcc\x2e\x02\x89\xcd\xbf\x25\xc9\x77' + + b'\xcf\x1b\xea\xdc\x04\x74\x21\x50\xbe\xea' + + b'\xd6\x96\x2d\xdd\xa9\xe9\x1e\x17') + self.assertTrue(self.rsa.RSASSA_PSS_verify(self.message, signed, + 'sha1', 10)) + + def test_RSASSA_PSS_verify_shortSign(self): + with self.assertRaises(InvalidSignature): + signed = bytearray(b'\x96\xc3\xf6\x92\x70\x1d\x14\xeb\xbe\xf9' + + b'\x22\xa5\xc2\x25\x7f\x71\x3d\x20\xa9\x2c' + + b'\x69\x38\x74\xe0\x35\xb5\xb0\x65\x15\x92' + + b'\xab\x1b\x96\x43\xd3\x81\xd6\xb4\xa9\x70') + + self.assertTrue(self.rsa.RSASSA_PSS_verify(self.message, signed, + 'sha1', 10)) + + def test_RSASSA_PSS_verify_sha224(self): + signed = bytearray(b'\x47\xbd\x25\xf8\x19\xbe\x0f\x7e\xe8\x48\xa3' + + b'\x3c\x19\x54\xb5\xbb\xc5\xb0\x0f\xf1\x04\xa2' + + b'\xab\x98\xf4\x8c\x38\xe0\x17\x6a\x74\xd7\x07' + + b'\xb4\x4c\x36\xdf\x8d\x8c\x12\xda\x49\xec\xec' + + b'\x7b\xdc\xc3\x51\x45\x39\xdb\x2b\xd8\xe0\x64' + + b'\xca\x62\x89\xaf\xd0\x72\xfd\x86\xc9\xf4\x2e' + + b'\x56\x58\xb4\x35\x5b\x34\x19\x30\x4e\x0a\xe9' + + b'\x28\x57\x12\x8a\x3c\x5e\xbc\x9b\xa6\x01\x38' + + b'\xaf\x67\x44\xec\xf7\x52\x1a\xa1\x11\x94\xac' + + b'\x95\x20\x6c\xf7\xa8\x0b\xe9\xca\x5f\x4e\x58' + + b'\x49\xae\x67\xf0\x73\xdb\x7b\x69\x2f\xd9\x39' + + b'\xcb\x31\xed\x6b\xf5\xe0\x66') + self.assertTrue(self.rsa.RSASSA_PSS_verify(self.message, signed, + 'sha224', 10)) + + def test_RSASSA_PSS_verify_sha256(self): + signed = bytearray(b'\x11\xe1\x69\xf2\xfd\x40\xb0\x76\x41\xb9\x76' + + b'\x8a\x2a\xb1\x99\x65\xfb\x6c\x27\xf1\x0f\xcf' + + b'\x03\x23\xfc\xc6\xd1\x2e\xb4\xf1\xc0\x6b\x33' + + b'\x0d\xda\xa1\xea\x50\x44\x07\xaf\xa2\x9d\xe9' + + b'\xeb\xe0\x37\x4f\xe9\xd1\xe7\xd0\xff\xbd\x5f' + + b'\xc1\xcf\x3a\x34\x46\xe4\x14\x54\x15\xd2\xab' + + b'\x24\xf7\x89\xb3\x46\x4c\x5c\x43\xa2\x56\xbb' + + b'\xc1\xd6\x92\xcf\x7f\x04\x80\x1d\xac\x5b\xb4' + + b'\x01\xa4\xa0\x3a\xb7\xd5\x72\x8a\x86\x0c\x19' + + b'\xe1\xa4\xdc\x79\x7c\xa5\x42\xc8\x20\x3c\xec' + + b'\x2e\x60\x1e\xb0\xc5\x1f\x56\x7f\x2e\xda\x02' + + b'\x2b\x0b\x9e\xbd\xde\xee\xfa') + self.assertTrue(self.rsa.RSASSA_PSS_verify(self.message, signed, + 'sha256', 10)) + + def test_RSASSA_PSS_verify_sha384(self): + signed = bytearray(b'\xb2\x81\xad\x93\x4b\x27\x75\xc0\xcb\xa5\xfb' + + b'\x10\xaa\x57\x4d\x2e\xd8\x5c\x7f\x99\xb9\x42' + + b'\xb7\x8e\x49\x70\x24\x80\x06\x93\x62\xed\x39' + + b'\x4b\xad\xed\x55\xe5\x6c\xfc\xbe\x7b\x0b\x8d' + + b'\x22\x17\xa0\x5a\x60\xe1\xac\xd7\x25\xcb\x09' + + b'\x06\x0d\xfa\xc5\x85\xbc\x21\x32\xb9\x9b\x41' + + b'\xcd\xbd\x53\x0c\x69\xd1\x7c\xdb\xc8\x4b\xc6' + + b'\xb9\x83\x0f\xc7\xdc\x8e\x1b\x24\x12\xcf\xe0' + + b'\x6d\xcf\x8c\x1a\x0c\xc3\x45\x3f\x93\xf2\x5e' + + b'\xbf\x10\xcb\x0c\x90\x33\x4f\xac\x57\x3f\x44' + + b'\x91\x38\x61\x6e\x1a\x19\x4c\x67\xf4\x4e\xfa' + + b'\xc3\x4c\xc0\x7a\x52\x62\x67') + self.assertTrue(self.rsa.RSASSA_PSS_verify(self.message, signed, + 'sha384', 10)) + + def test_RSASSA_PSS_verify_sha512(self): + signed = bytearray(b'\x8f\xfc\x38\xf9\xb8\x20\xef\x6b\x08\x0f\xd2' + + b'\xec\x7d\xe5\x62\x6c\x65\x8d\x79\x05\x6f\x3e' + + b'\xdf\x61\x0a\x29\x5b\x7b\x05\x46\xf7\x3e\x01' + + b'\xff\xdf\x4d\x00\x70\xeb\xf7\x9c\x33\xfd\x86' + + b'\xc2\xd6\x08\xbe\x94\x38\xb3\xd4\x20\xd0\x95' + + b'\x35\xb9\x7c\xd3\xd8\x46\xec\xaf\x8f\x65\x51' + + b'\xcd\xf9\x31\x97\xe9\xf8\xfb\x04\x80\x44\x47' + + b'\x3a\xb4\x1a\x80\x1e\x9f\x7f\xc9\x83\xc6\x2b' + + b'\x32\x43\x61\xda\xde\x9f\x71\xa6\x59\x52\xbd' + + b'\x35\xc5\x9f\xaa\xa4\xd6\xff\x46\x2f\x68\xa6' + + b'\xc4\xec\x0b\x42\x8a\xa4\x73\x36\xf2\x17\x8a' + + b'\xeb\x27\x61\x36\x56\x3b\x7d') + self.assertTrue(self.rsa.RSASSA_PSS_verify(self.message, signed, + 'sha512', 10)) + + def test_RSASSA_PSS_verify_noSalt(self): + signed = bytearray(b'\xafe\x03\xb5\xaf5\x0b\t\xd1?9\x89\xee\x0eP\xcc' + + b'\x82\xef%\xc2t<\xa2\xff\xd6\x13[\x97\xbd\xac' + + b'\xda\x97;\xcb!\xfa"\x10\t\xb7\x81\xb9\x8f\x9a' + + b'\x1a\xc87\xa3,\xb4\xea\xddG7\xe8RI\xf9\x91m\x8e' + + b'\x91\xe3\xf8Y\xdd \x92\xd7I\xcc`czm\x01~\x85' + + b'\xf6\xa6\xd6_PF3\xc9\xb5\x192\xf4U\\|\xcc' + + b'\xcd6|7d\xca,\x8dIF\x02\xf8\xcd\x81\xdd\x88' + + b'\xb0\xae\xe9\x1f\x93\xf3\xfa\x90\x0f\xcd' + + b'\xe2|\xbc Date: Wed, 14 Sep 2016 19:45:05 +0200 Subject: [PATCH 362/574] Implementation of universal hashAndSign and hashAndVerify --- tlslite/errors.py | 2 +- tlslite/utils/rsakey.py | 77 +++++-- unit_tests/test_tlslite_keyexchange.py | 1 - unit_tests/test_tlslite_utils_rsakey.py | 261 ++++++++++++++++++++++++ 4 files changed, 323 insertions(+), 18 deletions(-) diff --git a/tlslite/errors.py b/tlslite/errors.py index cddc9e85..07661eca 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -256,4 +256,4 @@ class InvalidSignature(EncryptionError): class UnknownRSAType(EncryptionError): """Unknown RSA algorithm type passed""" - pass \ No newline at end of file + pass diff --git a/tlslite/utils/rsakey.py b/tlslite/utils/rsakey.py index 3f06d314..171c85ef 100644 --- a/tlslite/utils/rsakey.py +++ b/tlslite/utils/rsakey.py @@ -51,45 +51,88 @@ def hasPrivateKey(self): """ raise NotImplementedError() - def hashAndSign(self, bytes): + def hashAndSign(self, bytes, rsaScheme='PKCS1', hAlg='sha1', sLen=0): """Hash and sign the passed-in bytes. This requires the key to have a private component. It performs - a PKCS1-SHA1 signature on the passed-in data. + a PKCS1 or PSS signature on the passed-in data with selected hash + algorithm. @type bytes: str or L{bytearray} of unsigned bytes @param bytes: The value which will be hashed and signed. + @type rsaScheme: str + @param rsaScheme: The type of RSA scheme that will be applied, + "PKCS1" for RSASSA-PKCS#1 v1.5 signature and "PSS" + for RSASSA-PSS with MGF1 signature method + + @type hAlg: str + @param hAlg: The hash algorithm that will be used + + @type sLen: int + @param sLen: The length of intended salt value, applicable only + for RSASSA-PSS signatures + @rtype: L{bytearray} of unsigned bytes. - @return: A PKCS1-SHA1 signature on the passed-in data. + @return: A PKCS1 or PSS signature on the passed-in data. """ - hashBytes = SHA1(bytearray(bytes)) - prefixedHashBytes = self.addPKCS1SHA1Prefix(hashBytes) - sigBytes = self.sign(prefixedHashBytes) + if rsaScheme == "PKCS1": + hashBytes = secureHash(bytearray(bytes), hAlg) + prefixedHashBytes = self.addPKCS1Prefix(hashBytes, hAlg) + sigBytes = self.sign(prefixedHashBytes) + elif rsaScheme == "PSS": + sigBytes = self.RSASSA_PSS_sign(bytearray(bytes), hAlg, sLen) + else: + raise UnknownRSAType("Unknown RSA algorithm type") return sigBytes - def hashAndVerify(self, sigBytes, bytes): + def hashAndVerify(self, sigBytes, bytes, rsaScheme='PKCS1', hAlg='sha1', + sLen=0): """Hash and verify the passed-in bytes with the signature. - This verifies a PKCS1-SHA1 signature on the passed-in data. + This verifies a PKCS1 or PSS signature on the passed-in data + with selected hash algorithm. @type sigBytes: L{bytearray} of unsigned bytes - @param sigBytes: A PKCS1-SHA1 signature. + @param sigBytes: A PKCS1 or PSS signature. @type bytes: str or L{bytearray} of unsigned bytes @param bytes: The value which will be hashed and verified. + @type rsaScheme: str + @param rsaScheme: The type of RSA scheme that will be applied, + "PKCS1" for RSASSA-PKCS#1 v1.5 signature and "PSS" + for RSASSA-PSS with MGF1 signature method + + @type hAlg: str + @param hAlg: The hash algorithm that will be used + + @type sLen: int + @param sLen: The length of intended salt value, applicable only + for RSASSA-PSS signatures + @rtype: bool @return: Whether the signature matches the passed-in data. """ - hashBytes = SHA1(bytearray(bytes)) # Try it with/without the embedded NULL - prefixedHashBytes1 = self.addPKCS1SHA1Prefix(hashBytes, False) - prefixedHashBytes2 = self.addPKCS1SHA1Prefix(hashBytes, True) - result1 = self.verify(sigBytes, prefixedHashBytes1) - result2 = self.verify(sigBytes, prefixedHashBytes2) - return (result1 or result2) + if rsaScheme == "PKCS1" and hAlg == 'sha1': + hashBytes = secureHash(bytearray(bytes), hAlg) + prefixedHashBytes1 = self.addPKCS1SHA1Prefix(hashBytes, False) + prefixedHashBytes2 = self.addPKCS1SHA1Prefix(hashBytes, True) + result1 = self.verify(sigBytes, prefixedHashBytes1) + result2 = self.verify(sigBytes, prefixedHashBytes2) + return (result1 or result2) + elif rsaScheme == 'PKCS1': + hashBytes = secureHash(bytearray(bytes), hAlg) + prefixedHashBytes = self.addPKCS1Prefix(hashBytes, hAlg) + r = self.verify(sigBytes, prefixedHashBytes) + return r + elif rsaScheme == "PSS": + r = self.RSASSA_PSS_verify(bytearray(bytes), sigBytes, hAlg, sLen) + return r + else: + raise UnknownRSAType("Unknown RSA algorithm type") def MGF1(self, mgfSeed, maskLen, hAlg): """Generate mask from passed-in seed. @@ -135,7 +178,8 @@ def EMSA_PSS_encode(self, M, emBits, hAlg, sLen=0): mHash = secureHash(M, hAlg) emLen = Poly1305.divceil(emBits, 8) if emLen < hashLen + sLen + 2: - raise EncodingError("The ending limit too short for selected hash and salt length") + raise EncodingError("The ending limit too short for " + + "selected hash and salt length") salt = getRandomBytes(sLen) M2 = bytearray(8) + mHash + salt H = secureHash(M2, hAlg) @@ -276,6 +320,7 @@ def sign(self, bytes): def verify(self, sigBytes, bytes): """Verify the passed-in bytes with the signature. + This verifies a PKCS1 signature on the passed-in data. @type sigBytes: L{bytearray} of unsigned bytes diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index af45a460..afc6af86 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -873,4 +873,3 @@ def test_client_ECDHE_key_exchange_with_invalid_server_curve(self): [GroupName.secp256r1]) with self.assertRaises(TLSIllegalParameterException): client_keyExchange.processServerKeyExchange(None, srv_key_ex) - diff --git a/unit_tests/test_tlslite_utils_rsakey.py b/unit_tests/test_tlslite_utils_rsakey.py index 9225a536..3466df07 100644 --- a/unit_tests/test_tlslite_utils_rsakey.py +++ b/unit_tests/test_tlslite_utils_rsakey.py @@ -21,10 +21,28 @@ from unittest.mock import call class TestRSAPSS_components(unittest.TestCase): + # component functions NOT tested from test vectors def setUp(self): self.rsa = Python_RSAKey() + def test_unknownRSAType(self): + message = bytearray(b'\xad\x8f\xd1\xf7\xf9' + + b'\x7fgrRS\xce}\x18\x985' + + b'\xb3') + signed = bytearray( + b'\xb80\x12s\xbb\xd9j\xce&U\x08\x14\xb2\x070' + + b'\xc7\xc8\xa8\xa0\xc1\xc3\xf3\xd41\xad\xbe' + + b'\xe8\x1dN\x94\xf6sx\x02\xed\xfb\x0b\x0b\x85' + + b'\xc5N\xff\x04z\xec\x13\x86O\x15\xe8|\xae\xc6' + + b'\x1c\r\xcd\xec\xf4\xb1\xb5$\xf2\x17\xff\xf6' + + b'\xc2\xf5\xd2\x8a\xd2\x98\xa8\xb7\xe0;\xab\xe0' + + b'\xe9P\xd9\xea\x86\xb3\xeb)\xa3\x98\xb4e\xb5P' + + b'\x07\x14\xf1?\xa8i\xb7\xc6\x94\x1c9\x1fX>@' + + b'\xe3') + with self.assertRaises(UnknownRSAType): + self.rsa.hashAndVerify(message, signed, 'sha29', 10) + def test_encodingError(self): with self.assertRaises(EncodingError): self.assertEqual(self.rsa.EMSA_PSS_encode( @@ -1187,6 +1205,225 @@ def m(leght): self.assertEqual(signed, intendedS) +class TestRSAPKCS1(unittest.TestCase): + n = int("a8d68acd413c5e195d5ef04e1b4faaf242365cb450196755e92e1215ba59802aa" + "fbadbf2564dd550956abb54f8b1c917844e5f36195d1088c600e07cada5c080ed" + "e679f50b3de32cf4026e514542495c54b1903768791aae9e36f082cd38e941ada" + "89baecada61ab0dd37ad536bcb0a0946271594836e92ab5517301d45176b5", + 16) + e = int("00000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000003", + 16) + p = int("c107a2fe924b76e206cb9bc4af2ab7008547c00846bf6d0680b3eac3ebcbd0c7f" + "d7a54c2b9899b08f80cde1d3691eaaa2816b1eb11822d6be7beaf4e30977c49", + 16) + q = int("dfea984ce4307eafc0d140c2bb82861e5dbac4f8567cbc981d70440dd63949207" + "9031486315e305eb83e591c4a2e96064966f7c894c3ca351925b5ce82d8ef0d", + 16) + d = int("1c23c1cce034ba598f8fd2b7af37f1d30b090f7362aee68e5187adae49b9955c7" + "29f24a863b7a38d6e3c748e2972f6d940b7ba89043a2d6c2100256a1cf0f56a8c" + "d35fc6ee205244876642f6f9c3820a3d9d2c8921df7d82aaadcaf2d7334d39893" + "1ddbba553190b3a416099f3aa07fd5b26214645a828419e122cfb857ad73b", + 16) + dP = d % (p - 1) + dQ = d % (q - 1) + qInv = invMod(q, p) + message = bytearray( + b'\xd7\x38\x29\x49\x7c\xdd\xbe\x41\xb7\x05\xfa\xac\x50\xe7' + + b'\x89\x9f\xdb\x5a\x38\xbf\x3a\x45\x9e\x53\x63\x57\x02\x9e' + + b'\x64\xf8\x79\x6b\xa4\x7f\x4f\xe9\x6b\xa5\xa8\xb9\xa4\x39' + + b'\x67\x46\xe2\x16\x4f\x55\xa2\x53\x68\xdd\xd0\xb9\xa5\x18' + + b'\x8c\x7a\xc3\xda\x2d\x1f\x74\x22\x86\xc3\xbd\xee\x69\x7f' + + b'\x9d\x54\x6a\x25\xef\xcf\xe5\x31\x91\xd7\x43\xfc\xc6\xb4' + + b'\x78\x33\xd9\x93\xd0\x88\x04\xda\xec\xa7\x8f\xb9\x07\x6c' + + b'\x3c\x01\x7f\x53\xe3\x3a\x90\x30\x5a\xf0\x62\x20\x97\x4d' + + b'\x46\xbf\x19\xed\x3c\x9b\x84\xed\xba\xe9\x8b\x45\xa8\x77' + + b'\x12\x58') + + def setUp(self): + self.rsa = Python_RSAKey(self.n, self.e, self.d, self.p, self.q, + self.dP, self.dQ, self.qInv) + + def test_hashAndSign_RSAPKCS1_sha1(self): + sigBytes = self.rsa.hashAndSign(self.message, + "PKCS1", "sha1") + self.assertEqual(sigBytes, bytearray( + b'\x17\x50\x15\xbd\xa5\x0a\xbe\x0f\xa7\xd3\x9a\x83\x53\x88' + + b'\x5c\xa0\x1b\xe3\xa7\xe7\xfc\xc5\x50\x45\x74\x41\x11\x36' + + b'\x2e\xe1\x91\x44\x73\xa4\x8d\xc5\x37\xd9\x56\x29\x4b\x9e' + + b'\x20\xa1\xef\x66\x1d\x58\x53\x7a\xcd\xc8\xde\x90\x8f\xa0' + + b'\x50\x63\x0f\xcc\x27\x2e\x6d\x00\x10\x45\xe6\xfd\xee\xd2' + + b'\xd1\x05\x31\xc8\x60\x33\x34\xc2\xe8\xdb\x39\xe7\x3e\x6d' + + b'\x96\x65\xee\x13\x43\xf9\xe4\x19\x83\x02\xd2\x20\x1b\x44' + + b'\xe8\xe8\xd0\x6b\x3e\xf4\x9c\xee\x61\x97\x58\x21\x63\xa8' + + b'\x49\x00\x89\xca\x65\x4c\x00\x12\xfc\xe1\xba\x65\x11\x08' + + b'\x97\x50')) + + def test_hashAndSign_wrongRSaAlgorithm(self): + with self.assertRaises(UnknownRSAType): + self.rsa.hashAndSign(self.message, + "PKC1", "sha1") + + def test_hashAndSign_RSAPKCS1_sha1_notSet(self): + sigBytes = self.rsa.hashAndSign(self.message, + "PKCS1") + self.assertEqual(sigBytes, bytearray( + b'\x17\x50\x15\xbd\xa5\x0a\xbe\x0f\xa7\xd3\x9a\x83\x53\x88' + + b'\x5c\xa0\x1b\xe3\xa7\xe7\xfc\xc5\x50\x45\x74\x41\x11\x36' + + b'\x2e\xe1\x91\x44\x73\xa4\x8d\xc5\x37\xd9\x56\x29\x4b\x9e' + + b'\x20\xa1\xef\x66\x1d\x58\x53\x7a\xcd\xc8\xde\x90\x8f\xa0' + + b'\x50\x63\x0f\xcc\x27\x2e\x6d\x00\x10\x45\xe6\xfd\xee\xd2' + + b'\xd1\x05\x31\xc8\x60\x33\x34\xc2\xe8\xdb\x39\xe7\x3e\x6d' + + b'\x96\x65\xee\x13\x43\xf9\xe4\x19\x83\x02\xd2\x20\x1b\x44' + + b'\xe8\xe8\xd0\x6b\x3e\xf4\x9c\xee\x61\x97\x58\x21\x63\xa8' + + b'\x49\x00\x89\xca\x65\x4c\x00\x12\xfc\xe1\xba\x65\x11\x08' + + b'\x97\x50')) + + def test_hashAndSign_RSAPKCS1_sha224(self): + sigBytes = self.rsa.hashAndSign(self.message, + "PKCS1", "sha224") + self.assertEqual(sigBytes, bytearray( + b'\x57\x67\x7b\x08\x9e\x20\x54\x86\xdf\x4f\x56\x75\x59\x72' + + b'\xe3\xaf\x88\xca\xbb\xc2\x3e\xfe\x29\x43\x9b\x8d\x1e\x60' + + b'\xac\x22\x6e\x99\x0d\xa4\x87\x85\x73\x92\x85\x6d\x12\xcd' + + b'\xce\xa3\x87\xa2\x69\xd1\xbb\xbc\x12\x85\x49\xa1\x13\x5a' + + b'\xb0\x62\x20\x1c\xab\x8a\xc0\x88\x86\xa3\x13\xaf\x85\x54' + + b'\x50\x6d\x7a\x93\x85\x5b\x84\x30\x86\xa1\xbf\x3d\xfb\xcb' + + b'\x00\x4c\xcd\xe7\x79\xc0\x84\xff\xa1\x72\x4b\x41\xd1\x7e' + + b'\x10\xc8\xdd\x67\xdc\x0d\xf2\x62\x00\x37\x65\x50\xed\xa1' + + b'\x44\x55\xd9\xb0\xb3\x1f\x1d\x8c\x5e\x8b\xb1\xd3\xd9\x63' + + b'\xd0\xd5')) + + def test_hashAndSign_RSAPKCS1_sha256(self): + sigBytes = self.rsa.hashAndSign(self.message, + "PKCS1", "sha256") + self.assertEqual(sigBytes, bytearray( + b'\x0b\x20\xe5\x09\x3c\x2a\x92\x62\x33\x10\x8a\xfb\xdd\x85' + + b'\x1b\x88\xee\xb5\x54\xf4\xbe\xaa\x7b\x18\xe5\x15\x19\xf7' + + b'\xd0\xec\x53\xb1\x81\xa3\xb0\x3e\x84\x84\xba\x8d\xe2\xaa' + + b'\x78\x64\xa4\x02\xe2\x20\x8e\x84\xec\x99\x14\xaf\x9d\x77' + + b'\x6e\xd1\x3c\x48\xbd\xeb\x64\x84\x25\x4d\xe1\x69\x31\x8a' + + b'\x87\xc4\x0f\x22\x65\xff\x16\x71\x4e\xae\x8a\xee\x2b\xc9' + + b'\xc3\xcb\x4d\xee\x04\x5e\x4f\x5d\x9d\x62\x52\x10\x12\x1b' + + b'\xfc\xf2\xbe\xd8\xd3\xff\xa6\x02\xce\x27\xff\xf4\xe6\x1c' + + b'\xf9\xbb\x65\x0e\x71\xa6\x92\x1a\xe6\xff\xa2\x96\xcb\x11' + + b'\xbd\xbb')) + + def test_hashAndSign_RSAPKCS1_sha384(self): + sigBytes = self.rsa.hashAndSign(self.message, + "PKCS1", "sha384") + self.assertEqual(sigBytes, bytearray( + b'\x7e\x3c\xcb\x6a\xb0\x3b\x41\x9a\x3e\x54\xf8\x13\x37\xa3' + + b'\xc3\xf7\x2e\x8c\x65\xbb\xd1\x9d\xdd\x50\x24\x6a\x36\xf5' + + b'\x1f\x58\x74\x1e\xc2\x45\xd2\xd0\xf0\x76\x77\xa4\xf8\x8a' + + b'\xa3\xb1\xca\xee\xcd\xff\xe5\xfd\x6e\xdc\xf8\xb8\xbc\xfb' + + b'\x56\x96\x37\xad\x02\xeb\x15\x4d\x17\xb8\x7a\x8f\x00\xd0' + + b'\xe6\x18\xa7\xf4\xa7\x0c\xe4\x07\xf2\x03\x59\x15\x3e\x5f' + + b'\x4a\x4d\x97\x44\xf3\xf3\xff\x44\x12\x0c\x08\xa4\x60\x50' + + b'\x0f\x03\x0f\xd3\x39\x8e\x97\xfc\xae\xf9\xd0\xa7\xe2\xac' + + b'\xef\x19\xa8\x1f\x70\x68\x05\xbe\x5f\xc0\x03\xd7\x8e\x5b' + + b'\x29\xc0')) + + def test_hashAndSign_RSAPKCS1_sha512(self): + sigBytes = self.rsa.hashAndSign(self.message, + "PKCS1", "sha512") + self.assertEqual(sigBytes, bytearray( + b'\x8b\x57\xa6\xf9\x16\x06\xba\x48\x13\xb8\x35\x36\x58\x1e' + + b'\xb1\x5d\x72\x87\x5d\xcb\xb0\xa5\x14\xb4\xc0\x3b\x6d\xf8' + + b'\xf2\x02\xfa\x85\x56\xe4\x00\x21\x22\xbe\xda\xf2\x6e\xaa' + + b'\x10\x7e\xce\x48\x60\x75\x23\x79\xec\x8b\xaa\x64\xf4\x00' + + b'\x98\xbe\x92\xa4\x21\x4b\x69\xe9\x8b\x24\xae\x1c\xc4\xd2' + + b'\xf4\x57\xcf\xf4\xf4\x05\xa8\x2e\xf9\x4c\x5f\x8d\xfa\xad' + + b'\xd3\x07\x8d\x7a\x92\x24\x88\x7d\xb8\x6c\x32\x18\xbf\x53' + + b'\xc9\x77\x9e\xd0\x98\x95\xb2\xcf\xb8\x4f\x1f\xad\x2e\x5b' + + b'\x1f\x8e\x4b\x20\x9c\x57\x85\xb9\xce\x33\x2c\xd4\x13\x56' + + b'\xc1\x71')) + + def test_hashAndVerify_PKCS1_sha1_notSet(self): + sigBytes = bytearray( + b'\x17\x50\x15\xbd\xa5\x0a\xbe\x0f\xa7\xd3\x9a\x83\x53\x88' + + b'\x5c\xa0\x1b\xe3\xa7\xe7\xfc\xc5\x50\x45\x74\x41\x11\x36' + + b'\x2e\xe1\x91\x44\x73\xa4\x8d\xc5\x37\xd9\x56\x29\x4b\x9e' + + b'\x20\xa1\xef\x66\x1d\x58\x53\x7a\xcd\xc8\xde\x90\x8f\xa0' + + b'\x50\x63\x0f\xcc\x27\x2e\x6d\x00\x10\x45\xe6\xfd\xee\xd2' + + b'\xd1\x05\x31\xc8\x60\x33\x34\xc2\xe8\xdb\x39\xe7\x3e\x6d' + + b'\x96\x65\xee\x13\x43\xf9\xe4\x19\x83\x02\xd2\x20\x1b\x44' + + b'\xe8\xe8\xd0\x6b\x3e\xf4\x9c\xee\x61\x97\x58\x21\x63\xa8' + + b'\x49\x00\x89\xca\x65\x4c\x00\x12\xfc\xe1\xba\x65\x11\x08' + + b'\x97\x50') + + self.assertTrue(self.rsa.hashAndVerify(sigBytes, + self.message, "PKCS1")) + + def test_hashAndVerify_PKCS1_sha224(self): + sigBytes = bytearray( + b'\x57\x67\x7b\x08\x9e\x20\x54\x86\xdf\x4f\x56\x75\x59\x72' + + b'\xe3\xaf\x88\xca\xbb\xc2\x3e\xfe\x29\x43\x9b\x8d\x1e\x60' + + b'\xac\x22\x6e\x99\x0d\xa4\x87\x85\x73\x92\x85\x6d\x12\xcd' + + b'\xce\xa3\x87\xa2\x69\xd1\xbb\xbc\x12\x85\x49\xa1\x13\x5a' + + b'\xb0\x62\x20\x1c\xab\x8a\xc0\x88\x86\xa3\x13\xaf\x85\x54' + + b'\x50\x6d\x7a\x93\x85\x5b\x84\x30\x86\xa1\xbf\x3d\xfb\xcb' + + b'\x00\x4c\xcd\xe7\x79\xc0\x84\xff\xa1\x72\x4b\x41\xd1\x7e' + + b'\x10\xc8\xdd\x67\xdc\x0d\xf2\x62\x00\x37\x65\x50\xed\xa1' + + b'\x44\x55\xd9\xb0\xb3\x1f\x1d\x8c\x5e\x8b\xb1\xd3\xd9\x63' + + b'\xd0\xd5') + + self.assertTrue(self.rsa.hashAndVerify(sigBytes, + self.message, "PKCS1", + 'sha224')) + + def test_hashAndVerify_PKCS1_sha256(self): + sigBytes = bytearray( + b'\x0b\x20\xe5\x09\x3c\x2a\x92\x62\x33\x10\x8a\xfb\xdd\x85' + + b'\x1b\x88\xee\xb5\x54\xf4\xbe\xaa\x7b\x18\xe5\x15\x19\xf7' + + b'\xd0\xec\x53\xb1\x81\xa3\xb0\x3e\x84\x84\xba\x8d\xe2\xaa' + + b'\x78\x64\xa4\x02\xe2\x20\x8e\x84\xec\x99\x14\xaf\x9d\x77' + + b'\x6e\xd1\x3c\x48\xbd\xeb\x64\x84\x25\x4d\xe1\x69\x31\x8a' + + b'\x87\xc4\x0f\x22\x65\xff\x16\x71\x4e\xae\x8a\xee\x2b\xc9' + + b'\xc3\xcb\x4d\xee\x04\x5e\x4f\x5d\x9d\x62\x52\x10\x12\x1b' + + b'\xfc\xf2\xbe\xd8\xd3\xff\xa6\x02\xce\x27\xff\xf4\xe6\x1c' + + b'\xf9\xbb\x65\x0e\x71\xa6\x92\x1a\xe6\xff\xa2\x96\xcb\x11' + + b'\xbd\xbb') + + self.assertTrue(self.rsa.hashAndVerify(sigBytes, + self.message, "PKCS1", + 'sha256')) + + def test_hashAndVerify_PKCS1_sha384(self): + sigBytes = bytearray( + b'\x7e\x3c\xcb\x6a\xb0\x3b\x41\x9a\x3e\x54\xf8\x13\x37\xa3' + + b'\xc3\xf7\x2e\x8c\x65\xbb\xd1\x9d\xdd\x50\x24\x6a\x36\xf5' + + b'\x1f\x58\x74\x1e\xc2\x45\xd2\xd0\xf0\x76\x77\xa4\xf8\x8a' + + b'\xa3\xb1\xca\xee\xcd\xff\xe5\xfd\x6e\xdc\xf8\xb8\xbc\xfb' + + b'\x56\x96\x37\xad\x02\xeb\x15\x4d\x17\xb8\x7a\x8f\x00\xd0' + + b'\xe6\x18\xa7\xf4\xa7\x0c\xe4\x07\xf2\x03\x59\x15\x3e\x5f' + + b'\x4a\x4d\x97\x44\xf3\xf3\xff\x44\x12\x0c\x08\xa4\x60\x50' + + b'\x0f\x03\x0f\xd3\x39\x8e\x97\xfc\xae\xf9\xd0\xa7\xe2\xac' + + b'\xef\x19\xa8\x1f\x70\x68\x05\xbe\x5f\xc0\x03\xd7\x8e\x5b' + + b'\x29\xc0') + + self.assertTrue(self.rsa.hashAndVerify(sigBytes, + self.message, "PKCS1", + 'sha384')) + + def test_hashAndVerify_PKCS1_sha512(self): + sigBytes = bytearray( + b'\x8b\x57\xa6\xf9\x16\x06\xba\x48\x13\xb8\x35\x36\x58\x1e' + + b'\xb1\x5d\x72\x87\x5d\xcb\xb0\xa5\x14\xb4\xc0\x3b\x6d\xf8' + + b'\xf2\x02\xfa\x85\x56\xe4\x00\x21\x22\xbe\xda\xf2\x6e\xaa' + + b'\x10\x7e\xce\x48\x60\x75\x23\x79\xec\x8b\xaa\x64\xf4\x00' + + b'\x98\xbe\x92\xa4\x21\x4b\x69\xe9\x8b\x24\xae\x1c\xc4\xd2' + + b'\xf4\x57\xcf\xf4\xf4\x05\xa8\x2e\xf9\x4c\x5f\x8d\xfa\xad' + + b'\xd3\x07\x8d\x7a\x92\x24\x88\x7d\xb8\x6c\x32\x18\xbf\x53' + + b'\xc9\x77\x9e\xd0\x98\x95\xb2\xcf\xb8\x4f\x1f\xad\x2e\x5b' + + b'\x1f\x8e\x4b\x20\x9c\x57\x85\xb9\xce\x33\x2c\xd4\x13\x56' + + b'\xc1\x71') + + self.assertTrue(self.rsa.hashAndVerify(sigBytes, + self.message, "PKCS1", + 'sha512')) # because RSAKey is an abstract class... class TestRSAKey(unittest.TestCase): @@ -1233,6 +1470,18 @@ def test_hashAndSign(self): b'\xae\x9a\x0b)\xb5K\xe8\x98|R\xac\xdc\xdc\n\x7f\x8b\xe7\xe6' + b'HQ\xc3hS\x19'), sigBytes) + def test_hashAndSign_PSS(self): + rsa = Python_RSAKey(self.N, self.e, self.d, self.p, self.q, self.dP, + self.dQ, self.qInv) + + sigBytes = rsa.hashAndSign(bytearray(b'text to sign'), "PSS", "sha1") + self.assertEqual(bytearray(b'op\xfa\x1d\xfa\xe8i\xf2zV\x9a\xf4\x8d' + + b'\xf1\xaf:\x1a\xb6\xce\xae3\xd1\xc2E[EG' + + b'\x8ba\xfe.\x8e\x9dJ\xc9 Date: Mon, 19 Sep 2016 18:07:36 +0200 Subject: [PATCH 363/574] document ASN1Parser --- tlslite/utils/asn1parser.py | 50 ++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/tlslite/utils/asn1parser.py b/tlslite/utils/asn1parser.py index 618e8559..9bcb3b38 100644 --- a/tlslite/utils/asn1parser.py +++ b/tlslite/utils/asn1parser.py @@ -3,13 +3,27 @@ # # See the LICENSE file for legal information regarding use of this file. -"""Class for parsing ASN.1""" -from .compat import * -from .codec import * +"""Abstract Syntax Notation One (ASN.1) parsing""" + +from .codec import Parser + -#Takes a byte array which has a DER TLV field at its head class ASN1Parser(object): + """ + Parser and storage of ASN.1 DER encoded objects. + + @type length: int + @ivar length: length of the value of the tag + @type value: bytearray + @ivar value: literal value of the tag + """ + def __init__(self, bytes): + """Create an object from bytes. + + @type bytes: bytearray + @param bytes: DER encoded ANS.1 object + """ p = Parser(bytes) p.get(1) #skip Type @@ -19,23 +33,41 @@ def __init__(self, bytes): #Get Value self.value = p.getFixBytes(self.length) - #Assuming this is a sequence... def getChild(self, which): + """ + Return n-th child assuming that the object is a SEQUENCE. + + @type which: int + @param which: ordinal of the child to return + + @rtype: ASN1Parser + @return: decoded child object + """ return ASN1Parser(self.getChildBytes(which)) def getChildBytes(self, which): + """ + Return raw encoding of n-th child, assume self is a SEQUENCE + + @type which: int + @param which: ordinal of the child to return + + @rtype: bytearray + @return: raw child object + """ p = Parser(self.value) - for x in range(which+1): + for _ in range(which+1): markIndex = p.index p.get(1) #skip Type length = self._getASN1Length(p) p.getFixBytes(length) return p.bytes[markIndex : p.index] - #Decode the ASN.1 DER length field - def _getASN1Length(self, p): + @staticmethod + def _getASN1Length(p): + """Decode the ASN.1 DER length field""" firstLength = p.get(1) - if firstLength<=127: + if firstLength <= 127: return firstLength else: lengthLength = firstLength & 0x7F From f0f8de088b6b4ba34c59321ab43dec6e9d2cf521 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 19 Sep 2016 18:37:38 +0200 Subject: [PATCH 364/574] add docs to Parser and do simple style fixes --- tlslite/utils/codec.py | 125 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 3 deletions(-) diff --git a/tlslite/utils/codec.py b/tlslite/utils/codec.py index 03fe60fe..03a405a1 100644 --- a/tlslite/utils/codec.py +++ b/tlslite/utils/codec.py @@ -3,7 +3,8 @@ """Classes for reading/writing binary data (such as TLS records).""" -from .compat import * +from __future__ import division + class Writer(object): def __init__(self): @@ -58,21 +59,72 @@ def addVarTupleSeq(self, seq, length, lengthLength): self.add(elem, length) class Parser(object): + """ + Parser for TLV and LV byte-based encodings. + + Parser that can handle arbitrary byte-based encodings usually employed in + Type-Length-Value or Length-Value binary encoding protocols like ASN.1 + or TLS + + Note: if the raw bytes don't match expected values (like trying to + read a 4-byte integer from a 2-byte buffer), most methods will raise a + SyntaxError exception. + + TODO: don't use an exception used by language parser to indicate errors + in application code. + + @type bytes: bytearray + @ivar bytes: data to be interpreted (buffer) + + @type index: int + @ivar index: current position in the buffer + + @type lengthCheck: int + @ivar lengthCheck: size of struct being parsed + + @type indexCheck: int + @ivar indexCheck: position at which the structure begins in buffer + """ + def __init__(self, bytes): + """ + Bind raw bytes with parser. + + @type bytes: bytearray + @param bytes: bytes to be parsed/interpreted + """ self.bytes = bytes self.index = 0 + self.indexCheck = 0 + self.lengthCheck = 0 def get(self, length): + """ + Read a single big-endian integer value encoded in 'length' bytes. + + @type length: int + @param length: number of bytes in which the value is encoded in + + @rtype: int + """ if self.index + length > len(self.bytes): raise SyntaxError() x = 0 - for count in range(length): + for _ in range(length): x <<= 8 x |= self.bytes[self.index] self.index += 1 return x def getFixBytes(self, lengthBytes): + """ + Read a string of bytes encoded in 'lengthBytes' bytes. + + @type lengthBytes: int + @param lengthBytes: number of bytes to return + + @rtype: bytearray + """ if self.index + lengthBytes > len(self.bytes): raise SyntaxError() bytes = self.bytes[self.index : self.index+lengthBytes] @@ -80,16 +132,47 @@ def getFixBytes(self, lengthBytes): return bytes def getVarBytes(self, lengthLength): + """ + Read a variable length string with a fixed length. + + @type lengthLength: int + @param lengthLength: number of bytes in which the length of the string + is encoded in + + @rtype: bytearray + """ lengthBytes = self.get(lengthLength) return self.getFixBytes(lengthBytes) def getFixList(self, length, lengthList): + """ + Read a list of static length with same-sized ints. + + @type length: int + @param length: size in bytes of a single element in list + + @type lengthList: int + @param lengthList: number of elements in list + + @rtype: list of int + """ l = [0] * lengthList for x in range(lengthList): l[x] = self.get(length) return l def getVarList(self, length, lengthLength): + """ + Read a variable length list of same-sized integers. + + @type length: int + @param length: size in bytes of a single element + + @type lengthLength: int + @param lengthLength: size of the encoded length of the list + + @rtype: list of int + """ lengthList = self.get(lengthLength) if lengthList % length != 0: raise SyntaxError() @@ -100,11 +183,19 @@ def getVarList(self, length, lengthLength): return l def getVarTupleList(self, elemLength, elemNum, lengthLength): - """Read a variable length list of same sized tuples + """ + Read a variable length list of same sized tuples. + @type elemLength: int @param elemLength: length in bytes of single tuple element + + @type elemNum: int @param elemNum: number of elements in tuple + + @type lengthLength: int @param lengthLength: length in bytes of the list length variable + + @rtype: list of tuple of int """ lengthList = self.get(lengthLength) if lengthList % (elemLength * elemNum) != 0: @@ -119,18 +210,45 @@ def getVarTupleList(self, elemLength, elemNum, lengthLength): return tupleList def startLengthCheck(self, lengthLength): + """ + Read length of struct and start a length check for parsing. + + @type lengthLength: int + @param lengthLength: number of bytes in which the length is encoded + """ self.lengthCheck = self.get(lengthLength) self.indexCheck = self.index def setLengthCheck(self, length): + """ + Set length of struct and start a length check for parsing. + + @type length: int + @param length: expected size of parsed struct in bytes + """ self.lengthCheck = length self.indexCheck = self.index def stopLengthCheck(self): + """ + Stop struct parsing, verify that no under- or overflow occurred. + + In case the expected length was mismatched with actual length of + processed data, raises an exception. + """ if (self.index - self.indexCheck) != self.lengthCheck: raise SyntaxError() def atLengthCheck(self): + """ + Check if there is data in structure left for parsing. + + Returns True if the whole structure was parsed, False if there is + some data left. + + Will raise an exception if overflow occured (amount of data read was + greater than expected size) + """ if (self.index - self.indexCheck) < self.lengthCheck: return False elif (self.index - self.indexCheck) == self.lengthCheck: @@ -139,4 +257,5 @@ def atLengthCheck(self): raise SyntaxError() def getRemainingLength(self): + """Return amount of data remaining in struct being parsed.""" return len(self.bytes) - self.index From af26540c14eefff49f1e6a1b09c4e1af5105fb80 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 21 Sep 2016 15:27:27 +0200 Subject: [PATCH 365/574] clean up ExtensionType list format comments, add doc, provide reverse mapping and sort by value --- tlslite/constants.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 002b0308..0e0d5f58 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -111,20 +111,24 @@ def toRepr(cls, value, blacklist=None): blacklist.append('all') return super(ContentType, cls).toRepr(value, blacklist) -class ExtensionType: # RFC 6066 / 4366 - server_name = 0 # RFC 6066 / 4366 - cert_type = 9 # RFC 6091 - supported_groups = 10 # RFC 4492, RFC-ietf-tls-negotiated-ff-dhe-10 - ec_point_formats = 11 # RFC 4492 - srp = 12 # RFC 5054 - signature_algorithms = 13 # RFC 5246 + +class ExtensionType(TLSEnum): + """TLS Extension Type registry values""" + + server_name = 0 # RFC 6066 / 4366 + cert_type = 9 # RFC 6091 + supported_groups = 10 # RFC 4492, RFC-ietf-tls-negotiated-ff-dhe-10 + ec_point_formats = 11 # RFC 4492 + srp = 12 # RFC 5054 + signature_algorithms = 13 # RFC 5246 alpn = 16 # RFC 7301 - client_hello_padding = 21 # RFC 7685 - encrypt_then_mac = 22 # RFC 7366 - extended_master_secret = 23 # RFC 7627 - tack = 0xF300 + client_hello_padding = 21 # RFC 7685 + encrypt_then_mac = 22 # RFC 7366 + extended_master_secret = 23 # RFC 7627 supports_npn = 13172 - renegotiation_info = 0xff01 + tack = 0xF300 + renegotiation_info = 0xff01 # RFC 5746 + class HashAlgorithm(TLSEnum): """Hash algorithm IDs used in TLSv1.2""" From 3efaaa9d0d134a5569e369f983a0f74ac488e8f0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 21 Sep 2016 17:09:08 +0200 Subject: [PATCH 366/574] add CertificateStatusRequest extension from RFC 6066 --- tlslite/constants.py | 8 ++ tlslite/extensions.py | 111 +++++++++++++++++++++++++- unit_tests/test_tlslite_extensions.py | 96 +++++++++++++++++++++- 3 files changed, 212 insertions(+), 3 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 0e0d5f58..e60dde9f 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -116,6 +116,7 @@ class ExtensionType(TLSEnum): """TLS Extension Type registry values""" server_name = 0 # RFC 6066 / 4366 + status_request = 5 # RFC 6066 / 4366 cert_type = 9 # RFC 6091 supported_groups = 10 # RFC 4492, RFC-ietf-tls-negotiated-ff-dhe-10 ec_point_formats = 11 # RFC 4492 @@ -226,6 +227,13 @@ class ECCurveType(TLSEnum): class NameType: host_name = 0 + +class CertificateStatusType(TLSEnum): + """Type of responses in the status_request and CertificateStatus msgs.""" + + ocsp = 1 + + class AlertLevel(TLSEnum): """Enumeration of TLS Alert protocol levels""" diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 23a2c192..13d625c5 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -9,7 +9,7 @@ from __future__ import generators from .utils.codec import Writer, Parser from collections import namedtuple -from .constants import NameType, ExtensionType +from .constants import NameType, ExtensionType, CertificateStatusType from .errors import TLSInternalError class TLSExtension(object): @@ -1248,9 +1248,118 @@ def parse(self, parser): return self +class StatusRequestExtension(TLSExtension): + """ + Handling of the Certificate Status Request extension from RFC 6066. + + @type status_type: int + @ivar status_type: type of the status request + + @type responder_id_list: list of bytearray + @ivar responder_id_list: list of DER encoded OCSP responder identifiers + that the client trusts + + @type request_extensions: bytearray + @ivar request_extensions: DER encoded list of OCSP extensions, as defined + in RFC 2560 + """ + + def __init__(self): + super(StatusRequestExtension, self).__init__( + extType=ExtensionType.status_request) + """Create instance of StatusRequestExtension.""" + self.status_type = None + self.responder_id_list = [] + self.request_extensions = bytearray() + + def __repr__(self): + """ + Create programmer-readable representation of object + + @rtype: str + """ + return ("StatusRequestExtension(status_type={0}, " + "responder_id_list={1!r}, " + "request_extensions={2!r})").format( + self.status_type, self.responder_id_list, + self.request_extensions) + + @property + def extData(self): + """ + Return encoded payload of the extension. + + @rtype: bytearray + """ + if self.status_type is None: + return bytearray() + + writer = Writer() + writer.add(self.status_type, 1) + writer2 = Writer() + for i in self.responder_id_list: + writer2.add(len(i), 2) + writer2.bytes += i + writer.add(len(writer2.bytes), 2) + writer.bytes += writer2.bytes + writer.add(len(self.request_extensions), 2) + writer.bytes += self.request_extensions + + return writer.bytes + + def create(self, status_type=CertificateStatusType.ocsp, + responder_id_list=tuple(), + request_extensions=b''): + """ + Create an instance of StatusRequestExtension with specified options. + + @type status_type: int + @param status_type: type of status returned + + @type responder_id_list: list + @param responder_id_list: list of encoded OCSP responder identifiers + that the client trusts + + @type request_extensions: bytearray + @param request_extensions: DER encoding of requested OCSP extensions + """ + self.status_type = status_type + self.responder_id_list = list(responder_id_list) + self.request_extensions = bytearray(request_extensions) + return self + + def parse(self, parser): + """ + Parse the extension from on the wire format. + + @type parser: L{tlslite.util.codec.Parser} + @param parser: data to be parsed as extension + + @rtype: L{StatusRequestExtension} + """ + # handling of server side message + if parser.getRemainingLength() == 0: + self.status_type = None + self.responder_id_list = [] + self.request_extensions = bytearray() + return self + + self.status_type = parser.get(1) + self.responder_id_list = [] + parser.startLengthCheck(2) + while not parser.atLengthCheck(): + self.responder_id_list.append(parser.getVarBytes(2)) + parser.stopLengthCheck() + self.request_extensions = parser.getVarBytes(2) + if parser.getRemainingLength() != 0: + raise SyntaxError("Trailing data after CertificateStatusRequest") + return self + + TLSExtension._universalExtensions = \ { ExtensionType.server_name: SNIExtension, + ExtensionType.status_request: StatusRequestExtension, ExtensionType.cert_type: ClientCertTypeExtension, ExtensionType.supported_groups: SupportedGroupsExtension, ExtensionType.ec_point_formats: ECPointFormatsExtension, diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index d4773cd4..f5dcf7a4 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -12,10 +12,11 @@ SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\ TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\ SignatureAlgorithmsExtension, PaddingExtension, VarListExtension, \ - RenegotiationInfoExtension, ALPNExtension + RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension from tlslite.utils.codec import Parser from tlslite.constants import NameType, ExtensionType, GroupName,\ - ECPointFormat, HashAlgorithm, SignatureAlgorithm + ECPointFormat, HashAlgorithm, SignatureAlgorithm, \ + CertificateStatusType from tlslite.errors import TLSInternalError class TestTLSExtension(unittest.TestCase): @@ -1587,5 +1588,96 @@ def test_parse_from_TLSExtension(self): bytearray(b'spdy/1')]) +class TestStatusRequestExtension(unittest.TestCase): + def setUp(self): + self.ext = StatusRequestExtension() + + def test___init__(self): + self.assertIsNotNone(self.ext) + self.assertEqual(self.ext.extType, 5) + self.assertEqual(self.ext.extData, bytearray()) + self.assertIsNone(self.ext.status_type) + self.assertEqual(self.ext.responder_id_list, []) + self.assertEqual(self.ext.request_extensions, bytearray()) + + def test__repr__(self): + self.assertEqual("StatusRequestExtension(status_type=None, " + "responder_id_list=[], " + "request_extensions=bytearray(b''))", repr(self.ext)) + + def test_create(self): + e = self.ext.create() + self.assertIs(e, self.ext) + self.assertEqual(e.status_type, 1) + self.assertEqual(e.responder_id_list, []) + self.assertEqual(e.request_extensions, bytearray()) + + def test_extData_with_default(self): + self.ext.create() + self.assertEqual(self.ext.extData, + bytearray(b'\x01\x00\x00\x00\x00')) + + def test_extData_with_data(self): + self.ext.create(status_type=15, + responder_id_list=[bytearray(b'abba'), + bytearray(b'xxx')], + request_extensions=bytearray(b'\x08\x09')) + + self.assertEqual(self.ext.extData, + bytearray(b'\x0f' + b'\x00\x0b' + b'\x00\x04abba' + b'\x00\x03xxx' + b'\x00\x02' + b'\x08\x09')) + + + def test_parse_empty(self): + parser = Parser(bytearray()) + + e = self.ext.parse(parser) + self.assertIs(e, self.ext) + + self.assertIsNone(e.status_type) + self.assertEqual(e.responder_id_list, []) + self.assertEqual(e.request_extensions, bytearray()) + + def test_parse_typical(self): + parser = Parser(bytearray(b'\x01\x00\x00\x00\x00')) + + e = self.ext.parse(parser) + self.assertIs(e, self.ext) + + self.assertEqual(self.ext.status_type, CertificateStatusType.ocsp) + self.assertEqual(self.ext.responder_id_list, []) + self.assertEqual(self.ext.request_extensions, bytearray()) + + def test_parse_with_values(self): + parser = Parser(bytearray(b'\x0f' + b'\x00\x0b' + b'\x00\x04abba' + b'\x00\x03xxx' + b'\x00\x02' + b'\x08\x09')) + + self.ext.parse(parser) + + self.assertEqual(self.ext.status_type, 15) + self.assertEqual(self.ext.responder_id_list, [bytearray(b'abba'), + bytearray(b'xxx')]) + self.assertEqual(self.ext.request_extensions, bytearray(b'\x08\x09')) + + def test_parse_with_trailing_data(self): + parser = Parser(bytearray(b'\x0f' + b'\x00\x0b' + b'\x00\x04abba' + b'\x00\x03xxx' + b'\x00\x02' + b'\x08\x09' + b'\x00')) + + with self.assertRaises(SyntaxError): + self.ext.parse(parser) + if __name__ == '__main__': unittest.main() From 5ee77be5c2b7c54449c95b8b0e18073096b4ad38 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 21 Sep 2016 15:43:52 +0200 Subject: [PATCH 367/574] add all alert descriptions defined in RFC 6066 --- tlslite/constants.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index e60dde9f..c87e0b20 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -294,7 +294,10 @@ class AlertDescription(TLSEnum): user_canceled = 90 no_renegotiation = 100 unsupported_extension = 110 # RFC 5246 + certificate_unobtainable = 111 # RFC 6066 unrecognized_name = 112 # RFC 6066 + bad_certificate_status_response = 113 # RFC 6066 + bad_certificate_hash_value = 114 # RFC 6066 unknown_psk_identity = 115 no_application_protocol = 120 # RFC 7301 From 3dff658f8fb3bd24898315227ba2bfda806189e2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 21 Sep 2016 16:28:44 +0200 Subject: [PATCH 368/574] add CertificateStatus handshake message --- tlslite/constants.py | 1 + tlslite/messages.py | 43 +++++++++++++++++++++++++++ unit_tests/test_tlslite_messages.py | 46 +++++++++++++++++++++++++++-- 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index c87e0b20..9f9e8e87 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -92,6 +92,7 @@ class HandshakeType(TLSEnum): certificate_verify = 15 client_key_exchange = 16 finished = 20 + certificate_status = 22 next_protocol = 67 class ContentType(TLSEnum): diff --git a/tlslite/messages.py b/tlslite/messages.py index 3c24b19a..354c3162 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1666,6 +1666,49 @@ def __init__(self): super(ServerFinished, self).__init__(SSL2HandshakeType.server_finished) +class CertificateStatus(HandshakeMsg): + """ + Handling of the CertificateStatus message from RFC 6066. + + Handling of the handshake protocol message that includes the OCSP staple. + + @type status_type: int + @ivar status_type: type of response returned + + @type ocsp: bytearray + @ivar ocsp: OCSPResponse from RFC 2560 + """ + + def __init__(self): + """Create the objet, set its type.""" + super(CertificateStatus, self).__init__( + HandshakeType.certificate_status) + self.status_type = None + self.ocsp = bytearray() + + def create(self, status_type, ocsp): + """Set up message payload.""" + self.status_type = status_type + self.ocsp = ocsp + return self + + def parse(self, parser): + """Deserialise the message from one the wire data.""" + parser.startLengthCheck(3) + self.status_type = parser.get(1) + self.ocsp = parser.getVarBytes(3) + parser.stopLengthCheck() + return self + + def write(self): + """Serialise the message.""" + writer = Writer() + writer.add(self.status_type, 1) + writer.add(len(self.ocsp), 3) + writer.bytes += self.ocsp + return self.postWrite(writer) + + class ApplicationData(object): def __init__(self): self.contentType = ContentType.application_data diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index ef811033..01852421 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -10,12 +10,12 @@ from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ - ClientMasterKey, ClientFinished, ServerFinished + ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ HashAlgorithm, SignatureAlgorithm, ECCurveType, GroupName, \ - SSL2HandshakeType + SSL2HandshakeType, CertificateStatusType from tlslite.extensions import SNIExtension, ClientCertTypeExtension, \ SRPExtension, TLSExtension, NPNExtension from tlslite.errors import TLSInternalError @@ -2362,5 +2362,47 @@ def test_parse(self): self.assertEqual(fin.verify_data, bytearray(b'\xc0\xfe')) self.assertEqual(fin.handshakeType, SSL2HandshakeType.server_finished) + +class TestCertificateStatus(unittest.TestCase): + def setUp(self): + self.msg = CertificateStatus() + + def test___init__(self): + self.assertIsNotNone(self.msg) + self.assertEqual(self.msg.handshakeType, + 22) + + def test_create(self): + m = self.msg.create(12, bytearray(b'\xab\xba')) + self.assertIs(m, self.msg) + + self.assertEqual(m.status_type, 12) + self.assertEqual(m.ocsp, bytearray(b'\xab\xba')) + + def test_parse(self): + parser = Parser(bytearray( + # type is handled by the dispatcher + b'\x00\x00\x05' # overall length + b'\x10' # type of the payload + b'\x00\x00\x01' # patyload length + b'\xfa')) + + m = self.msg.parse(parser) + self.assertIs(m, self.msg) + + self.assertEqual(m.status_type, 16) + self.assertEqual(m.ocsp, bytearray(b'\xfa')) + + def test_write(self): + self.msg.create(CertificateStatusType.ocsp, bytearray(b'\xbc\xaa')) + + self.assertEqual(self.msg.write(), bytearray( + b'\x16' + b'\x00\x00\x06' + b'\x01' + b'\x00\x00\x02' + b'\xbc\xaa')) + + if __name__ == '__main__': unittest.main() From 56de3e3e278f8259204019e0a27daadc205e9ba5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 29 Sep 2016 22:02:08 +0200 Subject: [PATCH 369/574] for safe primes, that we use, p-1 is also insecure --- tlslite/keyexchange.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 20225643..c3078c23 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -272,7 +272,8 @@ def processClientKeyExchange(self, clientKeyExchange): # First half of RFC 2631, Section 2.1.5. Validate the client's public # key. - if not 2 <= dh_Yc <= self.dh_p - 1: + # use of safe primes also means that the p-1 is invalid + if not 2 <= dh_Yc < self.dh_p - 1: raise TLSIllegalParameterException("Invalid dh_Yc value") S = powMod(dh_Yc, self.dh_Xs, self.dh_p) From ddf5fa25c714e98618e44f70f966ec71ceca6616 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 7 Oct 2016 15:41:50 +0200 Subject: [PATCH 370/574] make all types inherit from TLSEnum --- tlslite/constants.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 9f9e8e87..6209b313 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -47,18 +47,22 @@ def toStr(cls, value, blacklist=None): else: return '{0}'.format(value) -class CertificateType: + +class CertificateType(TLSEnum): x509 = 0 openpgp = 1 -class ClientCertificateType: + +class ClientCertificateType(TLSEnum): rsa_sign = 1 dss_sign = 2 rsa_fixed_dh = 3 dss_fixed_dh = 4 + class SSL2HandshakeType(TLSEnum): - """SSL2 Handshake Protocol message types""" + """SSL2 Handshake Protocol message types.""" + error = 0 client_hello = 1 client_master_key = 2 @@ -95,6 +99,7 @@ class HandshakeType(TLSEnum): certificate_status = 22 next_protocol = 67 + class ContentType(TLSEnum): """TLS record layer content types of payloads""" @@ -206,9 +211,9 @@ def toRepr(cls, value, blacklist=None): blacklist += ['all', 'allEC', 'allFF'] return super(GroupName, cls).toRepr(value, blacklist) -class ECPointFormat(object): - """Names and ID's of supported EC point formats""" +class ECPointFormat(TLSEnum): + """Names and ID's of supported EC point formats.""" uncompressed = 0 ansiX962_compressed_prime = 1 @@ -218,6 +223,15 @@ class ECPointFormat(object): ansiX962_compressed_prime, ansiX962_compressed_char2] + @classmethod + def toRepr(cls, value, blacklist=None): + """Convert numeric type to name representation.""" + if blacklist is None: + blacklist = [] + blacklist.append('all') + return super(ECPointFormat, cls).toRepr(value, blacklist) + + class ECCurveType(TLSEnum): """Types of ECC curves supported in TLS from RFC4492""" @@ -225,7 +239,10 @@ class ECCurveType(TLSEnum): explicit_char2 = 2 named_curve = 3 -class NameType: + +class NameType(TLSEnum): + """Type of entries in Server Name Indication extension.""" + host_name = 0 @@ -241,6 +258,7 @@ class AlertLevel(TLSEnum): warning = 1 fatal = 2 + class AlertDescription(TLSEnum): """ @cvar bad_record_mac: A TLS record failed to decrypt properly. From fc18936b2c84f3708d3630524650adad61eff175 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 13 Sep 2016 16:33:06 +0200 Subject: [PATCH 371/574] verify that cipher suites and compression methods are not empty --- tlslite/tlsconnection.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 9ca33617..418f5826 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1452,6 +1452,14 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, "Too old version: %s" % str(clientHello.client_version)): yield result + # there MUST be at least one value in both of those + if not clientHello.cipher_suites or \ + not clientHello.compression_methods: + for result in self._sendError( + AlertDescription.decode_error, + "Malformed Client Hello message"): + yield result + # Sanity check the ALPN extension alpnExt = clientHello.getExtension(ExtensionType.alpn) if alpnExt: From d0e8ac6f1ef82267b59dde13e0c6e6e6d933720e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 19 Sep 2016 17:50:33 +0200 Subject: [PATCH 372/574] check if compression_methods are valid --- tlslite/tlsconnection.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 418f5826..99c828d5 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1460,6 +1460,13 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, "Malformed Client Hello message"): yield result + # client hello MUST advertise uncompressed method + if 0 not in clientHello.compression_methods: + for result in self._sendError( + AlertDescription.illegal_parameter, + "Client Hello missing uncompressed method"): + yield result + # Sanity check the ALPN extension alpnExt = clientHello.getExtension(ExtensionType.alpn) if alpnExt: From b920037839c1d78676d31dc22058e5304d83ec9b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 11 Oct 2016 16:03:21 +0200 Subject: [PATCH 373/574] check if the signature_algorithms has correct values --- tlslite/tlsconnection.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 99c828d5..2e29e5d5 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1467,6 +1467,15 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, "Client Hello missing uncompressed method"): yield result + # the list of signatures methods is defined as <2..2^16-2>, which + # means it can't be empty, but it's only applicable to TLSv1.2 protocol + ext = clientHello.getExtension(ExtensionType.signature_algorithms) + if clientHello.client_version >= (3, 3) and ext and not ext.sigalgs: + for result in self._sendError( + AlertDescription.decode_error, + "Malformed signature_algorithms extension"): + yield result + # Sanity check the ALPN extension alpnExt = clientHello.getExtension(ExtensionType.alpn) if alpnExt: From ceae2847f4e0793dce823ff81a46704890d26ce8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 11 Oct 2016 16:25:15 +0200 Subject: [PATCH 374/574] properly handle second client hello in negotiation if we received a Client Hello message while negotiating, we responded with a no_renegotiation error, that's not correct, it's only applicable after the session was negotiated --- tlslite/tlsrecordlayer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 828e3058..d3183e37 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -712,8 +712,9 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): else: if subType == HandshakeType.client_hello: reneg = True - #Send no_renegotiation, then try again - if reneg: + # Send no_renegotiation if we're not negotiating + # a connection now, then try again + if reneg and self.session: alertMsg = Alert() alertMsg.create(AlertDescription.no_renegotiation, AlertLevel.warning) From 3591a358c80041f749629648273f419481ec9e6e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 11 Oct 2016 17:55:20 +0200 Subject: [PATCH 375/574] prepare for v0.7.0-alpha1 release --- README.md | 7 ++++++- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ddbe7d89..185b0369 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.6.0 2016-09-07 +tlslite-ng version 0.7.0-alpha1 2016-10-11 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -584,6 +584,11 @@ encrypt-then-MAC mode for CBC ciphers. 12 History =========== +0.7.0 - in-dev + +* basic support for RSA-PSS (Tomas Foukal) +* better documentation for Parser and ASN1Parser + 0.6.0 - 2016-09-07 * added support for ALPN from RFC 7301 diff --git a/setup.py b/setup.py index 24d79bf4..8b85c2fe 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.6.0", + version="0.7.0-alpha1", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 66db5032..5c4f1b03 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.6.0 +@version: 0.7.0-alpha1 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 870fad85..aa27ca3f 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.6.0" +__version__ = "0.7.0-alpha1" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From e0ae32cf232e9d0f6b2dc4448def2ee62e940473 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 20 Oct 2016 20:17:18 +0200 Subject: [PATCH 376/574] don't force sha-1 on mismatch of sigalgs RFC 5246 is explicit in that if the Client Hello includes any values, the signature MUST use one of them, only if the extension is completely absent, the server may default to sha-1 --- tlslite/errors.py | 7 +++++++ tlslite/tlsconnection.py | 20 ++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/tlslite/errors.py b/tlslite/errors.py index 07661eca..393ffd56 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -233,6 +233,13 @@ class TLSUnknownPSKIdentity(TLSProtocolException): pass + +class TLSHandshakeFailure(TLSProtocolException): + """Could not find acceptable set of handshake parameters""" + + pass + + class MaskTooLongError(EncryptionError): """The maskLen passed into function is too high""" diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 2e29e5d5..eef9e516 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1706,7 +1706,13 @@ def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, privateKey, verifierDB) - sigHash = self._pickServerKeyExchangeSig(settings, clientHello) + try: + sigHash = self._pickServerKeyExchangeSig(settings, clientHello) + except TLSHandshakeFailure as alert: + for result in self._sendError( + AlertDescription.handshake_failure, + str(alert)): + yield result #Create ServerKeyExchange, signing it if necessary try: @@ -1757,7 +1763,13 @@ def _serverCertKeyExchange(self, clientHello, serverHello, msgs.append(serverHello) msgs.append(Certificate(CertificateType.x509).create(serverCertChain)) - sigHashAlg = self._pickServerKeyExchangeSig(settings, clientHello) + try: + sigHashAlg = self._pickServerKeyExchangeSig(settings, clientHello) + except TLSHandshakeFailure as alert: + for result in self._sendError( + AlertDescription.handshake_failure, + str(alert)): + yield result serverKeyExchange = keyExchange.makeServerKeyExchange(sigHashAlg) if serverKeyExchange is not None: msgs.append(serverKeyExchange) @@ -2066,8 +2078,8 @@ def _pickServerKeyExchangeSig(settings, clientHello): if hashID in rsaHashes: return hashName - # if none match, default to sha1 - return "sha1" + # if no match, we must abort per RFC 5246 + raise TLSHandshakeFailure("No common signature algorithms") @staticmethod def _sigHashesToList(settings): From 1cbbe08732261362ff2f6f08abf394cb06ef884c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 24 Oct 2016 15:02:37 +0200 Subject: [PATCH 377/574] Add more strong pseudoprimes in tests If we fix isPrime() method, it needs to detect all known strong pseudoprimes, not only the smallest known. --- unit_tests/test_tlslite_utils_cryptomath.py | 37 +++++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index ff4924bc..b5f59a01 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -35,17 +35,32 @@ def test_with_hard_primes_to_test(self): with self.assertRaises(AssertionError): for i in range(100): # OEIS A014233 - self.assertFalse(isPrime(2047)) - self.assertFalse(isPrime(1373653)) - self.assertFalse(isPrime(25326001)) - self.assertFalse(isPrime(3215031751)) - self.assertFalse(isPrime(2152302898747)) - self.assertFalse(isPrime(3474749660383)) - self.assertFalse(isPrime(341550071728321)) - self.assertFalse(isPrime(341550071728321)) - self.assertFalse(isPrime(3825123056546413051)) - self.assertFalse(isPrime(3825123056546413051)) - self.assertFalse(isPrime(3825123056546413051)) + self.assertFalse(isPrime(2047)) # base 1 + self.assertFalse(isPrime(1373653)) # base 2 + self.assertFalse(isPrime(25326001)) # base 3 + self.assertFalse(isPrime(3215031751)) # base 4 + self.assertFalse(isPrime(2152302898747)) # base 5 + self.assertFalse(isPrime(3474749660383)) # base 6 + self.assertFalse(isPrime(341550071728321)) # base 7 + self.assertFalse(isPrime(341550071728321)) # base 8 + self.assertFalse(isPrime(3825123056546413051)) # base 9 + self.assertFalse(isPrime(3825123056546413051)) # base 10 + self.assertFalse(isPrime(3825123056546413051)) # base 11 + # Zhang (2007) + self.assertFalse(isPrime(318665857834031151167461)) # base 12 + self.assertFalse(isPrime(3317044064679887385961981)) # base 13 + # base 14 + self.assertFalse(isPrime(6003094289670105800312596501)) + # base 15 + self.assertFalse(isPrime(59276361075595573263446330101)) + # base 16 + self.assertFalse(isPrime(564132928021909221014087501701)) + # base 17 + self.assertFalse(isPrime(564132928021909221014087501701)) + # base 18 + self.assertFalse(isPrime(1543267864443420616877677640751301)) + # base 19 + self.assertFalse(isPrime(1543267864443420616877677640751301)) def test_with_big_primes(self): # NextPrime[2^256] From 018b3da94aad14725a161d3791456ddc485dc04e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 4 Nov 2016 15:19:54 +0100 Subject: [PATCH 378/574] check if key shares are not small subgroups by chance --- tlslite/keyexchange.py | 16 +++- unit_tests/test_tlslite_keyexchange.py | 113 ++++++++++++++++++++++++- 2 files changed, 126 insertions(+), 3 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index c3078c23..45f8eb48 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -277,21 +277,35 @@ def processClientKeyExchange(self, clientKeyExchange): raise TLSIllegalParameterException("Invalid dh_Yc value") S = powMod(dh_Yc, self.dh_Xs, self.dh_p) + if S in (1, self.dh_p - 1): + raise TLSIllegalParameterException("Small subgroup capture") return numberToByteArray(S) def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): - """Process the server key exchange, return premaster secret""" + """Process the server key exchange, return premaster secret.""" del srvPublicKey dh_p = serverKeyExchange.dh_p # TODO make the minimum changeable if dh_p < 2**1023: raise TLSInsufficientSecurity("DH prime too small") + dh_g = serverKeyExchange.dh_g + if not 2 <= dh_g < dh_p - 1: + raise TLSIllegalParameterException("Invalid DH generator") + dh_Xc = bytesToNumber(getRandomBytes(32)) dh_Ys = serverKeyExchange.dh_Ys + if not 2 <= dh_Ys < dh_p - 1: + raise TLSIllegalParameterException("Invalid server key share") + self.dh_Yc = powMod(dh_g, dh_Xc, dh_p) + if self.dh_Yc in (1, dh_p - 1): + raise TLSIllegalParameterException("Small subgroup capture") S = powMod(dh_Ys, dh_Xc, dh_p) + if S in (1, dh_p - 1): + raise TLSIllegalParameterException("Small subgroup capture") + return numberToByteArray(S) def makeClientKeyExchange(self): diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index b58f915f..fb7d05be 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -28,14 +28,19 @@ from tlslite.utils.keyfactory import parsePEMKey from tlslite.utils.codec import Parser from tlslite.utils.cryptomath import bytesToNumber, getRandomBytes, powMod, \ - numberToByteArray -from tlslite.mathtls import makeX, makeU, makeK + numberToByteArray, isPrime +from tlslite.mathtls import makeX, makeU, makeK, goodGroupParameters from tlslite.handshakehashes import HandshakeHashes from tlslite import VerifierDB from tlslite.extensions import SupportedGroupsExtension, SNIExtension from tlslite.utils.ecc import getCurveByName, decodeX962Point, encodeX962Point,\ getPointByteSize import ecdsa +from operator import mul +try: + from functools import reduce +except ImportError: + pass from tlslite.keyexchange import KeyExchange, RSAKeyExchange, \ DHE_RSAKeyExchange, SRPKeyExchange, ECDHE_RSAKeyExchange @@ -574,6 +579,16 @@ def test_DHE_RSA_key_exchange_with_invalid_client_key_share(self): with self.assertRaises(TLSIllegalParameterException): self.keyExchange.processClientKeyExchange(clientKeyExchange) + def test_DHE_RSA_key_exchange_with_small_subgroup_client_key_share(self): + clientKeyExchange = ClientKeyExchange(self.cipher_suite, + (3, 3)) + clientKeyExchange.createDH(2**512) + self.keyExchange.dh_Xs = 0 + + with self.assertRaises(TLSIllegalParameterException): + self.keyExchange.processClientKeyExchange(clientKeyExchange) + + def test_DHE_RSA_key_exchange_with_small_prime(self): client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, @@ -588,6 +603,100 @@ def test_DHE_RSA_key_exchange_with_small_prime(self): with self.assertRaises(TLSInsufficientSecurity): client_keyExchange.processServerKeyExchange(None, srv_key_ex) + def test_DHE_RSA_key_exchange_with_invalid_generator(self): + client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None) + srv_key_ex = ServerKeyExchange(self.cipher_suite, + self.server_hello.server_version) + g, p = goodGroupParameters[1] + srv_key_ex.createDH(p, p - 1, powMod(2**256, g, p)) + + with self.assertRaises(TLSIllegalParameterException): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) + + def test_DHE_RSA_key_exchange_with_invalid_server_key_share(self): + client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None) + srv_key_ex = ServerKeyExchange(self.cipher_suite, + self.server_hello.server_version) + g, p = goodGroupParameters[1] + srv_key_ex.createDH(p, g, p - 1) + + with self.assertRaises(TLSIllegalParameterException): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) + + + def test_DHE_RSA_key_exchange_with_unfortunate_random_value(self): + client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None) + srv_key_ex = ServerKeyExchange(self.cipher_suite, + self.server_hello.server_version) + g, p = goodGroupParameters[1] + srv_key_ex.createDH(p, g, p - 2) + + def m(_): + return numberToByteArray((p - 1) // 2) + with mock.patch('tlslite.keyexchange.getRandomBytes', m): + with self.assertRaises(TLSIllegalParameterException): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) + + + def test_DHE_RSA_key_exchange_with_small_subgroup_shared_secret(self): + client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None) + srv_key_ex = ServerKeyExchange(self.cipher_suite, + self.server_hello.server_version) + # RFC 5114 Group 22 + order = [2, 2, 2, 7, int("df", 16), + int("183a872bdc5f7a7e88170937189", 16), + int("228c5a311384c02e1f287c6b7b2d", 16), + int("5a857d66c65a60728c353e32ece8be1", 16), + int("f518aa8781a8df278aba4e7d64b7cb9d49462353", 16), + int("1a3adf8d6a69682661ca6e590b447e66ebd1bbdeab5e6f3744f06f4" + "6cf2a8300622ed50011479f18143d471a53d30113995663a447dcb8" + "e81bc24d988edc41f21", 16)] + # commented out for performance + #for i in order: + # self.assertTrue(isPrime(i)) + p = reduce(mul, order, 1) * 2 + 1 + self.assertTrue(isPrime(p)) + g = 2 + + # check the order of generator + # (below lines commented out for performance) + #for l in range(len(order)): + # for subset in itertools.combinations(order, l): + # n = reduce(mul, subset, 1) + # self.assertNotEqual(powMod(g, n, p), 1) + self.assertEqual(powMod(g, reduce(mul, order, 1), p), 1) + + Ys = powMod(g, 100, p) + # check order of the key share + # (commented out for performance) + #for l in range(len(order)): + # for subset in itertools.combinations(order, l): + # n = reduce(mul, subset, 1) + # #print(subset) + # self.assertNotEqual(powMod(Ys, n, p), 1) + self.assertEqual(powMod(Ys, reduce(mul, order[2:], 1), p), 1) + + srv_key_ex.createDH(p, g, Ys) + + def m(_): + return numberToByteArray(reduce(mul, order[2:], 1)) + with mock.patch('tlslite.keyexchange.getRandomBytes', m): + with self.assertRaises(TLSIllegalParameterException): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) + + def test_DHE_RSA_key_exchange_empty_signature(self): self.keyExchange.privateKey.sign = mock.Mock(return_value=bytearray(0)) with self.assertRaises(TLSInternalError): From c820e4946dd3d949e8285cfb1ebf1b8a12fff2d5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Nov 2016 15:31:10 +0100 Subject: [PATCH 379/574] increase timeouts for test connections --- tests/tlstest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index e4f225d1..aea82a93 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -89,13 +89,13 @@ def clientTestCmd(argv): #open synchronisation FIFO synchro = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - synchro.settimeout(5) + synchro.settimeout(15) synchro.connect((address[0], address[1]-1)) def connect(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if hasattr(sock, 'settimeout'): #It's a python 2.3 feature - sock.settimeout(5) + sock.settimeout(15) sock.connect(address) c = TLSConnection(sock) return c @@ -702,7 +702,7 @@ def connect(): test_no += 1 print("Test {0}: POP3 good".format(test_no)) - except socket.error as e: + except (socket.error, socket.timeout) as e: print("Non-critical error: socket error trying to reach internet server: ", e) synchro.close() From 987310502ec67af50f4ca164f6dd2317c553a06a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Nov 2016 16:53:28 +0100 Subject: [PATCH 380/574] method for finding first matching element --- tlslite/utils/lists.py | 24 ++++++++++++++++++ unit_tests/test_tlslite_utils_lists.py | 35 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 tlslite/utils/lists.py create mode 100644 unit_tests/test_tlslite_utils_lists.py diff --git a/tlslite/utils/lists.py b/tlslite/utils/lists.py new file mode 100644 index 00000000..a921518b --- /dev/null +++ b/tlslite/utils/lists.py @@ -0,0 +1,24 @@ +# Authors: +# Hubert Kario (2016) +# +# See the LICENSE file for legal information regarding use of this file. + +"""Helper functions for handling lists""" + + +def getFirstMatching(values, matches): + """ + Return the first element in values that is also in matches. + + Return None if values is None, empty or no element in values is also in + matches. + + @type values: collections.abc.Iterable + @param values: list of items to look through, can be None + @type matches: collections.abc.Container + @param matches: list of items to check against + """ + assert matches is not None + if not values: + return None + return next((i for i in values if i in matches), None) diff --git a/unit_tests/test_tlslite_utils_lists.py b/unit_tests/test_tlslite_utils_lists.py new file mode 100644 index 00000000..442c059a --- /dev/null +++ b/unit_tests/test_tlslite_utils_lists.py @@ -0,0 +1,35 @@ +# Copyright (c) 2016, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +from tlslite.utils.lists import getFirstMatching + +class TestGetFirstMatching(unittest.TestCase): + def test_empty_list(self): + self.assertIsNone(getFirstMatching([], [1, 2, 3])) + + def test_first_matching(self): + self.assertEqual(getFirstMatching([1, 7, 8, 9], [1, 2, 3]), 1) + + def test_last_matching(self): + self.assertEqual(getFirstMatching([7, 8, 9, 1], [1, 2, 3]), 1) + + def test_no_matching(self): + self.assertIsNone(getFirstMatching([7, 8, 9], [1, 2, 3])) + + def test_no_list(self): + self.assertIsNone(getFirstMatching(None, [1, 2, 3])) + + def test_empty_matches(self): + self.assertIsNone(getFirstMatching([1, 2, 3], [])) + + def test_no_matches(self): + with self.assertRaises(AssertionError): + getFirstMatching([1, 2, 3], None) From 669b2bf3db7bbb0467f373fbffba74b344ff8f4a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Nov 2016 18:03:51 +0100 Subject: [PATCH 381/574] wrap the ugly next() construct to get first matching element --- tlslite/keyexchange.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 45f8eb48..c3ed1f32 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -15,6 +15,7 @@ from .utils.rsakey import RSAKey from .utils.cryptomath import bytesToNumber, getRandomBytes, powMod, \ numBits, numberToByteArray +from .utils.lists import getFirstMatching import ecdsa class KeyExchange(object): @@ -159,8 +160,7 @@ def makeCertificateVerify(version, handshakeHashes, validSigAlgs, # in TLS 1.2 we must decide which algorithm to use for signing if version == (3, 3): serverSigAlgs = certificateRequest.supported_signature_algs - signatureAlgorithm = next((sigAlg for sigAlg in validSigAlgs \ - if sigAlg in serverSigAlgs), None) + signatureAlgorithm = getFirstMatching(validSigAlgs, serverSigAlgs) # if none acceptable, do a last resort: if signatureAlgorithm is None: signatureAlgorithm = validSigAlgs[0] @@ -362,8 +362,7 @@ def makeServerKeyExchange(self, sigHash=None): client_curves = client_curves.groups #Pick first client preferred group we support - self.group_id = next((x for x in client_curves \ - if x in self.acceptedCurves), None) + self.group_id = getFirstMatching(client_curves, self.acceptedCurves) if self.group_id is None: raise TLSInsufficientSecurity("No mutual groups") generator = getCurveByName(GroupName.toRepr(self.group_id)).generator From 17ec70a657fc64af2067785a0d17601dd12c87bc Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 27 Oct 2016 15:05:32 +0200 Subject: [PATCH 382/574] verify signatures created in cert verify the CRT leaks are as applicable to ServerKeyExchange as they are to CertificateVerify, if only harder to exploit --- tlslite/keyexchange.py | 2 ++ tlslite/tlsconnection.py | 7 ++++++- unit_tests/test_tlslite_keyexchange.py | 22 ++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index c3ed1f32..87d15e44 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -170,6 +170,8 @@ def makeCertificateVerify(version, handshakeHashes, validSigAlgs, clientRandom, serverRandom) signedBytes = privateKey.sign(verifyBytes) + if not privateKey.verify(signedBytes, verifyBytes): + raise TLSInternalError("Certificate Verify signature invalid") certificateVerify = CertificateVerify(version) certificateVerify.create(signedBytes, signatureAlgorithm) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 9ca33617..25db6e12 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -980,7 +980,8 @@ def _clientKeyExchange(self, settings, cipherSuite, #CertificateVerify if certificateRequest and privateKey: validSigAlgs = self._sigHashesToList(settings) - certificateVerify = KeyExchange.makeCertificateVerify(\ + try: + certificateVerify = KeyExchange.makeCertificateVerify( self.version, self._handshake_hash, validSigAlgs, @@ -989,6 +990,10 @@ def _clientKeyExchange(self, settings, cipherSuite, premasterSecret, clientRandom, serverRandom) + except TLSInternalError as exception: + for result in self._sendError( + AlertDescription.internal_error, exception): + yield result for result in self._sendMsg(certificateVerify): yield result diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index fb7d05be..b58a2aa3 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -378,6 +378,28 @@ def test_with_TLS1_1(self): b'\x19~&\xd9\xaa\xc2\t,s\xde\xb1' )) + def test_with_failed_signature(self): + certificate_request = CertificateRequest((3, 3)) + certificate_request.create([CertificateType.x509], + [], + [(HashAlgorithm.sha256, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + self.clnt_private_key.sign = mock.Mock(return_value=bytearray(20)) + + with self.assertRaises(TLSInternalError): + certVerify = KeyExchange.makeCertificateVerify( + (3, 3), + self.handshake_hashes, + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha512, + SignatureAlgorithm.rsa)], + self.clnt_private_key, + certificate_request, + None, None, None) + class TestRSAKeyExchange(unittest.TestCase): def setUp(self): self.srv_private_key = parsePEMKey(srv_raw_key, private=True) From 9bbf2db245caab1b90ac39461275498884cd4989 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 27 Oct 2016 15:06:37 +0200 Subject: [PATCH 383/574] less code duplication in keyexchange --- tlslite/keyexchange.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 87d15e44..ca566590 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -177,6 +177,19 @@ def makeCertificateVerify(version, handshakeHashes, validSigAlgs, return certificateVerify +class AuthenticatedKeyExchange(KeyExchange): + """ + Common methods for key exchanges that authenticate Server Key Exchange + + Methods for signing Server Key Exchange message + """ + + def makeServerKeyExchange(self, sigHash=None): + """Prepare server side of key exchange with selected parameters""" + ske = super(AuthenticatedKeyExchange, self).makeServerKeyExchange() + self.signServerKeyExchange(ske, sigHash) + return ske + class RSAKeyExchange(KeyExchange): """ @@ -319,7 +332,7 @@ def makeClientKeyExchange(self): # the DHE_RSA part comes from IETF ciphersuite names, we want to keep it #pylint: disable = invalid-name -class DHE_RSAKeyExchange(ADHKeyExchange): +class DHE_RSAKeyExchange(AuthenticatedKeyExchange, ADHKeyExchange): """ Handling of ephemeral Diffe-Hellman Key exchange @@ -332,12 +345,6 @@ def __init__(self, cipherSuite, clientHello, serverHello, privateKey): #pylint: enable = invalid-name self.privateKey = privateKey - def makeServerKeyExchange(self, sigHash=None): - """Prepare server side of key exchange with selected parameters""" - ske = super(DHE_RSAKeyExchange, self).makeServerKeyExchange() - self.signServerKeyExchange(ske, sigHash) - return ske - class AECDHKeyExchange(KeyExchange): """ @@ -423,7 +430,7 @@ def makeClientKeyExchange(self): # The ECDHE_RSA part comes from the IETF names of ciphersuites, so we want to # keep it #pylint: disable = invalid-name -class ECDHE_RSAKeyExchange(AECDHKeyExchange): +class ECDHE_RSAKeyExchange(AuthenticatedKeyExchange, AECDHKeyExchange): """Helper class for conducting ECDHE key exchange""" def __init__(self, cipherSuite, clientHello, serverHello, privateKey, @@ -434,12 +441,6 @@ def __init__(self, cipherSuite, clientHello, serverHello, privateKey, #pylint: enable = invalid-name self.privateKey = privateKey - def makeServerKeyExchange(self, sigHash=None): - """Create ECDHE version of Server Key Exchange""" - ske = super(ECDHE_RSAKeyExchange, self).makeServerKeyExchange() - self.signServerKeyExchange(ske, sigHash) - return ske - class SRPKeyExchange(KeyExchange): """Helper class for conducting SRP key exchange""" From 3859eea866c9879af7cdcd1e258f1930e2f94a46 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 27 Oct 2016 16:21:24 +0200 Subject: [PATCH 384/574] fix formatting of goodGroupParameters initializer also fixes the incorrect generator for 3072-bit params, it was 2, but should be 5. Keep the old params in the list, but don't use them for creating the verifier values (see makeVerifier) --- tlslite/mathtls.py | 258 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 250 insertions(+), 8 deletions(-) diff --git a/tlslite/mathtls.py b/tlslite/mathtls.py index 6884bc50..81f11d42 100644 --- a/tlslite/mathtls.py +++ b/tlslite/mathtls.py @@ -15,14 +15,256 @@ import hmac -#1024, 1536, 2048, 3072, 4096, 6144, and 8192 bit groups] -goodGroupParameters = [(2,0xEEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3),\ - (2,0x9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA9614B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F84380B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0BE3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF56EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734AF7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB),\ - (2,0xAC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73),\ - (2,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF),\ - (5,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF),\ - (5,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF),\ - (5,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF)] +# 1024, 1536, 2048, 3072, 4096, 6144, and 8192 bit groups] +# Formatted to match lines in RFC + # RFC 5054, 1, 1024-bit Group +goodGroupParameters = [(2, int("EEAF0AB9ADB38DD69C33F80AFA8FC5E860726187" + "75FF3C0B9EA2314C" + "9C256576D674DF7496EA81D3383B4813D692C6E0" + "E0D5D8E250B98BE4" + "8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD" + "69B15D4982559B29" + "7BCF1885C529F566660E57EC68EDBC3C05726CC0" + "2FD4CBF4976EAA9A" + "FD5138FE8376435B9FC61D2FC0EB06E3", 16)), + # RFC 5054, 2, 1536-bit Group + (2, int("9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF4" + "99AC4C80BEEEA961" + "4B19CC4D5F4F5F556E27CBDE51C6A94BE4607A29" + "1558903BA0D0F843" + "80B655BB9A22E8DCDF028A7CEC67F0D08134B1C8" + "B97989149B609E0B" + "E3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1" + "158BFD3E2B9C8CF5" + "6EDF019539349627DB2FD53D24B7C48665772E43" + "7D6C7F8CE442734A" + "F7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E" + "5A021FFF5E91479E" + "8CE7A28C2442C6F315180F93499A234DCF76E3FE" + "D135F9BB", 16)), + # RFC 5054, 3, 2048-bit Group + (2, int("AC6BDB41324A9A9BF166DE5E1389582FAF72B665" + "1987EE07FC319294" + "3DB56050A37329CBB4A099ED8193E0757767A13D" + "D52312AB4B03310D" + "CD7F48A9DA04FD50E8083969EDB767B0CF609517" + "9A163AB3661A05FB" + "D5FAAAE82918A9962F0B93B855F97993EC975EEA" + "A80D740ADBF4FF74" + "7359D041D5C33EA71D281E446B14773BCA97B43A" + "23FB801676BD207A" + "436C6481F1D2B9078717461A5B9D32E688F87748" + "544523B524B0D57D" + "5EA77A2775D2ECFA032CFBDBF52FB37861602790" + "04E57AE6AF874E73" + "03CE53299CCC041C7BC308D82A5698F3A8D0C382" + "71AE35F8E9DBFBB6" + "94B5C803D89F7AE435DE236D525F54759B65E372" + "FCD68EF20FA7111F" + "9E4AFF73", 16)), + # RFC 5054, 4, 3072-bit Group + (5, int("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B" + "80DC1CD129024E08" + "8A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B" + "302B0A6DF25F14374FE1356D6D51C245E485B576" + "625E7EC6F44C42E9" + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5" + "AE9F24117C4B1FE6" + "49286651ECE45B3DC2007CB8A163BF0598DA4836" + "1C55D39A69163FA8" + "FD24CF5F83655D23DCA3AD961C62F356208552BB" + "9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E46" + "2E36CE3BE39E772C" + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + "DE2BCBF695581718" + "3995497CEA956AE515D2261898FA051015728E5A" + "8AAAC42DAD33170D" + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A" + "8AEA71575D060C7D" + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E0" + "4A25619DCEE3D226" + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64" + "521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA0" + "74E5AB3143DB5BFC" + "E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", + 16)), + # RFC 5054, 5, 4096-bit Group + (5, int("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B" + "80DC1CD129024E08" + "8A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B" + "302B0A6DF25F14374FE1356D6D51C245E485B576" + "625E7EC6F44C42E9" + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5" + "AE9F24117C4B1FE6" + "49286651ECE45B3DC2007CB8A163BF0598DA4836" + "1C55D39A69163FA8" + "FD24CF5F83655D23DCA3AD961C62F356208552BB" + "9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E46" + "2E36CE3BE39E772C" + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + "DE2BCBF695581718" + "3995497CEA956AE515D2261898FA051015728E5A" + "8AAAC42DAD33170D" + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A" + "8AEA71575D060C7D" + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E0" + "4A25619DCEE3D226" + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64" + "521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA0" + "74E5AB3143DB5BFC" + "E0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B26" + "99C327186AF4E23C1A946834B6150BDA2583E9CA" + "2AD44CE8DBBBC2DB" + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D" + "99B2964FA090C3A2" + "233BA186515BE7ED1F612970CEE2D7AFB81BDD76" + "2170481CD0069127" + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F" + "4DF435C934063199" + "FFFFFFFFFFFFFFFF", 16)), + # RFC 5054, 6, 6144-bit Group + (5, int("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B" + "80DC1CD129024E08" + "8A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B" + "302B0A6DF25F14374FE1356D6D51C245E485B576" + "625E7EC6F44C42E9" + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5" + "AE9F24117C4B1FE6" + "49286651ECE45B3DC2007CB8A163BF0598DA4836" + "1C55D39A69163FA8" + "FD24CF5F83655D23DCA3AD961C62F356208552BB" + "9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E46" + "2E36CE3BE39E772C" + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + "DE2BCBF695581718" + "3995497CEA956AE515D2261898FA051015728E5A" + "8AAAC42DAD33170D" + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A" + "8AEA71575D060C7D" + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E0" + "4A25619DCEE3D226" + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64" + "521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA0" + "74E5AB3143DB5BFC" + "E0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B26" + "99C327186AF4E23C1A946834B6150BDA2583E9CA" + "2AD44CE8DBBBC2DB" + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D" + "99B2964FA090C3A2" + "233BA186515BE7ED1F612970CEE2D7AFB81BDD76" + "2170481CD0069127" + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F" + "4DF435C934028492" + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E76" + "3DBA37BDF8FF9406" + "AD9E530EE5DB382F413001AEB06A53ED9027D831" + "179727B0865A8918" + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447" + "E6CC254B33205151" + "2BD7AF426FB8F401378CD2BF5983CA01C64B92EC" + "F032EA15D1721D03" + "F482D7CE6E74FEF6D55E702F46980C82B5A84031" + "900B1C9E59E7C97F" + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC5" + "4BD407B22B4154AA" + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EE" + "F29BE32806A1D58B" + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + "DA56C9EC2EF29632" + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B" + "0B7474D6E694F91E" + "6DCC4024FFFFFFFFFFFFFFFF", 16)), + # RFC 5054, 7, 8192-bit Group + (5, int("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B" + "80DC1CD129024E08" + "8A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B" + "302B0A6DF25F14374FE1356D6D51C245E485B576" + "625E7EC6F44C42E9" + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5" + "AE9F24117C4B1FE6" + "49286651ECE45B3DC2007CB8A163BF0598DA4836" + "1C55D39A69163FA8" + "FD24CF5F83655D23DCA3AD961C62F356208552BB" + "9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E46" + "2E36CE3BE39E772C" + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + "DE2BCBF695581718" + "3995497CEA956AE515D2261898FA051015728E5A" + "8AAAC42DAD33170D" + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A" + "8AEA71575D060C7D" + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E0" + "4A25619DCEE3D226" + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64" + "521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA0" + "74E5AB3143DB5BFC" + "E0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B26" + "99C327186AF4E23C1A946834B6150BDA2583E9CA" + "2AD44CE8DBBBC2DB" + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D" + "99B2964FA090C3A2" + "233BA186515BE7ED1F612970CEE2D7AFB81BDD76" + "2170481CD0069127" + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F" + "4DF435C934028492" + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E76" + "3DBA37BDF8FF9406" + "AD9E530EE5DB382F413001AEB06A53ED9027D831" + "179727B0865A8918" + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447" + "E6CC254B33205151" + "2BD7AF426FB8F401378CD2BF5983CA01C64B92EC" + "F032EA15D1721D03" + "F482D7CE6E74FEF6D55E702F46980C82B5A84031" + "900B1C9E59E7C97F" + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC5" + "4BD407B22B4154AA" + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EE" + "F29BE32806A1D58B" + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + "DA56C9EC2EF29632" + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B" + "0B7474D6E694F91E" + "6DBE115974A3926F12FEE5E438777CB6A932DF8C" + "D8BEC4D073B931BA" + "3BC832B68D9DD300741FA7BF8AFC47ED2576F693" + "6BA424663AAB639C" + "5AE4F5683423B4742BF1C978238F16CBE39D652D" + "E3FDB8BEFC848AD9" + "22222E04A4037C0713EB57A81A23F0C73473FC64" + "6CEA306B4BCBC886" + "2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" + "062B3CF5B3A278A6" + "6D2A13F83F44F82DDF310EE074AB6A364597E899" + "A0255DC164F31CC5" + "0846851DF9AB48195DED7EA1B1D510BD7EE74D73" + "FAF36BC31ECFA268" + "359046F4EB879F924009438B481C6CD7889A002E" + "D5EE382BC9190DA6" + "FC026E479558E4475677E9AA9E3050E2765694DF" + "C81F56E880B96E71" + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", 16))] + +# old versions of tlslite had an incorrect generator for 3072 bit group +# from RFC 5054. Since the group is a safe prime, the generator of "2" is +# cryptographically safe, so we don't have reason to reject connections +# from old tlslite, so add the old invalid value to the "known good" list +goodGroupParameters.append((2, goodGroupParameters[3][1])) def P_hash(macFunc, secret, seed, length): bytes = bytearray(length) From 9db7c07bae8552da7be9dd84ae87356336018dc0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 27 Oct 2016 17:41:40 +0200 Subject: [PATCH 385/574] cleanup comment --- tlslite/mathtls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/mathtls.py b/tlslite/mathtls.py index 81f11d42..851c0b15 100644 --- a/tlslite/mathtls.py +++ b/tlslite/mathtls.py @@ -15,7 +15,7 @@ import hmac -# 1024, 1536, 2048, 3072, 4096, 6144, and 8192 bit groups] +# 1024, 1536, 2048, 3072, 4096, 6144, and 8192 bit groups # Formatted to match lines in RFC # RFC 5054, 1, 1024-bit Group goodGroupParameters = [(2, int("EEAF0AB9ADB38DD69C33F80AFA8FC5E860726187" From ddfccf7759f0e1f52f25fdd1f2f77ecc94335468 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 27 Oct 2016 17:56:49 +0200 Subject: [PATCH 386/574] method to provide security estimate for DH and RSA keys --- tlslite/mathtls.py | 39 +++++++++++++++++++++++++++ unit_tests/test_tlslite_mathtls.py | 43 +++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/tlslite/mathtls.py b/tlslite/mathtls.py index 851c0b15..6c0db8cc 100644 --- a/tlslite/mathtls.py +++ b/tlslite/mathtls.py @@ -266,6 +266,45 @@ # from old tlslite, so add the old invalid value to the "known good" list goodGroupParameters.append((2, goodGroupParameters[3][1])) + +def paramStrength(param): + """ + Return level of security for DH, DSA and RSA parameters. + + Provide the approximate level of security for algorithms based on finite + field (DSA, DH) or integer factorisation cryptography (RSA) when provided + with the prime defining the field or the modulus of the public key. + + @param param: prime or modulus + @type param: int + """ + size = numBits(param) + if size < 512: + return 48 + elif size < 768: + return 56 + elif size < 816: + return 64 + elif size < 1023: + return 72 + elif size < 1535: + return 80 # NIST SP 800-57 + elif size < 2047: + return 88 # rounded RFC 3526 + elif size < 3071: + return 112 # NIST SP 800-57 + elif size < 4095: + return 128 # NIST SP 800-57 + elif size < 6144: + return 152 # rounded RFC 3526 + elif size < 7679: + return 168 # rounded RFC 3526 + elif size < 15359: + return 192 # NIST SP 800-57 + else: + return 256 # NIST SP 800-57 + + def P_hash(macFunc, secret, seed, length): bytes = bytearray(length) A = seed diff --git a/unit_tests/test_tlslite_mathtls.py b/unit_tests/test_tlslite_mathtls.py index 62df4787..721e0340 100644 --- a/unit_tests/test_tlslite_mathtls.py +++ b/unit_tests/test_tlslite_mathtls.py @@ -10,7 +10,7 @@ import unittest from tlslite.mathtls import PRF_1_2, calcMasterSecret, calcFinished, \ - calcExtendedMasterSecret + calcExtendedMasterSecret, paramStrength from tlslite.handshakehashes import HandshakeHashes from tlslite.constants import CipherSuite @@ -206,3 +206,44 @@ def test_if_client_and_server_values_differ(self): CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384, self.hhashes, False) self.assertNotEqual(self.finished, ret_srv) + + +class TestParamStrength(unittest.TestCase): + def test_480(self): + self.assertEqual(48, paramStrength(2**480)) + + def test_512(self): + self.assertEqual(56, paramStrength(2**512)) + + def test_768(self): + self.assertEqual(64, paramStrength(2**768)) + + def test_900(self): + self.assertEqual(72, paramStrength(2**900)) + + def test_1024(self): + self.assertEqual(80, paramStrength(2**1024)) + + def test_1536(self): + self.assertEqual(88, paramStrength(2**1536)) + + def test_2048(self): + self.assertEqual(112, paramStrength(2**2048)) + + def test_3072(self): + self.assertEqual(128, paramStrength(2**3072)) + + def test_4096(self): + self.assertEqual(152, paramStrength(2**4096)) + + def test_6144(self): + self.assertEqual(168, paramStrength(2**6144)) + + def test_7680(self): + self.assertEqual(192, paramStrength(2**7680)) + + def test_8192(self): + self.assertEqual(192, paramStrength(2**8192)) + + def test_15360(self): + self.assertEqual(256, paramStrength(2**15360)) From 7bb5a65637ef2dd9f8b0a7932a1c92221d3ac22f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 27 Oct 2016 18:10:30 +0200 Subject: [PATCH 387/574] move divceil to utils module --- tlslite/utils/cryptomath.py | 11 ++++++++--- tlslite/utils/poly1305.py | 10 +++------- tlslite/utils/rsakey.py | 9 ++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index d7f6f8b7..e687b737 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -15,8 +15,6 @@ import binascii import sys -from .poly1305 import Poly1305 - from .compat import compat26Str, compatHMAC, compatLong @@ -110,7 +108,7 @@ def HMAC_SHA384(k, b): return secureHMAC(k, b, 'sha384') def HKDF_expand(PRK, info, L, algorithm): - N = Poly1305.divceil(L, getattr(hashlib, algorithm)().digest_size) + N = divceil(L, getattr(hashlib, algorithm)().digest_size) T = bytearray() Titer = bytearray() for x in range(1, N+2): @@ -248,6 +246,13 @@ def powMod(base, power, modulus): else: return pow(base, power, modulus) + +def divceil(divident, divisor): + """Integer division with rounding up""" + quot, r = divmod(divident, divisor) + return quot + int(bool(r)) + + #Pre-calculate a sieve of the ~100 primes < 1000: def makeSieve(n): sieve = list(range(n)) diff --git a/tlslite/utils/poly1305.py b/tlslite/utils/poly1305.py index 83e3233f..935178c0 100644 --- a/tlslite/utils/poly1305.py +++ b/tlslite/utils/poly1305.py @@ -3,6 +3,8 @@ # See the LICENSE file for legal information regarding use of this file. """Implementation of Poly1305 authenticator for RFC 7539""" +from .cryptomath import divceil + class Poly1305(object): """Poly1305 authenticator""" @@ -27,12 +29,6 @@ def num_to_16_le_bytes(num): num >>= 8 return bytearray(ret) - @staticmethod - def divceil(divident, divisor): - """Integer division with rounding up""" - quot, r = divmod(divident, divisor) - return quot + int(bool(r)) - def __init__(self, key): """Set the authenticator key""" if len(key) != 32: @@ -44,7 +40,7 @@ def __init__(self, key): def create_tag(self, data): """Calculate authentication tag for data""" - for i in range(0, self.divceil(len(data), 16)): + for i in range(0, divceil(len(data), 16)): n = self.le_bytes_to_num(data[i*16:(i+1)*16] + b'\x01') self.acc += n self.acc = (self.r * self.acc) % self.P diff --git a/tlslite/utils/rsakey.py b/tlslite/utils/rsakey.py index 171c85ef..a347b2ea 100644 --- a/tlslite/utils/rsakey.py +++ b/tlslite/utils/rsakey.py @@ -4,7 +4,6 @@ """Abstract class for RSA.""" from .cryptomath import * -from .poly1305 import Poly1305 from . import tlshashlib as hashlib from ..errors import MaskTooLongError, MessageTooLongError, EncodingError, \ InvalidSignature, UnknownRSAType @@ -152,7 +151,7 @@ def MGF1(self, mgfSeed, maskLen, hAlg): if maskLen > (2 ** 32) * hashLen: raise MaskTooLongError("Incorrect parameter maskLen") T = bytearray() - end = (Poly1305.divceil(maskLen, hashLen)) + end = divceil(maskLen, hashLen) for x in range(0, end): C = numberToByteArray(x, 4) T += secureHash(mgfSeed + C, hAlg) @@ -176,7 +175,7 @@ def EMSA_PSS_encode(self, M, emBits, hAlg, sLen=0): @param sLen: length of salt""" hashLen = getattr(hashlib, hAlg)().digest_size mHash = secureHash(M, hAlg) - emLen = Poly1305.divceil(emBits, 8) + emLen = divceil(emBits, 8) if emLen < hashLen + sLen + 2: raise EncodingError("The ending limit too short for " + "selected hash and salt length") @@ -236,7 +235,7 @@ def EMSA_PSS_verify(self, M, EM, emBits, hAlg, sLen=0): """ hashLen = getattr(hashlib, hAlg)().digest_size mHash = secureHash(M, hAlg) - emLen = Poly1305.divceil(emBits, 8) + emLen = divceil(emBits, 8) if emLen < hashLen + sLen + 2: raise InvalidSignature("Invalid signature") if EM[-1] != 0xbc: @@ -289,7 +288,7 @@ def RSASSA_PSS_verify(self, M, S, hAlg, sLen=0): raise InvalidSignature s = bytesToNumber(S) m = self._rawPublicKeyOp(s) - EM = numberToByteArray(m, Poly1305.divceil(numBits(self.n) - 1, 8)) + EM = numberToByteArray(m, divceil(numBits(self.n) - 1, 8)) result = self.EMSA_PSS_verify(M, EM, numBits(self.n) - 1, hAlg, sLen) if result: return True From e9b899c592cd7cd6bed0a958dc7bf5eac495dfa0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Nov 2016 18:06:27 +0100 Subject: [PATCH 388/574] keyExchange with customizable DH parameters --- tlslite/keyexchange.py | 43 ++++++++++++++++---------- unit_tests/test_tlslite_keyexchange.py | 31 ++++++++++++++++++- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index ca566590..e7dd9f9b 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -4,7 +4,8 @@ # See the LICENSE file for legal information regarding use of this file. """Handling of cryptographic operations for key exchange""" -from .mathtls import goodGroupParameters, makeK, makeU, makeX, calcMasterSecret +from .mathtls import goodGroupParameters, makeK, makeU, makeX, \ + calcMasterSecret, paramStrength from .errors import TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ TLSIllegalParameterException, TLSDecryptionFailed, TLSInternalError from .messages import ServerKeyExchange, ClientKeyExchange, CertificateVerify @@ -14,7 +15,7 @@ getPointByteSize from .utils.rsakey import RSAKey from .utils.cryptomath import bytesToNumber, getRandomBytes, powMod, \ - numBits, numberToByteArray + numBits, numberToByteArray, divceil from .utils.lists import getFirstMatching import ecdsa @@ -252,27 +253,27 @@ class ADHKeyExchange(KeyExchange): FFDHE without signing serverKeyExchange useful for anonymous DH """ - def __init__(self, cipherSuite, clientHello, serverHello): + def __init__(self, cipherSuite, clientHello, serverHello, + dhParams=None): super(ADHKeyExchange, self).__init__(cipherSuite, clientHello, serverHello) #pylint: enable = invalid-name self.dh_Xs = None self.dh_Yc = None - - # 2048-bit MODP Group (RFC 3526, Section 3) - # TODO make configurable - dh_g, dh_p = goodGroupParameters[2] - - # RFC 3526, Section 8. - strength = 160 + if dhParams: + self.dh_g, self.dh_p = dhParams + else: + # 2048-bit MODP Group (RFC 5054, group 3) + self.dh_g, self.dh_p = goodGroupParameters[2] def makeServerKeyExchange(self): """ Prepare server side of anonymous key exchange with selected parameters """ # 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)) + # of the strength of the group. + randBytesNeeded = divceil(paramStrength(self.dh_p) * 2, 8) + self.dh_Xs = bytesToNumber(getRandomBytes(randBytesNeeded)) dh_Ys = powMod(self.dh_g, self.dh_Xs, self.dh_p) version = self.serverHello.server_version @@ -334,14 +335,22 @@ def makeClientKeyExchange(self): #pylint: disable = invalid-name class DHE_RSAKeyExchange(AuthenticatedKeyExchange, ADHKeyExchange): """ - Handling of ephemeral Diffe-Hellman Key exchange - - NOT stable API, do NOT use + Handling of authenticated ephemeral Diffe-Hellman Key exchange. """ - def __init__(self, cipherSuite, clientHello, serverHello, privateKey): + def __init__(self, cipherSuite, clientHello, serverHello, privateKey, + dhParams=None): + """ + Create helper object for Diffie-Hellamn key exchange. + + @param dhParams: Diffie-Hellman parameters that will be used by + server. First element of the tuple is the generator, the second + is the prime. If not specified it will use a secure set (currently + a 2048-bit safe prime). + @type dhParams: 2-element tuple of int + """ super(DHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, - serverHello) + serverHello, dhParams) #pylint: enable = invalid-name self.privateKey = privateKey diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index b58a2aa3..a344ec23 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -28,7 +28,7 @@ from tlslite.utils.keyfactory import parsePEMKey from tlslite.utils.codec import Parser from tlslite.utils.cryptomath import bytesToNumber, getRandomBytes, powMod, \ - numberToByteArray, isPrime + numberToByteArray, isPrime, numBits from tlslite.mathtls import makeX, makeU, makeK, goodGroupParameters from tlslite.handshakehashes import HandshakeHashes from tlslite import VerifierDB @@ -593,6 +593,35 @@ def test_DHE_RSA_key_exchange_with_client(self): self.assertEqual(client_premaster, server_premaster) + def test_DHE_RSA_key_exchange_with_custom_parameters(self): + self.keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key, + # 6144 bit group + goodGroupParameters[5]) + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None,) + client_premaster = client_keyExchange.processServerKeyExchange( + None, + srv_key_ex) + # because the agreed upon secret can be any value between 1 and p-1, + # we can't check the exact length. At the same time, short shared + # secrets are exceedingly rare, a share shorter by 4 bytes will + # happen only once in 256^4 negotiations or 1 in about 4 milliards + self.assertLessEqual(len(client_premaster), 6144 // 8) + self.assertGreaterEqual(len(client_premaster), 6144 // 8 - 4) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange( + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) + def test_DHE_RSA_key_exchange_with_invalid_client_key_share(self): clientKeyExchange = ClientKeyExchange(self.cipher_suite, (3, 3)) From c0341611af50c49ef9227f2e384ee1f56f712178 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 27 Oct 2016 19:31:06 +0200 Subject: [PATCH 389/574] add dhParams to handshake settings --- tlslite/handshakesettings.py | 9 ++++++++- tlslite/tlsconnection.py | 3 ++- tlslite/utils/compat.py | 4 ++++ unit_tests/test_tlslite_handshakesettings.py | 6 ++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index a1d61562..317d7242 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -10,7 +10,7 @@ from .constants import CertificateType from .utils import cryptomath from .utils import cipherfactory -from .utils.compat import ecdsaAllCurves +from .utils.compat import ecdsaAllCurves, int_types CIPHER_NAMES = ["chacha20-poly1305", "aes256gcm", "aes128gcm", @@ -157,6 +157,7 @@ def __init__(self): self.usePaddingExtension = True self.useExtendedMasterSecret = True self.requireExtendedMasterSecret = False + self.dhParams = None @staticmethod def _sanityCheckKeySizes(other): @@ -267,6 +268,7 @@ def validate(self): other.eccCurves = self.eccCurves other.useExtendedMasterSecret = self.useExtendedMasterSecret other.requireExtendedMasterSecret = self.requireExtendedMasterSecret + other.dhParams = self.dhParams if not cipherfactory.tripleDESPresent: other.cipherNames = [i for i in self.cipherNames if i != "3des"] @@ -300,6 +302,11 @@ def validate(self): if len(other.rsaSigHashes) == 0 and other.maxVersion >= (3, 3): raise ValueError("TLS 1.2 requires signature algorithms to be set") + if other.dhParams and (len(other.dhParams) != 2 or + not isinstance(other.dhParams[0], int_types) or + not isinstance(other.dhParams[1], int_types)): + raise ValueError("DH parameters need to be a tuple of integers") + return other def getCertificateTypes(self): diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 25db6e12..573561fc 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1361,7 +1361,8 @@ def _handshakeServerAsyncHelper(self, verifierDB, keyExchange = DHE_RSAKeyExchange(cipherSuite, clientHello, serverHello, - privateKey) + privateKey, + settings.dhParams) elif cipherSuite in CipherSuite.ecdheCertSuites: acceptedCurves = self._curveNamesToList(settings) keyExchange = ECDHE_RSAKeyExchange(cipherSuite, diff --git a/tlslite/utils/compat.py b/tlslite/utils/compat.py index c971eff8..704b67ac 100644 --- a/tlslite/utils/compat.py +++ b/tlslite/utils/compat.py @@ -55,6 +55,8 @@ def readStdinBinary(): def compatLong(num): return int(num) + int_types = tuple([int]) + def formatExceptionTrace(e): """Return exception information formatted as string""" return str(e) @@ -95,6 +97,8 @@ def b2a_base64(b): def compatLong(num): return long(num) + int_types = (int, long) + # pylint on Python3 goes nuts for the sys dereferences... #pylint: disable=no-member diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index fd24acf6..2b2471e1 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -260,5 +260,11 @@ def test_invalid_usePaddingExtension(self): with self.assertRaises(ValueError): hs.validate() + def test_invalid_dhParams(self): + hs = HandshakeSettings() + hs.dhParams = (2, 'bd') + with self.assertRaises(ValueError): + hs.validate() + if __name__ == '__main__': unittest.main() From 52b62797654770065dc2c5f4921660df2bdccd01 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 2 Nov 2016 19:07:52 +0100 Subject: [PATCH 390/574] parsing OpenSSL dhparam output file --- tlslite/dh.py | 42 +++++++++++++++++++++++++++ unit_tests/test_tlslite_dh.py | 54 +++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 tlslite/dh.py create mode 100644 unit_tests/test_tlslite_dh.py diff --git a/tlslite/dh.py b/tlslite/dh.py new file mode 100644 index 00000000..2266706b --- /dev/null +++ b/tlslite/dh.py @@ -0,0 +1,42 @@ +# Author: +# Hubert Kario + +"""Handling of Diffie-Hellman parameter files.""" + +from .utils.asn1parser import ASN1Parser +from .utils.pem import dePem +from .utils.cryptomath import bytesToNumber + + +def parseBinary(data): + """ + Parse DH parameters from ASN.1 DER encoded binary string. + + @param data: DH parameters + @rtype: tuple of int + """ + parser = ASN1Parser(data) + + prime = parser.getChild(0) + gen = parser.getChild(1) + + return (bytesToNumber(gen.value), bytesToNumber(prime.value)) + + +def parse(data): + """ + Parses DH parameters from a binary string. + + The string can either by PEM or DER encoded + + @param data: DH parameters + @rtype: tuple of int + @return: generator and prime + """ + try: + return parseBinary(data) + except (SyntaxError, TypeError): + pass + + binData = dePem(data, "DH PARAMETERS") + return parseBinary(binData) diff --git a/unit_tests/test_tlslite_dh.py b/unit_tests/test_tlslite_dh.py new file mode 100644 index 00000000..750123c3 --- /dev/null +++ b/unit_tests/test_tlslite_dh.py @@ -0,0 +1,54 @@ + +try: + import unittest2 as unittest +except ImportError: + import unittest + +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call + +from tlslite.dh import parse, parseBinary + + +class TestParse(unittest.TestCase): + def test_pem(self): + data = ( + "-----BEGIN DH PARAMETERS-----\n" + "MIGHAoGBAIj4luOWCbrxyrPJOAhn4tG6jO8F1AaiiBBm1eAEEdQTKuhdV1uBBQDL\n" + "ve3O/ZrR9x+ILs9PIUgZMSFP8X5ldBAFjEIoTmfneSB4TcKN27gpiRFZK0eFTi9F\n" + "mofd/BgLWrNAHAOhBG7V6Gz7lZaFOxhxGTH+Lx6HxiTM7+RsLMSLAgEC\n" + "-----END DH PARAMETERS-----\n") + + g, p = parse(data) + + self.assertEqual(p, int("88F896E39609BAF1CAB3C9380867E2D1BA8CEF05D406" + "A2881066D5E00411D4132AE85D575B810500CBBDEDCEFD9AD1F71F882ECF4F21" + "481931214FF17E657410058C42284E67E77920784DC28DDBB8298911592B4785" + "4E2F459A87DDFC180B5AB3401C03A1046ED5E86CFB9596853B18711931FE2F1E" + "87C624CCEFE46C2CC48B", 16)) + self.assertEqual(g, 2) + + def test_der(self): + data = bytearray( + b"\x30\x81\x87\x02\x81\x81\x00\x88\xf8\x96\xe3\x96\x09\xba\xf1\xca" + b"\xb3\xc9\x38\x08\x67\xe2\xd1\xba\x8c\xef\x05\xd4\x06\xa2\x88\x10" + b"\x66\xd5\xe0\x04\x11\xd4\x13\x2a\xe8\x5d\x57\x5b\x81\x05\x00\xcb" + b"\xbd\xed\xce\xfd\x9a\xd1\xf7\x1f\x88\x2e\xcf\x4f\x21\x48\x19\x31" + b"\x21\x4f\xf1\x7e\x65\x74\x10\x05\x8c\x42\x28\x4e\x67\xe7\x79\x20" + b"\x78\x4d\xc2\x8d\xdb\xb8\x29\x89\x11\x59\x2b\x47\x85\x4e\x2f\x45" + b"\x9a\x87\xdd\xfc\x18\x0b\x5a\xb3\x40\x1c\x03\xa1\x04\x6e\xd5\xe8" + b"\x6c\xfb\x95\x96\x85\x3b\x18\x71\x19\x31\xfe\x2f\x1e\x87\xc6\x24" + b"\xcc\xef\xe4\x6c\x2c\xc4\x8b\x02\x01\x02") + + g, p = parse(data) + + self.assertEqual(p, int("88F896E39609BAF1CAB3C9380867E2D1BA8CEF05D406" + "A2881066D5E00411D4132AE85D575B810500CBBDEDCEFD9AD1F71F882ECF4F21" + "481931214FF17E657410058C42284E67E77920784DC28DDBB8298911592B4785" + "4E2F459A87DDFC180B5AB3401C03A1046ED5E86CFB9596853B18711931FE2F1E" + "87C624CCEFE46C2CC48B", 16)) + self.assertEqual(g, 2) From bde265b85cdf861792ebcce6b09c2d9c8ab0ab11 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 3 Nov 2016 16:49:39 +0100 Subject: [PATCH 391/574] support loading DH params from file in tls.py --- scripts/tls.py | 18 +++++++++++++++--- tlslite/api.py | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/tls.py b/scripts/tls.py index e0653ca2..d41c4d7d 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -73,7 +73,7 @@ def printUsage(s=None): server [-k KEY] [-c CERT] [-t TACK] [-v VERIFIERDB] [-d DIR] [-l LABEL] [-L LENGTH] - [--reqcert] HOST:PORT + [--reqcert] [--param DHFILE] HOST:PORT client [-k KEY] [-c CERT] [-u USER] [-p PASS] [-l LABEL] [-L LENGTH] [-a ALPN] @@ -83,6 +83,8 @@ def printUsage(s=None): LENGTH - amount of info to export using TLS exporter ALPN - name of protocol for ALPN negotiation, can be present multiple times in client to specify multiple protocols supported + DHFILE - file that includes Diffie-Hellman parameters to be used with DHE + key exchange """) sys.exit(-1) @@ -112,7 +114,8 @@ def handleArgs(argv, argString, flagsList=[]): expLabel = None expLength = 20 alpn = [] - + dhparam = None + for opt, arg in opts: if opt == "-k": s = open(arg, "rb").read() @@ -147,6 +150,11 @@ def handleArgs(argv, argString, flagsList=[]): expLength = int(arg) elif opt == "-a": alpn.append(bytearray(arg, 'utf-8')) + elif opt == "--param": + s = open(arg, "rb").read() + if sys.version_info[0] >= 3: + s = str(s, 'utf-8') + dhparam = parseDH(s) else: assert(False) @@ -188,6 +196,8 @@ def handleArgs(argv, argString, flagsList=[]): retList.append(expLength) if "a" in argString: retList.append(alpn) + if "param=" in flagsList: + retList.append(dhparam) return retList @@ -306,7 +316,8 @@ def clientCmd(argv): def serverCmd(argv): (address, privateKey, certChain, tacks, verifierDB, directory, reqCert, - expLabel, expLength) = handleArgs(argv, "kctbvdlL", ["reqcert"]) + expLabel, expLength, dhparam) = handleArgs(argv, "kctbvdlL", + ["reqcert", "param="]) if (certChain and not privateKey) or (not certChain and privateKey): @@ -347,6 +358,7 @@ def handshake(self, connection): start = time.clock() settings = HandshakeSettings() settings.useExperimentalTackExtension=True + settings.dhParams = dhparam connection.handshakeServer(certChain=certChain, privateKey=privateKey, verifierDB=verifierDB, diff --git a/tlslite/api.py b/tlslite/api.py index aa27ca3f..b9215602 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -29,3 +29,4 @@ from .utils.keyfactory import generateRSAKey, parsePEMKey, \ parseAsPublicKey, parsePrivateKey from .utils.tackwrapper import tackpyLoaded +from .dh import parse as parseDH From f3ca624d81c4d21f554d7dcd8d2adabd19ebecba Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 3 Nov 2016 19:22:07 +0100 Subject: [PATCH 392/574] add groups from RFC 7919 --- tlslite/mathtls.py | 151 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/tlslite/mathtls.py b/tlslite/mathtls.py index 6c0db8cc..ed8b6f25 100644 --- a/tlslite/mathtls.py +++ b/tlslite/mathtls.py @@ -266,6 +266,157 @@ # from old tlslite, so add the old invalid value to the "known good" list goodGroupParameters.append((2, goodGroupParameters[3][1])) +RFC7919_GROUPS = [] + +# RFC 7919 ffdhe2048 bit group +FFDHE2048 = (2, + int("FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B423861285C97FFFFFFFFFFFFFFFF", 16)) +goodGroupParameters.append(FFDHE2048) +RFC7919_GROUPS.append(FFDHE2048) + +# RFC 7919 ffdhe3072 bit group +FFDHE3072 = (2, + int("FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" + "3C1B20EE3FD59D7C25E41D2B66C62E37FFFFFFFFFFFFFFFF", 16)) +goodGroupParameters.append(FFDHE3072) +RFC7919_GROUPS.append(FFDHE3072) + +# RFC 7919 ffdhe4096 bit group +FFDHE4096 = (2, + int("FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004" + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + "A907600A918130C46DC778F971AD0038092999A333CB8B7A" + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E655F6A" + "FFFFFFFFFFFFFFFF", 16)) +goodGroupParameters.append(FFDHE4096) +RFC7919_GROUPS.append(FFDHE4096) + +# RFC 7919 ffdhe6144 bit group +FFDHE6144 = (2, + int("FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004" + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + "A907600A918130C46DC778F971AD0038092999A333CB8B7A" + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD902" + "0BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA6" + "3BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3A" + "CDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477" + "A52471F7A9A96910B855322EDB6340D8A00EF092350511E3" + "0ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4" + "763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6" + "B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C" + "D72B03746AE77F5E62292C311562A846505DC82DB854338A" + "E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04" + "5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1" + "A41D570D7938DAD4A40E329CD0E40E65FFFFFFFFFFFFFFFF", 16)) +goodGroupParameters.append(FFDHE6144) +RFC7919_GROUPS.append(FFDHE6144) + +# RFC 7919 ffdhe8192 bit group +FFDHE8192 = (2, + int("FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1" + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561" + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735" + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19" + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73" + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238" + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3" + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF" + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004" + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + "A907600A918130C46DC778F971AD0038092999A333CB8B7A" + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD902" + "0BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA6" + "3BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3A" + "CDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477" + "A52471F7A9A96910B855322EDB6340D8A00EF092350511E3" + "0ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4" + "763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6" + "B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C" + "D72B03746AE77F5E62292C311562A846505DC82DB854338A" + "E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04" + "5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1" + "A41D570D7938DAD4A40E329CCFF46AAA36AD004CF600C838" + "1E425A31D951AE64FDB23FCEC9509D43687FEB69EDD1CC5E" + "0B8CC3BDF64B10EF86B63142A3AB8829555B2F747C932665" + "CB2C0F1CC01BD70229388839D2AF05E454504AC78B758282" + "2846C0BA35C35F5C59160CC046FD8251541FC68C9C86B022" + "BB7099876A460E7451A8A93109703FEE1C217E6C3826E52C" + "51AA691E0E423CFC99E9E31650C1217B624816CDAD9A95F9" + "D5B8019488D9C0A0A1FE3075A577E23183F81D4A3F2FA457" + "1EFC8CE0BA8A4FE8B6855DFE72B0A66EDED2FBABFBE58A30" + "FAFABE1C5D71A87E2F741EF8C1FE86FEA6BBFDE530677F0D" + "97D11D49F7A8443D0822E506A9F4614E011E2A94838FF88C" + "D68C8BB7C5C6424CFFFFFFFFFFFFFFFF", 16)) +goodGroupParameters.append(FFDHE8192) +RFC7919_GROUPS.append(FFDHE8192) + def paramStrength(param): """ From 93e336be230f31d8b4318e0f111d1a7986fdaef4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 3 Nov 2016 19:24:35 +0100 Subject: [PATCH 393/574] support for ffdhe in keyexchange --- tlslite/constants.py | 2 +- tlslite/keyexchange.py | 20 +++++-- unit_tests/test_tlslite_keyexchange.py | 83 ++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 5 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 6209b313..8235f609 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -193,7 +193,7 @@ class GroupName(TLSEnum): brainpoolP512r1 = 28 allEC.extend(list(range(26, 29))) - # RFC-ietf-tls-negotiated-ff-dhe-10 + # RFC7919 ffdhe2048 = 256 ffdhe3072 = 257 ffdhe4096 = 258 diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index e7dd9f9b..577e9a38 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -5,7 +5,7 @@ """Handling of cryptographic operations for key exchange""" from .mathtls import goodGroupParameters, makeK, makeU, makeX, \ - calcMasterSecret, paramStrength + calcMasterSecret, paramStrength, RFC7919_GROUPS from .errors import TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ TLSIllegalParameterException, TLSDecryptionFailed, TLSInternalError from .messages import ServerKeyExchange, ClientKeyExchange, CertificateVerify @@ -254,7 +254,7 @@ class ADHKeyExchange(KeyExchange): """ def __init__(self, cipherSuite, clientHello, serverHello, - dhParams=None): + dhParams=None, dhGroups=None): super(ADHKeyExchange, self).__init__(cipherSuite, clientHello, serverHello) #pylint: enable = invalid-name @@ -265,11 +265,22 @@ def __init__(self, cipherSuite, clientHello, serverHello, else: # 2048-bit MODP Group (RFC 5054, group 3) self.dh_g, self.dh_p = goodGroupParameters[2] + self.dhGroups = dhGroups def makeServerKeyExchange(self): """ Prepare server side of anonymous key exchange with selected parameters """ + # Check for RFC 7919 support + ext = self.clientHello.getExtension(ExtensionType.supported_groups) + if ext and self.dhGroups: + commonGroup = getFirstMatching(ext.groups, self.dhGroups) + if commonGroup: + self.dh_g, self.dh_p = RFC7919_GROUPS[commonGroup - 256] + elif getFirstMatching(ext.groups, range(256, 512)): + raise TLSInternalError("DHE key exchange attempted despite no " + "overlap between supported groups") + # Per RFC 3526, Section 1, the exponent should have double the entropy # of the strength of the group. randBytesNeeded = divceil(paramStrength(self.dh_p) * 2, 8) @@ -339,7 +350,7 @@ class DHE_RSAKeyExchange(AuthenticatedKeyExchange, ADHKeyExchange): """ def __init__(self, cipherSuite, clientHello, serverHello, privateKey, - dhParams=None): + dhParams=None, dhGroups=None): """ Create helper object for Diffie-Hellamn key exchange. @@ -350,7 +361,8 @@ def __init__(self, cipherSuite, clientHello, serverHello, privateKey, @type dhParams: 2-element tuple of int """ super(DHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, - serverHello, dhParams) + serverHello, dhParams, + dhGroups) #pylint: enable = invalid-name self.privateKey = privateKey diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index a344ec23..ec361f75 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -622,6 +622,89 @@ def test_DHE_RSA_key_exchange_with_custom_parameters(self): self.assertEqual(client_premaster, server_premaster) + + def test_DHE_RSA_key_exchange_with_rfc7919_groups(self): + suppGroupsExt = SupportedGroupsExtension().create([GroupName.ffdhe3072, + GroupName.ffdhe4096] + ) + self.client_hello.extensions = [suppGroupsExt] + self.keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key, + dhGroups=GroupName.allFF) + + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None,) + client_premaster = client_keyExchange.processServerKeyExchange( + None, + srv_key_ex) + # because the agreed upon secret can be any value between 1 and p-1, + # we can't check the exact length. At the same time, short shared + # secrets are exceedingly rare, a share shorter by 4 bytes will + # happen only once in 256^4 negotiations or 1 in about 4 milliards + self.assertLessEqual(len(client_premaster), 3072 // 8) + self.assertGreaterEqual(len(client_premaster), 3072 // 8 - 4) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange( + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) + + + def test_DHE_RSA_key_exchange_with_ECC_groups(self): + suppGroupsExt = SupportedGroupsExtension().create([GroupName.secp256r1, + GroupName.secp521r1, + 650] + ) + self.client_hello.extensions = [suppGroupsExt] + self.keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key, + dhGroups=GroupName.allFF) + + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None,) + client_premaster = client_keyExchange.processServerKeyExchange( + None, + srv_key_ex) + # because the agreed upon secret can be any value between 1 and p-1, + # we can't check the exact length. At the same time, short shared + # secrets are exceedingly rare, a share shorter by 4 bytes will + # happen only once in 256^4 negotiations or 1 in about 4 milliards + self.assertLessEqual(len(client_premaster), 2048 // 8) + self.assertGreaterEqual(len(client_premaster), 2048 // 8 - 4) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange( + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) + + + def test_DHE_RSA_key_exchange_with_unknown_ffdhe_group(self): + suppGroupsExt = SupportedGroupsExtension().create([511]) + self.client_hello.extensions = [suppGroupsExt] + self.keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key, + dhGroups=GroupName.allFF) + + with self.assertRaises(TLSInternalError): + self.keyExchange.makeServerKeyExchange('sha1') + + def test_DHE_RSA_key_exchange_with_invalid_client_key_share(self): clientKeyExchange = ClientKeyExchange(self.cipher_suite, (3, 3)) From c493b2e5cfd70ec585532703d39a5fb80d396f79 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 3 Nov 2016 19:01:09 +0100 Subject: [PATCH 394/574] add ffdhe groups in handshakesettings --- tlslite/handshakesettings.py | 10 ++++++++++ unit_tests/test_tlslite_handshakesettings.py | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 317d7242..2e71907c 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -32,6 +32,8 @@ ALL_CURVE_NAMES = CURVE_NAMES + ["secp256k1"] if ecdsaAllCurves: ALL_CURVE_NAMES += ["secp224r1", "secp192r1"] +ALL_DH_GROUP_NAMES = ["ffdhe2048", "ffdhe3072", "ffdhe4096", "ffdhe6144", + "ffdhe8192"] class HandshakeSettings(object): """This class encapsulates various parameters that can be used with @@ -158,6 +160,7 @@ def __init__(self): self.useExtendedMasterSecret = True self.requireExtendedMasterSecret = False self.dhParams = None + self.dhGroups = list(ALL_DH_GROUP_NAMES) @staticmethod def _sanityCheckKeySizes(other): @@ -213,6 +216,12 @@ def _sanityCheckPrimitivesNames(other): raise ValueError("Unknown RSA signature hash: '{0}'".\ format(unknownSigHash)) + unknownDHGroup = [val for val in other.dhGroups + if val not in ALL_DH_GROUP_NAMES] + if unknownDHGroup: + raise ValueError("Unknown FFDHE group name: '{0}'" + .format(unknownDHGroup)) + @staticmethod def _sanityCheckProtocolVersions(other): """Check if set protocol version are sane""" @@ -269,6 +278,7 @@ def validate(self): other.useExtendedMasterSecret = self.useExtendedMasterSecret other.requireExtendedMasterSecret = self.requireExtendedMasterSecret other.dhParams = self.dhParams + other.dhGroups = self.dhGroups if not cipherfactory.tripleDESPresent: other.cipherNames = [i for i in self.cipherNames if i != "3des"] diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index 2b2471e1..d8a6b19b 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -266,5 +266,11 @@ def test_invalid_dhParams(self): with self.assertRaises(ValueError): hs.validate() + def test_invalid_dhGroups(self): + hs = HandshakeSettings() + hs.dhGroups = ["ffdhe2048", "ffdhe1024"] + with self.assertRaises(ValueError): + hs.validate() + if __name__ == '__main__': unittest.main() From c3e38249bcc88bcb57e6bdd319dd1528f7eb9aa6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 3 Nov 2016 19:10:55 +0100 Subject: [PATCH 395/574] server side of FFDHE negotiation (RFC7919) --- tlslite/tlsconnection.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 573561fc..440f03a8 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1358,11 +1358,13 @@ def _handshakeServerAsyncHelper(self, verifierDB, serverHello, privateKey) elif cipherSuite in CipherSuite.dheCertSuites: + dhGroups = self._groupNamesToList(settings) keyExchange = DHE_RSAKeyExchange(cipherSuite, clientHello, serverHello, privateKey, - settings.dhParams) + settings.dhParams, + dhGroups) elif cipherSuite in CipherSuite.ecdheCertSuites: acceptedCurves = self._curveNamesToList(settings) keyExchange = ECDHE_RSAKeyExchange(cipherSuite, @@ -1384,8 +1386,10 @@ def _handshakeServerAsyncHelper(self, verifierDB, elif (cipherSuite in CipherSuite.anonSuites or cipherSuite in CipherSuite.ecdhAnonSuites): if cipherSuite in CipherSuite.anonSuites: + dhGroups = self._groupNamesToList(settings) keyExchange = ADHKeyExchange(cipherSuite, clientHello, - serverHello) + serverHello, settings.dhParams, + dhGroups) else: acceptedCurves = self._curveNamesToList(settings) keyExchange = AECDHKeyExchange(cipherSuite, clientHello, @@ -1487,12 +1491,26 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, #server client_groups = clientHello.getExtension(ExtensionType.supported_groups) group_intersect = [] + # if there is no extension, then allow DHE + ffgroup_intersect = [GroupName.ffdhe2048] if client_groups is not None: client_groups = client_groups.groups if client_groups is None: client_groups = [] server_groups = self._curveNamesToList(settings) group_intersect = [x for x in client_groups if x in server_groups] + # RFC 7919 groups + server_groups = self._groupNamesToList(settings) + ffgroup_intersect = [i for i in client_groups + if i in server_groups] + # if there is no overlap, but there are no FFDHE groups listed, + # allow DHE, prohibit otherwise + if not ffgroup_intersect: + if any(i for i in client_groups if i in range(256, 512)): + ffgroup_intersect = [] + else: + ffgroup_intersect = [GroupName.ffdhe2048] + #Now that the version is known, limit to only the ciphers available to #that version and client capabilities. @@ -1506,7 +1524,9 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, if group_intersect: cipherSuites += CipherSuite.getEcdheCertSuites(settings, self.version) - cipherSuites += CipherSuite.getDheCertSuites(settings, self.version) + if ffgroup_intersect: + cipherSuites += CipherSuite.getDheCertSuites(settings, + self.version) cipherSuites += CipherSuite.getCertSuites(settings, self.version) elif anon: cipherSuites += CipherSuite.getAnonSuites(settings, self.version) @@ -2064,3 +2084,8 @@ def _sigHashesToList(settings): def _curveNamesToList(settings): """Convert list of acceptable curves to array identifiers""" return [getattr(GroupName, val) for val in settings.eccCurves] + + @staticmethod + def _groupNamesToList(settings): + """Convert list of acceptable ff groups to TLS identifiers.""" + return [getattr(GroupName, val) for val in settings.dhGroups] From 00b30f4a244c9c1192ee4736857ff78fdde4a4da Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 3 Nov 2016 19:39:12 +0100 Subject: [PATCH 396/574] add ffdhe support to client side --- tlslite/tlsconnection.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 440f03a8..eeaea6e7 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -625,13 +625,20 @@ def _clientSendClientHello(self, settings, session, srpUsername, extensions.append(TLSExtension().create(ExtensionType. extended_master_secret, bytearray(0))) + groups = [] #Send the ECC extensions only if we advertise ECC ciphers if next((cipher for cipher in cipherSuites \ if cipher in CipherSuite.ecdhAllSuites), None) is not None: - extensions.append(SupportedGroupsExtension().\ - create(self._curveNamesToList(settings))) + groups.extend(self._curveNamesToList(settings)) extensions.append(ECPointFormatsExtension().\ create([ECPointFormat.uncompressed])) + # Advertise FFDHE groups if we have DHE ciphers + if next((cipher for cipher in cipherSuites + if cipher in CipherSuite.dhAllSuites), None) is not None: + groups.extend(self._groupNamesToList(settings)) + # Send the extension only if it will be non empty + if groups: + extensions.append(SupportedGroupsExtension().create(groups)) # In TLS1.2 advertise support for additional signature types if settings.maxVersion >= (3, 3): sigList = self._sigHashesToList(settings) From 0c3c575add427ac96fdf3ed0022f265b97f1e900 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Nov 2016 12:54:43 +0100 Subject: [PATCH 397/574] correct alert for no shared ffdhe groups rfc 7919 mandates that in case the client advertised any ffdhe groups (including unrecognised ones), if server can't find a fallback cipher, it needs to fail connection with insufficient_security alert --- tlslite/tlsconnection.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index eeaea6e7..39d03b79 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1681,10 +1681,20 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, if cipherSuite in clientHello.cipher_suites: break else: - for result in self._sendError(\ - AlertDescription.handshake_failure, - "No mutual ciphersuite"): - yield result + if client_groups and \ + any(i in range(256, 512) for i in client_groups) and \ + any(i in CipherSuite.dhAllSuites + for i in clientHello.cipher_suites): + for result in self._sendError( + AlertDescription.insufficient_security, + "FFDHE groups not acceptable and no other common " + "ciphers"): + yield result + else: + for result in self._sendError(\ + AlertDescription.handshake_failure, + "No mutual ciphersuite"): + yield result if cipherSuite in CipherSuite.srpAllSuites and \ not clientHello.srp_username: for result in self._sendError(\ From a7d3128b5092f073bd5c12cb837d17332c97fc8f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 11 Nov 2016 19:30:14 +0100 Subject: [PATCH 398/574] fix docstring typo --- tlslite/utils/codec.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tlslite/utils/codec.py b/tlslite/utils/codec.py index a74fcb45..4b70e5d9 100644 --- a/tlslite/utils/codec.py +++ b/tlslite/utils/codec.py @@ -29,7 +29,7 @@ def addTwo(self, val): self.bytes += pack('>H', val) def addThree(self, val): - """Add a thee-byte wide element to buffer, see add().""" + """Add a three-byte wide element to buffer, see add().""" if not 0 <= val <= 0xffffff: raise ValueError("Can't represent value in specified length") self.bytes += pack('>BH', val >> 16, val & 0xffff) @@ -48,7 +48,7 @@ def addTwo(self, val): raise ValueError("Can't represent value in specified length") def addThree(self, val): - """Add a thee-byte wide element to buffer, see add().""" + """Add a three-byte wide element to buffer, see add().""" try: self.bytes += pack('>BH', val >> 16, val & 0xffff) except struct.error: From d69c210826ede718a605271a384377b47ed02777 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 18 Nov 2016 15:31:25 +0100 Subject: [PATCH 399/574] mark as 0.7.0-alpha2 --- README.md | 5 ++++- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 185b0369..745f82a6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.7.0-alpha1 2016-10-11 +tlslite-ng version 0.7.0-alpha2 2016-11-18 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -588,6 +588,9 @@ encrypt-then-MAC mode for CBC ciphers. * basic support for RSA-PSS (Tomas Foukal) * better documentation for Parser and ASN1Parser +* stricter checks on network messages +* faster Codec +* faster AES implementation initialization 0.6.0 - 2016-09-07 diff --git a/setup.py b/setup.py index 8b85c2fe..6b754936 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.7.0-alpha1", + version="0.7.0-alpha2", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 5c4f1b03..1fcb44f2 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.7.0-alpha1 +@version: 0.7.0-alpha2 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index aa27ca3f..873dd4ac 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.7.0-alpha1" +__version__ = "0.7.0-alpha2" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From f27245c36c18f034763104924e713d9cf07420ba Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 25 Nov 2016 18:31:22 +0100 Subject: [PATCH 400/574] post-merge README update --- README | 1 + README.md | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README b/README index 6ee377a9..d258c582 100644 --- a/README +++ b/README @@ -29,6 +29,7 @@ Functionality implemented include: - Keying material exporter (RFC 5705) - Next Protocol Negotiation - Application-Layer Protocol Negotiation Extension (RFC 7301) + - FFDHE prime/group negotiation (RFC 7919) tlslite-ng aims to be a drop-in replacement for tlslite while providing more diff --git a/README.md b/README.md index 745f82a6..08122937 100644 --- a/README.md +++ b/README.md @@ -589,8 +589,11 @@ encrypt-then-MAC mode for CBC ciphers. * basic support for RSA-PSS (Tomas Foukal) * better documentation for Parser and ASN1Parser * stricter checks on network messages -* faster Codec +* faster Codec (faster encoding of messages to binary format) * faster AES implementation initialization +* ability to set custom Diffie-Hellman parameters for connection +* support for negotiation of bigger Diffie-Hellman groups using RFC 7919 + mechanism 0.6.0 - 2016-09-07 From 6cc5905e9f29cbe03de0d81d944fccaa5bc6bd41 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 4 Jan 2017 18:29:07 +0100 Subject: [PATCH 401/574] fix DHE interoperability issue with NSS Because NSS zero-pads the key share in SKE message, the writeParams and thus hash calculation for the message won't match and the signature verification will fail this patch extends the message parser to store the length of the field together with value and recreate it on write --- tlslite/messages.py | 60 +++++++++++++++++++++++------ unit_tests/test_tlslite_messages.py | 50 ++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 12 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index f64ab1dd..5b28fc76 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1082,18 +1082,30 @@ class ServerKeyExchange(HandshakeMsg): @cvar cipherSuite: id of ciphersuite selected in Server Hello message @type srp_N: int @cvar srp_N: SRP protocol prime + @type srp_N_len: int + @cvar srp_N_len: length of srp_N in bytes @type srp_g: int @cvar srp_g: SRP protocol generator + @type srp_g_len: int + @cvar srp_g_len: length of srp_g in bytes @type srp_s: bytearray @cvar srp_s: SRP protocol salt value @type srp_B: int @cvar srp_B: SRP protocol server public value + @type srp_B_len: int + @cvar srp_B_len: length of srp_B in bytes @type dh_p: int @cvar dh_p: FFDHE protocol prime + @type dh_p_len: int + @cvar dh_p_len: length of dh_p in bytes @type dh_g: int @cvar dh_g: FFDHE protocol generator + @type dh_g_len: int + @type dh_g_len: length of dh_g in bytes @type dh_Ys: int @cvar dh_Ys: FFDH protocol server key share + @type dh_Ys_len: int + @cvar dh_Ys_len: length of dh_Ys in bytes @type curve_type: int @cvar curve_type: Type of curve used (explicit, named, etc.) @type named_curve: int @@ -1119,13 +1131,19 @@ def __init__(self, cipherSuite, version): self.cipherSuite = cipherSuite self.version = version self.srp_N = 0 + self.srp_N_len = None self.srp_g = 0 + self.srp_g_len = None self.srp_s = bytearray(0) self.srp_B = 0 + self.srp_B_len = None # Anon DH params: self.dh_p = 0 + self.dh_p_len = None self.dh_g = 0 + self.dh_g_len = None self.dh_Ys = 0 + self.dh_Ys_len = None # EC settings self.curve_type = None self.named_curve = None @@ -1158,16 +1176,22 @@ def __repr__(self): def createSRP(self, srp_N, srp_g, srp_s, srp_B): """Set SRP protocol parameters.""" self.srp_N = srp_N + self.srp_N_len = None self.srp_g = srp_g + self.srp_g_len = None self.srp_s = srp_s self.srp_B = srp_B + self.srp_B_len = None return self def createDH(self, dh_p, dh_g, dh_Ys): """Set FFDH protocol parameters.""" self.dh_p = dh_p + self.dh_p_len = None self.dh_g = dh_g + self.dh_g_len = None self.dh_Ys = dh_Ys + self.dh_Ys_len = None return self def createECDH(self, curve_type, named_curve=None, point=None): @@ -1185,14 +1209,20 @@ def parse(self, parser): """ parser.startLengthCheck(3) if self.cipherSuite in CipherSuite.srpAllSuites: - self.srp_N = bytesToNumber(parser.getVarBytes(2)) - self.srp_g = bytesToNumber(parser.getVarBytes(2)) + self.srp_N_len = parser.get(2) + self.srp_N = bytesToNumber(parser.getFixBytes(self.srp_N_len)) + self.srp_g_len = parser.get(2) + self.srp_g = bytesToNumber(parser.getFixBytes(self.srp_g_len)) self.srp_s = parser.getVarBytes(1) - self.srp_B = bytesToNumber(parser.getVarBytes(2)) + self.srp_B_len = parser.get(2) + self.srp_B = bytesToNumber(parser.getFixBytes(self.srp_B_len)) elif self.cipherSuite in CipherSuite.dhAllSuites: - self.dh_p = bytesToNumber(parser.getVarBytes(2)) - self.dh_g = bytesToNumber(parser.getVarBytes(2)) - self.dh_Ys = bytesToNumber(parser.getVarBytes(2)) + self.dh_p_len = parser.get(2) + self.dh_p = bytesToNumber(parser.getFixBytes(self.dh_p_len)) + self.dh_g_len = parser.get(2) + self.dh_g = bytesToNumber(parser.getFixBytes(self.dh_g_len)) + self.dh_Ys_len = parser.get(2) + self.dh_Ys = bytesToNumber(parser.getFixBytes(self.dh_Ys_len)) elif self.cipherSuite in CipherSuite.ecdhAllSuites: self.curve_type = parser.get(1) # only named curves supported @@ -1219,14 +1249,20 @@ def writeParams(self): """ writer = Writer() if self.cipherSuite in CipherSuite.srpAllSuites: - writer.addVarSeq(numberToByteArray(self.srp_N), 1, 2) - writer.addVarSeq(numberToByteArray(self.srp_g), 1, 2) + writer.addVarSeq(numberToByteArray(self.srp_N, self.srp_N_len), + 1, 2) + writer.addVarSeq(numberToByteArray(self.srp_g, self.srp_g_len), + 1, 2) writer.addVarSeq(self.srp_s, 1, 1) - writer.addVarSeq(numberToByteArray(self.srp_B), 1, 2) + writer.addVarSeq(numberToByteArray(self.srp_B, self.srp_B_len), + 1, 2) elif self.cipherSuite in CipherSuite.dhAllSuites: - writer.addVarSeq(numberToByteArray(self.dh_p), 1, 2) - writer.addVarSeq(numberToByteArray(self.dh_g), 1, 2) - writer.addVarSeq(numberToByteArray(self.dh_Ys), 1, 2) + writer.addVarSeq(numberToByteArray(self.dh_p, self.dh_p_len), + 1, 2) + writer.addVarSeq(numberToByteArray(self.dh_g, self.dh_g_len), + 1, 2) + writer.addVarSeq(numberToByteArray(self.dh_Ys, self.dh_Ys_len), + 1, 2) elif self.cipherSuite in CipherSuite.ecdhAllSuites: writer.add(self.curve_type, 1) assert self.curve_type == 3 diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 01852421..9255a6fa 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1965,6 +1965,33 @@ def test_parse_with_SRP(self): self.assertEqual(ske.srp_s, bytearray(3)) self.assertEqual(ske.srp_B, 4) + def test_parse_and_write_with_zero_padded_SRP(self): + ske = ServerKeyExchange(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA, + (3, 3)) + + msg = bytearray( + b'\x00\x00\x10' + # overall length + b'\x00\x02' + # N parameter length + b'\x00\x01' + # N value + b'\x00\x02' + # g parameter length + b'\x00\x02' + # g value + b'\x03' + # s parameter length + b'\x00'*3 + # s value + b'\x00\x02' + # B parameter length + b'\x00\x04' # B value + ) + + parser = Parser(msg) + + ske.parse(parser) + + self.assertEqual(ske.srp_N, 1) + self.assertEqual(ske.srp_g, 2) + self.assertEqual(ske.srp_s, bytearray(3)) + self.assertEqual(ske.srp_B, 4) + + self.assertEqual(ske.write()[1:], msg) + def test_parser_with_SRP_RSA(self): ske = ServerKeyExchange( CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, @@ -2042,6 +2069,29 @@ def test_parser_with_DH(self): self.assertEqual(ske.dh_g, 2) self.assertEqual(ske.dh_Ys, 16) + def test_parser_read_write_with_zero_padded_DH(self): + ske = ServerKeyExchange(CipherSuite.TLS_DH_ANON_WITH_AES_128_CBC_SHA, + (3, 3)) + msg = bytearray( + b'\x00\x00\x0c' + # overall length + b'\x00\x02' + # p parameter length + b'\x00\x1f' + # p value + b'\x00\x02' + # g parameter length + b'\x00\x02' + # g value + b'\x00\x02' + # Ys parameter length + b'\x00\x10' # Ys value + ) + + parser = Parser(msg) + + ske.parse(parser) + + self.assertEqual(ske.dh_p, 31) + self.assertEqual(ske.dh_g, 2) + self.assertEqual(ske.dh_Ys, 16) + + self.assertEqual(ske.write()[1:], msg) + def test_parser_with_ECDH(self): ske = ServerKeyExchange(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, (3, 3)) From cc2fa123ada330b3dc5e6692be23575cbbb71443 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 25 Jan 2017 13:20:50 +0100 Subject: [PATCH 402/574] send correct alerts in case the ALPN ext is malformed since the extension is specified as: opaque ProtocolName<1..2^8-1>; struct { ProtocolName protocol_name_list<2..2^16-1> } ProtocolNameList; empty list or empty protocol name is a protocol violation, as such, it should cause sending a decode_error alert --- tlslite/tlsconnection.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index f291ed52..4f9b2a8c 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1496,10 +1496,15 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, # Sanity check the ALPN extension alpnExt = clientHello.getExtension(ExtensionType.alpn) if alpnExt: + if not alpnExt.protocol_names: + for result in self._sendError( + AlertDescription.decode_error, + "Client sent empty list of ALPN names"): + yield result for protocolName in alpnExt.protocol_names: if not protocolName: for result in self._sendError( - AlertDescription.illegal_parameter, + AlertDescription.decode_error, "Client sent empty name in ALPN extension"): yield result From 73f615ed819593ba313db10acaa4546bc50a0828 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 25 Jan 2017 18:32:47 +0100 Subject: [PATCH 403/574] release 0.7.0-alpha3 --- README.md | 2 +- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 08122937..f85d1abe 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.7.0-alpha2 2016-11-18 +tlslite-ng version 0.7.0-alpha3 2017-01-25 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` diff --git a/setup.py b/setup.py index 6b754936..0c6ccc8c 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.7.0-alpha2", + version="0.7.0-alpha3", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 1fcb44f2..1fdfef26 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.7.0-alpha2 +@version: 0.7.0-alpha3 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 98f23ae0..8cbef99a 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.7.0-alpha2" +__version__ = "0.7.0-alpha3" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 34f3bc96955ccee92fb42fee245425a4a5787ec8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 19 Apr 2017 11:40:16 +0200 Subject: [PATCH 404/574] add a big composite number to test the non-working of M-R primality test --- unit_tests/test_tlslite_utils_cryptomath.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index b5f59a01..28108f9f 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -61,6 +61,17 @@ def test_with_hard_primes_to_test(self): self.assertFalse(isPrime(1543267864443420616877677640751301)) # base 19 self.assertFalse(isPrime(1543267864443420616877677640751301)) + # F. Arnault "Constructing Carmichael Numbers Which Are Strong + # Pseudoprimes to Several Bases". Journal of Symbolic + # Computation. 20 (2): 151-161. doi:10.1006/jsco.1995.1042. + # Section 4.4 Large Example (a pseudoprime to all bases up to + # 300) + p = int("29 674 495 668 685 510 550 154 174 642 905 332 730 " + "771 991 799 853 043 350 995 075 531 276 838 753 171 " + "770 199 594 238 596 428 121 188 033 664 754 218 345 " + "562 493 168 782 883".replace(" ", "")) + self.assertTrue(isPrime(p)) + self.assertFalse(p * (313 * (p - 1) + 1) * (353 * (p - 1) + 1)) def test_with_big_primes(self): # NextPrime[2^256] From 5a23c98f6e74fbe1b8f615f7a7e0ab3afed45006 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 24 Apr 2017 19:51:06 +0200 Subject: [PATCH 405/574] remove correct file on Windows the DBM module on Windows creates databases as .dat files by default (even if the original filename did not include it) so retry deleting alternative name if the original one failed --- tests/tlstest.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index aea82a93..3d376afc 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -876,7 +876,12 @@ def connect(): testConnServer(connection) connection.close() finally: - os.remove(db_name) + try: + os.remove(db_name) + except FileNotFoundError: + # dbm module may create files with different names depending on + # platform + os.remove(db_name + ".dat") test_no += 1 From 6dca3868217c1a076def4a685971409387c02eea Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 26 Apr 2017 13:30:00 +0200 Subject: [PATCH 406/574] move the file database test before the memory test for SRP --- tests/tlstest.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index 3d376afc..e0063b96 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -195,7 +195,7 @@ def connect(): test_no += 1 - print("Test {0} - good SRP".format(test_no)) + print("Test {0} - good SRP (db)".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientSRP("test", "password") @@ -204,7 +204,7 @@ def connect(): test_no += 1 - print("Test {0} - good SRP (db)".format(test_no)) + print("Test {0} - good SRP".format(test_no)) synchro.recv(1) connection = connect() connection.handshakeClientSRP("test", "password") @@ -845,20 +845,6 @@ def connect(): test_no += 1 - print("Test {0} - good SRP".format(test_no)) - verifierDB = VerifierDB() - verifierDB.create() - entry = VerifierDB.makeVerifier("test", "password", 1536) - verifierDB[b"test"] = entry - - synchro.send(b'R') - connection = connect() - connection.handshakeServer(verifierDB=verifierDB) - testConnServer(connection) - connection.close() - - test_no += 1 - print("Test {0} - good SRP (db)".format(test_no)) try: (db_file, db_name) = mkstemp() @@ -885,6 +871,20 @@ def connect(): test_no += 1 + print("Test {0} - good SRP".format(test_no)) + verifierDB = VerifierDB() + verifierDB.create() + entry = VerifierDB.makeVerifier("test", "password", 1536) + verifierDB[b"test"] = entry + + synchro.send(b'R') + connection = connect() + connection.handshakeServer(verifierDB=verifierDB) + testConnServer(connection) + connection.close() + + test_no += 1 + print("Test {0} - SRP faults".format(test_no)) for fault in Fault.clientSrpFaults + Fault.genericFaults: synchro.send(b'R') From dabab445f2aa3f07bae35ce139b555a229b1dd99 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 25 Jan 2017 19:53:35 +0100 Subject: [PATCH 407/574] send valid hostname in padding check --- unit_tests/test_tlslite_tlsconnection.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index 0d376ba4..39a802eb 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -214,15 +214,15 @@ def test_padding_extension_with_hello_over_256(self): conn = TLSConnection(sock) # create hostname extension with self.assertRaises(TLSRemoteAlert): - # use serverName with 254 bytes + # use serverName with 252 bytes conn.handshakeClientCert( - serverName='aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd' + - 'eeeeeeeeeeffffffffffgggggggggghhhhhhhhhh' + - 'iiiiiiiiiijjjjjjjjjjkkkkkkkkkkllllllllll' + - 'mmmmmmmmmmnnnnnnnnnnoooooooooopppppppppp' + - 'qqqqqqqqqqrrrrrrrrrrsssssssssstttttttttt' + - 'uuuuuuuuuuvvvvvvvvvvwwwwwwwwwwxxxxxxxxxx' + - 'yyyyyyyyyy.com') + serverName='aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd.' + + 'eeeeeeeeeeffffffffffgggggggggghhhhhhhhhh.' + + 'iiiiiiiiiijjjjjjjjjjkkkkkkkkkkllllllllll.' + + 'mmmmmmmmmmnnnnnnnnnnoooooooooopppppppppp.' + + 'qqqqqqqqqqrrrrrrrrrrsssssssssstttttttttt.' + + 'uuuuuuuuuuvvvvvvvvvvwwwwwwwwwwxxxxxxxxxx.' + + 'y.com') self.assertEqual(len(sock.sent), 1) # check for version and content type (handshake) From 1bc2b16ddee4346fed416fcc4cc2e3e85fd9cfe5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 4 May 2017 17:58:00 +0200 Subject: [PATCH 408/574] add checks for names received in SNI ext --- tlslite/integration/clienthelper.py | 9 ++++- tlslite/tlsconnection.py | 23 ++++++++++++ tlslite/utils/dns_utils.py | 41 ++++++++++++++++++++++ unit_tests/test_tlslite_utils_dns_utils.py | 40 +++++++++++++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 tlslite/utils/dns_utils.py create mode 100644 unit_tests/test_tlslite_utils_dns_utils.py diff --git a/tlslite/integration/clienthelper.py b/tlslite/integration/clienthelper.py index aeaafa89..b329ce16 100644 --- a/tlslite/integration/clienthelper.py +++ b/tlslite/integration/clienthelper.py @@ -10,6 +10,7 @@ """ from tlslite.checker import Checker +from tlslite.utils.dns_utils import is_valid_hostname class ClientHelper(object): """This is a helper class used to integrate TLS Lite with various @@ -103,8 +104,14 @@ def __init__(self, self.tlsSession = None - if not self._isIP(host): + if host is not None and not self._isIP(host): + # name for SNI so port can't be sent + colon = host.find(':') + if colon > 0: + host = host[:colon] self.serverName = host + if host and not is_valid_hostname(host): + raise ValueError("Invalid hostname: {0}".format(host)) else: self.serverName = None diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index f291ed52..0efe74e7 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -22,6 +22,7 @@ from .session import Session from .constants import * from .utils.cryptomath import getRandomBytes +from .utils.dns_utils import is_valid_hostname from .errors import * from .messages import * from .mathtls import * @@ -1503,6 +1504,28 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, "Client sent empty name in ALPN extension"): yield result + # Sanity check the SNI extension + sniExt = clientHello.getExtension(ExtensionType.server_name) + if sniExt and sniExt.hostNames: + # RFC 6066 limitation + if len(sniExt.hostNames) > 1: + for result in self._sendError( + AlertDescription.illegal_parameter, + "Client sent multiple host names in SNI extension"): + yield result + try: + name = sniExt.hostNames[0].decode('ascii', 'strict') + except UnicodeDecodeError: + for result in self._sendError( + AlertDescription.illegal_parameter, + "Host name in SNI is not valid ASCII"): + yield result + if not is_valid_hostname(name): + for result in self._sendError( + AlertDescription.illegal_parameter, + "Host name in SNI is not valid DNS name"): + yield result + #If client's version is too high, propose my highest version elif clientHello.client_version > settings.maxVersion: self.version = settings.maxVersion diff --git a/tlslite/utils/dns_utils.py b/tlslite/utils/dns_utils.py new file mode 100644 index 00000000..1ae8756e --- /dev/null +++ b/tlslite/utils/dns_utils.py @@ -0,0 +1,41 @@ +# Copyright (c) 2017 Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +"""Utilities for handling DNS hostnames""" + +import re + + +def is_valid_hostname(hostname): + """ + Check if the parameter is a valid hostname. + + @type hostname: str or bytearray + @rtype: boolean + """ + try: + if not isinstance(hostname, str): + hostname = hostname.decode('ascii', 'strict') + except UnicodeDecodeError: + return False + if hostname[-1] == ".": + # strip exactly one dot from the right, if present + hostname = hostname[:-1] + # the maximum length of the domain name is 255 bytes, but because they + # are encoded as labels (which is a length byte and an up to 63 character + # ascii string), you change the dots to the length bytes, but the + # host element of the FQDN doesn't start with a dot and the name doesn't + # end with a dot (specification of a root label), we need to subtract 2 + # bytes from the 255 byte maximum when looking at dot-deliminated FQDN + # with the trailing dot removed + # see RFC 1035 + if len(hostname) > 253: + return False + + # must not be all-numeric, so that it can't be confused with an ip-address + if re.match(r"[\d.]+$", hostname): + return False + + allowed = re.compile(r"(?!-)[A-Z\d-]{1,63}(? Date: Thu, 4 May 2017 17:33:26 +0200 Subject: [PATCH 409/574] add support for checking sent SNI --- scripts/tls.py | 7 ++++++- tlslite/tlsconnection.py | 26 +++++++++++++++++++------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/scripts/tls.py b/scripts/tls.py index d41c4d7d..3cc6a0b9 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -34,6 +34,7 @@ GroupName from tlslite import __version__ from tlslite.utils.compat import b2a_hex +from tlslite.utils.dns_utils import is_valid_hostname try: from tack.structures.Tack import Tack @@ -343,6 +344,9 @@ def serverCmd(argv): ############# sessionCache = SessionCache() username = None + sni = None + if is_valid_hostname(address[0]): + sni = address[0] class MyHTTPServer(ThreadingMixIn, TLSSocketServerMixIn, HTTPServer): def handshake(self, connection): @@ -368,7 +372,8 @@ def handshake(self, connection): settings=settings, nextProtos=[b"http/1.1"], alpn=[bytearray(b'http/1.1')], - reqCert=reqCert) + reqCert=reqCert, + sni=sni) # As an example (does not work here): #nextProtos=[b"spdy/3", b"spdy/2", b"http/1.1"]) stop = time.clock() diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 0efe74e7..98f304c2 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -425,6 +425,9 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, raise ValueError("Caller passed no nextProtos") if alpn is not None and not alpn: raise ValueError("Caller passed empty alpn list") + if serverName and not is_valid_hostname(serverName): + raise ValueError("Caller provided invalid server host name: {0}" + .format(serverName)) # Validates the settings and filters out any unsupported ciphers # or crypto libraries that were requested @@ -1083,7 +1086,7 @@ def handshakeServer(self, verifierDB=None, sessionCache=None, settings=None, checker=None, reqCAs = None, tacks=None, activationFlags=0, - nextProtos=None, anon=False, alpn=None): + nextProtos=None, anon=False, alpn=None, sni=None): """Perform a handshake in the role of server. This function performs an SSL or TLS handshake. Depending on @@ -1157,6 +1160,9 @@ def handshakeServer(self, verifierDB=None, Note that it will be used instead of NPN if both were advertised by client. + @type sni: bytearray + @param sni: expected virtual name hostname. + @raise socket.error: If a socket error occurs. @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed without a preceding alert. @@ -1168,7 +1174,7 @@ def handshakeServer(self, verifierDB=None, certChain, privateKey, reqCert, sessionCache, settings, checker, reqCAs, tacks=tacks, activationFlags=activationFlags, - nextProtos=nextProtos, anon=anon, alpn=alpn): + nextProtos=nextProtos, anon=anon, alpn=alpn, sni=sni): pass @@ -1177,7 +1183,7 @@ def handshakeServerAsync(self, verifierDB=None, sessionCache=None, settings=None, checker=None, reqCAs=None, tacks=None, activationFlags=0, - nextProtos=None, anon=False, alpn=None + nextProtos=None, anon=False, alpn=None, sni=None ): """Start a server handshake operation on the TLS connection. @@ -1196,7 +1202,7 @@ def handshakeServerAsync(self, verifierDB=None, sessionCache=sessionCache, settings=settings, reqCAs=reqCAs, tacks=tacks, activationFlags=activationFlags, - nextProtos=nextProtos, anon=anon, alpn=alpn) + nextProtos=nextProtos, anon=anon, alpn=alpn, sni=sni) for result in self._handshakeWrapperAsync(handshaker, checker): yield result @@ -1205,7 +1211,7 @@ def _handshakeServerAsyncHelper(self, verifierDB, certChain, privateKey, reqCert, sessionCache, settings, reqCAs, tacks, activationFlags, - nextProtos, anon, alpn): + nextProtos, anon, alpn, sni): self._handshakeStart(client=False) @@ -1239,7 +1245,7 @@ def _handshakeServerAsyncHelper(self, verifierDB, # Handle ClientHello and resumption for result in self._serverGetClientHello(settings, certChain, verifierDB, sessionCache, - anon, alpn): + anon, alpn, sni): if result in (0,1): yield result elif result == None: self._handshakeDone(resumed=True) @@ -1449,7 +1455,7 @@ def _handshakeServerAsyncHelper(self, verifierDB, def _serverGetClientHello(self, settings, certChain, verifierDB, - sessionCache, anon, alpn): + sessionCache, anon, alpn, sni): #Tentatively set version to most-desirable version, so if an error #occurs parsing the ClientHello, this is what we'll use for the #error alert @@ -1525,6 +1531,12 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, AlertDescription.illegal_parameter, "Host name in SNI is not valid DNS name"): yield result + # warn the client if the name didn't match the expected value + if sni and sni != name: + alert = Alert().create(AlertDescription.unrecognized_name, + AlertLevel.warning) + for result in self._sendMsg(alert): + yield result #If client's version is too high, propose my highest version elif clientHello.client_version > settings.maxVersion: From 0f81b567a40851aa82ac01405b536d00903513a0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 4 May 2017 17:27:50 +0200 Subject: [PATCH 410/574] add docs for anon and host in clienthelper --- tlslite/integration/clienthelper.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tlslite/integration/clienthelper.py b/tlslite/integration/clienthelper.py index b329ce16..7e886a1c 100644 --- a/tlslite/integration/clienthelper.py +++ b/tlslite/integration/clienthelper.py @@ -70,6 +70,16 @@ def __init__(self, @param settings: Various settings which can be used to control the ciphersuites, certificate types, and SSL/TLS versions offered by the client. + + @type anon: bool + @param anon: set to True if the negotiation should advertise only + anonymous TLS ciphersuites. Mutually exclusive with client certificate + authentication or SRP authentication + + @type host: str or None + @param host: the hostname that the connection is made to. Can be an + IP address (in which case the SNI extension won't be sent). Can + include the port (in which case the port will be stripped and ignored). """ self.username = None From e533af115b5bd37a62462e29178feb7d5909c82f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 May 2017 12:40:38 +0200 Subject: [PATCH 411/574] explain why the check is duplicate in tlsconnection --- tlslite/tlsconnection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 98f304c2..a4e8cde5 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -425,6 +425,7 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, raise ValueError("Caller passed no nextProtos") if alpn is not None and not alpn: raise ValueError("Caller passed empty alpn list") + # reject invalid hostnames but accept empty/None ones if serverName and not is_valid_hostname(serverName): raise ValueError("Caller provided invalid server host name: {0}" .format(serverName)) From 174a9d64b7b66d44a3ad5943edd9f15b31f9acd1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 May 2017 13:26:51 +0200 Subject: [PATCH 412/574] post-merge changelog update --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f85d1abe..2c2e4622 100644 --- a/README.md +++ b/README.md @@ -594,6 +594,12 @@ encrypt-then-MAC mode for CBC ciphers. * ability to set custom Diffie-Hellman parameters for connection * support for negotiation of bigger Diffie-Hellman groups using RFC 7919 mechanism +* fix sent alerts in case the ALPN extension is malformed +* add support for checking SNI on server side, making sure we send valid + hostnames in extension +* fix testsuite when run on Windows +* fix interoperability issue in DHE key exchange (failure happening in about + 1 in 256 negotiations) caused by handling of Server Key Exchange messages 0.6.0 - 2016-09-07 From fd670e37498f0e8fb9860164c2e54047d12528dd Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 5 May 2017 14:10:25 +0200 Subject: [PATCH 413/574] release 0.7.0-alpha4 --- README.md | 2 +- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2c2e4622..6640aa51 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.7.0-alpha3 2017-01-25 +tlslite-ng version 0.7.0-alpha4 2017-05-05 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` diff --git a/setup.py b/setup.py index 0c6ccc8c..5a4d63e6 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.7.0-alpha3", + version="0.7.0-alpha4", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 1fdfef26..6137f2ab 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.7.0-alpha3 +@version: 0.7.0-alpha4 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 8cbef99a..3fe34fcf 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.7.0-alpha3" +__version__ = "0.7.0-alpha4" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From c5d0b9aa22a632085be972ed1eb79309c99b5e28 Mon Sep 17 00:00:00 2001 From: morrme Date: Sun, 7 May 2017 14:33:35 -0500 Subject: [PATCH 414/574] add python 3.6 to .travis.yml Fixes: #163 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 8cc0825a..76ba185d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ python: - 3.3 - 3.4 - 3.5 + - 3.6 env: - TACKPY=true From c03c7b9c83fea7eb739b438914c29e59397b3eec Mon Sep 17 00:00:00 2001 From: morrme Date: Sun, 7 May 2017 14:36:38 -0500 Subject: [PATCH 415/574] Update .travis.yml --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 76ba185d..5bca9649 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,18 +42,24 @@ matrix: env: PYCRYPTO=true - python: 3.5 env: PYCRYPTO=true + - python: 3.6 + env: PYCRYPTO=true - python: 2.7 env: GMPY=true - python: 3.4 env: GMPY=true - python: 3.5 env: GMPY=true + - python: 3.6 + env: GMPY=true - python: 2.7 env: M2CRYPTO=true PYCRYPTO=true GMPY=true - python: 3.4 env: PYCRYPTO=true GMPY=true - python: 3.5 env: PYCRYPTO=true GMPY=true + - python: 3.6 + env: PYCRYPTO=true GMPY=true before_install: - | From 15daa209ce29b4cf36d615ca3ac345cdd2522dcf Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 7 Mar 2017 16:02:36 +0100 Subject: [PATCH 416/574] save state of Extended Master Secret for resumption --- tlslite/tlsconnection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 58520134..7a541c9d 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1444,6 +1444,7 @@ def _handshakeServerAsyncHelper(self, verifierDB, tackExt, (serverHello.tackExt is not None), serverName, encryptThenMAC=self._recordLayer.encryptThenMAC, + extendedMasterSecret=self.extendedMasterSecret, appProto=selectedALPN) #Add the session object to the session cache From 110f5deba465e9a6c530ec2ebae4376f41e96412 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 7 Mar 2017 16:04:00 +0100 Subject: [PATCH 417/574] don't resume without EMS if new connection asks for it if old session was negotiated without EMS, we need to create a completely new session for EMS to be useful --- tlslite/tlsconnection.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 7a541c9d..7380303a 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1647,12 +1647,19 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, for result in self._sendError(\ AlertDescription.handshake_failure): yield result + # if old session used EMS, new connection MUST use EMS if session.extendedMasterSecret and \ not clientHello.getExtension( ExtensionType.extended_master_secret): for result in self._sendError(\ AlertDescription.handshake_failure): yield result + # if old session didn't use EMS but new connection + # advertises EMS, create a new session + elif not session.extendedMasterSecret and \ + clientHello.getExtension( + ExtensionType.extended_master_secret): + session = None except KeyError: pass From bc763ce7816134cdc9df5f3b49e20cbbee5d5277 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 5 Apr 2017 15:26:29 +0200 Subject: [PATCH 418/574] fix EMS with client certificates when the Extended Master Secret extension is negotiated with client certificates, the used session hash for master secret calculation is not correct, fix it --- tlslite/tlsconnection.py | 27 ++++++++++++++++++++++----- tlslite/tlsrecordlayer.py | 5 +++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 7380303a..d1e87043 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -988,6 +988,11 @@ def _clientKeyExchange(self, settings, cipherSuite, for result in self._sendMsg(clientKeyExchange): yield result + # the Extended Master Secret calculation uses the same handshake + # hashes as the Certificate Verify calculation so we need to + # make a copy of it + self._certificate_verify_handshake_hash = self._handshake_hash.copy() + #if client auth was requested and we have a private key, send a #CertificateVerify if certificateRequest and privateKey: @@ -995,7 +1000,7 @@ def _clientKeyExchange(self, settings, cipherSuite, try: certificateVerify = KeyExchange.makeCertificateVerify( self.version, - self._handshake_hash, + self._certificate_verify_handshake_hash, validSigAlgs, privateKey, certificateRequest, @@ -1014,10 +1019,15 @@ def _clientKeyExchange(self, settings, cipherSuite, def _clientFinished(self, premasterSecret, clientRandom, serverRandom, cipherSuite, cipherImplementations, nextProto): if self.extendedMasterSecret: + cvhh = self._certificate_verify_handshake_hash + # in case of session resumption, or when the handshake doesn't + # use the certificate authentication, the hashes are the same + if not cvhh: + cvhh = self._handshake_hash masterSecret = calcExtendedMasterSecret(self.version, cipherSuite, premasterSecret, - self._handshake_hash) + cvhh) else: masterSecret = calcMasterSecret(self.version, cipherSuite, @@ -1934,8 +1944,8 @@ def _serverCertKeyExchange(self, clientHello, serverHello, yield result #Get and check CertificateVerify, if relevant + self._certificate_verify_handshake_hash = self._handshake_hash.copy() if clientCertChain: - handshakeHash = self._handshake_hash.copy() for result in self._getMsg(ContentType.handshake, HandshakeType.certificate_verify): if result in (0, 1): @@ -1952,8 +1962,9 @@ def _serverCertKeyExchange(self, clientHello, serverHello, yield result signatureAlgorithm = certificateVerify.signatureAlgorithm + cvhh = self._certificate_verify_handshake_hash verifyBytes = KeyExchange.calcVerifyBytes(self.version, - handshakeHash, + cvhh, signatureAlgorithm, premasterSecret, clientHello.random, @@ -2010,10 +2021,16 @@ def _serverAnonKeyExchange(self, serverHello, keyExchange, cipherSuite): def _serverFinished(self, premasterSecret, clientRandom, serverRandom, cipherSuite, cipherImplementations, nextProtos): if self.extendedMasterSecret: + cvhh = self._certificate_verify_handshake_hash + # in case of resumption or lack of certificate authentication, + # the CVHH won't be initialised, but then it would also be equal + # to regular handshake hash + if not cvhh: + cvhh = self._handshake_hash masterSecret = calcExtendedMasterSecret(self.version, cipherSuite, premasterSecret, - self._handshake_hash) + cvhh) else: masterSecret = calcMasterSecret(self.version, cipherSuite, diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index d3183e37..f1e077af 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -123,6 +123,10 @@ def __init__(self, sock): #Handshake digests self._handshake_hash = HandshakeHashes() + # Handshake digest used for Certificate Verify signature and + # also for EMS calculation, in practice, it excludes + # CertificateVerify and all following messages (Finished) + self._certificate_verify_handshake_hash = None #Is the connection open? self.closed = True #read-only @@ -893,6 +897,7 @@ def _handshakeStart(self, client): raise ValueError("Renegotiation disallowed for security reasons") self._client = client self._handshake_hash = HandshakeHashes() + self._certificate_verify_handshake_hash = None self._defragmenter.clearBuffers() self.allegedSrpUsername = None self._refCount = 1 From 7a029b6ed5c1130ac93a2c14571e384fa8b6deb8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 30 Jun 2017 12:43:44 +0200 Subject: [PATCH 419/574] update README after EMS fixes --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6640aa51..0388d3c8 100644 --- a/README.md +++ b/README.md @@ -600,6 +600,8 @@ encrypt-then-MAC mode for CBC ciphers. * fix testsuite when run on Windows * fix interoperability issue in DHE key exchange (failure happening in about 1 in 256 negotiations) caused by handling of Server Key Exchange messages +* Fix incorrect handling of Extended Master Secret with client certificates, + follow RFC recommendations with regards to session resumption. 0.6.0 - 2016-09-07 From b31304188ffeed8331becd9b63247a9715862c7c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 30 Jun 2017 12:46:10 +0200 Subject: [PATCH 420/574] add Python 3.6 to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 5a4d63e6..e7d31094 100755 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Topic :: Security :: Cryptography', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: System :: Networking' From cbf4ac97740953d645becb58dbd42ba03dac36f1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 30 Jun 2017 12:46:38 +0200 Subject: [PATCH 421/574] 0.7.0-alpha5 release --- README.md | 2 +- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0388d3c8..e4bd35fc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.7.0-alpha4 2017-05-05 +tlslite-ng version 0.7.0-alpha5 2017-06-30 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` diff --git a/setup.py b/setup.py index e7d31094..6754b6c2 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.7.0-alpha4", + version="0.7.0-alpha5", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 6137f2ab..5637431f 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.7.0-alpha4 +@version: 0.7.0-alpha5 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 3fe34fcf..7af0ca10 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.7.0-alpha4" +__version__ = "0.7.0-alpha5" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From f9a5d40c0afd84e00e1cdc741fca5d2bea045d11 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 2 Mar 2017 17:48:34 +0100 Subject: [PATCH 422/574] more usable API for RSA --- tlslite/utils/rsakey.py | 18 +++++++++++------- unit_tests/test_tlslite_utils_rsakey.py | 3 ++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/tlslite/utils/rsakey.py b/tlslite/utils/rsakey.py index a347b2ea..019fa578 100644 --- a/tlslite/utils/rsakey.py +++ b/tlslite/utils/rsakey.py @@ -50,7 +50,7 @@ def hasPrivateKey(self): """ raise NotImplementedError() - def hashAndSign(self, bytes, rsaScheme='PKCS1', hAlg='sha1', sLen=0): + def hashAndSign(self, bytes, rsaScheme='PKCS1', hAlg='SHA1', sLen=0): """Hash and sign the passed-in bytes. This requires the key to have a private component. It performs @@ -75,11 +75,13 @@ def hashAndSign(self, bytes, rsaScheme='PKCS1', hAlg='sha1', sLen=0): @rtype: L{bytearray} of unsigned bytes. @return: A PKCS1 or PSS signature on the passed-in data. """ - if rsaScheme == "PKCS1": + rsaScheme = rsaScheme.lower() + hAlg = hAlg.lower() + if rsaScheme == "pkcs1": hashBytes = secureHash(bytearray(bytes), hAlg) prefixedHashBytes = self.addPKCS1Prefix(hashBytes, hAlg) sigBytes = self.sign(prefixedHashBytes) - elif rsaScheme == "PSS": + elif rsaScheme == "pss": sigBytes = self.RSASSA_PSS_sign(bytearray(bytes), hAlg, sLen) else: raise UnknownRSAType("Unknown RSA algorithm type") @@ -113,21 +115,23 @@ def hashAndVerify(self, sigBytes, bytes, rsaScheme='PKCS1', hAlg='sha1', @rtype: bool @return: Whether the signature matches the passed-in data. """ - + rsaScheme = rsaScheme.lower() + hAlg = hAlg.lower() + # Try it with/without the embedded NULL - if rsaScheme == "PKCS1" and hAlg == 'sha1': + if rsaScheme == "pkcs1" and hAlg == 'sha1': hashBytes = secureHash(bytearray(bytes), hAlg) prefixedHashBytes1 = self.addPKCS1SHA1Prefix(hashBytes, False) prefixedHashBytes2 = self.addPKCS1SHA1Prefix(hashBytes, True) result1 = self.verify(sigBytes, prefixedHashBytes1) result2 = self.verify(sigBytes, prefixedHashBytes2) return (result1 or result2) - elif rsaScheme == 'PKCS1': + elif rsaScheme == 'pkcs1': hashBytes = secureHash(bytearray(bytes), hAlg) prefixedHashBytes = self.addPKCS1Prefix(hashBytes, hAlg) r = self.verify(sigBytes, prefixedHashBytes) return r - elif rsaScheme == "PSS": + elif rsaScheme == "pss": r = self.RSASSA_PSS_verify(bytearray(bytes), sigBytes, hAlg, sLen) return r else: diff --git a/unit_tests/test_tlslite_utils_rsakey.py b/unit_tests/test_tlslite_utils_rsakey.py index 3466df07..b6697965 100644 --- a/unit_tests/test_tlslite_utils_rsakey.py +++ b/unit_tests/test_tlslite_utils_rsakey.py @@ -41,7 +41,8 @@ def test_unknownRSAType(self): b'\x07\x14\xf1?\xa8i\xb7\xc6\x94\x1c9\x1fX>@' + b'\xe3') with self.assertRaises(UnknownRSAType): - self.rsa.hashAndVerify(message, signed, 'sha29', 10) + self.rsa.hashAndVerify(message, signed, rsaScheme='sha29', + hAlg='foo') def test_encodingError(self): with self.assertRaises(EncodingError): From 07d02af0defee297d7fe069df36e383b5527a9a5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 8 Mar 2017 17:45:01 +0100 Subject: [PATCH 423/574] change the RSA-PSS methods to accept hash not message because for handling messages like Certificate Verify, it is necessary to sign or verify a raw hash, not a message, we need to be able to create RSA-PSS signatures over raw hashes too update the methods to support both signing raw hashes and signing with hashing --- tlslite/utils/rsakey.py | 150 ++++++++++++++---------- unit_tests/test_tlslite_utils_rsakey.py | 76 +++++++----- 2 files changed, 136 insertions(+), 90 deletions(-) diff --git a/tlslite/utils/rsakey.py b/tlslite/utils/rsakey.py index 019fa578..db565d2e 100644 --- a/tlslite/utils/rsakey.py +++ b/tlslite/utils/rsakey.py @@ -50,7 +50,7 @@ def hasPrivateKey(self): """ raise NotImplementedError() - def hashAndSign(self, bytes, rsaScheme='PKCS1', hAlg='SHA1', sLen=0): + def hashAndSign(self, bytes, rsaScheme='PKCS1', hAlg='sha1', sLen=0): """Hash and sign the passed-in bytes. This requires the key to have a private component. It performs @@ -77,15 +77,9 @@ def hashAndSign(self, bytes, rsaScheme='PKCS1', hAlg='SHA1', sLen=0): """ rsaScheme = rsaScheme.lower() hAlg = hAlg.lower() - if rsaScheme == "pkcs1": - hashBytes = secureHash(bytearray(bytes), hAlg) - prefixedHashBytes = self.addPKCS1Prefix(hashBytes, hAlg) - sigBytes = self.sign(prefixedHashBytes) - elif rsaScheme == "pss": - sigBytes = self.RSASSA_PSS_sign(bytearray(bytes), hAlg, sLen) - else: - raise UnknownRSAType("Unknown RSA algorithm type") - return sigBytes + hashBytes = secureHash(bytearray(bytes), hAlg) + return self.sign(hashBytes, padding=rsaScheme, hashAlg=hAlg, + saltLen=sLen) def hashAndVerify(self, sigBytes, bytes, rsaScheme='PKCS1', hAlg='sha1', sLen=0): @@ -118,24 +112,8 @@ def hashAndVerify(self, sigBytes, bytes, rsaScheme='PKCS1', hAlg='sha1', rsaScheme = rsaScheme.lower() hAlg = hAlg.lower() - # Try it with/without the embedded NULL - if rsaScheme == "pkcs1" and hAlg == 'sha1': - hashBytes = secureHash(bytearray(bytes), hAlg) - prefixedHashBytes1 = self.addPKCS1SHA1Prefix(hashBytes, False) - prefixedHashBytes2 = self.addPKCS1SHA1Prefix(hashBytes, True) - result1 = self.verify(sigBytes, prefixedHashBytes1) - result2 = self.verify(sigBytes, prefixedHashBytes2) - return (result1 or result2) - elif rsaScheme == 'pkcs1': - hashBytes = secureHash(bytearray(bytes), hAlg) - prefixedHashBytes = self.addPKCS1Prefix(hashBytes, hAlg) - r = self.verify(sigBytes, prefixedHashBytes) - return r - elif rsaScheme == "pss": - r = self.RSASSA_PSS_verify(bytearray(bytes), sigBytes, hAlg, sLen) - return r - else: - raise UnknownRSAType("Unknown RSA algorithm type") + hashBytes = secureHash(bytearray(bytes), hAlg) + return self.verify(sigBytes, hashBytes, rsaScheme, hAlg, sLen) def MGF1(self, mgfSeed, maskLen, hAlg): """Generate mask from passed-in seed. @@ -161,13 +139,13 @@ def MGF1(self, mgfSeed, maskLen, hAlg): T += secureHash(mgfSeed + C, hAlg) return T[:maskLen] - def EMSA_PSS_encode(self, M, emBits, hAlg, sLen=0): + def EMSA_PSS_encode(self, mHash, emBits, hAlg, sLen=0): """Encode the passed in message This encodes the message using selected hash algorithm - @type M: bytearray - @param M: Message to be encoded + @type mHash: bytearray + @param mHash: Hash of message to be encoded @type emBits: int @param emBits: maximal length of returned EM @@ -178,7 +156,6 @@ def EMSA_PSS_encode(self, M, emBits, hAlg, sLen=0): @type sLen: int @param sLen: length of salt""" hashLen = getattr(hashlib, hAlg)().digest_size - mHash = secureHash(M, hAlg) emLen = divceil(emBits, 8) if emLen < hashLen + sLen + 2: raise EncodingError("The ending limit too short for " + @@ -196,20 +173,20 @@ def EMSA_PSS_encode(self, M, emBits, hAlg, sLen=0): EM = maskedDB + H + bytearray(b'\xbc') return EM - def RSASSA_PSS_sign(self, M, hAlg, sLen=0): + def RSASSA_PSS_sign(self, mHash, hAlg, sLen=0): """"Sign the passed in message This signs the message using selected hash algorithm - @type M: bytearray - @param M: Message to be signed + @type mHash: bytearray + @param mHash: Hash of message to be signed @type hAlg: str @param hAlg: hash algorithm to be used @type sLen: int @param sLen: length of salt""" - EM = self.EMSA_PSS_encode(M, numBits(self.n) - 1, hAlg, sLen) + EM = self.EMSA_PSS_encode(mHash, numBits(self.n) - 1, hAlg, sLen) m = bytesToNumber(EM) if m >= self.n: raise MessageTooLongError("Encode output too long") @@ -217,13 +194,13 @@ def RSASSA_PSS_sign(self, M, hAlg, sLen=0): S = numberToByteArray(s, numBytes(self.n)) return S - def EMSA_PSS_verify(self, M, EM, emBits, hAlg, sLen=0): + def EMSA_PSS_verify(self, mHash, EM, emBits, hAlg, sLen=0): """Verify signature in passed in encoded message This verifies the signature in encoded message - @type M: bytearray - @param M: Original not signed message + @type mHash: bytearray + @param mHash: Hash of the original not signed message @type EM: bytearray @param EM: Encoded message @@ -238,7 +215,6 @@ def EMSA_PSS_verify(self, M, EM, emBits, hAlg, sLen=0): @param sLen: Length of salt """ hashLen = getattr(hashlib, hAlg)().digest_size - mHash = secureHash(M, hAlg) emLen = divceil(emBits, 8) if emLen < hashLen + sLen + 2: raise InvalidSignature("Invalid signature") @@ -271,13 +247,13 @@ def EMSA_PSS_verify(self, M, EM, emBits, hAlg, sLen=0): else: raise InvalidSignature("Invalid signature") - def RSASSA_PSS_verify(self, M, S, hAlg, sLen=0): + def RSASSA_PSS_verify(self, mHash, S, hAlg, sLen=0): """Verify the signature in passed in message This verifies the signature in the signed message - @type M: bytearray - @param M: Original message + @type mHash: bytearray + @param mHash: Hash of original message @type S: bytearray @param S: Signed message @@ -293,13 +269,26 @@ def RSASSA_PSS_verify(self, M, S, hAlg, sLen=0): s = bytesToNumber(S) m = self._rawPublicKeyOp(s) EM = numberToByteArray(m, divceil(numBits(self.n) - 1, 8)) - result = self.EMSA_PSS_verify(M, EM, numBits(self.n) - 1, hAlg, sLen) + result = self.EMSA_PSS_verify(mHash, EM, numBits(self.n) - 1, + hAlg, sLen) if result: return True else: raise InvalidSignature("Invalid signature") - def sign(self, bytes): + def _raw_pkcs1_sign(self, bytes): + """Perform signature on raw data, add PKCS#1 padding.""" + if not self.hasPrivateKey(): + raise AssertionError() + paddedBytes = self._addPKCS1Padding(bytes, 1) + m = bytesToNumber(paddedBytes) + if m >= self.n: + raise ValueError() + c = self._rawPrivateKeyOp(m) + sigBytes = numberToByteArray(c, numBytes(self.n)) + return sigBytes + + def sign(self, bytes, padding='pkcs1', hashAlg=None, saltLen=None): """Sign the passed-in bytes. This requires the key to have a private component. It performs @@ -308,20 +297,47 @@ def sign(self, bytes): @type bytes: L{bytearray} of unsigned bytes @param bytes: The value which will be signed. + @type padding: str + @param padding: name of the rsa padding mode to use, supported: + "pkcs1" for RSASSA-PKCS1_1_5 and "pss" for RSASSA-PSS. + + @type hashAlg: str + @param hashAlg: name of hash to be encoded using the PKCS#1 prefix + for "pkcs1" padding or the hash used for MGF1 in "pss". Parameter + is mandatory for "pss" padding. + + @type saltLen: int + @param saltLen: length of salt used for the PSS padding. Default + is the length of the hash output used. + @rtype: L{bytearray} of unsigned bytes. @return: A PKCS1 signature on the passed-in data. """ - if not self.hasPrivateKey(): - raise AssertionError() - paddedBytes = self._addPKCS1Padding(bytes, 1) - m = bytesToNumber(paddedBytes) - if m >= self.n: - raise ValueError() - c = self._rawPrivateKeyOp(m) - sigBytes = numberToByteArray(c, numBytes(self.n)) + padding = padding.lower() + if padding == 'pkcs1': + if hashAlg is not None: + bytes = self.addPKCS1Prefix(bytes, hashAlg) + sigBytes = self._raw_pkcs1_sign(bytes) + elif padding == "pss": + sigBytes = self.RSASSA_PSS_sign(bytes, hashAlg, saltLen) + else: + raise UnknownRSAType("Unknown RSA algorithm type") return sigBytes - def verify(self, sigBytes, bytes): + def _raw_pkcs1_verify(self, sigBytes, bytes): + """Perform verification operation on raw PKCS#1 padded signature""" + if len(sigBytes) != numBytes(self.n): + return False + paddedBytes = self._addPKCS1Padding(bytes, 1) + c = bytesToNumber(sigBytes) + if c >= self.n: + return False + m = self._rawPublicKeyOp(c) + checkBytes = numberToByteArray(m, numBytes(self.n)) + return checkBytes == paddedBytes + + def verify(self, sigBytes, bytes, padding='pkcs1', hashAlg=None, + saltLen=None): """Verify the passed-in bytes with the signature. This verifies a PKCS1 signature on the passed-in data. @@ -335,15 +351,23 @@ def verify(self, sigBytes, bytes): @rtype: bool @return: Whether the signature matches the passed-in data. """ - if len(sigBytes) != numBytes(self.n): - return False - paddedBytes = self._addPKCS1Padding(bytes, 1) - c = bytesToNumber(sigBytes) - if c >= self.n: - return False - m = self._rawPublicKeyOp(c) - checkBytes = numberToByteArray(m, numBytes(self.n)) - return checkBytes == paddedBytes + if padding == "pkcs1" and hashAlg == 'sha1': + # Try it with/without the embedded NULL + prefixedHashBytes1 = self.addPKCS1SHA1Prefix(bytes, False) + prefixedHashBytes2 = self.addPKCS1SHA1Prefix(bytes, True) + result1 = self._raw_pkcs1_verify(sigBytes, prefixedHashBytes1) + result2 = self._raw_pkcs1_verify(sigBytes, prefixedHashBytes2) + return (result1 or result2) + elif padding == 'pkcs1': + if hashAlg is not None: + bytes = self.addPKCS1Prefix(bytes, hashAlg) + r = self._raw_pkcs1_verify(sigBytes, bytes) + return r + elif padding == "pss": + r = self.RSASSA_PSS_verify(bytes, sigBytes, hashAlg, saltLen) + return r + else: + raise UnknownRSAType("Unknown RSA algorithm type") def encrypt(self, bytes): """Encrypt the passed-in bytes. diff --git a/unit_tests/test_tlslite_utils_rsakey.py b/unit_tests/test_tlslite_utils_rsakey.py index b6697965..92041de9 100644 --- a/unit_tests/test_tlslite_utils_rsakey.py +++ b/unit_tests/test_tlslite_utils_rsakey.py @@ -41,12 +41,11 @@ def test_unknownRSAType(self): b'\x07\x14\xf1?\xa8i\xb7\xc6\x94\x1c9\x1fX>@' + b'\xe3') with self.assertRaises(UnknownRSAType): - self.rsa.hashAndVerify(message, signed, rsaScheme='sha29', - hAlg='foo') + self.rsa.hashAndVerify(message, signed, rsaScheme='foo', + hAlg='sha1') def test_encodingError(self): - with self.assertRaises(EncodingError): - self.assertEqual(self.rsa.EMSA_PSS_encode( + mHash = secureHash( bytearray(b'\xc7\xf5\'\x0f\xcar_\x9b\xd1\x9fQ\x9a\x8d|\xca<' + b'\xc5\xc0y\x02@)\xf3\xba\xe5\x10\xf9\xb0!@\xfe#' + b'\x89\x08\xe4\xf6\xc1\x8f\x07\xa8\x9ch|\x86\x84f' + @@ -55,7 +54,10 @@ def test_encodingError(self): b'\xddnR\x8d\x16\xfe,\x9f=\xb4\xcf\xaflM\xce\x8c' + b'\x849\xaf8\xce\xaa\xaa\x9c\xe2\xec\xae{\xc8\xf4' + b'\xa5\xa5^;\xf9m\xf9\xcdW\\O\x9c\xb3\'\x95\x1b' + - b'\x8c\xdf\xe4\x08qh'), 10, 'sha1', 10), + b'\x8c\xdf\xe4\x08qh'), + 'sha1') + with self.assertRaises(EncodingError): + self.assertEqual(self.rsa.EMSA_PSS_encode(mHash, 10, 'sha1', 10), bytearray(b'eA=!Fq4\xce\xef5?\xf4\xec\xd8\xa6FPX\xdc~(\xe3' + b'\x92\x17z\xa5-\xcfV\xd4)\x99\x8fJ\xb2\x08\xa2\xcc\xbeX&$\xfd\x96\xd3\x1e\x92' + b'\xf5\x9b\xbd\x1a\xaa\t\x85>\x13\xb5\xf1s\xa7YN' + b'\x1f\xdb\xa1*\xcc\x93\xa2\xbf\xfd\xe0\xda>0') + mHash = secureHash(self.message, 'sha1') self.assertTrue(self.rsa.RSASSA_PSS_verify( - self.message, signed, 'sha1', 0)) + mHash, signed, 'sha1', 0)) class TestRSAPSS_mod1024(unittest.TestCase): # Test cases from http://csrc.nist.gov/groups/STM/cavp/ @@ -457,7 +462,8 @@ def test_RSAPSS_sha1(self): def m(leght): return self.salt with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): - signed = self.rsa.RSASSA_PSS_sign(self.message, 'sha1', 10) + mHash = secureHash(self.message, 'sha1') + signed = self.rsa.RSASSA_PSS_sign(mHash, 'sha1', 10) self.assertEqual(signed, intendedS) def test_RSAPSS_sha224(self): @@ -476,7 +482,8 @@ def test_RSAPSS_sha224(self): def m(leght): return self.salt with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): - signed = self.rsa.RSASSA_PSS_sign(self.message, 'sha224', 10) + mHash = secureHash(self.message, 'sha224') + signed = self.rsa.RSASSA_PSS_sign(mHash, 'sha224', 10) self.assertEqual(signed, intendedS) @@ -496,7 +503,8 @@ def test_RSAPSS_sha256(self): def m(leght): return self.salt with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): - signed = self.rsa.RSASSA_PSS_sign(self.message, 'sha256', 10) + mHash = secureHash(self.message, 'sha256') + signed = self.rsa.RSASSA_PSS_sign(mHash, 'sha256', 10) self.assertEqual(signed, intendedS) @@ -516,7 +524,8 @@ def test_RSAPSS_sha384(self): def m(leght): return self.salt with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): - signed = self.rsa.RSASSA_PSS_sign(self.message, 'sha384', 10) + mHash = secureHash(self.message, 'sha384') + signed = self.rsa.RSASSA_PSS_sign(mHash, 'sha384', 10) self.assertEqual(signed, intendedS) @@ -536,7 +545,8 @@ def test_RSAPSS_sha512(self): def m(leght): return self.salt with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): - signed = self.rsa.RSASSA_PSS_sign(self.message, 'sha512', 10) + mHash = secureHash(self.message, 'sha512') + signed = self.rsa.RSASSA_PSS_sign(mHash, 'sha512', 10) self.assertEqual(signed, intendedS) def test_RSASSA_PSS_verify_sha1(self): @@ -553,7 +563,8 @@ def test_RSASSA_PSS_verify_sha1(self): b'\x47\xcc\x2e\x02\x89\xcd\xbf\x25\xc9\x77' + b'\xcf\x1b\xea\xdc\x04\x74\x21\x50\xbe\xea' + b'\xd6\x96\x2d\xdd\xa9\xe9\x1e\x17') - self.assertTrue(self.rsa.RSASSA_PSS_verify(self.message, signed, + mHash = secureHash(self.message, 'sha1') + self.assertTrue(self.rsa.RSASSA_PSS_verify(mHash, signed, 'sha1', 10)) def test_RSASSA_PSS_verify_shortSign(self): @@ -579,7 +590,8 @@ def test_RSASSA_PSS_verify_sha224(self): b'\x95\x20\x6c\xf7\xa8\x0b\xe9\xca\x5f\x4e\x58' + b'\x49\xae\x67\xf0\x73\xdb\x7b\x69\x2f\xd9\x39' + b'\xcb\x31\xed\x6b\xf5\xe0\x66') - self.assertTrue(self.rsa.RSASSA_PSS_verify(self.message, signed, + mHash = secureHash(self.message, 'sha224') + self.assertTrue(self.rsa.RSASSA_PSS_verify(mHash, signed, 'sha224', 10)) def test_RSASSA_PSS_verify_sha256(self): @@ -595,7 +607,8 @@ def test_RSASSA_PSS_verify_sha256(self): b'\xe1\xa4\xdc\x79\x7c\xa5\x42\xc8\x20\x3c\xec' + b'\x2e\x60\x1e\xb0\xc5\x1f\x56\x7f\x2e\xda\x02' + b'\x2b\x0b\x9e\xbd\xde\xee\xfa') - self.assertTrue(self.rsa.RSASSA_PSS_verify(self.message, signed, + mHash = secureHash(self.message, 'sha256') + self.assertTrue(self.rsa.RSASSA_PSS_verify(mHash, signed, 'sha256', 10)) def test_RSASSA_PSS_verify_sha384(self): @@ -611,7 +624,8 @@ def test_RSASSA_PSS_verify_sha384(self): b'\xbf\x10\xcb\x0c\x90\x33\x4f\xac\x57\x3f\x44' + b'\x91\x38\x61\x6e\x1a\x19\x4c\x67\xf4\x4e\xfa' + b'\xc3\x4c\xc0\x7a\x52\x62\x67') - self.assertTrue(self.rsa.RSASSA_PSS_verify(self.message, signed, + mHash = secureHash(self.message, 'sha384') + self.assertTrue(self.rsa.RSASSA_PSS_verify(mHash, signed, 'sha384', 10)) def test_RSASSA_PSS_verify_sha512(self): @@ -627,7 +641,8 @@ def test_RSASSA_PSS_verify_sha512(self): b'\x35\xc5\x9f\xaa\xa4\xd6\xff\x46\x2f\x68\xa6' + b'\xc4\xec\x0b\x42\x8a\xa4\x73\x36\xf2\x17\x8a' + b'\xeb\x27\x61\x36\x56\x3b\x7d') - self.assertTrue(self.rsa.RSASSA_PSS_verify(self.message, signed, + mHash = secureHash(self.message, 'sha512') + self.assertTrue(self.rsa.RSASSA_PSS_verify(mHash, signed, 'sha512', 10)) def test_RSASSA_PSS_verify_noSalt(self): @@ -640,7 +655,8 @@ def test_RSASSA_PSS_verify_noSalt(self): b'\xcd6|7d\xca,\x8dIF\x02\xf8\xcd\x81\xdd\x88' + b'\xb0\xae\xe9\x1f\x93\xf3\xfa\x90\x0f\xcd' + b'\xe2|\xbc Date: Thu, 2 Mar 2017 14:08:36 +0100 Subject: [PATCH 424/574] use hashAndSign and hashAndVerify in ServerKeyExchange --- tlslite/keyexchange.py | 69 +++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 577e9a38..548d6502 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -69,27 +69,38 @@ def processServerKeyExchange(self, srvPublicKey, def signServerKeyExchange(self, serverKeyExchange, sigHash=None): """ - Sign a server key best matching supported algorithms + Sign a server key exchange using default or specified algorithm @type sigHash: str - @param sigHash: name of the hash used for signing + @param sigHash: name of the signature hash to be used for signing """ - if self.serverHello.server_version >= (3, 3): + if self.serverHello.server_version < (3, 3): + hashBytes = serverKeyExchange.hash(self.clientHello.random, + self.serverHello.random) + + serverKeyExchange.signature = self.privateKey.sign(hashBytes) + + if not serverKeyExchange.signature: + raise TLSInternalError("Empty signature") + + if not self.privateKey.verify(serverKeyExchange.signature, + hashBytes): + raise TLSInternalError("Server Key Exchange signature invalid") + else: serverKeyExchange.signAlg = SignatureAlgorithm.rsa serverKeyExchange.hashAlg = getattr(HashAlgorithm, sigHash) - hashBytes = serverKeyExchange.hash(self.clientHello.random, - self.serverHello.random) - - if self.serverHello.server_version >= (3, 3): - hashBytes = RSAKey.addPKCS1Prefix(hashBytes, sigHash) + hashBytes = self.clientHello.random + self.serverHello.random + \ + serverKeyExchange.writeParams() - serverKeyExchange.signature = self.privateKey.sign(hashBytes) + serverKeyExchange.signature = \ + self.privateKey.hashAndSign(hashBytes, hAlg=sigHash) - if not serverKeyExchange.signature: - raise TLSInternalError("Empty signature") + if not serverKeyExchange.signature: + raise TLSInternalError("Empty signature") - if not self.privateKey.verify(serverKeyExchange.signature, hashBytes): - raise TLSInternalError("Server Key Exchange signature invalid") + if not self.privateKey.hashAndVerify(serverKeyExchange.signature, + hashBytes, hAlg=sigHash): + raise TLSInternalError("Server Key Exchange signature invalid") @staticmethod def verifyServerKeyExchange(serverKeyExchange, publicKey, clientRandom, @@ -98,29 +109,37 @@ def verifyServerKeyExchange(serverKeyExchange, publicKey, clientRandom, the only acceptable signature algorithms are specified by validSigAlgs """ - if serverKeyExchange.version >= (3, 3): + if serverKeyExchange.version < (3, 3): + hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) + sigBytes = serverKeyExchange.signature + + if not sigBytes: + raise TLSIllegalParameterException("Empty signature") + + if not publicKey.verify(sigBytes, hashBytes): + raise TLSDecryptionFailed("Server Key Exchange signature " + "invalid") + else: if (serverKeyExchange.hashAlg, serverKeyExchange.signAlg) not in \ validSigAlgs: raise TLSIllegalParameterException("Server selected " "invalid signature " "algorithm") - assert serverKeyExchange.signAlg == SignatureAlgorithm.rsa + hashName = HashAlgorithm.toRepr(serverKeyExchange.hashAlg) if hashName is None: raise TLSIllegalParameterException("Unknown signature " "algorithm") - hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) - - if serverKeyExchange.version == (3, 3): - hashBytes = RSAKey.addPKCS1Prefix(hashBytes, hashName) + hashBytes = clientRandom + serverRandom + \ + ServerKeyExchange.writeParams(serverKeyExchange) - sigBytes = serverKeyExchange.signature - if not sigBytes: - raise TLSIllegalParameterException("Empty signature") + sigBytes = serverKeyExchange.signature + if not sigBytes: + raise TLSIllegalParameterException("Empty signature") - if not publicKey.verify(sigBytes, hashBytes): - raise TLSDecryptionFailed("Server Key Exchange signature " - "invalid") + if not publicKey.hashAndVerify(sigBytes, hashBytes, hAlg=hashName): + raise TLSDecryptionFailed("Server Key Exchange signature " + "invalid") @staticmethod def calcVerifyBytes(version, handshakeHashes, signatureAlg, From 48d4dd97087c49da3e5534ea1da57d6c976385f2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 2 Mar 2017 14:09:03 +0100 Subject: [PATCH 425/574] add SignatureScheme to constants --- tlslite/constants.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 8235f609..0c97fef9 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -156,6 +156,24 @@ class SignatureAlgorithm(TLSEnum): dsa = 2 ecdsa = 3 + +class SignatureScheme(TLSEnum): + """ + Signature scheme used for signalling supported signature algorithms. + + This is the replacement for the HashAlgorithm and SignatureAlgorithm + lists. Introduced with TLSv1.3. + """ + + rsa_pkcs1_sha1 = (2, 1) + rsa_pkcs1_sha256 = (4, 1) + rsa_pkcs1_sha384 = (5, 1) + rsa_pkcs1_sha512 = (6, 1) + rsa_pss_sha256 = (8, 4) + rsa_pss_sha384 = (8, 5) + rsa_pss_sha512 = (8, 6) + + class GroupName(TLSEnum): """Name of groups supported for (EC)DH key exchange""" From ea1e5be58a35728cc06e0662ebbcb0c5066db92a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 2 Mar 2017 16:44:50 +0100 Subject: [PATCH 426/574] more test coverage for keyexchange --- unit_tests/test_tlslite_keyexchange.py | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index ec361f75..104067d5 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -167,6 +167,27 @@ def test_signServerKeyExchange_in_TLS1_1(self): self.assertEqual(server_key_exchange.write(), self.expected_tls1_1_SKE) + + def test_signServerKeyExchange_in_TLS1_1_signature_invalid(self): + srv_private_key = parsePEMKey(srv_raw_key, private=True) + client_hello = ClientHello() + cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + server_hello = ServerHello().create((3, 2), + bytearray(32), + bytearray(0), + cipher_suite) + keyExchange = KeyExchange(cipher_suite, + client_hello, + server_hello, + srv_private_key) + server_key_exchange = ServerKeyExchange(cipher_suite, (3, 2)) \ + .createDH(5, 2, 3) + + with self.assertRaises(TLSInternalError): + keyExchange.privateKey.sign = mock.Mock( + return_value=bytearray(b'wrong')) + keyExchange.signServerKeyExchange(server_key_exchange) + class TestKeyExchangeVerifyServerKeyExchange(TestKeyExchange): def setUp(self): self.srv_cert_chain = X509CertChain([X509().parse(srv_raw_certificate)]) @@ -235,6 +256,15 @@ def test_verifyServerKeyExchange_in_TLS1_1(self): bytearray(32), None) + def test_verifyServerKeyExchange_with_damaged_signature_in_TLS1_1(self): + self.ske_tls1_1.signature[-1] ^= 0x01 + with self.assertRaises(TLSDecryptionFailed): + KeyExchange.verifyServerKeyExchange(self.ske_tls1_1, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + None) + class TestCalcVerifyBytes(unittest.TestCase): def setUp(self): self.handshake_hashes = HandshakeHashes() @@ -836,11 +866,23 @@ def test_DHE_RSA_key_exchange_empty_signature(self): with self.assertRaises(TLSInternalError): self.keyExchange.makeServerKeyExchange('sha1') + def test_DHE_RSA_key_exchange_empty_signature_in_TLS_1_1(self): + self.keyExchange.serverHello.server_version = (3, 2) + self.keyExchange.privateKey.sign = mock.Mock(return_value=bytearray(0)) + with self.assertRaises(TLSInternalError): + self.keyExchange.makeServerKeyExchange('sha1') + def test_DHE_RSA_key_exchange_wrong_signature(self): self.keyExchange.privateKey.sign = mock.Mock(return_value=bytearray(20)) with self.assertRaises(TLSInternalError): self.keyExchange.makeServerKeyExchange('sha1') + def test_DHE_RSA_key_exchange_wrong_signature_in_TLS_1_1(self): + self.keyExchange.serverHello.server_version = (3, 2) + self.keyExchange.privateKey.sign = mock.Mock(return_value=bytearray(20)) + with self.assertRaises(TLSInternalError): + self.keyExchange.makeServerKeyExchange() + class TestSRPKeyExchange(unittest.TestCase): def setUp(self): self.srv_private_key = parsePEMKey(srv_raw_key, private=True) From 7e8522857ac3e6a9e559d20b21c0fd318079d18c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 11 Jul 2017 14:39:47 +0200 Subject: [PATCH 427/574] Support rsa-pss in ServerKeyExchange --- tlslite/constants.py | 45 ++++++++++ tlslite/keyexchange.py | 117 ++++++++++++++++++------- unit_tests/test_tlslite_constants.py | 41 ++++++++- unit_tests/test_tlslite_keyexchange.py | 77 ++++++++++++++++ 4 files changed, 247 insertions(+), 33 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 0c97fef9..1f37f99a 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -148,6 +148,7 @@ class HashAlgorithm(TLSEnum): sha384 = 5 sha512 = 6 + class SignatureAlgorithm(TLSEnum): """Signing algorithms used in TLSv1.2""" @@ -173,6 +174,50 @@ class SignatureScheme(TLSEnum): rsa_pss_sha384 = (8, 5) rsa_pss_sha512 = (8, 6) + @classmethod + def toRepr(cls, value, blacklist=None): + """Convert numeric type to name representation""" + if blacklist is None: + blacklist = [] + blacklist += ['getKeyType', 'getPadding', 'getHash'] + return super(SignatureScheme, cls).toRepr(value, blacklist) + + @staticmethod + def getKeyType(scheme): + """ + Return the name of the signature algorithm used in scheme. + + E.g. for "rsa_pkcs1_sha1" it returns "rsa" + """ + try: + getattr(SignatureScheme, scheme) + except AttributeError: + raise ValueError("\"{0}\" scheme is unknown".format(scheme)) + kType, _, _ = scheme.split('_') + return kType + + @staticmethod + def getPadding(scheme): + """Return the name of padding scheme used in signature scheme.""" + try: + getattr(SignatureScheme, scheme) + except AttributeError: + raise ValueError("\"{0}\" scheme is unknown".format(scheme)) + kType, padding, _ = scheme.split('_') + assert kType == 'rsa' + return padding + + @staticmethod + def getHash(scheme): + """Return the name of hash used in signature scheme.""" + try: + getattr(SignatureScheme, scheme) + except AttributeError: + raise ValueError("\"{0}\" scheme is unknown".format(scheme)) + kType, _, hName = scheme.split('_') + assert kType == 'rsa' + return hName + class GroupName(TLSEnum): """Name of groups supported for (EC)DH key exchange""" diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 548d6502..e3ab7d76 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -10,13 +10,14 @@ TLSIllegalParameterException, TLSDecryptionFailed, TLSInternalError from .messages import ServerKeyExchange, ClientKeyExchange, CertificateVerify from .constants import SignatureAlgorithm, HashAlgorithm, CipherSuite, \ - ExtensionType, GroupName, ECCurveType + ExtensionType, GroupName, ECCurveType, SignatureScheme from .utils.ecc import decodeX962Point, encodeX962Point, getCurveByName, \ getPointByteSize from .utils.rsakey import RSAKey from .utils.cryptomath import bytesToNumber, getRandomBytes, powMod, \ numBits, numberToByteArray, divceil from .utils.lists import getFirstMatching +from .utils import tlshashlib as hashlib import ecdsa class KeyExchange(object): @@ -67,6 +68,44 @@ def processServerKeyExchange(self, srvPublicKey, """Process the server KEX and return premaster secret""" raise NotImplementedError() + def _tls12_signSKE(self, serverKeyExchange, sigHash=None): + """Sign a TLSv1.2 SKE message.""" + try: + serverKeyExchange.hashAlg, serverKeyExchange.signAlg = \ + getattr(SignatureScheme, sigHash) + keyType = SignatureScheme.getKeyType(sigHash) + padType = SignatureScheme.getPadding(sigHash) + hashName = SignatureScheme.getHash(sigHash) + saltLen = getattr(hashlib, hashName)().digest_size + except AttributeError: + serverKeyExchange.signAlg = SignatureAlgorithm.rsa + serverKeyExchange.hashAlg = getattr(HashAlgorithm, sigHash) + keyType = 'rsa' + padType = 'pkcs1' + hashName = sigHash + saltLen = 0 + + assert keyType == 'rsa' + + hashBytes = self.clientHello.random + self.serverHello.random + \ + serverKeyExchange.writeParams() + + serverKeyExchange.signature = \ + self.privateKey.hashAndSign(hashBytes, + rsaScheme=padType, + hAlg=hashName, + sLen=saltLen) + + if not serverKeyExchange.signature: + raise TLSInternalError("Empty signature") + + if not self.privateKey.hashAndVerify(serverKeyExchange.signature, + hashBytes, + rsaScheme=padType, + hAlg=hashName, + sLen=saltLen): + raise TLSInternalError("Server Key Exchange signature invalid") + def signServerKeyExchange(self, serverKeyExchange, sigHash=None): """ Sign a server key exchange using default or specified algorithm @@ -87,20 +126,51 @@ def signServerKeyExchange(self, serverKeyExchange, sigHash=None): hashBytes): raise TLSInternalError("Server Key Exchange signature invalid") else: - serverKeyExchange.signAlg = SignatureAlgorithm.rsa - serverKeyExchange.hashAlg = getattr(HashAlgorithm, sigHash) - hashBytes = self.clientHello.random + self.serverHello.random + \ - serverKeyExchange.writeParams() + self._tls12_signSKE(serverKeyExchange, sigHash) - serverKeyExchange.signature = \ - self.privateKey.hashAndSign(hashBytes, hAlg=sigHash) + @staticmethod + def _tls12_verify_SKE(serverKeyExchange, publicKey, clientRandom, + serverRandom, validSigAlgs): + """Verify TLSv1.2 version of SKE.""" + if (serverKeyExchange.hashAlg, serverKeyExchange.signAlg) not in \ + validSigAlgs: + raise TLSIllegalParameterException("Server selected " + "invalid signature " + "algorithm") + schemeID = (serverKeyExchange.hashAlg, + serverKeyExchange.signAlg) + scheme = SignatureScheme.toRepr(schemeID) + if scheme is not None: + keyType = SignatureScheme.getKeyType(scheme) + padType = SignatureScheme.getPadding(scheme) + hashName = SignatureScheme.getHash(scheme) + saltLen = getattr(hashlib, hashName)().digest_size + else: + if serverKeyExchange.signAlg != SignatureAlgorithm.rsa: + raise TLSInternalError("non-RSA sigs are not supported") + keyType = 'rsa' + padType = 'pkcs1' + saltLen = 0 + hashName = HashAlgorithm.toRepr(serverKeyExchange.hashAlg) + if hashName is None: + msg = "Unknown hash ID: {0}"\ + .format(serverKeyExchange.hashAlg) + raise TLSIllegalParameterException(msg) + assert keyType == 'rsa' - if not serverKeyExchange.signature: - raise TLSInternalError("Empty signature") + hashBytes = clientRandom + serverRandom + \ + ServerKeyExchange.writeParams(serverKeyExchange) - if not self.privateKey.hashAndVerify(serverKeyExchange.signature, - hashBytes, hAlg=sigHash): - raise TLSInternalError("Server Key Exchange signature invalid") + sigBytes = serverKeyExchange.signature + if not sigBytes: + raise TLSIllegalParameterException("Empty signature") + + if not publicKey.hashAndVerify(sigBytes, hashBytes, + rsaScheme=padType, + hAlg=hashName, + sLen=saltLen): + raise TLSDecryptionFailed("Server Key Exchange signature " + "invalid") @staticmethod def verifyServerKeyExchange(serverKeyExchange, publicKey, clientRandom, @@ -120,26 +190,9 @@ def verifyServerKeyExchange(serverKeyExchange, publicKey, clientRandom, raise TLSDecryptionFailed("Server Key Exchange signature " "invalid") else: - if (serverKeyExchange.hashAlg, serverKeyExchange.signAlg) not in \ - validSigAlgs: - raise TLSIllegalParameterException("Server selected " - "invalid signature " - "algorithm") - - hashName = HashAlgorithm.toRepr(serverKeyExchange.hashAlg) - if hashName is None: - raise TLSIllegalParameterException("Unknown signature " - "algorithm") - hashBytes = clientRandom + serverRandom + \ - ServerKeyExchange.writeParams(serverKeyExchange) - - sigBytes = serverKeyExchange.signature - if not sigBytes: - raise TLSIllegalParameterException("Empty signature") - - if not publicKey.hashAndVerify(sigBytes, hashBytes, hAlg=hashName): - raise TLSDecryptionFailed("Server Key Exchange signature " - "invalid") + KeyExchange._tls12_verify_SKE(serverKeyExchange, publicKey, + clientRandom, serverRandom, + validSigAlgs) @staticmethod def calcVerifyBytes(version, handshakeHashes, signatureAlg, diff --git a/unit_tests/test_tlslite_constants.py b/unit_tests/test_tlslite_constants.py index 6c52403e..e1a65838 100644 --- a/unit_tests/test_tlslite_constants.py +++ b/unit_tests/test_tlslite_constants.py @@ -11,7 +11,7 @@ from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ ContentType, AlertDescription, AlertLevel, HandshakeType, GroupName, \ - TLSEnum + TLSEnum, SignatureScheme class TestTLSEnumSubClassing(unittest.TestCase): @@ -126,3 +126,42 @@ def test_filterForVersion_with_TLS_1_2_ciphers(self): [CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_RSA_WITH_RC4_128_MD5]) + + +class TestSignatureScheme(unittest.TestCase): + def test_toRepr_with_valid_value(self): + ret = SignatureScheme.toRepr((6, 1)) + + self.assertEqual(ret, "rsa_pkcs1_sha512") + + def test_toRepr_with_obsolete_value(self): + ret = SignatureScheme.toRepr((1, 1)) + + self.assertIsNone(ret) + + def test_getKeyType_with_valid_name(self): + ret = SignatureScheme.getKeyType('rsa_pkcs1_sha256') + + self.assertEqual(ret, 'rsa') + + def test_getKeyType_with_invalid_name(self): + with self.assertRaises(ValueError): + SignatureScheme.getKeyType('eddsa_sha512') + + def test_getPadding_with_valid_name(self): + ret = SignatureScheme.getPadding('rsa_pss_sha512') + + self.assertEqual(ret, 'pss') + + def test_getPadding_with_invalid_name(self): + with self.assertRaises(ValueError): + SignatureScheme.getPadding('rsa_oead_sha256') + + def test_getHash_with_valid_name(self): + ret = SignatureScheme.getHash('rsa_pss_sha256') + + self.assertEqual(ret, 'sha256') + + def test_getHash_with_invalid_name(self): + with self.assertRaises(ValueError): + SignatureScheme.getHash('rsa_oead_sha256') diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index 104067d5..9c28b3fa 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -227,6 +227,16 @@ def test_verifyServerKeyExchange_with_unknown_hash(self): [(244, SignatureAlgorithm.rsa)]) + def test_verifyServerKeyExchange_with_unknown_sig(self): + self.server_key_exchange.signAlg = 244 + with self.assertRaises(TLSInternalError): + KeyExchange.verifyServerKeyExchange(self.server_key_exchange, + self.srv_pub_key, + self.client_hello.random, + bytearray(32), + [(HashAlgorithm.sha1, + 244)]) + def test_verifyServerKeyExchange_with_empty_signature(self): self.server_key_exchange.signature = bytearray(0) @@ -1213,3 +1223,70 @@ def test_client_ECDHE_key_exchange_with_invalid_server_curve(self): [GroupName.secp256r1]) with self.assertRaises(TLSIllegalParameterException): client_keyExchange.processServerKeyExchange(None, srv_key_ex) + +class TestRSAKeyExchange_with_PSS_scheme(unittest.TestCase): + def setUp(self): + self.srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = srv_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA + self.client_hello = ClientHello().create((3, 3), + bytearray(32), + bytearray(0), + []) + self.server_hello = ServerHello().create((3, 3), + bytearray(32), + bytearray(0), + self.cipher_suite) + + self.keyExchange = DHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key) + + + def test_signServerKeyExchange_with_sha256_in_TLS1_2(self): + def m(length): + return bytearray(length) + + with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): + ske = ServerKeyExchange(self.cipher_suite, (3, 3)) + ske = ske.createDH(5, 2, 3) + + self.keyExchange.signServerKeyExchange(ske, 'rsa_pss_sha256') + + self.assertEqual(ske.signature, + bytearray(b'E\xae\x8e\xbe~RU\n\xab4\x8e\x10y\x94' + b'\x01\xdfVr\x8b\x03\xa4\xb7\x9dI\xf1' + b'\xb7\x16\xfa\xa0-\x9a\x16^pZ\x979\xc2' + b'&\xa5\xfcU\x9a"\xc7~u\x1e_y\xc1w\x91' + b'\x98L\x10\xb4\xed\x103\xdf\xac\xba' + b'\x19Q\x0e\x8an\x13\x99\x8d1\x17XK\x9a' + b'\x00\xcdno\xc7\xae\x92:pU\xf8\xfbl' + b'\xeeg\xe0s\x03\xc8\xcb\xe5\xc4\xb9z' + b'\xcf\nv\xca\x80`\xbe\xc9\x85\xcfM\x89' + b'\xaeE\xf0\xa1\xd8`\x99\x93\xa0Bp\x1cw' + b'W\xce\x8e')) + + def test_signServerKeyExchange_with_sha384_in_TLS1_2(self): + def m(length): + return bytearray(length) + + with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): + ske = ServerKeyExchange(self.cipher_suite, (3, 3)) + ske = ske.createDH(5, 2, 3) + + self.keyExchange.signServerKeyExchange(ske, 'rsa_pss_sha384') + + self.assertEqual(ske.signature, + bytearray(b"QH\x02Xl2\xa37\xeeV\x9d\x84\x96E;_iJ" + b"\xcd\xed\x85#\x96\x0c\xc2\x94\xbd\xfa" + b"\xbbt&\xffo\xe2o\xa2\xbb\x08\xf1v\xdb" + b"\xdc\xcdj\x96R\x88\xf8{\x182\xfd\x99t" + b"\x9d\xb8\xba\x87\xd3\x8f\x8b\x88\xe8" + b"\x1c\x02\xa2\xfd5\x0b\x9b\xe1\x8c\xc0" + b"O\x13\x8d\xc5SU\xd5pN\xe2\xa9\xe1F|" + b"\xe9\xb5\xa9\x80s_\x91\xeb:\xcd\xee(" + b"\x03\xe5[\xf5\xc7z\x02\'/\x0f\xdc\x1f" + b"\xd2\x93\x8b\x12\x01%\x1d\x04\xf1[" + b"\xe4\x9a\x83\xf8\xd3#+")) From e8caddb57e3ec139201542facb6583c19f12483e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 2 Mar 2017 19:16:01 +0100 Subject: [PATCH 428/574] switch back to sign() and verify() for SKE --- tlslite/keyexchange.py | 33 ++++++++++++++++----------------- tlslite/messages.py | 12 ++++++++---- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index e3ab7d76..cc40618d 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -87,23 +87,23 @@ def _tls12_signSKE(self, serverKeyExchange, sigHash=None): assert keyType == 'rsa' - hashBytes = self.clientHello.random + self.serverHello.random + \ - serverKeyExchange.writeParams() + hashBytes = serverKeyExchange.hash(self.clientHello.random, + self.serverHello.random) serverKeyExchange.signature = \ - self.privateKey.hashAndSign(hashBytes, - rsaScheme=padType, - hAlg=hashName, - sLen=saltLen) + self.privateKey.sign(hashBytes, + padding=padType, + hashAlg=hashName, + saltLen=saltLen) if not serverKeyExchange.signature: raise TLSInternalError("Empty signature") - if not self.privateKey.hashAndVerify(serverKeyExchange.signature, - hashBytes, - rsaScheme=padType, - hAlg=hashName, - sLen=saltLen): + if not self.privateKey.verify(serverKeyExchange.signature, + hashBytes, + padding=padType, + hashAlg=hashName, + saltLen=saltLen): raise TLSInternalError("Server Key Exchange signature invalid") def signServerKeyExchange(self, serverKeyExchange, sigHash=None): @@ -158,17 +158,16 @@ def _tls12_verify_SKE(serverKeyExchange, publicKey, clientRandom, raise TLSIllegalParameterException(msg) assert keyType == 'rsa' - hashBytes = clientRandom + serverRandom + \ - ServerKeyExchange.writeParams(serverKeyExchange) + hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) sigBytes = serverKeyExchange.signature if not sigBytes: raise TLSIllegalParameterException("Empty signature") - if not publicKey.hashAndVerify(sigBytes, hashBytes, - rsaScheme=padType, - hAlg=hashName, - sLen=saltLen): + if not publicKey.verify(sigBytes, hashBytes, + padding=padType, + hashAlg=hashName, + saltLen=saltLen): raise TLSDecryptionFailed("Server Key Exchange signature " "invalid") diff --git a/tlslite/messages.py b/tlslite/messages.py index 5b28fc76..caf71a84 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1296,10 +1296,14 @@ def hash(self, clientRandom, serverRandom): """ bytesToHash = clientRandom + serverRandom + self.writeParams() if self.version >= (3, 3): - hashAlg = HashAlgorithm.toRepr(self.hashAlg) - if hashAlg is None: - raise AssertionError("Unknown hash algorithm: {0}". - format(self.hashAlg)) + sigScheme = SignatureScheme.toRepr((self.hashAlg, self.signAlg)) + if sigScheme is None: + hashAlg = HashAlgorithm.toRepr(self.hashAlg) + if hashAlg is None: + raise AssertionError("Unknown hash algorithm: {0}". + format(self.hashAlg)) + else: + hashAlg = SignatureScheme.getHash(sigScheme) return secureHash(bytesToHash, hashAlg) return MD5(bytesToHash) + SHA1(bytesToHash) From c8f67d9e4976099201bf08d0c8c9e47e8e98f056 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 2 Mar 2017 19:32:25 +0100 Subject: [PATCH 429/574] test case with md5 sig over CertificateVerify --- unit_tests/test_tlslite_keyexchange.py | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index 9c28b3fa..77b44c9b 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -360,6 +360,36 @@ def test_with_TLS1_2(self): b'\xb4:\x9b\xc1U\x1c\xba\xa3\x05\xceOn\x0fY\xcaK*\x0b\x04\xa5' )) + def test_with_TLS1_2_md5(self): + certificate_request = CertificateRequest((3, 3)) + certificate_request.create([CertificateType.x509], + [], + [(HashAlgorithm.md5, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + certVerify = KeyExchange.makeCertificateVerify((3, 3), + self.handshake_hashes, + [(HashAlgorithm.md5, + SignatureAlgorithm.rsa)], + self.clnt_private_key, + certificate_request, + None, None, None) + + self.assertIsNotNone(certVerify) + self.assertEqual(certVerify.version, (3, 3)) + self.assertEqual(certVerify.signatureAlgorithm, (HashAlgorithm.md5, + SignatureAlgorithm.rsa)) + self.assertEqual(certVerify.signature, bytearray( + b'H5\x03U]\x0c\xb6\xc0Y\x98^\x0f \xf4\x15}\x8d\xf7k\x97\\&8j\x94' + b'\xc6\x04*e\xa6\x95\xc5\xf3\xb1\xd0\xe6\x85[<9\x91K\xc51\xc3\xe9' + b'\xc6\x15&\x1c\xfb\xb2?\r|\r\xfa"\x8c\xdaHo]\x89\xc8mOE\x9c]\xa0' + b'\xab#\xf8\xea(\xefE\xb3\x83)f+hS\xa8\x00\xe1\x11\xbd\xfb\xd5\xf5' + b'[\x9b7\xb1p\xd7\xa3\xc8\xf37K \x91\x0e\x16\xd6\x94t\xec\xe6\xb1Z' + b'K\xeeg\xb6)>\x91?\xc2\xe2S\xdf\xa9')) + + def test_with_TLS1_2_and_no_overlap(self): certificate_request = CertificateRequest((3, 3)) certificate_request.create([CertificateType.x509], From dbf92b066f142f6916d07f3451784d166f61586a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 2 Mar 2017 19:45:24 +0100 Subject: [PATCH 430/574] add support of RSA-PSS to CertificateVerify --- tlslite/keyexchange.py | 32 +++++++++++++++++++++--- tlslite/tlsconnection.py | 19 +++++++++++++- unit_tests/test_tlslite_keyexchange.py | 34 +++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 6 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index cc40618d..16dedc35 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -207,9 +207,16 @@ def calcVerifyBytes(version, handshakeHashes, signatureAlg, elif version in ((3, 1), (3, 2)): verifyBytes = handshakeHashes.digest() elif version == (3, 3): - hashName = HashAlgorithm.toRepr(signatureAlg[0]) + scheme = SignatureScheme.toRepr(signatureAlg) + if scheme is None: + hashName = HashAlgorithm.toRepr(signatureAlg[0]) + padding = 'pkcs1' + else: + hashName = SignatureScheme.getHash(scheme) + padding = SignatureScheme.getPadding(scheme) verifyBytes = handshakeHashes.digest(hashName) - verifyBytes = RSAKey.addPKCS1Prefix(verifyBytes, hashName) + if padding == 'pkcs1': + verifyBytes = RSAKey.addPKCS1Prefix(verifyBytes, hashName) return verifyBytes @staticmethod @@ -241,8 +248,25 @@ def makeCertificateVerify(version, handshakeHashes, validSigAlgs, premasterSecret, clientRandom, serverRandom) - signedBytes = privateKey.sign(verifyBytes) - if not privateKey.verify(signedBytes, verifyBytes): + scheme = SignatureScheme.toRepr(signatureAlgorithm) + # for pkcs1 signatures hash is used to add PKCS#1 prefix, but + # that was already done by calcVerifyBytes + hashName = None + saltLen = 0 + if scheme is None: + padding = 'pkcs1' + else: + padding = SignatureScheme.getPadding(scheme) + if padding == 'pss': + hashName = SignatureScheme.getHash(scheme) + saltLen = getattr(hashlib, hashName)().digest_size + + signedBytes = privateKey.sign(verifyBytes, + padding, + hashName, + saltLen) + if not privateKey.verify(signedBytes, verifyBytes, padding, hashName, + saltLen): raise TLSInternalError("Certificate Verify signature invalid") certificateVerify = CertificateVerify(version) certificateVerify.create(signedBytes, signatureAlgorithm) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index d1e87043..5b175abc 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1982,7 +1982,24 @@ def _serverCertKeyExchange(self, clientHello, serverHello, "Client's public key too large: %d" % len(publicKey)): yield result - if not publicKey.verify(certificateVerify.signature, verifyBytes): + scheme = SignatureScheme.toRepr(signatureAlgorithm) + # for pkcs1 signatures hash is used to add PKCS#1 prefix, but + # that was already done by calcVerifyBytes + hashName = None + saltLen = 0 + if scheme is None: + padding = 'pkcs1' + else: + padding = SignatureScheme.getPadding(scheme) + if padding == 'pss': + hashName = SignatureScheme.getHash(scheme) + saltLen = getattr(hashlib, hashName)().digest_size + + if not publicKey.verify(certificateVerify.signature, + verifyBytes, + padding, + hashName, + saltLen): for result in self._sendError(\ AlertDescription.decrypt_error, "Signature failed to verify"): diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index 77b44c9b..04818a51 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -19,7 +19,7 @@ from tlslite.messages import ServerHello, ClientHello, ServerKeyExchange,\ CertificateRequest, ClientKeyExchange from tlslite.constants import CipherSuite, CertificateType, AlertDescription, \ - HashAlgorithm, SignatureAlgorithm, GroupName + HashAlgorithm, SignatureAlgorithm, GroupName, SignatureScheme from tlslite.errors import TLSLocalAlert, TLSIllegalParameterException, \ TLSDecryptionFailed, TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ TLSInternalError @@ -389,6 +389,38 @@ def test_with_TLS1_2_md5(self): b'[\x9b7\xb1p\xd7\xa3\xc8\xf37K \x91\x0e\x16\xd6\x94t\xec\xe6\xb1Z' b'K\xeeg\xb6)>\x91?\xc2\xe2S\xdf\xa9')) + def test_with_TLS1_2_rsa_pss_sha256(self): + def m(length): + return bytearray(length) + + with mock.patch('tlslite.utils.rsakey.getRandomBytes', m): + certificate_request = CertificateRequest((3, 3)) + certificate_request.create([CertificateType.x509], + [], + [SignatureScheme.rsa_pss_sha256, + (HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + certVerify = KeyExchange.makeCertificateVerify((3, 3), + self.handshake_hashes, + [SignatureScheme.\ + rsa_pss_sha256], + self.clnt_private_key, + certificate_request, + None, None, None) + + self.assertIsNotNone(certVerify) + self.assertEqual(certVerify.version, (3, 3)) + self.assertEqual(certVerify.signatureAlgorithm, + SignatureScheme.rsa_pss_sha256) + self.assertEqual(certVerify.signature, bytearray( + b'mj{\xb8\xe1\xfdV\x8f>\xc4\x7fy\xe3h}\xb0\xda\xff\xab1\xab=' + b'\xa7x\xf4x\xcduL\xbbN"\xd9\xad\x7f@N\xae\xb1\xc5\x1c\'\x81' + b'\x7f\xc4\xe3\xc9:Y6\xf77\xb0\xd8\xc3\xbeo\xd0&\xf6\x05x\xc6' + b'\x9c\xce\xb4\x1eQx \x13\x93qCy\x8d>tCONS\x83\x15\xf7\xf1' + b'\x96\x15\x1eXv<\xb6\x80\x7fI\x85\xa3\xe1\x18\xd4\xd6\xbe)68' + b'\xad\xae\x08\xad\x91\xe9rg\x8b\xc8M\xfe{\x0c\xf5\x0fj\'E"9' + b'\r')) def test_with_TLS1_2_and_no_overlap(self): certificate_request = CertificateRequest((3, 3)) From 5ba0bbe560e8f80d38a5f7b33427b709fa194257 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 8 Mar 2017 17:08:31 +0100 Subject: [PATCH 431/574] add support for RSA-PSS on TLS client side --- scripts/tls.py | 9 +++++--- tlslite/handshakesettings.py | 9 ++++++++ tlslite/tlsconnection.py | 23 +++++++++++++++----- unit_tests/test_tlslite_handshakesettings.py | 6 +++++ 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/scripts/tls.py b/scripts/tls.py index 3cc6a0b9..501e5298 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -31,7 +31,7 @@ from tlslite.api import * from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ - GroupName + GroupName, SignatureScheme from tlslite import __version__ from tlslite.utils.compat import b2a_hex from tlslite.utils.dns_utils import is_valid_hostname @@ -220,9 +220,12 @@ def printGoodConnection(connection, seconds): print(" Server X.509 SHA1 fingerprint: %s" % connection.session.serverCertChain.getFingerprint()) if connection.version >= (3, 3) and connection.serverSigAlg is not None: - print(" Key exchange signature: {1}+{0}".format(\ + scheme = SignatureScheme.toRepr(connection.serverSigAlg) + if scheme is None: + scheme = "{1}+{0}".format( HashAlgorithm.toStr(connection.serverSigAlg[0]), - SignatureAlgorithm.toStr(connection.serverSigAlg[1]))) + SignatureAlgorithm.toStr(connection.serverSigAlg[1])) + print(" Key exchange signature: {0}".format(scheme)) if connection.ecdhCurve is not None: print(" Group used for key exchange: {0}".format(\ GroupName.toStr(connection.ecdhCurve))) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 2e71907c..0d9d9d5c 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -26,6 +26,7 @@ CERTIFICATE_TYPES = ["x509"] RSA_SIGNATURE_HASHES = ["sha512", "sha384", "sha256", "sha224", "sha1"] ALL_RSA_SIGNATURE_HASHES = RSA_SIGNATURE_HASHES + ["md5"] +RSA_SCHEMES = ["pss", "pkcs1"] # while secp521r1 is the most secure, it's also much slower than the others # so place it as the last one CURVE_NAMES = ["secp384r1", "secp256r1", "secp521r1"] @@ -155,6 +156,7 @@ def __init__(self): self.sendFallbackSCSV = False self.useEncryptThenMAC = True self.rsaSigHashes = list(RSA_SIGNATURE_HASHES) + self.rsaSchemes = list(RSA_SCHEMES) self.eccCurves = list(CURVE_NAMES) self.usePaddingExtension = True self.useExtendedMasterSecret = True @@ -216,6 +218,12 @@ def _sanityCheckPrimitivesNames(other): raise ValueError("Unknown RSA signature hash: '{0}'".\ format(unknownSigHash)) + unknownRSAPad = [val for val in other.rsaSchemes + if val not in RSA_SCHEMES] + if unknownRSAPad: + raise ValueError("Unknown RSA padding mode: '{0}'".\ + format(unknownRSAPad)) + unknownDHGroup = [val for val in other.dhGroups if val not in ALL_DH_GROUP_NAMES] if unknownDHGroup: @@ -274,6 +282,7 @@ def validate(self): other.useEncryptThenMAC = self.useEncryptThenMAC other.usePaddingExtension = self.usePaddingExtension other.rsaSigHashes = self.rsaSigHashes + other.rsaSchemes = self.rsaSchemes other.eccCurves = self.eccCurves other.useExtendedMasterSecret = self.useExtendedMasterSecret other.requireExtendedMasterSecret = self.requireExtendedMasterSecret diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 5b175abc..031e01d7 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -996,7 +996,7 @@ def _clientKeyExchange(self, settings, cipherSuite, #if client auth was requested and we have a private key, send a #CertificateVerify if certificateRequest and privateKey: - validSigAlgs = self._sigHashesToList(settings) + validSigAlgs = self._sigHashesToList(settings, privateKey) try: certificateVerify = KeyExchange.makeCertificateVerify( self.version, @@ -2208,12 +2208,25 @@ def _pickServerKeyExchangeSig(settings, clientHello): raise TLSHandshakeFailure("No common signature algorithms") @staticmethod - def _sigHashesToList(settings): + def _sigHashesToList(settings, privateKey=None): """Convert list of valid signature hashes to array of tuples""" sigAlgs = [] - for hashName in settings.rsaSigHashes: - sigAlgs.append((getattr(HashAlgorithm, hashName), - SignatureAlgorithm.rsa)) + for schemeName in settings.rsaSchemes: + for hashName in settings.rsaSigHashes: + try: + # 1024 bit keys are too small to create valid + # rsa-pss-SHA512 signatures + if schemeName == 'pss' and hashName == 'sha512'\ + and privateKey and privateKey.n < 2**2047: + continue + sigAlgs.append(getattr(SignatureScheme, + "rsa_{0}_{1}".format(schemeName, + hashName))) + except AttributeError: + if schemeName == 'pkcs1': + sigAlgs.append((getattr(HashAlgorithm, hashName), + SignatureAlgorithm.rsa)) + continue return sigAlgs @staticmethod diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index d8a6b19b..243121ab 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -272,5 +272,11 @@ def test_invalid_dhGroups(self): with self.assertRaises(ValueError): hs.validate() + def test_invalid_rsaScheme(self): + hs = HandshakeSettings() + hs.rsaSchemes += ["rsassa-pkcs1-1_5"] + with self.assertRaises(ValueError): + hs.validate() + if __name__ == '__main__': unittest.main() From 87eb498734100bb5df01261159c26dec53519c28 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 11 Jul 2017 14:37:01 +0200 Subject: [PATCH 432/574] allow server to select RSA-PSS in SKE --- tlslite/tlsconnection.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 031e01d7..98e13323 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -2197,12 +2197,16 @@ def _pickServerKeyExchangeSig(settings, clientHello): # sha1 should be picked return "sha1" - rsaHashes = [alg[0] for alg in hashAndAlgsExt.sigalgs - if alg[1] == SignatureAlgorithm.rsa] - for hashName in settings.rsaSigHashes: - hashID = getattr(HashAlgorithm, hashName) - if hashID in rsaHashes: - return hashName + supported = TLSConnection._sigHashesToList(settings) + + for schemeID in supported: + if schemeID in hashAndAlgsExt.sigalgs: + name = SignatureScheme.toRepr(schemeID) + if not name and schemeID[1] == SignatureAlgorithm.rsa: + name = HashAlgorithm.toRepr(schemeID[0]) + + if name: + return name # if no match, we must abort per RFC 5246 raise TLSHandshakeFailure("No common signature algorithms") From 616a067a9d482c1853b1bb411a236f23d8fafb9e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 8 Mar 2017 13:57:52 +0100 Subject: [PATCH 433/574] test with certificates signed with RSA-PSS --- tests/serverRSAPSSSigCert.pem | 21 +++++++++++++++++++++ tests/serverRSAPSSSigKey.pem | 28 ++++++++++++++++++++++++++++ tests/tlstest.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 tests/serverRSAPSSSigCert.pem create mode 100644 tests/serverRSAPSSSigKey.pem diff --git a/tests/serverRSAPSSSigCert.pem b/tests/serverRSAPSSSigCert.pem new file mode 100644 index 00000000..6b9c270e --- /dev/null +++ b/tests/serverRSAPSSSigCert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAhSgAwIBAgIJAIcG1jfQrLgJMD4GCSqGSIb3DQEBCjAxoA0wCwYJYIZI +AWUDBAIBoRowGAYJKoZIhvcNAQEIMAsGCWCGSAFlAwQCAaIEAgIA3jAUMRIwEAYD +VQQDDAlsb2NhbGhvc3QwHhcNMTcwMzA4MTI0NTMxWhcNMTcwNDA3MTI0NTMxWjAU +MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQClk5dNlSUuwv7om2xygvniaOeMyg3tDIPe1x7jzsLAcMtKZ3Kt/X6Thiri +nWFRuG+5nasjaeEWMjpxJaOtuZbo7GkhpF8DnUL6Gjoxiy5W8y/FM4I10/okiPjE +1waOuWGt44/ew3Nma1KBJKlw3R8ZjZUKseh55hNqYrbHSBJdhSP+YtC4d5iZh9Yn +GlGjoYuJOqTPjIdmKORms1JovAST87zFlRIYpqU+XzTQmSNPzjPmOVjcg3qNlAgw +xlXWUkic3MQofZF9WD6xv59X27lzE8Ovc5knMffNWEE14vJPViWQnWuYGL/Q1hRX +Yd6xWVH2jWOTGE8yd1FITbQdeg1bAgMBAAGjUDBOMB0GA1UdDgQWBBR8vDdFwaUI +Nbq8unRxsmEDhu5+wDAfBgNVHSMEGDAWgBR8vDdFwaUINbq8unRxsmEDhu5+wDAM +BgNVHRMEBTADAQH/MD4GCSqGSIb3DQEBCjAxoA0wCwYJYIZIAWUDBAIBoRowGAYJ +KoZIhvcNAQEIMAsGCWCGSAFlAwQCAaIEAgIA3gOCAQEAXRhB2MLbQuIzbUUmFQR/ +WTxmjpNPdS35/zjcbxoJrJMUY6+sUSB5/Oce4EhaLgcZvRdNE5/PGQUYy/duvRwu +vAXclBpwyP57nIm+1fwACSXp633WMj3Z0cT1veP07k1EUOBIBSt0OefGhC3DTSbu +rEZJ/KuTXQ71XqIeyKN9z0U8B6R0Qhbd3UmZaj/Cs343UaFck6M6K5tR2qfUhp4T +vEvd+qfcL1Ziw7c4rhS/wKLJ31eOUDNpq+QMlTaIVFLUplGwis/XI8RA7hGtcmzH +oELfAkAhSV3vy/K6i5wV1SEI4c69gFjZA+pmOqebcw8q2IG/KUqBbKvRBC6KGrGs +Vg== +-----END CERTIFICATE----- diff --git a/tests/serverRSAPSSSigKey.pem b/tests/serverRSAPSSSigKey.pem new file mode 100644 index 00000000..597b4170 --- /dev/null +++ b/tests/serverRSAPSSSigKey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQClk5dNlSUuwv7o +m2xygvniaOeMyg3tDIPe1x7jzsLAcMtKZ3Kt/X6ThirinWFRuG+5nasjaeEWMjpx +JaOtuZbo7GkhpF8DnUL6Gjoxiy5W8y/FM4I10/okiPjE1waOuWGt44/ew3Nma1KB +JKlw3R8ZjZUKseh55hNqYrbHSBJdhSP+YtC4d5iZh9YnGlGjoYuJOqTPjIdmKORm +s1JovAST87zFlRIYpqU+XzTQmSNPzjPmOVjcg3qNlAgwxlXWUkic3MQofZF9WD6x +v59X27lzE8Ovc5knMffNWEE14vJPViWQnWuYGL/Q1hRXYd6xWVH2jWOTGE8yd1FI +TbQdeg1bAgMBAAECggEBAJdaQa1uj02ee6sfH9PRPCClajv9Gxu8G/MgS6FMCCQn +Nv0tcNvw47pwo1UBLcuov6A5qw21JEbI5Tk2gdc01ejW6gcRnLTOFUJPpTBSnKpo +pBssSXNw8LBi8JvmPp1KgiqU4JdtrFhL9GAOpkCAMISIMlxTYkHDea3+zDt/+yG2 +MRTHaY7uX+iaC++8EUwW06IkjnyOrDzTopyrWvpAzhNtdtgnUSpzk4g6US1Smt27 +n3lWwwgriqdSo+QtRWk4LYRHkf6Ei5z8Sa/Hb7FSclar/Z+F/331Z6trEj+Kxp8Q +2AdQmTQhtlS2GVOlMkM7UQq5wr25RUsFfEL3yyIeCdECgYEA1dTu/WPgZEMaPOt6 +SI592pRegaYWKp+oBXX2FALbmRfNCDK7+ncGy5N4KUZso82noU6GDp3kx0ECRxfz +bS1bAgUhdna/1jZd0TwqJsjAOhR3S9VUzNGT/P6u/+xq/pxem6UUK5Gp24MNwh0q +03oVAvFXcdZfsRIOINb4X91Lk7MCgYEAxjqLIoaeS2V5fSQn/XVj9nQAlxDgn3dt +PMwxtroYd4je1xyrY1rjxjdmgLXroPQ5XIS+Nxm+Ra99kPwaNd878/MYH1mfo3OO +wCfeibRZOgbBVjxzSQvcTY9vRa1kC9gG9wVQcFKwvcHHdTSTiQlsvcrbAs16B++x +KtWPD5Id67kCgYEAiMPpm94OvnIAzSgbjV699ljhXXEaSmbVc7CwFLqDNBb6B51T +h+4rRXfr6hJmGBrIda6gpaatlhfpWglio9jXlaIsdfWb22nodZRSrX3HHR7L8o/e +7iW5zp4i1gOjp3fiNxDNptRC3OikmxxGsP4rhDdSo7fmen2+1ZhJQh9rYGsCgYAu +6a//7cgMo+lKmoH0VBHiQ7XYCqB+XO83UkP5sLUVpQjH8/sDuoDpajcSAvvOzb29 +jZTOyYLkTxHyfBdf5IL1yvRMhxNmxXRlOYpLIKl1OCANXqUSBoKOwIFfBhgFTYzo +bROKYad+YRvpOxYnelmH2ThXw3U755gQQRZoYvY90QKBgHZZxTcELBjcM6K+uytC +sR3ZswdFJr/A5hWTYRs6YKYWUo/Cc9AHrVMhAQ7vzUwJcTB7Yb0tlb5B6Fz4q3JE +Mc2bsYkcfC7Aj9+HA+jqb8RbI0GoSlHMgwrJrIDWO/5b0DyV7+zHhArMkYngXU3R ++/Ng3WRLkgTFi5L4mj+Fba1K +-----END PRIVATE KEY----- diff --git a/tests/tlstest.py b/tests/tlstest.py index e0063b96..2d57780f 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -126,6 +126,19 @@ def connect(): test_no += 1 + print("Test {0} - good X.509/w RSA-PSS sig".format(test_no)) + synchro.recv(1) + connection = connect() + connection.handshakeClientCert(serverName=address[0]) + testConnClient(connection) + assert(isinstance(connection.session.serverCertChain, X509CertChain)) + assert(connection.session.serverName == address[0]) + assert(connection.session.cipherSuite in constants.CipherSuite.aeadSuites) + assert(connection.encryptThenMAC == False) + connection.close() + + test_no += 1 + print("Test {0} - good X509, SSLv3".format(test_no)) synchro.recv(1) connection = connect() @@ -757,6 +770,12 @@ def connect(): s = open(os.path.join(dir, "serverX509Key.pem")).read() x509Key = parsePEMKey(s, private=True) + with open(os.path.join(dir, "serverRSAPSSSigCert.pem")) as f: + x509CertRSAPSSSig = X509().parse(f.read()) + x509ChainRSAPSSSig = X509CertChain([x509CertRSAPSSSig]) + with open(os.path.join(dir, "serverRSAPSSSigKey.pem")) as f: + x509KeyRSAPSSSig = parsePEMKey(f.read(), private=True) + test_no = 0 print("Test {0} - Anonymous server handshake".format(test_no)) @@ -779,6 +798,18 @@ def connect(): test_no += 1 + print("Test {0} - good X.509/w RSA-PSS sig".format(test_no)) + synchro.send(b'R') + connection = connect() + connection.handshakeServer(certChain=x509ChainRSAPSSSig, + privateKey=x509KeyRSAPSSSig) + assert(connection.session.serverName == address[0]) + assert(connection.extendedMasterSecret) + testConnServer(connection) + connection.close() + + test_no += 1 + print("Test {0} - good X.509, SSL v3".format(test_no)) synchro.send(b'R') connection = connect() From 4f7f0cc6b756140a134b7a781c0ed2a5ddbd5ce5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 8 Mar 2017 16:27:07 +0100 Subject: [PATCH 434/574] support for rsassa-pss certificates and keys --- tlslite/utils/python_rsakey.py | 5 +- tlslite/x509.py | 11 +++- unit_tests/test_tlslite_utils_keyfactory.py | 60 +++++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/tlslite/utils/python_rsakey.py b/tlslite/utils/python_rsakey.py index 1281f1dc..0bcd838b 100644 --- a/tlslite/utils/python_rsakey.py +++ b/tlslite/utils/python_rsakey.py @@ -104,7 +104,10 @@ def _parsePKCS8(bytes): raise SyntaxError("Unrecognized PKCS8 version") rsaOID = p.getChild(1).value - if list(rsaOID) != [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]: + if list(rsaOID) == [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]\ + or list(rsaOID) == [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 10]: + pass + else: raise SyntaxError("Unrecognized AlgorithmIdentifier") #Get the privateKey diff --git a/tlslite/x509.py b/tlslite/x509.py index 94dd00d8..1aa6ea01 100644 --- a/tlslite/x509.py +++ b/tlslite/x509.py @@ -23,12 +23,17 @@ class X509(object): @type subject: L{bytearray} of unsigned bytes @ivar subject: The DER-encoded ASN.1 subject distinguished name. + + @type certAlg: str + @ivar certAlg: algorithm of the public key, "rsa" for RSASSA-PKCS#1 v1.5 + and "rsa-pss" for RSASSA-PSS """ def __init__(self): self.bytes = bytearray(0) self.publicKey = None self.subject = None + self.certAlg = None def parse(self, s): """Parse a PEM-encoded X.509 certificate. @@ -74,7 +79,11 @@ def parseBinary(self, bytes): #Get the algorithm algorithmP = subjectPublicKeyInfoP.getChild(0) rsaOID = algorithmP.value - if list(rsaOID) != [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]: + if list(rsaOID) == [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]: + self.certAlg = "rsa" + elif list(rsaOID) == [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 10]: + self.certAlg = "rsa-pss" + else: raise SyntaxError("Unrecognized AlgorithmIdentifier") #Get the subjectPublicKey diff --git a/unit_tests/test_tlslite_utils_keyfactory.py b/unit_tests/test_tlslite_utils_keyfactory.py index 2c0f334a..cf4b2c67 100644 --- a/unit_tests/test_tlslite_utils_keyfactory.py +++ b/unit_tests/test_tlslite_utils_keyfactory.py @@ -119,6 +119,43 @@ class TestParsePEMKey(unittest.TestCase): "rBjtgPGNR6tCjWMh0+2AUF5lTbXAPqECeV6MIvJXGpg=\n"\ "-----END RSA PRIVATE KEY-----\n"\ ) + # generated with: + # openssl req -x509 -newkey rsa-pss -keyout localhost.key + # -out localhost.crt -subj /CN=localhost -nodes -batch + # -config /etc/pki/tls/openssl.cnf -pkeyopt rsa_keygen_bits:1024 -sha256 + privRSAPSSKey_str = str( + "-----BEGIN PRIVATE KEY-----" + "MIICdQIBADALBgkqhkiG9w0BAQoEggJhMIICXQIBAAKBgQCn8gxBHPCDZIWDXOuJ" + "jDv2/sLFrtnrwrHVaHRKvJQ4La5X6juRb6SoStMmhgfBiQHqLN7CphcjqhU5G5u1" + "3GRWd8PsauSQWfAVeT7AO99PwlTsR3oigN4HfaBkEXpDcUdxw0CapjQFEeVD14Ds" + "ylqGxuX63FZAoY7fSNW9xInqOQIDAQABAoGAEk1YdIgY1djQi/5GVNkJd+NPioeB" + "jCXNh3o4oiRm6rBfvYjzMOg/w29UD3Cvy7GImeKF7CR5hRN1+KE/mNQJww1cPe2X" + "DZ7VlWqg4zuXFxOjL4qA+crk4Th7KQhOWmjbB4dtRAa/YJSpQR0a0NMvKPXhvwxy" + "Mj+lLgCycy14lzkCQQDRQlseMlc3VudfNo2ei2PkuOG+za4PoBEumsC7dg2+Sxvv" + "JkXEGdJ9DZGxqZTI4Q4OtFZ7PTwAvHgmvyyI03E3AkEAzXVVlsl6hOl2Wy+hpKDk" + "GOL4er9eubHzP70bSkgSvlUkxvSSP4ixnLv14XPqCRLzoMxQEQxymq1aO87iGc/4" + "DwJBAIM2fngSzMlwfqgfRvHxKXQT0cmYoto9XkjA1LU3MyrtYdi1QO3T2z56sa6b" + "TSYgqHXj8o5YOTWk+BojqcMqAkUCQBSg1zsQd5CosA1vttcEoGIvR6trU2Npjnaz" + "0e2fVuJtQggHvjdKzipiZMmCDdljYbqfSNqtWURWa1zd5K2ax9kCQQC7Eg+ktzi3" + "1wAXDgXMdW+TsDPBHrRqRGzFXKe83e05/nVc8EwiS0mYdkpblm+uzUqiSsa20Guo" + "Xf3/znMC6LAS" + "-----END PRIVATE KEY-----") + certRSAPSS_str = str( + "-----BEGIN CERTIFICATE-----" + "MIICVDCCAY2gAwIBAgIJANyAcqPhR4KqMD0GCSqGSIb3DQEBCjAwoA0wCwYJYIZI" + "AWUDBAIBoRowGAYJKoZIhvcNAQEIMAsGCWCGSAFlAwQCAaIDAgFeMBQxEjAQBgNV" + "BAMMCWxvY2FsaG9zdDAeFw0xNzAzMDgxNjExMTRaFw0xNzA0MDcxNjExMTRaMBQx" + "EjAQBgNVBAMMCWxvY2FsaG9zdDCBnTALBgkqhkiG9w0BAQoDgY0AMIGJAoGBAKfy" + "DEEc8INkhYNc64mMO/b+wsWu2evCsdVodEq8lDgtrlfqO5FvpKhK0yaGB8GJAeos" + "3sKmFyOqFTkbm7XcZFZ3w+xq5JBZ8BV5PsA730/CVOxHeiKA3gd9oGQRekNxR3HD" + "QJqmNAUR5UPXgOzKWobG5frcVkChjt9I1b3Eieo5AgMBAAGjUDBOMB0GA1UdDgQW" + "BBRWzEKe6ouCtJLTw2rlmBUL5GzQwTAfBgNVHSMEGDAWgBRWzEKe6ouCtJLTw2rl" + "mBUL5GzQwTAMBgNVHRMEBTADAQH/MD0GCSqGSIb3DQEBCjAwoA0wCwYJYIZIAWUD" + "BAIBoRowGAYJKoZIhvcNAQEIMAsGCWCGSAFlAwQCAaIDAgFeA4GBABxQF45TtTZk" + "Fg449AsS+ISz/wJ7QlvYQRhKCRTqf5AOiW4vS1u8ojyIxS7+e5XMfibH4tYRhc7t" + "c+Grb0yGpyTfKeKn1XpYPs0LGHrSzFjE66fI3IwLs081yOPiboEwtfk89YP2eJ3K" + "8QRoPc5B+fSXFVJbBZdxR4audnIv5FM7" + "-----END CERTIFICATE-----") @unittest.skipIf(cryptomath.m2cryptoLoaded, "requires no M2Crypto") def test_with_missing_m2crypto(self): @@ -210,3 +247,26 @@ def test_rsa_key_parse_with_new_lines_using_python(self): self.assertIsInstance(key, RSAKey) self.assertEqual(1024, len(key)) self.assertTrue(key.hasPrivateKey()) + + @unittest.skipUnless(cryptomath.m2cryptoLoaded, "requires M2Crypto") + @unittest.expectedFailure + def test_rsa_pss_key_parse_using_openssl(self): + try: + key = parsePEMKey(self.privRSAPSSKey_str, + private=True, + implementations=["openssl"]) + except SyntaxError: + self.fail("Unexpected exception raised") + + self.assertIsInstance(key, RSAKey) + self.assertEqual(1024, len(key)) + self.assertTrue(key.hasPrivateKey()) + + def test_rsa_pss_key_parse_using_python(self): + key = parsePEMKey(self.privRSAPSSKey_str, + private=True, + implementations=["python"]) + + self.assertIsInstance(key, RSAKey) + self.assertEqual(1024, len(key)) + self.assertTrue(key.hasPrivateKey()) From 4e6e348a62c9a161392c04cecafe1338a927feaa Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 8 Mar 2017 16:28:14 +0100 Subject: [PATCH 435/574] test with RSA-PSS certificates --- tests/serverRSAPSSCert.pem | 20 ++++++++++++++++++++ tests/serverRSAPSSKey.pem | 28 ++++++++++++++++++++++++++++ tests/tlstest.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 tests/serverRSAPSSCert.pem create mode 100644 tests/serverRSAPSSKey.pem diff --git a/tests/serverRSAPSSCert.pem b/tests/serverRSAPSSCert.pem new file mode 100644 index 00000000..83e61f60 --- /dev/null +++ b/tests/serverRSAPSSCert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWzCCAhKgAwIBAgIJAM94DjB2Qf+GMD4GCSqGSIb3DQEBCjAxoA0wCwYJYIZI +AWUDBAIBoRowGAYJKoZIhvcNAQEIMAsGCWCGSAFlAwQCAaIEAgIA3jAUMRIwEAYD +VQQDDAlsb2NhbGhvc3QwHhcNMTcwMzA4MTMzMzU4WhcNMTcwNDA3MTMzMzU4WjAU +MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEgMAsGCSqGSIb3DQEBCgOCAQ8AMIIBCgKC +AQEApq5FnZRNmtJy+WjNRB8w0ol2+IEcATrUkZpz7HNqq8+EL/GN21m35luz5fcA +8ifkm4pKxfwtxY7u912tAfsEavUr/UoyLScXQevhr6SLXR8UO3XE6ne1F88eZNpg +KVpocDVKRLjIqKHSSbBNkG76mGKFYyNZXm3qRxaUSHVLnN/opOGfgVK8Vbj0v2EH +5L5dGLQLV2ZfQ75I4OGrg8wse5fhgt2oczhhe72sEuMDc08WHGRp9744mMQvVYrt +pWUHkP5tdD4i7/zYf3nig21/G56JY20JpW+1J5fEn+PEqiJsv5prNJuPp/zdjD3u +ImqfXZzXUeRIQr9l1qXI6JPInwIDAQABo1AwTjAdBgNVHQ4EFgQUcTYhLu7pODIv +B6KhR6eyFBB5wacwHwYDVR0jBBgwFoAUcTYhLu7pODIvB6KhR6eyFBB5wacwDAYD +VR0TBAUwAwEB/zA+BgkqhkiG9w0BAQowMaANMAsGCWCGSAFlAwQCAaEaMBgGCSqG +SIb3DQEBCDALBglghkgBZQMEAgGiBAICAN4DggEBAKMgweHM6WTwlWEQHLG5K+7B +hrAUEAsuK8F7sKGKzLEFzYdzZpkJw8LahE4dFayjx/7MD4rZ5IiHQhJcGCdHIVVv +ocunlEUTgiKkMxTw4JxqSq0snvNBie04vnn+zUjD7FrctTUutzlH1yKftwbJpGk6 +CrTW6ctFTAIDwZHd+WX4RPewGY0LTfC+RjcMwWZBmbfVLxuJs0sidSUoNW6GgGE1 +DIDVeW2yKGeNhjK/3aDzfQWbz1J64aRfccVzXYMPsoABnNJnJgRETh1/Ci0sQ9Vd +1OR6iS4hl88/1d7utc00MyFVk1sUIGf54EeCvrNB4bhKtawEJk8Q8AGIRhs93sk= +-----END CERTIFICATE----- diff --git a/tests/serverRSAPSSKey.pem b/tests/serverRSAPSSKey.pem new file mode 100644 index 00000000..dfb447a0 --- /dev/null +++ b/tests/serverRSAPSSKey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEugIBADALBgkqhkiG9w0BAQoEggSmMIIEogIBAAKCAQEApq5FnZRNmtJy+WjN +RB8w0ol2+IEcATrUkZpz7HNqq8+EL/GN21m35luz5fcA8ifkm4pKxfwtxY7u912t +AfsEavUr/UoyLScXQevhr6SLXR8UO3XE6ne1F88eZNpgKVpocDVKRLjIqKHSSbBN +kG76mGKFYyNZXm3qRxaUSHVLnN/opOGfgVK8Vbj0v2EH5L5dGLQLV2ZfQ75I4OGr +g8wse5fhgt2oczhhe72sEuMDc08WHGRp9744mMQvVYrtpWUHkP5tdD4i7/zYf3ni +g21/G56JY20JpW+1J5fEn+PEqiJsv5prNJuPp/zdjD3uImqfXZzXUeRIQr9l1qXI +6JPInwIDAQABAoIBAA0BqFkFi5C7P1GLBgl8zZiANZJqsL6/0nqSLN2UnAxQIyaC +mOk29Qy6ty0Iu0AqMMKaZf9REFlMMAWJf8iZx9x4yTf4pDW1yTDRsUi9dEqN9Ew3 +gmgxcyYqeVqxV7OiZGftIKCAMthF2Fz7rvHIVzGw7muwBHdD6HYnouaMkJvrFLkW +a41VKi2oJJA4ZXrxHORm9lfAfnvoJVIRvG9z9NDMvi+PBx/wSdFwlVXhSjVnYuTH +laaYBUaH7D9BL8O1aVIRLCDw3Q/4ciTHGByI+6Iremk9nRZEO5igYlK427eKIRGW +lvvy+/+EXPiVwWX9V11CDWm2hOTWYs8wNE7fsSECgYEA2h+gK81yGTpR3/SXxW3i +ojRXXLVxZpi94ZUAvBmOgb+wZQeHWDO0dN37MwAhimHrWsaBEezVKVj6ntBU3Je2 +oC+MjLxDaTDvTsvuKvh4zhuiUGcY+XfP9yv9HX3U8Ys3GISJ4HdOBLsISA8zJs+D +vNC6Be/ez9uORb9jfDBG9BcCgYEAw5/UZGWmZLFcwhO5QX8JytXAj9xiMANGBhJb +wQBMEgRpSgHvKI2i32oUOuCQz7wcIgwtgmIhCBz8ld4ky6CYOfQXj+sW9V/drRTl +4M9H+wdwOsB0/ELIZYlFZ82zMgMYJrEFGZR05DSFbeUHEzm8RG9hbsdxkRBtHQIv +AJOoPLkCgYAJZUlZ+ayLh6aVNgz/lR8pC4Yj2TD8UWIEgI2ajKNF1YL8pxleZEPG +sPUsGjpXoqYnr9tJcWExOcL56lFtex+DwOiV+1oQAuqcA07MDQ3vGuOgAQDjZhTQ +OdXaWlw811lVNghWYe07aO8PY5A5gMDU9ky9CrsXSwbS3E6lv9KemwKBgBhjEm05 +ptairbecEdoyZhwdLZZBmRP3NIGJRFr5GIKefim1uATMM2O6q67zU9oxzygHcJzy +cr+6LVrZiKjB6ng/D7jnS8NnIhFzq3ytGoIW2UzZtTvFb4oI5Ngd8prne9lG9CXO +NgxE5+VdSdaBuhCl+fV/c47sB044eXeO8MgxAoGAQUL40ZtfXrbPHBjHwsEHf8hS +XUPtd3cVyPZigz+6P3Cr54GvicRaoaYeUt2zrbjqgiX/bAW/Xq6Pu+UpDyCQ6Er5 +OvDrbz1v5sfhn3Eubh2a4LZy7EiKveTtOpmqFs6XZ1FYoMSdeMr44Mql8G2MGa2d +n15sR5bRKF3dVy2qO0A= +-----END PRIVATE KEY----- diff --git a/tests/tlstest.py b/tests/tlstest.py index 2d57780f..11700a95 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -126,6 +126,19 @@ def connect(): test_no += 1 + print("Test {0} - good X.509/w RSA-PSS cert".format(test_no)) + synchro.recv(1) + connection = connect() + connection.handshakeClientCert(serverName=address[0]) + testConnClient(connection) + assert(isinstance(connection.session.serverCertChain, X509CertChain)) + assert(connection.session.serverName == address[0]) + assert(connection.session.cipherSuite in constants.CipherSuite.aeadSuites) + assert(connection.encryptThenMAC == False) + connection.close() + + test_no += 1 + print("Test {0} - good X.509/w RSA-PSS sig".format(test_no)) synchro.recv(1) connection = connect() @@ -776,6 +789,14 @@ def connect(): with open(os.path.join(dir, "serverRSAPSSSigKey.pem")) as f: x509KeyRSAPSSSig = parsePEMKey(f.read(), private=True) + with open(os.path.join(dir, "serverRSAPSSCert.pem")) as f: + x509CertRSAPSS = X509().parse(f.read()) + x509ChainRSAPSS = X509CertChain([x509CertRSAPSS]) + assert x509CertRSAPSS.certAlg == "rsa-pss" + with open(os.path.join(dir, "serverRSAPSSKey.pem")) as f: + x509KeyRSAPSS = parsePEMKey(f.read(), private=True, + implementations=["python"]) + test_no = 0 print("Test {0} - Anonymous server handshake".format(test_no)) @@ -810,6 +831,18 @@ def connect(): test_no += 1 + print("Test {0} - good X.509/w RSA-PSS cert".format(test_no)) + synchro.send(b'R') + connection = connect() + connection.handshakeServer(certChain=x509ChainRSAPSS, + privateKey=x509KeyRSAPSS) + assert(connection.session.serverName == address[0]) + assert(connection.extendedMasterSecret) + testConnServer(connection) + connection.close() + + test_no += 1 + print("Test {0} - good X.509, SSL v3".format(test_no)) synchro.send(b'R') connection = connect() From bf01e05b7345ff1cbfb2d9af0e43cfd0d9ae32ea Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 8 Mar 2017 16:28:32 +0100 Subject: [PATCH 436/574] support rsa-pss in tls.py script --- scripts/tls.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/tls.py b/scripts/tls.py index 501e5298..b9f4b92e 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -122,7 +122,9 @@ def handleArgs(argv, argString, flagsList=[]): s = open(arg, "rb").read() if sys.version_info[0] >= 3: s = str(s, 'utf-8') - privateKey = parsePEMKey(s, private=True) + # OpenSSL/m2crypto does not support RSASSA-PSS certificates + privateKey = parsePEMKey(s, private=True, + implementations=["python"]) elif opt == "-c": s = open(arg, "rb").read() if sys.version_info[0] >= 3: From b511fc1f62b6011bdbe48f593a6ac8b3d8de5abb Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 8 Mar 2017 17:04:10 +0100 Subject: [PATCH 437/574] do not use pkcs#1 scheme with rsa-pss certificates --- tlslite/tlsconnection.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 98e13323..1a2f7cfa 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -905,7 +905,8 @@ def _clientKeyExchange(self, settings, cipherSuite, #Check the server's signature, if the server chose an authenticated # PFS-enabled ciphersuite if serverKeyExchange: - validSigAlgs = self._sigHashesToList(settings) + validSigAlgs = self._sigHashesToList(settings, + certList=serverCertChain) try: KeyExchange.verifyServerKeyExchange(serverKeyExchange, publicKey, @@ -996,7 +997,8 @@ def _clientKeyExchange(self, settings, cipherSuite, #if client auth was requested and we have a private key, send a #CertificateVerify if certificateRequest and privateKey: - validSigAlgs = self._sigHashesToList(settings, privateKey) + validSigAlgs = self._sigHashesToList(settings, privateKey, + clientCertChain) try: certificateVerify = KeyExchange.makeCertificateVerify( self.version, @@ -1809,7 +1811,8 @@ def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, verifierDB) try: - sigHash = self._pickServerKeyExchangeSig(settings, clientHello) + sigHash = self._pickServerKeyExchangeSig(settings, clientHello, + serverCertChain) except TLSHandshakeFailure as alert: for result in self._sendError( AlertDescription.handshake_failure, @@ -1866,7 +1869,8 @@ def _serverCertKeyExchange(self, clientHello, serverHello, msgs.append(serverHello) msgs.append(Certificate(CertificateType.x509).create(serverCertChain)) try: - sigHashAlg = self._pickServerKeyExchangeSig(settings, clientHello) + sigHashAlg = self._pickServerKeyExchangeSig(settings, clientHello, + serverCertChain) except TLSHandshakeFailure as alert: for result in self._sendError( AlertDescription.handshake_failure, @@ -2187,7 +2191,7 @@ def _handshakeWrapperAsync(self, handshaker, checker): raise @staticmethod - def _pickServerKeyExchangeSig(settings, clientHello): + def _pickServerKeyExchangeSig(settings, clientHello, certList=None): """Pick a hash that matches most closely the supported ones""" hashAndAlgsExt = clientHello.getExtension(\ ExtensionType.signature_algorithms) @@ -2197,7 +2201,8 @@ def _pickServerKeyExchangeSig(settings, clientHello): # sha1 should be picked return "sha1" - supported = TLSConnection._sigHashesToList(settings) + supported = TLSConnection._sigHashesToList(settings, + certList=certList) for schemeID in supported: if schemeID in hashAndAlgsExt.sigalgs: @@ -2212,11 +2217,19 @@ def _pickServerKeyExchangeSig(settings, clientHello): raise TLSHandshakeFailure("No common signature algorithms") @staticmethod - def _sigHashesToList(settings, privateKey=None): + def _sigHashesToList(settings, privateKey=None, certList=None): """Convert list of valid signature hashes to array of tuples""" + certType = None + if certList: + certType = certList.x509List[0].certAlg + sigAlgs = [] for schemeName in settings.rsaSchemes: for hashName in settings.rsaSigHashes: + # rsa-pss certificates can't be used to make PKCS#1 v1.5 + # signatures + if certType == "rsa-pss" and schemeName == "pkcs1": + continue try: # 1024 bit keys are too small to create valid # rsa-pss-SHA512 signatures From e3b2b5dbdfaebc0b7ddb937be6cf18cd473c8bbd Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 9 Mar 2017 16:36:25 +0100 Subject: [PATCH 438/574] fix off by one error in RSA-PSS verify step 10 in Section 9.1.2. statest that it's emLen - hLen - sLen - 2 leftmost octets that need to be zero, but python's subscripts are righ-exclusive and start with zero so we need to check up to that number exactly --- tlslite/utils/rsakey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/utils/rsakey.py b/tlslite/utils/rsakey.py index db565d2e..394cee5c 100644 --- a/tlslite/utils/rsakey.py +++ b/tlslite/utils/rsakey.py @@ -232,7 +232,7 @@ def EMSA_PSS_verify(self, mHash, EM, emBits, hAlg, sLen=0): mLen = emLen*8 - emBits mask = (1 << 8 - mLen) - 1 DB[0] &= mask - if any(x != 0 for x in DB[0:emLen - hashLen - sLen - 2 - 1]): + if any(x != 0 for x in DB[0:emLen - hashLen - sLen - 2]): raise InvalidSignature("Invalid signature") if DB[emLen - hashLen - sLen - 2] != 0x01: raise InvalidSignature("Invalid signature") From 7a13e415acf97f048a74aafc78b917aed1e90607 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 13 Mar 2017 20:16:20 +0100 Subject: [PATCH 439/574] parse rsa-pss parameters with restrictions as RSA-PSS parameters may have restrictions on used hash algorithm, salt length, etc. the actual encoding of AlgorithmIdentifier can be variable - ignore the parameters for the rsa-pss Alg Id's --- tlslite/utils/asn1parser.py | 18 +++++++++++++++++ tlslite/utils/python_rsakey.py | 35 +++++++++++++++++++++++++++------- tlslite/x509.py | 24 ++++++++++++++++++----- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/tlslite/utils/asn1parser.py b/tlslite/utils/asn1parser.py index 9bcb3b38..dd3e3593 100644 --- a/tlslite/utils/asn1parser.py +++ b/tlslite/utils/asn1parser.py @@ -45,6 +45,24 @@ def getChild(self, which): """ return ASN1Parser(self.getChildBytes(which)) + def getChildCount(self): + """ + Return number of children, assuming that the object is a SEQUENCE. + + @rtype: int + @return number of children in the object + """ + p = Parser(self.value) + count = 0 + while True: + if p.getRemainingLength() == 0: + break + p.get(1) # skip Type + length = self._getASN1Length(p) + p.getFixBytes(length) # skip value + count += 1 + return count + def getChildBytes(self, which): """ Return raw encoding of n-th child, assume self is a SEQUENCE diff --git a/tlslite/utils/python_rsakey.py b/tlslite/utils/python_rsakey.py index 0bcd838b..17d0bf00 100644 --- a/tlslite/utils/python_rsakey.py +++ b/tlslite/utils/python_rsakey.py @@ -99,16 +99,37 @@ def parsePEM(s, passwordCallback=None): def _parsePKCS8(bytes): p = ASN1Parser(bytes) - version = p.getChild(0).value[0] - if version != 0: + # first element in PrivateKeyInfo is an INTEGER + version = p.getChild(0).value + if bytesToNumber(version) != 0: raise SyntaxError("Unrecognized PKCS8 version") - rsaOID = p.getChild(1).value - if list(rsaOID) == [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]\ - or list(rsaOID) == [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 10]: - pass + # second element in PrivateKeyInfo is a SEQUENCE of type + # AlgorithmIdentifier + algIdent = p.getChild(1) + seqLen = algIdent.getChildCount() + # first item of AlgorithmIdentifier is an OBJECT (OID) + oid = algIdent.getChild(0) + if list(oid.value) == [42, 134, 72, 134, 247, 13, 1, 1, 1]: + keyType = "rsa" + elif list(oid.value) == [42, 134, 72, 134, 247, 13, 1, 1, 10]: + keyType = "rsa-pss" else: - raise SyntaxError("Unrecognized AlgorithmIdentifier") + raise SyntaxError("Unrecognized AlgorithmIdentifier: {0}" + .format(list(oid.value))) + # second item of AlgorithmIdentifier are parameters (defined by + # above algorithm) + if keyType == "rsa": + if seqLen != 2: + raise SyntaxError("Missing parameters for RSA algorith ID") + parameters = algIdent.getChild(1) + if parameters.value != bytearray(0): + raise SyntaxError("RSA parameters are not NULL") + else: # rsa-pss + pass # ignore parameters - don't apply restrictions + + if seqLen > 2: + raise SyntaxError("Invalid encoding of AlgorithmIdentifier") #Get the privateKey privateKeyP = p.getChild(2) diff --git a/tlslite/x509.py b/tlslite/x509.py index 1aa6ea01..3245406a 100644 --- a/tlslite/x509.py +++ b/tlslite/x509.py @@ -76,16 +76,30 @@ def parseBinary(self, bytes): subjectPublicKeyInfoP = tbsCertificateP.getChild(\ subjectPublicKeyInfoIndex) - #Get the algorithm - algorithmP = subjectPublicKeyInfoP.getChild(0) - rsaOID = algorithmP.value - if list(rsaOID) == [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]: + # Get the AlgorithmIdentifier + algIdentifier = subjectPublicKeyInfoP.getChild(0) + algIdentifierLen = algIdentifier.getChildCount() + # first item of AlgorithmIdentifier is the algorithm + alg = algIdentifier.getChild(0) + rsaOID = alg.value + if list(rsaOID) == [42, 134, 72, 134, 247, 13, 1, 1, 1]: self.certAlg = "rsa" - elif list(rsaOID) == [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 10]: + elif list(rsaOID) == [42, 134, 72, 134, 247, 13, 1, 1, 10]: self.certAlg = "rsa-pss" else: raise SyntaxError("Unrecognized AlgorithmIdentifier") + # for RSA the parameters of AlgorithmIdentifier should be a NULL + if self.certAlg == "rsa": + if algIdentifierLen != 2: + raise SyntaxError("Missing parameters in AlgorithmIdentifier") + params = algIdentifier.getChild(1) + if params.value != bytearray(0): + raise SyntaxError("Unexpected non-NULL parameters in " + "AlgorithmIdentifier") + else: # rsa-pss + pass # ignore parameters, if any - don't apply key restrictions + #Get the subjectPublicKey subjectPublicKeyP = subjectPublicKeyInfoP.getChild(1) From 88e3516d913a84df8ff51dd97c83d286a38f3afd Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 13 Jul 2017 14:23:24 +0200 Subject: [PATCH 440/574] reject non-empty EMS extension --- tlslite/tlsconnection.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index d1e87043..f57e9c92 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1555,6 +1555,15 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, for result in self._sendMsg(alert): yield result + # sanity check the EMS extension + emsExt = clientHello.getExtension(ExtensionType.extended_master_secret) + if emsExt and emsExt.extData: + for result in self._sendError( + AlertDescription.decode_error, + "Non empty payload of the Extended " + "Master Secret extension"): + yield result + #If client's version is too high, propose my highest version elif clientHello.client_version > settings.maxVersion: self.version = settings.maxVersion From 9dd6c24ed483724eccea5076e76a12e0120e616c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 13 Jul 2017 14:25:31 +0200 Subject: [PATCH 441/574] negotiate ECDHE even if no curves advertised by client, assume prime256v1 --- tlslite/keyexchange.py | 10 +++++++--- tlslite/tlsconnection.py | 4 +++- unit_tests/test_tlslite_keyexchange.py | 9 ++++++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 577e9a38..f710b386 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -386,10 +386,14 @@ def makeServerKeyExchange(self, sigHash=None): #Get client supported groups client_curves = self.clientHello.getExtension(\ ExtensionType.supported_groups) - if client_curves is None or client_curves.groups is None or \ - len(client_curves.groups) == 0: + if client_curves is None: + # in case there is no extension, we can pick any curve, assume + # the most common + client_curves = [GroupName.secp256r1] + elif not client_curves.groups: raise TLSInternalError("Can't do ECDHE with no client curves") - client_curves = client_curves.groups + else: + client_curves = client_curves.groups #Pick first client preferred group we support self.group_id = getFirstMatching(client_curves, self.acceptedCurves) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index f57e9c92..54aeae2b 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1582,7 +1582,9 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, #Check if there's intersection between supported curves by client and #server client_groups = clientHello.getExtension(ExtensionType.supported_groups) - group_intersect = [] + # in case the client didn't advertise any curves, we can pick any, + # but limit the choice to the most common + group_intersect = [GroupName.secp256r1] # if there is no extension, then allow DHE ffgroup_intersect = [GroupName.ffdhe2048] if client_groups is not None: diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index ec361f75..8f23ac24 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -19,7 +19,7 @@ from tlslite.messages import ServerHello, ClientHello, ServerKeyExchange,\ CertificateRequest, ClientKeyExchange from tlslite.constants import CipherSuite, CertificateType, AlertDescription, \ - HashAlgorithm, SignatureAlgorithm, GroupName + HashAlgorithm, SignatureAlgorithm, GroupName, ECCurveType from tlslite.errors import TLSLocalAlert, TLSIllegalParameterException, \ TLSDecryptionFailed, TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ TLSInternalError @@ -1133,8 +1133,11 @@ def test_ECDHE_key_exchange_with_invalid_CKE(self): def test_ECDHE_key_exchange_with_missing_curves(self): self.client_hello.extensions = [SNIExtension().create(bytearray(b"a"))] - with self.assertRaises(TLSInternalError): - self.keyExchange.makeServerKeyExchange('sha1') + + ske = self.keyExchange.makeServerKeyExchange('sha1') + + self.assertEqual(ske.curve_type, ECCurveType.named_curve) + self.assertEqual(ske.named_curve, GroupName.secp256r1) def test_ECDHE_key_exchange_with_no_mutual_curves(self): ext = SupportedGroupsExtension().create([GroupName.secp160r1]) From 4fbc44271d55ab335dc95c69c258387b2f2dd991 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 13 Jul 2017 17:17:25 +0200 Subject: [PATCH 442/574] fix typo in message --- tlslite/utils/python_rsakey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/utils/python_rsakey.py b/tlslite/utils/python_rsakey.py index 17d0bf00..e01df3e1 100644 --- a/tlslite/utils/python_rsakey.py +++ b/tlslite/utils/python_rsakey.py @@ -121,7 +121,7 @@ def _parsePKCS8(bytes): # above algorithm) if keyType == "rsa": if seqLen != 2: - raise SyntaxError("Missing parameters for RSA algorith ID") + raise SyntaxError("Missing parameters for RSA algorithm ID") parameters = algIdent.getChild(1) if parameters.value != bytearray(0): raise SyntaxError("RSA parameters are not NULL") From 31c7ee1634b506d2a928c2c932335511d15b3220 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 13 Jul 2017 19:11:48 +0200 Subject: [PATCH 443/574] release v0.7.0-alpha6 --- README | 5 +++-- README.md | 9 +++++++-- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README b/README index d258c582..e383ad87 100644 --- a/README +++ b/README @@ -12,8 +12,9 @@ Functionality implemented include: - MD5, SHA1, SHA256 and SHA384 HMACs as well as AEAD mode of operation with GCM or Poly1305 authenticator - RSA, DHE_RSA and ECDHE_RSA key exchange - - full set of signature hashes (md5, sha1, sha224, sha256, sha384 and sha512) - for ServerKeyExchange and CertfificateVerify in TLS v1.2 + - full set of signature hashes (md5, sha1, sha224, sha256, sha384, sha512, + rsa_pss_sha256, rsa_pss_sha384 and rsa_pss_sha512) for ServerKeyExchange + and CertfificateVerify in TLS v1.2 - secp256r1, secp384r1, secp521r1, secp256k1, secp224r1 and secp192r1 curves for ECDHE_RSA key exchange (support for last two depends on the version of ecdsa library used) diff --git a/README.md b/README.md index e4bd35fc..aad12854 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.7.0-alpha5 2017-06-30 +tlslite-ng version 0.7.0-alpha6 2017-07-13 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -57,6 +57,7 @@ Implemented features of TLS include: * Extended master secret * padding extension * keying material exporter +* RSA-PSS signatures in TLSv1.2, RSA-PSS in certificates (TLSv1.3 extension) * (experimental) TACK extension 2 Licenses/Acknowledgements @@ -587,6 +588,7 @@ encrypt-then-MAC mode for CBC ciphers. 0.7.0 - in-dev * basic support for RSA-PSS (Tomas Foukal) +* support for RSA-PSS in TLSv1.2 * better documentation for Parser and ASN1Parser * stricter checks on network messages * faster Codec (faster encoding of messages to binary format) @@ -601,7 +603,10 @@ encrypt-then-MAC mode for CBC ciphers. * fix interoperability issue in DHE key exchange (failure happening in about 1 in 256 negotiations) caused by handling of Server Key Exchange messages * Fix incorrect handling of Extended Master Secret with client certificates, - follow RFC recommendations with regards to session resumption. + follow RFC recommendations with regards to session resumption, reject + non-empty +* Allow negotiation of ECDHE ciphersuites even if client doesn't advertise + any curves, assume P-256 curve support. 0.6.0 - 2016-09-07 diff --git a/setup.py b/setup.py index 6754b6c2..02b90d3a 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup(name="tlslite-ng", - version="0.7.0-alpha5", + version="0.7.0-alpha6", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 5637431f..21f6d0a2 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.7.0-alpha5 +@version: 0.7.0-alpha6 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 7af0ca10..9c769dd7 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.7.0-alpha5" +__version__ = "0.7.0-alpha6" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 8b2aca2c080855496439d5958061fc331ade8688 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 14 Jul 2017 12:38:31 +0200 Subject: [PATCH 444/574] make ECDHE and FFDHE ciphersuite enabling more logical --- tlslite/tlsconnection.py | 45 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 53851c34..46f5de8e 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -23,6 +23,7 @@ from .constants import * from .utils.cryptomath import getRandomBytes from .utils.dns_utils import is_valid_hostname +from .utils.lists import getFirstMatching from .errors import * from .messages import * from .mathtls import * @@ -1583,29 +1584,27 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, #Check if there's intersection between supported curves by client and #server - client_groups = clientHello.getExtension(ExtensionType.supported_groups) - # in case the client didn't advertise any curves, we can pick any, - # but limit the choice to the most common - group_intersect = [GroupName.secp256r1] - # if there is no extension, then allow DHE - ffgroup_intersect = [GroupName.ffdhe2048] - if client_groups is not None: - client_groups = client_groups.groups - if client_groups is None: - client_groups = [] - server_groups = self._curveNamesToList(settings) - group_intersect = [x for x in client_groups if x in server_groups] + clientGroups = clientHello.getExtension(ExtensionType.supported_groups) + # in case the client didn't advertise any curves, we can pick any so + # enable ECDHE + ecGroupIntersect = True + # if there is no extension, then enable DHE + ffGroupIntersect = True + if clientGroups is not None: + clientGroups = clientGroups.groups + serverGroups = self._curveNamesToList(settings) + ecGroupIntersect = getFirstMatching(clientGroups, serverGroups) # RFC 7919 groups - server_groups = self._groupNamesToList(settings) - ffgroup_intersect = [i for i in client_groups - if i in server_groups] + serverGroups = self._groupNamesToList(settings) + ffGroupIntersect = getFirstMatching(clientGroups, serverGroups) # if there is no overlap, but there are no FFDHE groups listed, # allow DHE, prohibit otherwise - if not ffgroup_intersect: - if any(i for i in client_groups if i in range(256, 512)): - ffgroup_intersect = [] + if not ffGroupIntersect: + if clientGroups and \ + any(i for i in clientGroups if i in range(256, 512)): + ffGroupIntersect = False else: - ffgroup_intersect = [GroupName.ffdhe2048] + ffGroupIntersect = True #Now that the version is known, limit to only the ciphers available to @@ -1617,10 +1616,10 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, CipherSuite.getSrpCertSuites(settings, self.version) cipherSuites += CipherSuite.getSrpSuites(settings, self.version) elif certChain: - if group_intersect: + if ecGroupIntersect: cipherSuites += CipherSuite.getEcdheCertSuites(settings, self.version) - if ffgroup_intersect: + if ffGroupIntersect: cipherSuites += CipherSuite.getDheCertSuites(settings, self.version) cipherSuites += CipherSuite.getCertSuites(settings, self.version) @@ -1777,8 +1776,8 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, if cipherSuite in clientHello.cipher_suites: break else: - if client_groups and \ - any(i in range(256, 512) for i in client_groups) and \ + if clientGroups and \ + any(i in range(256, 512) for i in clientGroups) and \ any(i in CipherSuite.dhAllSuites for i in clientHello.cipher_suites): for result in self._sendError( From 059328ee2a313c7bcd530c0ab2e7ee0e811caae7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 14 Jul 2017 13:47:48 +0200 Subject: [PATCH 445/574] correctly handle errors from RSA_PSS verification --- tlslite/utils/rsakey.py | 13 +++--- unit_tests/test_tlslite_utils_rsakey.py | 60 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/tlslite/utils/rsakey.py b/tlslite/utils/rsakey.py index 394cee5c..87ccdc06 100644 --- a/tlslite/utils/rsakey.py +++ b/tlslite/utils/rsakey.py @@ -265,7 +265,7 @@ def RSASSA_PSS_verify(self, mHash, S, hAlg, sLen=0): @param sLen: Length of salt """ if len(bytearray(S)) != len(numberToByteArray(self.n)): - raise InvalidSignature + raise InvalidSignature("Invalid signature") s = bytesToNumber(S) m = self._rawPublicKeyOp(s) EM = numberToByteArray(m, divceil(numBits(self.n) - 1, 8)) @@ -361,11 +361,14 @@ def verify(self, sigBytes, bytes, padding='pkcs1', hashAlg=None, elif padding == 'pkcs1': if hashAlg is not None: bytes = self.addPKCS1Prefix(bytes, hashAlg) - r = self._raw_pkcs1_verify(sigBytes, bytes) - return r + res = self._raw_pkcs1_verify(sigBytes, bytes) + return res elif padding == "pss": - r = self.RSASSA_PSS_verify(bytes, sigBytes, hashAlg, saltLen) - return r + try: + res = self.RSASSA_PSS_verify(bytes, sigBytes, hashAlg, saltLen) + except InvalidSignature: + res = False + return res else: raise UnknownRSAType("Unknown RSA algorithm type") diff --git a/unit_tests/test_tlslite_utils_rsakey.py b/unit_tests/test_tlslite_utils_rsakey.py index 92041de9..73331fa7 100644 --- a/unit_tests/test_tlslite_utils_rsakey.py +++ b/unit_tests/test_tlslite_utils_rsakey.py @@ -1447,6 +1447,38 @@ def test_hashAndVerify_PKCS1_sha512(self): self.assertTrue(self.rsa.hashAndVerify(sigBytes, self.message, "PKCS1", 'sha512')) + def test_verify_PKCS1_sha512(self): + sigBytes = bytearray( + b'\x8b\x57\xa6\xf9\x16\x06\xba\x48\x13\xb8\x35\x36\x58\x1e' + + b'\xb1\x5d\x72\x87\x5d\xcb\xb0\xa5\x14\xb4\xc0\x3b\x6d\xf8' + + b'\xf2\x02\xfa\x85\x56\xe4\x00\x21\x22\xbe\xda\xf2\x6e\xaa' + + b'\x10\x7e\xce\x48\x60\x75\x23\x79\xec\x8b\xaa\x64\xf4\x00' + + b'\x98\xbe\x92\xa4\x21\x4b\x69\xe9\x8b\x24\xae\x1c\xc4\xd2' + + b'\xf4\x57\xcf\xf4\xf4\x05\xa8\x2e\xf9\x4c\x5f\x8d\xfa\xad' + + b'\xd3\x07\x8d\x7a\x92\x24\x88\x7d\xb8\x6c\x32\x18\xbf\x53' + + b'\xc9\x77\x9e\xd0\x98\x95\xb2\xcf\xb8\x4f\x1f\xad\x2e\x5b' + + b'\x1f\x8e\x4b\x20\x9c\x57\x85\xb9\xce\x33\x2c\xd4\x13\x56' + + b'\xc1\x71') + self.assertTrue(self.rsa.verify(sigBytes, + secureHash(self.message, "sha512"), + hashAlg="sha512")) + + def test_verify_invalid_PKCS1_sha512(self): + sigBytes = bytearray( + b'\x0b\x57\xa6\xf9\x16\x06\xba\x48\x13\xb8\x35\x36\x58\x1e' + + b'\xb1\x5d\x72\x87\x5d\xcb\xb0\xa5\x14\xb4\xc0\x3b\x6d\xf8' + + b'\xf2\x02\xfa\x85\x56\xe4\x00\x21\x22\xbe\xda\xf2\x6e\xaa' + + b'\x10\x7e\xce\x48\x60\x75\x23\x79\xec\x8b\xaa\x64\xf4\x00' + + b'\x98\xbe\x92\xa4\x21\x4b\x69\xe9\x8b\x24\xae\x1c\xc4\xd2' + + b'\xf4\x57\xcf\xf4\xf4\x05\xa8\x2e\xf9\x4c\x5f\x8d\xfa\xad' + + b'\xd3\x07\x8d\x7a\x92\x24\x88\x7d\xb8\x6c\x32\x18\xbf\x53' + + b'\xc9\x77\x9e\xd0\x98\x95\xb2\xcf\xb8\x4f\x1f\xad\x2e\x5b' + + b'\x1f\x8e\x4b\x20\x9c\x57\x85\xb9\xce\x33\x2c\xd4\x13\x56' + + b'\xc1\x71') + self.assertFalse(self.rsa.verify(sigBytes, + secureHash(self.message, "sha512"), + hashAlg="sha512")) + # because RSAKey is an abstract class... class TestRSAKey(unittest.TestCase): @@ -1529,6 +1561,34 @@ def test_hashAndVerify_PSS(self): self.assertTrue(rsa.hashAndVerify(sigBytes, bytearray(b'text to sign'), "PSS", 'sha1')) + def test_verify_PSS(self): + rsa = Python_RSAKey(self.N, self.e) + + sigBytes = bytearray( + b'op\xfa\x1d\xfa\xe8i\xf2zV\x9a\xf4\x8d\xf1\xaf:\x1a\xb6\xce' + + b'\xae3\xd1\xc2E[EG\x8ba\xfe.\x8e\x9dJ\xc9 Date: Fri, 14 Jul 2017 15:10:10 +0200 Subject: [PATCH 446/574] release v0.7.0-alpha7 --- README.md | 2 +- setup.py | 8 ++++++-- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index aad12854..4f1ff91e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.7.0-alpha6 2017-07-13 +tlslite-ng version 0.7.0-alpha7 2017-07-14 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` diff --git a/setup.py b/setup.py index 02b90d3a..4b57af4b 100755 --- a/setup.py +++ b/setup.py @@ -4,15 +4,19 @@ # See the LICENSE file for legal information regarding use of this file. from distutils.core import setup +import os - +here = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(here, "README")) as f: + README = f.read() setup(name="tlslite-ng", - version="0.7.0-alpha6", + version="0.7.0-alpha7", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", description="Pure python implementation of SSL and TLS.", + long_description=README, license="LGPLv2", scripts=["scripts/tls.py", "scripts/tlsdb.py"], packages=["tlslite", "tlslite.utils", "tlslite.integration"], diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 21f6d0a2..9ab67a0b 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.7.0-alpha6 +@version: 0.7.0-alpha7 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 9c769dd7..71e24cdb 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.7.0-alpha6" +__version__ = "0.7.0-alpha7" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 87527edab96077fd317c437980af904f643cdc39 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Jan 2017 15:18:21 +0100 Subject: [PATCH 447/574] reorder ciphers according to cipher ID --- tlslite/constants.py | 140 ++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 75 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 1f37f99a..6adc9049 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -476,103 +476,79 @@ class CipherSuite: # SSL2 ciphersuites which use 192 bit key ssl2_192Key = [SSL_CK_DES_192_EDE3_CBC_WITH_MD5] - # Weird pseudo-ciphersuite from RFC 5746 - # Signals that "secure renegotiation" is supported - # We actually don't do any renegotiation, but this - # prevents renegotiation attacks - TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF - ietfNames[0x00FF] = 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV' - - # RFC 7507 - Fallback Signaling Cipher Suite Value for Preventing Protocol - # Downgrade Attacks - TLS_FALLBACK_SCSV = 0x5600 - ietfNames[0x5600] = 'TLS_FALLBACK_SCSV' - - # RFC 5054 - Secure Remote Password (SRP) Protocol for TLS Authentication - TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A - ietfNames[0xC01A] = 'TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA' - TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D - ietfNames[0xC01D] = 'TLS_SRP_SHA_WITH_AES_128_CBC_SHA' - TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020 - ietfNames[0xC020] = 'TLS_SRP_SHA_WITH_AES_256_CBC_SHA' - - # RFC 5054 - Secure Remote Password (SRP) Protocol for TLS Authentication - TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0xC01B - ietfNames[0xC01B] = 'TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA' - TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0xC01E - ietfNames[0xC01E] = 'TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA' - TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021 - ietfNames[0xC021] = 'TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA' + # + # SSLv3 and TLS cipher suite definitions + # # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_NULL_MD5 = 0x0001 ietfNames[0x0001] = 'TLS_RSA_WITH_NULL_MD5' TLS_RSA_WITH_NULL_SHA = 0x0002 ietfNames[0x0002] = 'TLS_RSA_WITH_NULL_SHA' - TLS_RSA_WITH_NULL_SHA256 = 0x003B - ietfNames[0x003B] = 'TLS_RSA_WITH_NULL_SHA256' - - # RFC 5246 - TLS v1.2 Protocol - TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A - ietfNames[0x000A] = 'TLS_RSA_WITH_3DES_EDE_CBC_SHA' - TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F - ietfNames[0x002F] = 'TLS_RSA_WITH_AES_128_CBC_SHA' - TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 - ietfNames[0x0035] = 'TLS_RSA_WITH_AES_256_CBC_SHA' - TLS_RSA_WITH_RC4_128_SHA = 0x0005 - ietfNames[0x0005] = 'TLS_RSA_WITH_RC4_128_SHA' - - # RFC 5246 - TLS v1.2 Protocol TLS_RSA_WITH_RC4_128_MD5 = 0x0004 ietfNames[0x0004] = 'TLS_RSA_WITH_RC4_128_MD5' - - # RFC 5246 - TLS v1.2 Protocol + TLS_RSA_WITH_RC4_128_SHA = 0x0005 + ietfNames[0x0005] = 'TLS_RSA_WITH_RC4_128_SHA' + TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A + ietfNames[0x000A] = 'TLS_RSA_WITH_3DES_EDE_CBC_SHA' TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016 ietfNames[0x0016] = 'TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA' - TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 - ietfNames[0x0033] = 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA' - TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 - ietfNames[0x0039] = 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA' - - # RFC 5246 - TLS v1.2 Protocol TLS_DH_ANON_WITH_RC4_128_MD5 = 0x0018 ietfNames[0x0018] = 'TLS_DH_ANON_WITH_RC4_128_MD5' TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA = 0x001B ietfNames[0x001B] = 'TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA' + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F + ietfNames[0x002F] = 'TLS_RSA_WITH_AES_128_CBC_SHA' + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 + ietfNames[0x0033] = 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA' TLS_DH_ANON_WITH_AES_128_CBC_SHA = 0x0034 ietfNames[0x0034] = 'TLS_DH_ANON_WITH_AES_128_CBC_SHA' + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 + ietfNames[0x0035] = 'TLS_RSA_WITH_AES_256_CBC_SHA' + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 + ietfNames[0x0039] = 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA' TLS_DH_ANON_WITH_AES_256_CBC_SHA = 0x003A ietfNames[0x003A] = 'TLS_DH_ANON_WITH_AES_256_CBC_SHA' - TLS_DH_ANON_WITH_AES_128_CBC_SHA256 = 0x006C - ietfNames[0x006C] = 'TLS_DH_ANON_WITH_AES_128_CBC_SHA256' - TLS_DH_ANON_WITH_AES_256_CBC_SHA256 = 0x006D - ietfNames[0x006D] = 'TLS_DH_ANON_WITH_AES_256_CBC_SHA256' - TLS_DH_ANON_WITH_AES_128_GCM_SHA256 = 0x00A6 - ietfNames[0x00A6] = 'TLS_DH_ANON_WITH_AES_128_GCM_SHA256' - TLS_DH_ANON_WITH_AES_256_GCM_SHA384 = 0x00A7 - ietfNames[0x00A7] = 'TLS_DH_ANON_WITH_AES_256_GCM_SHA384' - - # RFC 5246 - TLS v1.2 Protocol + TLS_RSA_WITH_NULL_SHA256 = 0x003B + ietfNames[0x003B] = 'TLS_RSA_WITH_NULL_SHA256' TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C ietfNames[0x003C] = 'TLS_RSA_WITH_AES_128_CBC_SHA256' TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D ietfNames[0x003D] = 'TLS_RSA_WITH_AES_256_CBC_SHA256' - - # RFC 5246 - TLS v1.2 TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 ietfNames[0x0067] = 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA256' TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B ietfNames[0x006B] = 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA256' + TLS_DH_ANON_WITH_AES_128_CBC_SHA256 = 0x006C + ietfNames[0x006C] = 'TLS_DH_ANON_WITH_AES_128_CBC_SHA256' + TLS_DH_ANON_WITH_AES_256_CBC_SHA256 = 0x006D + ietfNames[0x006D] = 'TLS_DH_ANON_WITH_AES_256_CBC_SHA256' # RFC 5288 - AES-GCM ciphers for TLSv1.2 TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C ietfNames[0x009C] = 'TLS_RSA_WITH_AES_128_GCM_SHA256' - TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E - ietfNames[0x009E] = 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256' TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D ietfNames[0x009D] = 'TLS_RSA_WITH_AES_256_GCM_SHA384' + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E + ietfNames[0x009E] = 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256' TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F ietfNames[0x009F] = 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384' + TLS_DH_ANON_WITH_AES_128_GCM_SHA256 = 0x00A6 + ietfNames[0x00A6] = 'TLS_DH_ANON_WITH_AES_128_GCM_SHA256' + TLS_DH_ANON_WITH_AES_256_GCM_SHA384 = 0x00A7 + ietfNames[0x00A7] = 'TLS_DH_ANON_WITH_AES_256_GCM_SHA384' + + # Weird pseudo-ciphersuite from RFC 5746 + # Signals that "secure renegotiation" is supported + # We actually don't do any renegotiation, but this + # prevents renegotiation attacks + TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF + ietfNames[0x00FF] = 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV' + + # RFC 7507 - Fallback Signaling Cipher Suite Value for Preventing Protocol + # Downgrade Attacks + TLS_FALLBACK_SCSV = 0x5600 + ietfNames[0x5600] = 'TLS_FALLBACK_SCSV' # RFC 4492 - ECC Cipher Suites for TLS TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010 @@ -592,18 +568,19 @@ class CipherSuite: TLS_ECDH_ANON_WITH_AES_256_CBC_SHA = 0xC019 ietfNames[0xC019] = 'TLS_ECDH_ANON_WITH_AES_256_CBC_SHA' - # draft-ietf-tls-chacha20-poly1305-00 - # ChaCha20/Poly1305 based Cipher Suites for TLS1.2 - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00 = 0xcca1 - ietfNames[0xcca1] = 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00' - TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00 = 0xcca3 - ietfNames[0xcca3] = 'TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00' - - # RFC 7905 - ChaCha20-Poly1305 Cipher Suites for TLS - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xcca8 - ietfNames[0xcca8] = 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256' - TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xccaa - ietfNames[0xccaa] = 'TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256' + # RFC 5054 - Secure Remote Password (SRP) Protocol for TLS Authentication + TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A + ietfNames[0xC01A] = 'TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA' + TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0xC01B + ietfNames[0xC01B] = 'TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA' + TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D + ietfNames[0xC01D] = 'TLS_SRP_SHA_WITH_AES_128_CBC_SHA' + TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0xC01E + ietfNames[0xC01E] = 'TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA' + TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020 + ietfNames[0xC020] = 'TLS_SRP_SHA_WITH_AES_256_CBC_SHA' + TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021 + ietfNames[0xC021] = 'TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA' # RFC 5289 - ECC Ciphers with SHA-256/SHA284 HMAC and AES-GCM TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 @@ -615,6 +592,19 @@ class CipherSuite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 ietfNames[0xC030] = 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384' + # draft-ietf-tls-chacha20-poly1305-00 + # ChaCha20/Poly1305 based Cipher Suites for TLS1.2 + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00 = 0xCCA1 + ietfNames[0xCCA1] = 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00' + TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00 = 0xCCA3 + ietfNames[0xCCA3] = 'TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00' + + # RFC 7905 - ChaCha20-Poly1305 Cipher Suites for TLS + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8 + ietfNames[0xCCA8] = 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256' + TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAA + ietfNames[0xCCAA] = 'TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256' + #pylint: enable = invalid-name # # Define cipher suite families below From 594ad425641a2c8e838643cd6c51668cf500ca99 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Jan 2017 15:52:46 +0100 Subject: [PATCH 448/574] add TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA --- tlslite/constants.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 6adc9049..c00eba52 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -553,6 +553,8 @@ class CipherSuite: # RFC 4492 - ECC Cipher Suites for TLS TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010 ietfNames[0xC010] = 'TLS_ECDHE_RSA_WITH_NULL_SHA' + TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xC012 + ietfNames[0xC012] = 'TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA' TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 ietfNames[0xC013] = 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA' TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 @@ -612,6 +614,7 @@ class CipherSuite: # 3DES CBC ciphers tripleDESSuites = [] + tripleDESSuites.append(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) 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) @@ -707,6 +710,7 @@ class CipherSuite: shaSuites.append(TLS_RSA_WITH_NULL_SHA) shaSuites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) shaSuites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) + shaSuites.append(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) shaSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) shaSuites.append(TLS_ECDH_ANON_WITH_AES_256_CBC_SHA) shaSuites.append(TLS_ECDH_ANON_WITH_AES_128_CBC_SHA) @@ -907,6 +911,7 @@ def getDheCertSuites(cls, settings, version=None): ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) @classmethod From b8aa961dd4e1d3736ca225fedab9a82b21abaed6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 26 Jan 2017 15:54:33 +0100 Subject: [PATCH 449/574] add TLS_ECDHE_RSA_WITH_RC4_128_SHA --- tlslite/constants.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index c00eba52..bba9dd48 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -553,6 +553,8 @@ class CipherSuite: # RFC 4492 - ECC Cipher Suites for TLS TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010 ietfNames[0xC010] = 'TLS_ECDHE_RSA_WITH_NULL_SHA' + TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xC011 + ietfNames[0xC011] = 'TLS_ECDHE_RSA_WITH_RC4_128_SHA' TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xC012 ietfNames[0xC012] = 'TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA' TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 @@ -676,6 +678,7 @@ class CipherSuite: # RC4 128 stream cipher rc4Suites = [] + rc4Suites.append(TLS_ECDHE_RSA_WITH_RC4_128_SHA) rc4Suites.append(TLS_DH_ANON_WITH_RC4_128_MD5) rc4Suites.append(TLS_RSA_WITH_RC4_128_SHA) rc4Suites.append(TLS_RSA_WITH_RC4_128_MD5) @@ -711,6 +714,7 @@ class CipherSuite: shaSuites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) shaSuites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) shaSuites.append(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) + shaSuites.append(TLS_ECDHE_RSA_WITH_RC4_128_SHA) shaSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) shaSuites.append(TLS_ECDH_ANON_WITH_AES_256_CBC_SHA) shaSuites.append(TLS_ECDH_ANON_WITH_AES_128_CBC_SHA) @@ -912,6 +916,7 @@ def getDheCertSuites(cls, settings, version=None): ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_RC4_128_SHA) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) @classmethod From a96cd3d6b8fb4fca321ab34499681068e9a46460 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 15 Feb 2017 15:21:20 +0100 Subject: [PATCH 450/574] add ECDSA, ECDH/ECDSA and ECDH/RSA ciphers add definitions of ciphersuites that use ECDSA certificates, useful for decoding, fuzzing and scanning, even if unsupported for tlslite-ng itself --- tlslite/constants.py | 133 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 2 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index bba9dd48..507545e1 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -550,6 +550,39 @@ class CipherSuite: TLS_FALLBACK_SCSV = 0x5600 ietfNames[0x5600] = 'TLS_FALLBACK_SCSV' + # RFC 4492 - ECC Cipher Suites for TLS + # unsupported - no support for ECDSA certificates + TLS_ECDH_ECDSA_WITH_NULL_SHA = 0xC001 + ietfNames[0xC001] = 'TLS_ECDH_ECDSA_WITH_NULL_SHA' + TLS_ECDH_ECDSA_WITH_RC4_128_SHA = 0xC002 + ietfNames[0xC002] = 'TLS_ECDH_ECDSA_WITH_RC4_128_SHA' + TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC003 + ietfNames[0xC003] = 'TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA' + TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = 0xC004 + ietfNames[0xC004] = 'TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA' + TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xC005 + ietfNames[0xC005] = 'TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA' + TLS_ECDHE_ECDSA_WITH_NULL_SHA = 0xC006 + ietfNames[0xC006] = 'TLS_ECDHE_ECDSA_WITH_NULL_SHA' + TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = 0xC007 + ietfNames[0xC007] = 'TLS_ECDHE_ECDSA_WITH_RC4_128_SHA' + TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC008 + ietfNames[0xC008] = 'TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA' + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 + ietfNames[0xC009] = 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA' + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A + ietfNames[0xC00A] = 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA' + TLS_ECDH_RSA_WITH_NULL_SHA = 0xC00B + ietfNames[0xC00B] = 'TLS_ECDH_RSA_WITH_NULL_SHA' + TLS_ECDH_RSA_WITH_RC4_128_SHA = 0xC00C + ietfNames[0xC00C] = 'TLS_ECDH_RSA_WITH_RC4_128_SHA' + TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = 0xC00D + ietfNames[0xC00D] = 'TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA' + TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = 0xC00E + ietfNames[0xC00E] = 'TLS_ECDH_RSA_WITH_AES_128_CBC_SHA' + TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = 0xC00F + ietfNames[0xC00F] = 'TLS_ECDH_RSA_WITH_AES_256_CBC_SHA' + # RFC 4492 - ECC Cipher Suites for TLS TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010 ietfNames[0xC010] = 'TLS_ECDHE_RSA_WITH_NULL_SHA' @@ -586,16 +619,51 @@ class CipherSuite: TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021 ietfNames[0xC021] = 'TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA' - # RFC 5289 - ECC Ciphers with SHA-256/SHA284 HMAC and AES-GCM + # RFC 5289 - ECC Ciphers with SHA-256/SHA-384 HMAC and AES-GCM + # unsupported! - no support for ECDSA certificates + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 + ietfNames[0xC023] = 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256' + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 + ietfNames[0xC024] = 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384' + TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC025 + ietfNames[0xC025] = 'TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256' + TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC026 + ietfNames[0xC026] = 'TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384' + + # RFC 5289 - ECC Ciphers with SHA-256/SHA-384 HMAC and AES-GCM TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 ietfNames[0xC027] = 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256' TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 ietfNames[0xC028] = 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384' + + # RFC 5289 - ECC Ciphers with SHA-256/SHA-384 HMAC and AES-GCM + # unsupported + TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = 0xC029 + ietfNames[0xC029] = 'TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256' + TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = 0xC02A + ietfNames[0xC02A] = 'TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384' + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B + ietfNames[0xC02B] = 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256' + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C + ietfNames[0xC02C] = 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384' + TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02D + ietfNames[0xC02D] = 'TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256' + TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02E + ietfNames[0xC02E] = 'TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384' + + # RFC 5289 - ECC Ciphers with SHA-256/SHA-384 HMAC and AES-GCM TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F ietfNames[0xC02F] = 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 ietfNames[0xC030] = 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384' + # RFC 5289 - ECC Ciphers with SHA-256/SHA-384 HMAC and AES-GCM + # unsupported + TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = 0xC031 + ietfNames[0xC031] = 'TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256' + TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xC032 + ietfNames[0xC032] = 'TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384' + # draft-ietf-tls-chacha20-poly1305-00 # ChaCha20/Poly1305 based Cipher Suites for TLS1.2 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00 = 0xCCA1 @@ -616,6 +684,9 @@ class CipherSuite: # 3DES CBC ciphers tripleDESSuites = [] + tripleDESSuites.append(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA) # unsupp + tripleDESSuites.append(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA) # unsupported + tripleDESSuites.append(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA) # unsupported tripleDESSuites.append(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) tripleDESSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) tripleDESSuites.append(TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA) @@ -634,6 +705,12 @@ class CipherSuite: aes128Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA256) aes128Suites.append(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256) aes128Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) + aes128Suites.append(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256) # unsupp + aes128Suites.append(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) # unsupported + aes128Suites.append(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256) # unsupported + aes128Suites.append(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA) # unsupported + aes128Suites.append(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) # unsupported + aes128Suites.append(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA) # unsupported aes128Suites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) aes128Suites.append(TLS_ECDH_ANON_WITH_AES_128_CBC_SHA) @@ -648,6 +725,12 @@ class CipherSuite: aes256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) aes256Suites.append(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256) aes256Suites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) + aes256Suites.append(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384) # unsupported + aes256Suites.append(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) # unsupported + aes256Suites.append(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384) # unsupported + aes256Suites.append(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA) # unsupported + aes256Suites.append(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) # unsupported + aes256Suites.append(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA) # unsupported aes256Suites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) aes256Suites.append(TLS_ECDH_ANON_WITH_AES_256_CBC_SHA) @@ -657,6 +740,9 @@ class CipherSuite: aes128GcmSuites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) aes128GcmSuites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) aes128GcmSuites.append(TLS_DH_ANON_WITH_AES_128_GCM_SHA256) + aes128GcmSuites.append(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) # unsupp + aes128GcmSuites.append(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256) # unsupp + aes128GcmSuites.append(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) # unsupp aes128GcmSuites.append(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) # AES-256-GCM ciphers (implicit SHA384, see sha384PrfSuites) @@ -664,6 +750,9 @@ class CipherSuite: aes256GcmSuites.append(TLS_RSA_WITH_AES_256_GCM_SHA384) aes256GcmSuites.append(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) aes256GcmSuites.append(TLS_DH_ANON_WITH_AES_256_GCM_SHA384) + aes256GcmSuites.append(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) # unsupp + aes256GcmSuites.append(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384) # unsupp + aes256GcmSuites.append(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) # unsupported aes256GcmSuites.append(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) # CHACHA20 cipher, 00'th IETF draft (implicit POLY1305 authenticator) @@ -679,6 +768,9 @@ class CipherSuite: # RC4 128 stream cipher rc4Suites = [] rc4Suites.append(TLS_ECDHE_RSA_WITH_RC4_128_SHA) + rc4Suites.append(TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) # unsupported + rc4Suites.append(TLS_ECDH_ECDSA_WITH_RC4_128_SHA) # unsupported + rc4Suites.append(TLS_ECDH_RSA_WITH_RC4_128_SHA) # unsupported rc4Suites.append(TLS_DH_ANON_WITH_RC4_128_MD5) rc4Suites.append(TLS_RSA_WITH_RC4_128_SHA) rc4Suites.append(TLS_RSA_WITH_RC4_128_MD5) @@ -689,6 +781,9 @@ class CipherSuite: nullSuites.append(TLS_RSA_WITH_NULL_MD5) nullSuites.append(TLS_RSA_WITH_NULL_SHA) nullSuites.append(TLS_RSA_WITH_NULL_SHA256) + nullSuites.append(TLS_ECDHE_ECDSA_WITH_NULL_SHA) # unsupported + nullSuites.append(TLS_ECDH_ECDSA_WITH_NULL_SHA) # unsupported + nullSuites.append(TLS_ECDH_RSA_WITH_NULL_SHA) # unsupported nullSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) nullSuites.append(TLS_ECDH_ANON_WITH_NULL_SHA) @@ -711,6 +806,21 @@ class CipherSuite: shaSuites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA) shaSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) shaSuites.append(TLS_RSA_WITH_NULL_SHA) + shaSuites.append(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) # unsupported + shaSuites.append(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) # unsupported + shaSuites.append(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA) # unsupported + shaSuites.append(TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) # unsupported + shaSuites.append(TLS_ECDHE_ECDSA_WITH_NULL_SHA) # unsupported + shaSuites.append(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA) # unsupported + shaSuites.append(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA) # unsupported + shaSuites.append(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA) # unsupported + shaSuites.append(TLS_ECDH_ECDSA_WITH_RC4_128_SHA) # unsupported + shaSuites.append(TLS_ECDH_ECDSA_WITH_NULL_SHA) # unsupported + shaSuites.append(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA) # unsupported + shaSuites.append(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA) # unsupported + shaSuites.append(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA) # unsupported + shaSuites.append(TLS_ECDH_RSA_WITH_RC4_128_SHA) # unsupported + shaSuites.append(TLS_ECDH_RSA_WITH_NULL_SHA) # unsupported shaSuites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) shaSuites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA) shaSuites.append(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA) @@ -731,10 +841,16 @@ class CipherSuite: sha256Suites.append(TLS_RSA_WITH_NULL_SHA256) sha256Suites.append(TLS_DH_ANON_WITH_AES_128_CBC_SHA256) sha256Suites.append(TLS_DH_ANON_WITH_AES_256_CBC_SHA256) + sha256Suites.append(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256) # unsupported + sha256Suites.append(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256) # unsupported + sha256Suites.append(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) # unsupported sha256Suites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) # SHA-384 HMAC, SHA-384 PRF sha384Suites = [] + sha384Suites.append(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384) # unsupported + sha384Suites.append(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384) # unsupported + sha384Suites.append(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) # unsupported sha384Suites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) # stream cipher construction @@ -927,6 +1043,18 @@ def getEcdheCertSuites(cls, settings, version=None): # RSA authentication certAllSuites = srpCertSuites + certSuites + dheCertSuites + ecdheCertSuites + # ECDHE key exchange, ECDSA authentication + ecdheEcdsaSuites = [] + ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) + ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) + ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384) + ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256) + ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) + ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) + ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA) + ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) + ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_NULL_SHA) + # anon FFDHE key exchange anonSuites = [] anonSuites.append(TLS_DH_ANON_WITH_AES_256_GCM_SHA384) @@ -958,7 +1086,8 @@ def getEcdhAnonSuites(cls, settings, version=None): """Provide anonymous ECDH ciphersuites matching settings""" return cls._filterSuites(CipherSuite.ecdhAnonSuites, settings, version) - ecdhAllSuites = ecdheCertSuites + ecdhAnonSuites + # all ciphersuites which use ephemeral ECDH key exchange + ecdhAllSuites = ecdheEcdsaSuites + ecdheCertSuites + ecdhAnonSuites @staticmethod def canonicalCipherName(ciphersuite): From ec62f3c5d20851c8dfcc479c729667e0357beef3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 18 Jul 2017 16:27:21 +0200 Subject: [PATCH 451/574] reject SNI ext with trailing data --- tlslite/extensions.py | 3 +++ unit_tests/test_tlslite_extensions.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index ef728b2a..74b08403 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -524,6 +524,9 @@ def parse(self, p): self.serverNames += [SNIExtension.ServerName(sn_type, sn_name)] p.stopLengthCheck() + if p.getRemainingLength(): + raise SyntaxError() + return self class ClientCertTypeExtension(VarListExtension): diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index f5dcf7a4..1aaefe3d 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -701,6 +701,20 @@ def test_parse_with_name_length_short_by_one(self): with self.assertRaises(SyntaxError): server_name = server_name.parse(p) + def test_parse_with_trailing_data(self): + server_name = SNIExtension() + + p = Parser(bytearray( + b'\x00\x04' + # length of array - 4 bytes + b'\x00' + # type of entry - host_name (0) + b'\x00\x01' + # length of name - 1 byte + b'e' + # entry + b'x' # trailing data + )) + + with self.assertRaises(SyntaxError): + server_name = server_name.parse(p) + def test___repr__(self): server_name = SNIExtension() server_name = server_name.create( From 5d322ee4ea3987b10176b5a2f54816f8427983f3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 18 Jul 2017 16:34:10 +0200 Subject: [PATCH 452/574] reject malformed SNI extensions (no entries, empty entries) --- tlslite/tlsconnection.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 46f5de8e..56b54ad7 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1532,6 +1532,12 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, # Sanity check the SNI extension sniExt = clientHello.getExtension(ExtensionType.server_name) + # check if extension is well formed + if sniExt and (not sniExt.extData or not sniExt.serverNames): + for result in self._sendError( + AlertDescription.decode_error, + "Recevived SNI extension is malformed"): + yield result if sniExt and sniExt.hostNames: # RFC 6066 limitation if len(sniExt.hostNames) > 1: @@ -1539,6 +1545,11 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, AlertDescription.illegal_parameter, "Client sent multiple host names in SNI extension"): yield result + if not sniExt.hostNames[0]: + for result in self._sendError( + AlertDescription.decode_error, + "Received SNI extension is malformed"): + yield result try: name = sniExt.hostNames[0].decode('ascii', 'strict') except UnicodeDecodeError: From 752cecd8aeca970625719dcdbad7a2d674bbeb50 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 19 Jul 2017 12:35:44 +0200 Subject: [PATCH 453/574] reject empty supported_groups extension as invalid the definition of the extension requires client to send at least one item in the list, thus empty extension is malformed - reject those --- tlslite/tlsconnection.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 46f5de8e..96225cbc 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1592,6 +1592,11 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, ffGroupIntersect = True if clientGroups is not None: clientGroups = clientGroups.groups + if not clientGroups: + for result in self._sendError( + AlertDescription.decode_error, + "Received malformed supported_groups extension"): + yield result serverGroups = self._curveNamesToList(settings) ecGroupIntersect = getFirstMatching(clientGroups, serverGroups) # RFC 7919 groups From 12916368f26dee7d1bf69484c071c67666ea79e5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 19 Jul 2017 12:34:51 +0200 Subject: [PATCH 454/574] default to a common curve if no curves advertised The ECC TLS RFC states that if the client doesn't advertise any curves, it means that it supports any and the server can then select any curve. That was OK when there was only one set of curves supported in TLS. Now we have the legacy curves, brainpool curves and djb (x25519 and x448) curves, meaning that client that doesn't advertise any curves is likely to support only the legacy curves, not the newer ones. Thus we need to separate the default curve from most wanted curve as the former must be most compatible while still relatively secure while the latter can be fastest or most secure, but will be negotiated only when client also supports it. --- tlslite/handshakesettings.py | 11 +++++++++++ tlslite/keyexchange.py | 18 +++++++++++------- tlslite/tlsconnection.py | 8 ++++++-- unit_tests/test_tlslite_handshakesettings.py | 6 ++++++ 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 0d9d9d5c..e66aa5c6 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -141,6 +141,11 @@ class HandshakeSettings(object): @ivar requireExtendedMasterSecret: whether to require negotiation of extended master secret calculation for successful connection. Requires useExtendedMasterSecret to be set to true. False by default. + + @type defaultCurve: str + @ivar defaultCurve: curve that will be used by server in case the client + did not advertise support for any curves. It does not have to be the + first curve for eccCurves and may be distinct from curves from that list. """ def __init__(self): self.minKeySize = 1023 @@ -163,6 +168,7 @@ def __init__(self): self.requireExtendedMasterSecret = False self.dhParams = None self.dhGroups = list(ALL_DH_GROUP_NAMES) + self.defaultCurve = "secp256r1" @staticmethod def _sanityCheckKeySizes(other): @@ -212,6 +218,10 @@ def _sanityCheckPrimitivesNames(other): if unknownCurve: raise ValueError("Unknown ECC Curve name: {0}".format(unknownCurve)) + if other.defaultCurve not in ALL_CURVE_NAMES: + raise ValueError("Unknown default ECC Curve name: {0}" + .format(other.defaultCurve)) + unknownSigHash = [val for val in other.rsaSigHashes \ if val not in ALL_RSA_SIGNATURE_HASHES] if unknownSigHash: @@ -288,6 +298,7 @@ def validate(self): other.requireExtendedMasterSecret = self.requireExtendedMasterSecret other.dhParams = self.dhParams other.dhGroups = self.dhGroups + other.defaultCurve = self.defaultCurve if not cipherfactory.tripleDESPresent: other.cipherNames = [i for i in self.cipherNames if i != "3des"] diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index cf14c44b..44dcf483 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -468,24 +468,27 @@ class AECDHKeyExchange(KeyExchange): ECDHE without signing serverKeyExchange useful for anonymous ECDH """ - def __init__(self, cipherSuite, clientHello, serverHello, acceptedCurves): + def __init__(self, cipherSuite, clientHello, serverHello, acceptedCurves, + defaultCurve=GroupName.secp256r1): super(AECDHKeyExchange, self).__init__(cipherSuite, clientHello, serverHello) self.ecdhXs = None self.acceptedCurves = acceptedCurves self.group_id = None self.ecdhYc = None + self.defaultCurve = defaultCurve def makeServerKeyExchange(self, sigHash=None): """Create AECDHE version of Server Key Exchange""" #Get client supported groups - client_curves = self.clientHello.getExtension(\ + client_curves = self.clientHello.getExtension( ExtensionType.supported_groups) if client_curves is None: - # in case there is no extension, we can pick any curve, assume - # the most common - client_curves = [GroupName.secp256r1] + # in case there is no extension, we can pick any curve, + # use the configured one + client_curves = [self.defaultCurve] elif not client_curves.groups: + # extension should have been validated before raise TLSInternalError("Can't do ECDHE with no client curves") else: client_curves = client_curves.groups @@ -554,10 +557,11 @@ class ECDHE_RSAKeyExchange(AuthenticatedKeyExchange, AECDHKeyExchange): """Helper class for conducting ECDHE key exchange""" def __init__(self, cipherSuite, clientHello, serverHello, privateKey, - acceptedCurves): + acceptedCurves, defaultCurve=GroupName.secp256r1): super(ECDHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, serverHello, - acceptedCurves) + acceptedCurves, + defaultCurve) #pylint: enable = invalid-name self.privateKey = privateKey diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 96225cbc..f4ddf277 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1395,11 +1395,13 @@ def _handshakeServerAsyncHelper(self, verifierDB, dhGroups) elif cipherSuite in CipherSuite.ecdheCertSuites: acceptedCurves = self._curveNamesToList(settings) + defaultCurve = getattr(GroupName, settings.defaultCurve) keyExchange = ECDHE_RSAKeyExchange(cipherSuite, clientHello, serverHello, privateKey, - acceptedCurves) + acceptedCurves, + defaultCurve) else: assert(False) for result in self._serverCertKeyExchange(clientHello, serverHello, @@ -1420,8 +1422,10 @@ def _handshakeServerAsyncHelper(self, verifierDB, dhGroups) else: acceptedCurves = self._curveNamesToList(settings) + defaultCurve = getattr(GroupName, settings.defaultCurve) keyExchange = AECDHKeyExchange(cipherSuite, clientHello, - serverHello, acceptedCurves) + serverHello, acceptedCurves, + defaultCurve) for result in self._serverAnonKeyExchange(serverHello, keyExchange, cipherSuite): if result in (0,1): yield result diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index 243121ab..9c9f550c 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -278,5 +278,11 @@ def test_invalid_rsaScheme(self): with self.assertRaises(ValueError): hs.validate() + def test_invalid_defaultCurve_name(self): + hs = HandshakeSettings() + hs.defaultCurve = "ffdhe2048" + with self.assertRaises(ValueError): + hs.validate() + if __name__ == '__main__': unittest.main() From f9ab9598a21d6466cf22ccb34435563cb1f99ebd Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Feb 2016 11:00:20 +0100 Subject: [PATCH 455/574] test coverage for bytesToNumber --- unit_tests/test_tlslite_utils_cryptomath.py | 23 ++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index 28108f9f..52c47176 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -11,10 +11,11 @@ from hypothesis import given, example from hypothesis.strategies import integers import math +import struct from tlslite.utils.cryptomath import isPrime, numBits, numBytes, \ numberToByteArray, MD5, SHA1, secureHash, HMAC_MD5, HMAC_SHA1, \ - HMAC_SHA256, HMAC_SHA384, HKDF_expand + HMAC_SHA256, HMAC_SHA384, HKDF_expand, bytesToNumber class TestIsPrime(unittest.TestCase): def test_with_small_primes(self): @@ -344,3 +345,23 @@ def test_SHA512(self): b'\x64\x3C\xE8\x0E' b'\x2A\x9A\xC9\x4F' b'\xA5\x4C\xA4\x9F')) + +class TestBytesToNumber(unittest.TestCase): + @given(integers(min_value=0, max_value=0xff)) + @example(0) + @example(0xff) + def test_small_numbers(self, number): + self.assertEqual(bytesToNumber(bytearray(struct.pack(">B", number))), + number) + + @given(integers(min_value=0, max_value=0xffffffff)) + @example(0xffffffff) + def test_multi_byte_numbers(self, number): + self.assertEqual(bytesToNumber(bytearray(struct.pack(">I", number))), + number) + + def test_very_long_numbers(self): + self.assertEqual(bytesToNumber(bytearray(b'\x80' + b'\x00' * 16)), + 1<<(8 * 16 + 7)) + self.assertEqual(bytesToNumber(bytearray(b'\xff'*16)), + (1<<(8*16))-1) From 1a84738534df53135ec20bf49c631d99943f9b78 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Feb 2016 11:20:29 +0100 Subject: [PATCH 456/574] clean up the bytesToNumber implementation use implementation with implicit looping --- tlslite/utils/cryptomath.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index e687b737..66080cf4 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -121,13 +121,7 @@ def HKDF_expand(PRK, info, L, algorithm): # ************************************************************************** def bytesToNumber(b): - total = 0 - multiplier = 1 - for count in range(len(b)-1, -1, -1): - byte = b[count] - total += multiplier * byte - multiplier *= 256 - return total + return sum(i << j for i, j in zip(b, range((len(b)-1)*8, -1, -8))) def numberToByteArray(n, howManyBytes=None): """Convert an integer into a bytearray, zero-pad to howManyBytes. From b3333dd032203049e6d2d4c271069b5ae8784810 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Feb 2016 13:25:39 +0100 Subject: [PATCH 457/574] add support for little endian int to bytes conversion --- tlslite/utils/cryptomath.py | 9 ++++-- unit_tests/test_tlslite_utils_cryptomath.py | 32 +++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index 66080cf4..6035825f 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -120,8 +120,13 @@ def HKDF_expand(PRK, info, L, algorithm): # Converter Functions # ************************************************************************** -def bytesToNumber(b): - return sum(i << j for i, j in zip(b, range((len(b)-1)*8, -1, -8))) +def bytesToNumber(b, endian="big"): + if endian == "big": + return sum(i << j for i, j in zip(b, range((len(b)-1)*8, -1, -8))) + elif endian == "little": + return sum(i << j for i, j in zip(b, range(0, len(b)*8, 8))) + else: + raise ValueError("Only 'big' and 'little' endian supported") def numberToByteArray(n, howManyBytes=None): """Convert an integer into a bytearray, zero-pad to howManyBytes. diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index 52c47176..27e868ed 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -361,7 +361,39 @@ def test_multi_byte_numbers(self, number): number) def test_very_long_numbers(self): + self.assertEqual(bytesToNumber(bytearray(b'\x00' * 16 + b'\x80')), + 0x80) self.assertEqual(bytesToNumber(bytearray(b'\x80' + b'\x00' * 16)), 1<<(8 * 16 + 7)) self.assertEqual(bytesToNumber(bytearray(b'\xff'*16)), (1<<(8*16))-1) + + @given(integers(min_value=0, max_value=0xff)) + @example(0) + @example(0xff) + def test_small_numbers_little_endian(self, number): + self.assertEqual(bytesToNumber(bytearray(struct.pack(" Date: Sun, 7 Feb 2016 13:52:49 +0100 Subject: [PATCH 458/574] more test coverage for numerToByteArray --- unit_tests/test_tlslite_utils_cryptomath.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index 27e868ed..3eade3fc 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -109,6 +109,24 @@ def test_numberToByteArray_with_not_enough_length(self): self.assertEqual(numberToByteArray(0x0a0b0c, 2), bytearray(b'\x0b\x0c')) + @given(integers(min_value=0, max_value=0xff)) + @example(0) + @example(0xff) + def test_small_number(self, number): + self.assertEqual(numberToByteArray(number, 1), + bytearray(struct.pack(">B", number))) + + @given(integers(min_value=0, max_value=0xffffffff)) + @example(0xffffffff) + def test_big_number(self, number): + self.assertEqual(numberToByteArray(number, 4), + bytearray(struct.pack(">L", number))) + + def test_very_large_number(self): + self.assertEqual(numberToByteArray((1<<128)-1), + bytearray(b'\xff'*16)) + + class TestNumBits(unittest.TestCase): @staticmethod From b9160ea8c6069f8b43b0a6abdbee82b388d638f3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Feb 2016 14:23:48 +0100 Subject: [PATCH 459/574] cleanup numberToByteArray do not do array subscripting, create the array on the fly using an iterator, nearly twice as fast --- tlslite/utils/cryptomath.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index 6035825f..fd02f0ac 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -129,19 +129,17 @@ def bytesToNumber(b, endian="big"): raise ValueError("Only 'big' and 'little' endian supported") def numberToByteArray(n, howManyBytes=None): - """Convert an integer into a bytearray, zero-pad to howManyBytes. + """ + Convert an integer into a bytearray, zero-pad to howManyBytes. The returned bytearray may be smaller than howManyBytes, but will not be larger. The returned bytearray will contain a big-endian encoding of the input integer (n). - """ + """ if howManyBytes == None: howManyBytes = numBytes(n) - b = bytearray(howManyBytes) - for count in range(howManyBytes-1, -1, -1): - b[count] = int(n % 256) - n >>= 8 - return b + return bytearray((n >> i) & 0xff + for i in range((howManyBytes-1)*8, -1, -8)) def mpiToNumber(mpi): #mpi is an openssl-format bignum string if (ord(mpi[4]) & 0x80) !=0: #Make sure this is a positive number From a2b831079da4d4485526dae2107f87ad49aa8c14 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 7 Feb 2016 14:31:57 +0100 Subject: [PATCH 460/574] add little endian support to numberToByteArray --- tlslite/utils/cryptomath.py | 12 ++++++--- unit_tests/test_tlslite_utils_cryptomath.py | 28 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index fd02f0ac..8f35b485 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -128,7 +128,7 @@ def bytesToNumber(b, endian="big"): else: raise ValueError("Only 'big' and 'little' endian supported") -def numberToByteArray(n, howManyBytes=None): +def numberToByteArray(n, howManyBytes=None, endian="big"): """ Convert an integer into a bytearray, zero-pad to howManyBytes. @@ -138,8 +138,14 @@ def numberToByteArray(n, howManyBytes=None): """ if howManyBytes == None: howManyBytes = numBytes(n) - return bytearray((n >> i) & 0xff - for i in range((howManyBytes-1)*8, -1, -8)) + if endian == "big": + return bytearray((n >> i) & 0xff + for i in range((howManyBytes-1)*8, -1, -8)) + elif endian == "little": + return bytearray((n >> i) & 0xff + for i in range(0, howManyBytes*8, 8)) + else: + raise ValueError("Only 'big' and 'little' endian supported") def mpiToNumber(mpi): #mpi is an openssl-format bignum string if (ord(mpi[4]) & 0x80) !=0: #Make sure this is a positive number diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index 3eade3fc..deb37e81 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -126,6 +126,34 @@ def test_very_large_number(self): self.assertEqual(numberToByteArray((1<<128)-1), bytearray(b'\xff'*16)) + @given(integers(min_value=0, max_value=0xff)) + @example(0) + @example(0xff) + def test_small_number_little_endian(self, number): + self.assertEqual(numberToByteArray(number, 1, endian="little"), + bytearray(struct.pack(" Date: Fri, 17 Feb 2017 20:30:17 +0100 Subject: [PATCH 461/574] make the bytesToNumber much faster --- tlslite/utils/cryptomath.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index 8f35b485..a0eed1aa 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -15,7 +15,7 @@ import binascii import sys -from .compat import compat26Str, compatHMAC, compatLong +from .compat import compat26Str, compatHMAC, compatLong, b2a_hex # ************************************************************************** @@ -122,9 +122,9 @@ def HKDF_expand(PRK, info, L, algorithm): def bytesToNumber(b, endian="big"): if endian == "big": - return sum(i << j for i, j in zip(b, range((len(b)-1)*8, -1, -8))) + return int(b2a_hex(b), 16) elif endian == "little": - return sum(i << j for i, j in zip(b, range(0, len(b)*8, 8))) + return int(b2a_hex(b[::-1]), 16) else: raise ValueError("Only 'big' and 'little' endian supported") From 20eb024dab068520c5661d848142def31143a3f4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 17 Feb 2017 20:46:42 +0100 Subject: [PATCH 462/574] add X25519 and X448 functions from RFC 7748 --- tlslite/utils/x25519.py | 124 +++++++++++++ unit_tests/test_tlslite_utils_x25519.py | 237 ++++++++++++++++++++++++ 2 files changed, 361 insertions(+) create mode 100644 tlslite/utils/x25519.py create mode 100644 unit_tests/test_tlslite_utils_x25519.py diff --git a/tlslite/utils/x25519.py b/tlslite/utils/x25519.py new file mode 100644 index 00000000..bac9ebb6 --- /dev/null +++ b/tlslite/utils/x25519.py @@ -0,0 +1,124 @@ +# Authors: +# Hubert Kario (2017) +# +# See the LICENSE file for legal information regarding use of this file. + +"""Handling X25519 and X448 curve based key agreement protocol.""" + +from .cryptomath import bytesToNumber, numberToByteArray, divceil +# the names of the variables come directly from RFC 7748 so changing them +# would make the code harder to audit/compare +# pylint: disable=invalid-name + + +def decodeUCoordinate(u, bits): + """Function to decode the public U coordinate of X25519-family curves.""" + if bits not in (255, 448): + raise ValueError("Invalid number of expected bits") + if bits % 8: + u[-1] &= (1 << (bits % 8)) - 1 + return bytesToNumber(u, endian="little") + + +def decodeScalar22519(k): + """Function to decode the private K parameter of the x25519 function.""" + k[0] &= 248 + k[31] &= 127 + k[31] |= 64 + return bytesToNumber(k, endian="little") + + +def decodeScalar448(k): + """Function to decode the private K parameter of the X448 function.""" + k[0] &= 252 + k[55] |= 128 + return bytesToNumber(k, endian="little") + + +def cswap(swap, x_2, x_3): + """Conditional swap function.""" + if swap: + return x_3, x_2 + else: + return x_2, x_3 + + +def x25519(k, u): + """ + Perform point multiplication on X25519 curve. + + @type k: bytearray + @param k: random secret value (multiplier), should be 32 byte long + + @type u: bytearray + @param u: curve generator or the other party key share + + @rtype: bytearray + """ + bits = 255 + k = decodeScalar22519(k) + u = decodeUCoordinate(u, bits) + + a24 = 121665 + p = 2**255 - 19 + + return _x25519_generic(k, u, bits, a24, p) + + +def x448(k, u): + """ + Perform point multiplication on X448 curve. + + @type k: bytearray + @param k: random secret value (multiplier), should be 56 bytes long + + @type u: bytearray + @param u: curve generator or the other party key share + + @rtype: bytearray + """ + bits = 448 + k = decodeScalar448(k) + u = decodeUCoordinate(u, bits) + + a24 = 39081 + p = 2**448 - 2**224 - 1 + + return _x25519_generic(k, u, bits, a24, p) + + +def _x25519_generic(k, u, bits, a24, p): + """Generic Montgomery ladder implementation of the x25519 algorithm.""" + x_1 = u + x_2 = 1 + z_2 = 0 + x_3 = u + z_3 = 1 + swap = 0 + + for t in range(bits-1, -1, -1): + k_t = (k >> t) & 1 + swap ^= k_t + x_2, x_3 = cswap(swap, x_2, x_3) + z_2, z_3 = cswap(swap, z_2, z_3) + swap = k_t + + A = (x_2 + z_2) % p + AA = pow(A, 2, p) + B = (x_2 - z_2) % p + BB = pow(B, 2, p) + E = (AA - BB) % p + C = (x_3 + z_3) % p + D = (x_3 - z_3) % p + DA = (D * A) % p + CB = (C * B) % p + x_3 = pow(DA + CB, 2, p) + z_3 = (x_1 * pow(DA - CB, 2, p)) % p + x_2 = (AA * BB) % p + z_2 = (E * (AA + a24 * E)) % p + + x_2, x_3 = cswap(swap, x_2, x_3) + z_2, z_3 = cswap(swap, z_2, z_3) + ret = (x_2 * pow(z_2, p - 2, p)) % p + return numberToByteArray(ret, divceil(bits, 8), endian="little") +# pylint: enable=invalid-name diff --git a/unit_tests/test_tlslite_utils_x25519.py b/unit_tests/test_tlslite_utils_x25519.py new file mode 100644 index 00000000..ad3423c2 --- /dev/null +++ b/unit_tests/test_tlslite_utils_x25519.py @@ -0,0 +1,237 @@ +# Copyright (c) 2017, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call + +from tlslite.utils.x25519 import decodeUCoordinate, decodeScalar22519, \ + decodeScalar448, x25519, x448 +from tlslite.utils.compat import a2b_hex + +class TestDecodeUCoordinate(unittest.TestCase): + def test_x25519_decode(self): + value = a2b_hex('e6db6867583030db3594c1a424b15f7c7' + '26624ec26b3353b10a903a6d0ab1c4c') + + scalar = decodeUCoordinate(value, 255) + + self.assertEqual(scalar, int("344264340339195944511551077811888216513" + "16167215306631574996226621102155684838")) + + + def test_u_decode_with_invalid_bits(self): + v = a2b_hex('e6db6867583030db3594c1a424b15f7c7' + '26624ec26b3353b10a903a6d0ab1c4c') + with self.assertRaises(ValueError): + decodeUCoordinate(v, 256) + + + def test_x448_decode(self): + value = a2b_hex('06fce640fa3487bfda5f6cf2d5263f8' + 'aad88334cbd07437f020f08f9' + '814dc031ddbdc38c19c6da2583fa542' + '9db94ada18aa7a7fb4ef8a086') + + scalar = decodeUCoordinate(value, 448) + + self.assertEqual(scalar, int("38223991081410733011622996123" + "4899377031416365" + "24057132514834655592243802516" + "2094455820962429" + "14297133958436003433731007979" + "1515452463053830")) + + + def test_x25519_decode_scalar(self): + value = a2b_hex('a546e36bf0527c9d3b16154b82465edd6' + '2144c0ac1fc5a18506a2244ba449ac4') + + scalar = decodeScalar22519(value) + + self.assertEqual(scalar, int("310298424921150409048955604518630896564" + "72772604678260265531221036453811406496")) + + + def test_x448_decode_scalar(self): + value = a2b_hex('3d262fddf9ec8e88495266fea19a34d2' + '8882acef045104d0d1aae121' + '700a779c984c24f8cdd78fbff44943eb' + 'a368f54b29259a4f1c600ad3') + + scalar = decodeScalar448(value) + + self.assertEqual(int("599189175373896402783756016145213256157230856" + "085026129926891459468622403380588640249457727" + "683869421921443004045221642549886377526240828"), + scalar) + + + # RFC 7748 Section 5.2, vector #1 + def test_x25519_1(self): + k = a2b_hex("a546e36bf0527c9d3b16154b82465ed" + "d62144c0ac1fc5a18506a2244ba449ac4") + u = a2b_hex("e6db6867583030db3594c1a424b15f7" + "c726624ec26b3353b10a903a6d0ab1c4c") + + ret = x25519(k, u) + + self.assertEqual(a2b_hex("c3da55379de9c6908e94ea4df28d084f" + "32eccf03491c71f754b4075577a28552"), + ret) + + + # RFC 7748 Section 5.2, vector #2 + def test_x25519_2(self): + k = a2b_hex("4b66e9d4d1b4673c5ad22691957d6af" + "5c11b6421e0ea01d42ca4169e7918ba0d") + u = a2b_hex("e5210f12786811d3f4b7959d0538ae2" + "c31dbe7106fc03c3efc4cd549c715a493") + + ret = x25519(k, u) + + self.assertEqual(ret, + a2b_hex("95cbde9476e8907d7aade45cb4b873f88" + "b595a68799fa152e6f8f7647aac7957")) + + + # RFC 7748 Section 5.2, vector #3 + def test_x448_1(self): + k = a2b_hex("3d262fddf9ec8e88495266fea19a34d" + "28882acef045104d0d1aae121" + "700a779c984c24f8cdd78fbff44943e" + "ba368f54b29259a4f1c600ad3") + u = a2b_hex("06fce640fa3487bfda5f6cf2d5263f8" + "aad88334cbd07437f020f08f9" + "814dc031ddbdc38c19c6da2583fa542" + "9db94ada18aa7a7fb4ef8a086") + + ret = x448(k, u) + + self.assertEqual(ret, + a2b_hex("ce3e4ff95a60dc6697da1db1d85e6afbd" + "f79b50a2412d7546d5f239f" + "e14fbaadeb445fc66a01b0779d9822396" + "1111e21766282f73dd96b6f")) + + + # RFC 7748 Section 5.2, vector #4 + def test_x448_2(self): + k = a2b_hex("203d494428b8399352665ddca42f9de" + "8fef600908e0d461cb021f8c5" + "38345dd77c3e4806e25f46d3315c44e" + "0a5b4371282dd2c8d5be3095f") + u = a2b_hex("0fbcc2f993cd56d3305b0b7d9e55d4c" + "1a8fb5dbb52f8e9a1e9b6201b" + "165d015894e56c4d3570bee52fe205e" + "28a78b91cdfbde71ce8d157db") + + ret = x448(k, u) + + self.assertEqual(ret, + a2b_hex("884a02576239ff7a2f2f63b2db6a9ff37" + "047ac13568e1e30fe63c4a7" + "ad1b3ee3a5700df34321d62077e63633c" + "575c1c954514e99da7c179d")) + + + def test_x25519_one_iteration(self): + k = a2b_hex("0900000000000000000000000000000" + "000000000000000000000000000000000") + u = bytearray(k) + + ret = x25519(k, u) + + self.assertEqual(ret, + a2b_hex("422c8e7a6227d7bca1350b3e2bb7279f7" + "897b87bb6854b783c60e80311ae3079")) + + + @unittest.skip("slow test case") + def test_x25519_thousand_iterations(self): + k = a2b_hex("0900000000000000000000000000000" + "000000000000000000000000000000000") + u = bytearray(k) + + for _ in range(1000): + u, k = bytearray(k), x25519(k, u) + + self.assertEqual(k, + a2b_hex("684cf59ba83309552800ef566f2f4d3c" + "1c3887c49360e3875f2eb94d99532c51")) + + + @unittest.skip("very slow test case") + def test_x25519_million_iterations(self): + k = a2b_hex("0900000000000000000000000000000" + "000000000000000000000000000000000") + u = bytearray(k) + + for _ in range(1000000): + u, k = bytearray(k), x25519(k, u) + + self.assertEqual(k, + a2b_hex("7c3911e0ab2586fd864497297e575e6f3b" + "c601c0883c30df5f4dd2d24f665424")) + + + def test_x448_one_iteration(self): + k = a2b_hex("05000000000000000000000000000000000000000" + "000000000000000" + "00000000000000000000000000000000000000000" + "000000000000000") + u = bytearray(k) + + ret = x448(k, u) + + self.assertEqual(ret, + a2b_hex("3f482c8a9f19b01e6c46ee9711d9dc14fd" + "4bf67af30765c2ae2b846a" + "4d23a8cd0db897086239492caf350b51f8" + "33868b9bc2b3bca9cf4113")) + + + @unittest.skip("slow test case") + def test_x448_thousand_iterations(self): + k = a2b_hex("05000000000000000000000000000000000000000" + "000000000000000" + "00000000000000000000000000000000000000000" + "000000000000000") + u = bytearray(k) + + for _ in range(1000): + u, k = bytearray(k), x448(k, u) + + self.assertEqual(k, + a2b_hex("aa3b4749d55b9daf1e5b00288826c46727" + "4ce3ebbdd5c17b975e09d4" + "af6c67cf10d087202db88286e2b79fceea" + "3ec353ef54faa26e219f38")) + + + @unittest.skip("very slow test case") + def test_x448_million_iterations(self): + k = a2b_hex("05000000000000000000000000000000000000000" + "000000000000000" + "00000000000000000000000000000000000000000" + "000000000000000") + u = bytearray(k) + + for _ in range(1000000): + u, k = bytearray(k), x448(k, u) + + self.assertEqual(k, + a2b_hex("077f453681caca3693198420bbe515cae" + "0002472519b3e67661a7e89" + "cab94695c8f4bcd66e61b9b9c946da8d5" + "24de3d69bd9d9d66b997e37")) From 73b857c596aaedf53aa4582a10d9409bb4b7c38c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 23 Feb 2017 16:02:49 +0100 Subject: [PATCH 463/574] test cases for ECDH with X25519 methods --- tlslite/utils/x25519.py | 12 ++++ unit_tests/test_tlslite_utils_x25519.py | 92 ++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/tlslite/utils/x25519.py b/tlslite/utils/x25519.py index bac9ebb6..7580c839 100644 --- a/tlslite/utils/x25519.py +++ b/tlslite/utils/x25519.py @@ -43,6 +43,12 @@ def cswap(swap, x_2, x_3): return x_2, x_3 +X25519_G = numberToByteArray(9, 32, endian="little") + + +X25519_ORDER = 256 + + def x25519(k, u): """ Perform point multiplication on X25519 curve. @@ -65,6 +71,12 @@ def x25519(k, u): return _x25519_generic(k, u, bits, a24, p) +X448_G = numberToByteArray(5, 56, endian="little") + + +X448_ORDER = 448 + + def x448(k, u): """ Perform point multiplication on X448 curve. diff --git a/unit_tests/test_tlslite_utils_x25519.py b/unit_tests/test_tlslite_utils_x25519.py index ad3423c2..8ab27016 100644 --- a/unit_tests/test_tlslite_utils_x25519.py +++ b/unit_tests/test_tlslite_utils_x25519.py @@ -16,8 +16,9 @@ from unittest.mock import call from tlslite.utils.x25519 import decodeUCoordinate, decodeScalar22519, \ - decodeScalar448, x25519, x448 + decodeScalar448, x25519, x448, X25519_G, X448_G from tlslite.utils.compat import a2b_hex +from tlslite.utils.cryptomath import numberToByteArray class TestDecodeUCoordinate(unittest.TestCase): def test_x25519_decode(self): @@ -235,3 +236,92 @@ def test_x448_million_iterations(self): "0002472519b3e67661a7e89" "cab94695c8f4bcd66e61b9b9c946da8d5" "24de3d69bd9d9d66b997e37")) + + + # RFC 7748, section 6.1 + def test_x25519_ecdh_a_share(self): + a_random = a2b_hex("77076d0a7318a57d3c16c17251b26645df4" + "c2f87ebc0992ab177fba51db92c2a") + a_public = x25519(a_random, bytearray(X25519_G)) + + self.assertEqual(a_public, + a2b_hex("8520f0098930a754748b7ddcb43ef75a0" + "dbf3a0d26381af4eba4a98eaa9b4e6a")) + + def test_x25519_ecdh_b_share(self): + b_random = a2b_hex("5dab087e624a8a4b79e17f8b83800ee6" + "6f3bb1292618b6fd1c2f8b27ff88e0eb") + b_public = x25519(b_random, bytearray(X25519_G)) + + self.assertEqual(b_public, + a2b_hex("de9edb7d7b7dc1b4d35b61c2ece43537" + "3f8343c85b78674dadfc7e146f882b4f")) + + def test_x25519_ecdh_shared(self): + a_random = a2b_hex("77076d0a7318a57d3c16c17251b26645df4" + "c2f87ebc0992ab177fba51db92c2a") + a_public = x25519(a_random, bytearray(X25519_G)) + + b_random = a2b_hex("5dab087e624a8a4b79e17f8b83800ee6" + "6f3bb1292618b6fd1c2f8b27ff88e0eb") + b_public = x25519(b_random, bytearray(X25519_G)) + + a_shared = x25519(a_random, b_public) + + b_shared = x25519(b_random, a_public) + + self.assertEqual(a_shared, b_shared) + self.assertEqual(a_shared, + a2b_hex("4a5d9d5ba4ce2de1728e3bf480350f25" + "e07e21c947d19e3376f09b3c1e161742")) + + + # RFC 7748, section 6.2 + def test_x448_ecdh_a_share(self): + a_random = a2b_hex("9a8f4925d1519f5775cf46b04b58" + "00d4ee9ee8bae8bc5565d498c28d" + "d9c9baf574a94197448973910063" + "82a6f127ab1d9ac2d8c0a598726b") + a_public = x448(a_random, bytearray(X448_G)) + + self.assertEqual(a_public, + a2b_hex("9b08f7cc31b7e3e67d22d5aea121" + "074a273bd2b83de09c63faa73d2c" + "22c5d9bbc836647241d953d40c5b" + "12da88120d53177f80e532c41fa0")) + + def test_x448_ecdh_b_share(self): + b_random = a2b_hex("1c306a7ac2a0e2e0990b294470cb" + "a339e6453772b075811d8fad0d1d" + "6927c120bb5ee8972b0d3e21374c" + "9c921b09d1b0366f10b65173992d") + b_public = x448(b_random, bytearray(X448_G)) + + self.assertEqual(b_public, + a2b_hex("3eb7a829b0cd20f5bcfc0b599b6f" + "eccf6da4627107bdb0d4f345b430" + "27d8b972fc3e34fb4232a13ca706" + "dcb57aec3dae07bdc1c67bf33609")) + + def test_x448_ecdh_shared(self): + a_random = a2b_hex("9a8f4925d1519f5775cf46b04b58" + "00d4ee9ee8bae8bc5565d498c28d" + "d9c9baf574a94197448973910063" + "82a6f127ab1d9ac2d8c0a598726b") + a_public = x448(a_random, bytearray(X448_G)) + + b_random = a2b_hex("1c306a7ac2a0e2e0990b294470cb" + "a339e6453772b075811d8fad0d1d" + "6927c120bb5ee8972b0d3e21374c" + "9c921b09d1b0366f10b65173992d") + b_public = x448(b_random, bytearray(X448_G)) + + a_shared = x448(a_random, b_public) + b_shared = x448(b_random, a_public) + + self.assertEqual(a_shared, b_shared) + self.assertEqual(a_shared, + a2b_hex("07fff4181ac6cc95ec1c16a94a0f" + "74d12da232ce40a77552281d282b" + "b60c0b56fd2464c335543936521c" + "24403085d59a449a5037514a879d")) From d119f9a66ab966aa2215e1d2b74b2bc82068caf1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 18 Jul 2017 19:48:12 +0200 Subject: [PATCH 464/574] add support for X25519 and X448 in TLS implement the key exchange based on X25519 and X448 methods as described in draft-ietf-tls-rfc4492bis --- tlslite/constants.py | 5 + tlslite/handshakesettings.py | 3 +- tlslite/keyexchange.py | 80 +++++++++++---- unit_tests/test_tlslite_keyexchange.py | 136 +++++++++++++++++++++++++ 4 files changed, 202 insertions(+), 22 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 1f37f99a..af1dc250 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -256,6 +256,11 @@ class GroupName(TLSEnum): brainpoolP512r1 = 28 allEC.extend(list(range(26, 29))) + # draft-ietf-tls-rfc4492bis + x25519 = 29 + x448 = 30 + allEC.extend(list(range(29, 31))) + # RFC7919 ffdhe2048 = 256 ffdhe3072 = 257 diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index e66aa5c6..fd8e39fa 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -29,7 +29,8 @@ RSA_SCHEMES = ["pss", "pkcs1"] # while secp521r1 is the most secure, it's also much slower than the others # so place it as the last one -CURVE_NAMES = ["secp384r1", "secp256r1", "secp521r1"] +CURVE_NAMES = ["x25519", "x448", "secp384r1", "secp256r1", + "secp521r1"] ALL_CURVE_NAMES = CURVE_NAMES + ["secp256k1"] if ecdsaAllCurves: ALL_CURVE_NAMES += ["secp224r1", "secp192r1"] diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 44dcf483..2a8cf1c7 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -18,6 +18,8 @@ numBits, numberToByteArray, divceil from .utils.lists import getFirstMatching from .utils import tlshashlib as hashlib +from .utils.x25519 import x25519, x448, X25519_G, X448_G, X25519_ORDER, \ + X448_ORDER import ecdsa class KeyExchange(object): @@ -497,10 +499,22 @@ def makeServerKeyExchange(self, sigHash=None): self.group_id = getFirstMatching(client_curves, self.acceptedCurves) if self.group_id is None: raise TLSInsufficientSecurity("No mutual groups") - generator = getCurveByName(GroupName.toRepr(self.group_id)).generator - self.ecdhXs = ecdsa.util.randrange(generator.order()) + if self.group_id in [GroupName.x25519, GroupName.x448]: + if self.group_id == GroupName.x25519: + generator = bytearray(X25519_G) + fun = x25519 + self.ecdhXs = getRandomBytes(divceil(X25519_ORDER, 8)) + else: + generator = bytearray(X448_G) + fun = x448 + self.ecdhXs = getRandomBytes(divceil(X448_ORDER, 8)) + ecdhYs = fun(self.ecdhXs, generator) + else: + curve = getCurveByName(GroupName.toRepr(self.group_id)) + generator = curve.generator + self.ecdhXs = ecdsa.util.randrange(generator.order()) - ecdhYs = encodeX962Point(generator * self.ecdhXs) + ecdhYs = encodeX962Point(generator * self.ecdhXs) version = self.serverHello.server_version serverKeyExchange = ServerKeyExchange(self.cipherSuite, version) @@ -512,17 +526,27 @@ def makeServerKeyExchange(self, sigHash=None): def processClientKeyExchange(self, clientKeyExchange): """Calculate premaster secret from previously generated SKE and CKE""" - curveName = GroupName.toRepr(self.group_id) - try: - ecdhYc = decodeX962Point(clientKeyExchange.ecdh_Yc, - getCurveByName(curveName)) - # TODO update python-ecdsa library to raise something more on point - except AssertionError: - raise TLSIllegalParameterException("Invalid ECC point") + if self.group_id in [GroupName.x25519, GroupName.x448]: + ecdhYc = clientKeyExchange.ecdh_Yc - sharedSecret = ecdhYc * self.ecdhXs + if self.group_id == GroupName.x25519: + sharedSecret = x25519(self.ecdhXs, ecdhYc) + else: + sharedSecret = x448(self.ecdhXs, ecdhYc) + return sharedSecret + else: + curveName = GroupName.toRepr(self.group_id) + try: + ecdhYc = decodeX962Point(clientKeyExchange.ecdh_Yc, + getCurveByName(curveName)) + # TODO update python-ecdsa library to raise something more on point + except AssertionError: + raise TLSIllegalParameterException("Invalid ECC point") - return numberToByteArray(sharedSecret.x(), getPointByteSize(ecdhYc)) + sharedSecret = ecdhYc * self.ecdhXs + + return numberToByteArray(sharedSecret.x(), + getPointByteSize(ecdhYc)) def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): """Process the server key exchange, return premaster secret""" @@ -533,15 +557,29 @@ def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): raise TLSIllegalParameterException("Server picked curve we " "didn't advertise") - curveName = GroupName.toStr(serverKeyExchange.named_curve) - curve = getCurveByName(curveName) - generator = curve.generator - - ecdhXc = ecdsa.util.randrange(generator.order()) - ecdhYs = decodeX962Point(serverKeyExchange.ecdh_Ys, curve) - self.ecdhYc = encodeX962Point(generator * ecdhXc) - S = ecdhYs * ecdhXc - return numberToByteArray(S.x(), getPointByteSize(S)) + if serverKeyExchange.named_curve in [GroupName.x25519, + GroupName.x448]: + if serverKeyExchange.named_curve == GroupName.x25519: + generator = bytearray(X25519_G) + fun = x25519 + ecdhXc = getRandomBytes(divceil(X25519_ORDER, 8)) + else: + generator = bytearray(X448_G) + fun = x448 + ecdhXc = getRandomBytes(divceil(X448_ORDER, 8)) + self.ecdhYc = fun(ecdhXc, generator) + S = fun(ecdhXc, serverKeyExchange.ecdh_Ys) + return S + else: + curveName = GroupName.toStr(serverKeyExchange.named_curve) + curve = getCurveByName(curveName) + generator = curve.generator + + ecdhXc = ecdsa.util.randrange(generator.order()) + ecdhYs = decodeX962Point(serverKeyExchange.ecdh_Ys, curve) + self.ecdhYc = encodeX962Point(generator * ecdhXc) + S = ecdhYs * ecdhXc + return numberToByteArray(S.x(), getPointByteSize(S)) def makeClientKeyExchange(self): """Make client key exchange for ECDHE""" diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index 128fd33f..cc86292b 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -45,6 +45,7 @@ from tlslite.keyexchange import KeyExchange, RSAKeyExchange, \ DHE_RSAKeyExchange, SRPKeyExchange, ECDHE_RSAKeyExchange +from tlslite.utils.x25519 import x25519, X25519_G, x448, X448_G srv_raw_key = str( "-----BEGIN RSA PRIVATE KEY-----\n"\ @@ -1290,6 +1291,7 @@ def test_client_ECDHE_key_exchange_with_invalid_server_curve(self): with self.assertRaises(TLSIllegalParameterException): client_keyExchange.processServerKeyExchange(None, srv_key_ex) + class TestRSAKeyExchange_with_PSS_scheme(unittest.TestCase): def setUp(self): self.srv_private_key = parsePEMKey(srv_raw_key, private=True) @@ -1356,3 +1358,137 @@ def m(length): b"\x03\xe5[\xf5\xc7z\x02\'/\x0f\xdc\x1f" b"\xd2\x93\x8b\x12\x01%\x1d\x04\xf1[" b"\xe4\x9a\x83\xf8\xd3#+")) + + +class TestECDHE_RSAKeyExchange_with_x25519(unittest.TestCase): + def setUp(self): + self.srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = srv_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + ext = [SupportedGroupsExtension().create([GroupName.x25519])] + self.client_hello = ClientHello().create((3, 3), + bytearray(32), + bytearray(0), + [], + extensions=ext) + self.server_hello = ServerHello().create((3, 3), + bytearray(32), + bytearray(0), + self.cipher_suite) + + self.keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key, + [GroupName.x25519]) + + def test_ECDHE_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + KeyExchange.verifyServerKeyExchange(srv_key_ex, + self.srv_pub_key, + self.client_hello.random, + self.server_hello.random, + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + self.assertEqual(srv_key_ex.named_curve, GroupName.x25519) + generator = bytearray(X25519_G) + cln_Xc = getRandomBytes(32) + cln_Ys = srv_key_ex.ecdh_Ys + cln_Yc = x25519(cln_Xc, generator) + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)) + cln_key_ex.createECDH(cln_Yc) + + cln_S = x25519(cln_Xc, cln_Ys) + + srv_premaster = self.keyExchange.processClientKeyExchange(cln_key_ex) + + self.assertEqual(cln_S, srv_premaster) + + def test_client_ECDHE_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.x25519]) + client_premaster = client_keyExchange.processServerKeyExchange(\ + None, + srv_key_ex) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) + + +class TestECDHE_RSAKeyExchange_with_x448(unittest.TestCase): + def setUp(self): + self.srv_private_key = parsePEMKey(srv_raw_key, private=True) + srv_chain = X509CertChain([X509().parse(srv_raw_certificate)]) + self.srv_pub_key = srv_chain.getEndEntityPublicKey() + self.cipher_suite = CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + ext = [SupportedGroupsExtension().create([GroupName.x448])] + self.client_hello = ClientHello().create((3, 3), + bytearray(32), + bytearray(0), + [], + extensions=ext) + self.server_hello = ServerHello().create((3, 3), + bytearray(32), + bytearray(0), + self.cipher_suite) + + self.keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + self.srv_private_key, + [GroupName.x448]) + + def test_ECDHE_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + KeyExchange.verifyServerKeyExchange(srv_key_ex, + self.srv_pub_key, + self.client_hello.random, + self.server_hello.random, + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + self.assertEqual(srv_key_ex.named_curve, GroupName.x448) + generator = bytearray(X448_G) + cln_Xc = getRandomBytes(56) + cln_Ys = srv_key_ex.ecdh_Ys + cln_Yc = x448(cln_Xc, generator) + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)) + cln_key_ex.createECDH(cln_Yc) + + cln_S = x448(cln_Xc, cln_Ys) + + srv_premaster = self.keyExchange.processClientKeyExchange(cln_key_ex) + + self.assertEqual(cln_S, srv_premaster) + + def test_client_ECDHE_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.x448]) + client_premaster = client_keyExchange.processServerKeyExchange(\ + None, + srv_key_ex) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + + server_premaster = self.keyExchange.processClientKeyExchange(\ + clientKeyExchange) + + self.assertEqual(client_premaster, server_premaster) From 337346230ef94fc9167f2f1b4e3c561491de7842 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 24 Feb 2017 16:54:28 +0100 Subject: [PATCH 465/574] uncommon cases for x25519 and x448 --- unit_tests/test_tlslite_utils_x25519.py | 59 ++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/unit_tests/test_tlslite_utils_x25519.py b/unit_tests/test_tlslite_utils_x25519.py index 8ab27016..59b431ba 100644 --- a/unit_tests/test_tlslite_utils_x25519.py +++ b/unit_tests/test_tlslite_utils_x25519.py @@ -17,7 +17,7 @@ from tlslite.utils.x25519 import decodeUCoordinate, decodeScalar22519, \ decodeScalar448, x25519, x448, X25519_G, X448_G -from tlslite.utils.compat import a2b_hex +from tlslite.utils.compat import a2b_hex, b2a_hex from tlslite.utils.cryptomath import numberToByteArray class TestDecodeUCoordinate(unittest.TestCase): @@ -54,6 +54,7 @@ def test_x448_decode(self): "1515452463053830")) +class TestDecodeScalar22519(unittest.TestCase): def test_x25519_decode_scalar(self): value = a2b_hex('a546e36bf0527c9d3b16154b82465edd6' '2144c0ac1fc5a18506a2244ba449ac4') @@ -64,6 +65,7 @@ def test_x25519_decode_scalar(self): "72772604678260265531221036453811406496")) +class TestDecodeScalar448(unittest.TestCase): def test_x448_decode_scalar(self): value = a2b_hex('3d262fddf9ec8e88495266fea19a34d2' '8882acef045104d0d1aae121' @@ -75,9 +77,62 @@ def test_x448_decode_scalar(self): self.assertEqual(int("599189175373896402783756016145213256157230856" "085026129926891459468622403380588640249457727" "683869421921443004045221642549886377526240828"), - scalar) + scalar) +class TestUncommonInputsX25519(unittest.TestCase): + def test_all_zero_k(self): + k = bytearray(32) + u = a2b_hex("e6db6867583030db3594c1a424b15f7" + "c726624ec26b3353b10a903a6d0ab1c4c") + + ret = x25519(k, u) + + self.assertEqual(ret, + a2b_hex("030d7ba1a76719f96d5c39122f690e78" + "56895ee9d24416279eb9182010287113")) + + def test_all_zero_u(self): + k = a2b_hex("a546e36bf0527c9d3b16154b82465ed" + "d62144c0ac1fc5a18506a2244ba449ac4") + u = bytearray(32) + + ret = x25519(k, u) + + self.assertEqual(ret, + bytearray(32)) + + +class TestUncommonInputsX448(unittest.TestCase): + def test_all_zero_k(self): + k = bytearray(56) + u = a2b_hex("06fce640fa3487bfda5f6cf2d5263f8" + "aad88334cbd07437f020f08f9" + "814dc031ddbdc38c19c6da2583fa542" + "9db94ada18aa7a7fb4ef8a086") + + ret = x448(k, u) + + self.assertEqual(ret, + a2b_hex("f8d21fea4fe227fa556d27ec5317d839" + "4db22217e27a96c8f7b47d36a4e15ba1" + "bef872684ba18ee5ce72577b0aed87e9" + "8a3714ab32d9d169")) + + def test_all_zero_u(self): + k = a2b_hex("3d262fddf9ec8e88495266fea19a34d" + "28882acef045104d0d1aae121" + "700a779c984c24f8cdd78fbff44943e" + "ba368f54b29259a4f1c600ad3") + u = bytearray(56) + + ret = x448(k, u) + + self.assertEqual(ret, + bytearray(56)) + + +class TestKnownAnswerTests(unittest.TestCase): # RFC 7748 Section 5.2, vector #1 def test_x25519_1(self): k = a2b_hex("a546e36bf0527c9d3b16154b82465ed" From 23bf57e5aada1aea1c6a0eb161cbde0623a149c1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 18 Jul 2017 20:05:17 +0200 Subject: [PATCH 466/574] error checking on TLS level (from draft-ietf-tls-rfc4492bis) --- tlslite/keyexchange.py | 27 +++++ unit_tests/test_tlslite_keyexchange.py | 136 +++++++++++++++++++++++++ 2 files changed, 163 insertions(+) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 2a8cf1c7..f693cbae 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -470,6 +470,20 @@ class AECDHKeyExchange(KeyExchange): ECDHE without signing serverKeyExchange useful for anonymous ECDH """ + + @staticmethod + def _non_zero_check(value): + """ + Verify using constant time operation that the bytearray is not zero + + @raises TLSIllegalParameterException: if the value is all zero + """ + summa = 0 + for i in value: + summa |= i + if summa == 0: + raise TLSIllegalParameterException("Invalid key share") + def __init__(self, cipherSuite, clientHello, serverHello, acceptedCurves, defaultCurve=GroupName.secp256r1): super(AECDHKeyExchange, self).__init__(cipherSuite, clientHello, @@ -530,9 +544,14 @@ def processClientKeyExchange(self, clientKeyExchange): ecdhYc = clientKeyExchange.ecdh_Yc if self.group_id == GroupName.x25519: + if len(ecdhYc) != 32: + raise TLSIllegalParameterException("Invalid key share") sharedSecret = x25519(self.ecdhXs, ecdhYc) else: + if len(ecdhYc) != 56: + raise TLSIllegalParameterException("Invalid key share") sharedSecret = x448(self.ecdhXs, ecdhYc) + self._non_zero_check(sharedSecret) return sharedSecret else: curveName = GroupName.toRepr(self.group_id) @@ -563,12 +582,20 @@ def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): generator = bytearray(X25519_G) fun = x25519 ecdhXc = getRandomBytes(divceil(X25519_ORDER, 8)) + if len(serverKeyExchange.ecdh_Ys) != 32: + raise TLSIllegalParameterException("Invalid server key " + "share") else: generator = bytearray(X448_G) fun = x448 ecdhXc = getRandomBytes(divceil(X448_ORDER, 8)) + if len(serverKeyExchange.ecdh_Ys) != 56: + raise TLSIllegalParameterException("Invalid server key " + "share") self.ecdhYc = fun(ecdhXc, generator) S = fun(ecdhXc, serverKeyExchange.ecdh_Ys) + # check if the secret is not all-zero + self._non_zero_check(S) return S else: curveName = GroupName.toStr(serverKeyExchange.named_curve) diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index cc86292b..bf8b5866 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -1426,6 +1426,101 @@ def test_client_ECDHE_key_exchange(self): self.assertEqual(client_premaster, server_premaster) + def test_client_ECDHE_key_exchange_with_invalid_size(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.x25519]) + client_premaster = client_keyExchange.processServerKeyExchange(\ + None, + srv_key_ex) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + clientKeyExchange.ecdh_Yc += bytearray(b'\x00') + + with self.assertRaises(TLSIllegalParameterException): + self.keyExchange.processClientKeyExchange(clientKeyExchange) + + def test_client_ECDHE_key_exchange_with_all_zero_share(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.x25519]) + client_premaster = client_keyExchange.processServerKeyExchange(\ + None, + srv_key_ex) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + clientKeyExchange.ecdh_Yc = bytearray(32) + + with self.assertRaises(TLSIllegalParameterException): + self.keyExchange.processClientKeyExchange(clientKeyExchange) + + def test_client_ECDHE_key_exchange_with_high_bit_set(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.x25519]) + client_premaster = client_keyExchange.processServerKeyExchange(\ + None, + srv_key_ex) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + clientKeyExchange.ecdh_Yc[-1] |= 0x80 + + S = self.keyExchange.processClientKeyExchange(clientKeyExchange) + + # we have modified public value, so can't actually compute shared + # value as a result, just sanity check + self.assertEqual(32, len(S)) + self.assertNotEqual(bytearray(32), S) + + def test_client_with_invalid_size_ECDHE_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.x25519]) + srv_key_ex.ecdh_Ys += bytearray(b'\x00') + with self.assertRaises(TLSIllegalParameterException): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) + + def test_client_with_all_zero_ECDHE_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.x25519]) + srv_key_ex.ecdh_Ys = bytearray(32) + with self.assertRaises(TLSIllegalParameterException): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) + + def test_client_with_high_bit_set_ECDHE_key_exchange(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.x25519]) + srv_key_ex.ecdh_Ys[-1] |= 0x80 + S = client_keyExchange.processServerKeyExchange(None, srv_key_ex) + + # we have modified public value, so can't calculate the resulting + # shared secret as a result, perform just a sanity check + self.assertEqual(32, len(S)) + self.assertNotEqual(bytearray(32), S) + class TestECDHE_RSAKeyExchange_with_x448(unittest.TestCase): def setUp(self): @@ -1492,3 +1587,44 @@ def test_client_ECDHE_key_exchange(self): clientKeyExchange) self.assertEqual(client_premaster, server_premaster) + + def test_client_ECDHE_key_exchange_with_invalid_size(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.x448]) + client_premaster = client_keyExchange.processServerKeyExchange(\ + None, + srv_key_ex) + clientKeyExchange = client_keyExchange.makeClientKeyExchange() + clientKeyExchange.ecdh_Yc += bytearray(b'\x00') + + with self.assertRaises(TLSIllegalParameterException): + self.keyExchange.processClientKeyExchange(clientKeyExchange) + + def test_client_with_invalid_size_ECDHE_key_share(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.x448]) + srv_key_ex.ecdh_Ys += bytearray(b'\x00') + with self.assertRaises(TLSIllegalParameterException): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) + + def test_client_with_all_zero_ECDHE_key_share(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.x448]) + srv_key_ex.ecdh_Ys = bytearray(56) + with self.assertRaises(TLSIllegalParameterException): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) From 4646646b2ff05b64564ffe857e329b80cbcaac98 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 19 Jul 2017 19:27:00 +0200 Subject: [PATCH 467/574] release 0.7.0-alpha8 --- README.md | 8 ++++++-- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4f1ff91e..42c13062 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.7.0-alpha7 2017-07-14 +tlslite-ng version 0.7.0-alpha8 2017-07-19 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -587,6 +587,10 @@ encrypt-then-MAC mode for CBC ciphers. 0.7.0 - in-dev +* enable and add missing definitions of TLS_ECDHE_RSA_WITH_RC4_128_SHA and + TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA +* add definitions of some ECDHE_ECDSA, ECDH_ECDSA and ECDH_RSA ciphersuites, + they remain unsupported, but IDs are useful for other projects * basic support for RSA-PSS (Tomas Foukal) * support for RSA-PSS in TLSv1.2 * better documentation for Parser and ASN1Parser @@ -606,7 +610,7 @@ encrypt-then-MAC mode for CBC ciphers. follow RFC recommendations with regards to session resumption, reject non-empty * Allow negotiation of ECDHE ciphersuites even if client doesn't advertise - any curves, assume P-256 curve support. + any curves, default to P-256 curve support, support configuring it. 0.6.0 - 2016-09-07 diff --git a/setup.py b/setup.py index 4b57af4b..6dc10b1b 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ README = f.read() setup(name="tlslite-ng", - version="0.7.0-alpha7", + version="0.7.0-alpha8", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 9ab67a0b..28476d2e 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.7.0-alpha7 +@version: 0.7.0-alpha8 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 71e24cdb..c648af0f 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.7.0-alpha7" +__version__ = "0.7.0-alpha8" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From e0f1a75aedcb6db3c9274984d9105e4fc38a7eb0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 20 Jul 2017 13:15:32 +0200 Subject: [PATCH 468/574] release 0.7.0-alpha9 --- README.md | 3 ++- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 42c13062..eeef7fa9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.7.0-alpha8 2017-07-19 +tlslite-ng version 0.7.0-alpha9 2017-07-20 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -611,6 +611,7 @@ encrypt-then-MAC mode for CBC ciphers. non-empty * Allow negotiation of ECDHE ciphersuites even if client doesn't advertise any curves, default to P-256 curve support, support configuring it. +* Stricter checks on received SNI (server_name) extension 0.6.0 - 2016-09-07 diff --git a/setup.py b/setup.py index 6dc10b1b..b4cd0fb5 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ README = f.read() setup(name="tlslite-ng", - version="0.7.0-alpha8", + version="0.7.0-alpha9", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 28476d2e..a2a8d22b 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.7.0-alpha8 +@version: 0.7.0-alpha9 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index c648af0f..b4b4a5d3 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.7.0-alpha8" +__version__ = "0.7.0-alpha9" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 3ae0ab93563836af32a340c5a2ef786f9c586fe4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 21 Jul 2017 12:41:07 +0200 Subject: [PATCH 469/574] improve readability of big endian variant of numberToByteArray --- tlslite/utils/cryptomath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index a0eed1aa..e0d30596 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -140,7 +140,7 @@ def numberToByteArray(n, howManyBytes=None, endian="big"): howManyBytes = numBytes(n) if endian == "big": return bytearray((n >> i) & 0xff - for i in range((howManyBytes-1)*8, -1, -8)) + for i in reversed(range(0, howManyBytes*8, 8))) elif endian == "little": return bytearray((n >> i) & 0xff for i in range(0, howManyBytes*8, 8)) From 3ed639b4d61272a5a514082ebfe5cd7cdb3fd309 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 21 Jul 2017 12:41:33 +0200 Subject: [PATCH 470/574] update docs of numberToByteArray to indicate support for little endian --- tlslite/utils/cryptomath.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index e0d30596..65de786d 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -133,8 +133,8 @@ def numberToByteArray(n, howManyBytes=None, endian="big"): Convert an integer into a bytearray, zero-pad to howManyBytes. The returned bytearray may be smaller than howManyBytes, but will - not be larger. The returned bytearray will contain a big-endian - encoding of the input integer (n). + not be larger. The returned bytearray will contain a big- or little-endian + encoding of the input integer (n). Big endian encoding is used by default. """ if howManyBytes == None: howManyBytes = numBytes(n) From 44b8682b0e0b76d009df502b0d9062e73ee506b8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 21 Jul 2017 19:11:10 +0200 Subject: [PATCH 471/574] make the name of X25519_ORDER reflect contents --- tlslite/keyexchange.py | 20 ++++++++++---------- tlslite/utils/x25519.py | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index f693cbae..90f87128 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -18,8 +18,8 @@ numBits, numberToByteArray, divceil from .utils.lists import getFirstMatching from .utils import tlshashlib as hashlib -from .utils.x25519 import x25519, x448, X25519_G, X448_G, X25519_ORDER, \ - X448_ORDER +from .utils.x25519 import x25519, x448, X25519_G, X448_G, X25519_ORDER_SIZE, \ + X448_ORDER_SIZE import ecdsa class KeyExchange(object): @@ -517,11 +517,11 @@ def makeServerKeyExchange(self, sigHash=None): if self.group_id == GroupName.x25519: generator = bytearray(X25519_G) fun = x25519 - self.ecdhXs = getRandomBytes(divceil(X25519_ORDER, 8)) + self.ecdhXs = getRandomBytes(X25519_ORDER_SIZE) else: generator = bytearray(X448_G) fun = x448 - self.ecdhXs = getRandomBytes(divceil(X448_ORDER, 8)) + self.ecdhXs = getRandomBytes(X448_ORDER_SIZE) ecdhYs = fun(self.ecdhXs, generator) else: curve = getCurveByName(GroupName.toRepr(self.group_id)) @@ -544,11 +544,11 @@ def processClientKeyExchange(self, clientKeyExchange): ecdhYc = clientKeyExchange.ecdh_Yc if self.group_id == GroupName.x25519: - if len(ecdhYc) != 32: + if len(ecdhYc) != X25519_ORDER_SIZE: raise TLSIllegalParameterException("Invalid key share") sharedSecret = x25519(self.ecdhXs, ecdhYc) else: - if len(ecdhYc) != 56: + if len(ecdhYc) != X448_ORDER_SIZE: raise TLSIllegalParameterException("Invalid key share") sharedSecret = x448(self.ecdhXs, ecdhYc) self._non_zero_check(sharedSecret) @@ -581,15 +581,15 @@ def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): if serverKeyExchange.named_curve == GroupName.x25519: generator = bytearray(X25519_G) fun = x25519 - ecdhXc = getRandomBytes(divceil(X25519_ORDER, 8)) - if len(serverKeyExchange.ecdh_Ys) != 32: + ecdhXc = getRandomBytes(X25519_ORDER_SIZE) + if len(serverKeyExchange.ecdh_Ys) != X25519_ORDER_SIZE: raise TLSIllegalParameterException("Invalid server key " "share") else: generator = bytearray(X448_G) fun = x448 - ecdhXc = getRandomBytes(divceil(X448_ORDER, 8)) - if len(serverKeyExchange.ecdh_Ys) != 56: + ecdhXc = getRandomBytes(X448_ORDER_SIZE) + if len(serverKeyExchange.ecdh_Ys) != X448_ORDER_SIZE: raise TLSIllegalParameterException("Invalid server key " "share") self.ecdhYc = fun(ecdhXc, generator) diff --git a/tlslite/utils/x25519.py b/tlslite/utils/x25519.py index 7580c839..ed3a87aa 100644 --- a/tlslite/utils/x25519.py +++ b/tlslite/utils/x25519.py @@ -46,7 +46,7 @@ def cswap(swap, x_2, x_3): X25519_G = numberToByteArray(9, 32, endian="little") -X25519_ORDER = 256 +X25519_ORDER_SIZE = 32 def x25519(k, u): @@ -74,7 +74,7 @@ def x25519(k, u): X448_G = numberToByteArray(5, 56, endian="little") -X448_ORDER = 448 +X448_ORDER_SIZE = 56 def x448(k, u): From 0b6e2a0515887466616b08a3d9560ebd94b69826 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 20 Jul 2017 20:04:05 +0200 Subject: [PATCH 472/574] sphinx settings --- .gitignore | 2 +- docs/Makefile | 225 ++++++++++++ docs/conf.py | 340 ++++++++++++++++++ docs/index.rst | 20 ++ docs/make.bat | 281 +++++++++++++++ docs/modules.rst | 7 + docs/tlslite.api.rst | 8 + docs/tlslite.basedb.rst | 9 + docs/tlslite.bufferedsocket.rst | 8 + docs/tlslite.checker.rst | 8 + docs/tlslite.constants.rst | 7 + docs/tlslite.defragmenter.rst | 8 + docs/tlslite.dh.rst | 8 + docs/tlslite.errors.rst | 8 + docs/tlslite.extensions.rst | 8 + docs/tlslite.handshakehashes.rst | 8 + docs/tlslite.handshakehelpers.rst | 7 + docs/tlslite.handshakesettings.rst | 8 + .../tlslite.integration.asyncstatemachine.rst | 8 + docs/tlslite.integration.clienthelper.rst | 8 + .../tlslite.integration.httptlsconnection.rst | 8 + docs/tlslite.integration.imap4_tls.rst | 8 + docs/tlslite.integration.pop3_tls.rst | 8 + docs/tlslite.integration.rst | 25 ++ docs/tlslite.integration.smtp_tls.rst | 8 + ...te.integration.tlsasyncdispatchermixin.rst | 8 + ...slite.integration.tlssocketservermixin.rst | 8 + docs/tlslite.integration.xmlrpcserver.rst | 8 + docs/tlslite.integration.xmlrpctransport.rst | 8 + docs/tlslite.keyexchange.rst | 8 + docs/tlslite.mathtls.rst | 8 + docs/tlslite.messages.rst | 8 + docs/tlslite.messagesocket.rst | 8 + docs/tlslite.recordlayer.rst | 8 + docs/tlslite.rst | 47 +++ docs/tlslite.session.rst | 8 + docs/tlslite.sessioncache.rst | 8 + docs/tlslite.tlsconnection.rst | 8 + docs/tlslite.tlsrecordlayer.rst | 8 + docs/tlslite.utils.aes.rst | 8 + docs/tlslite.utils.aesgcm.rst | 8 + docs/tlslite.utils.asn1parser.rst | 8 + docs/tlslite.utils.chacha.rst | 8 + docs/tlslite.utils.chacha20_poly1305.rst | 8 + docs/tlslite.utils.cipherfactory.rst | 8 + docs/tlslite.utils.codec.rst | 8 + docs/tlslite.utils.compat.rst | 8 + docs/tlslite.utils.constanttime.rst | 8 + docs/tlslite.utils.cryptomath.rst | 8 + docs/tlslite.utils.datefuncs.rst | 8 + docs/tlslite.utils.dns_utils.rst | 8 + docs/tlslite.utils.ecc.rst | 8 + docs/tlslite.utils.keyfactory.rst | 8 + docs/tlslite.utils.lists.rst | 8 + docs/tlslite.utils.openssl_aes.rst | 8 + docs/tlslite.utils.openssl_rc4.rst | 8 + docs/tlslite.utils.openssl_rsakey.rst | 8 + docs/tlslite.utils.openssl_tripledes.rst | 8 + docs/tlslite.utils.pem.rst | 8 + docs/tlslite.utils.poly1305.rst | 8 + docs/tlslite.utils.pycrypto_aes.rst | 8 + docs/tlslite.utils.pycrypto_aesgcm.rst | 8 + docs/tlslite.utils.pycrypto_rc4.rst | 8 + docs/tlslite.utils.pycrypto_rsakey.rst | 8 + docs/tlslite.utils.pycrypto_tripledes.rst | 8 + docs/tlslite.utils.python_aes.rst | 8 + docs/tlslite.utils.python_aesgcm.rst | 8 + ...tlslite.utils.python_chacha20_poly1305.rst | 8 + docs/tlslite.utils.python_rc4.rst | 8 + docs/tlslite.utils.python_rsakey.rst | 8 + docs/tlslite.utils.rc4.rst | 8 + docs/tlslite.utils.rijndael.rst | 8 + docs/tlslite.utils.rsakey.rst | 8 + docs/tlslite.utils.rst | 53 +++ docs/tlslite.utils.tackwrapper.rst | 8 + docs/tlslite.utils.tlshashlib.rst | 8 + docs/tlslite.utils.tripledes.rst | 8 + docs/tlslite.utils.x25519.rst | 8 + docs/tlslite.verifierdb.rst | 8 + docs/tlslite.x509.rst | 8 + docs/tlslite.x509certchain.rst | 8 + 81 files changed, 1574 insertions(+), 1 deletion(-) create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/modules.rst create mode 100644 docs/tlslite.api.rst create mode 100644 docs/tlslite.basedb.rst create mode 100644 docs/tlslite.bufferedsocket.rst create mode 100644 docs/tlslite.checker.rst create mode 100644 docs/tlslite.constants.rst create mode 100644 docs/tlslite.defragmenter.rst create mode 100644 docs/tlslite.dh.rst create mode 100644 docs/tlslite.errors.rst create mode 100644 docs/tlslite.extensions.rst create mode 100644 docs/tlslite.handshakehashes.rst create mode 100644 docs/tlslite.handshakehelpers.rst create mode 100644 docs/tlslite.handshakesettings.rst create mode 100644 docs/tlslite.integration.asyncstatemachine.rst create mode 100644 docs/tlslite.integration.clienthelper.rst create mode 100644 docs/tlslite.integration.httptlsconnection.rst create mode 100644 docs/tlslite.integration.imap4_tls.rst create mode 100644 docs/tlslite.integration.pop3_tls.rst create mode 100644 docs/tlslite.integration.rst create mode 100644 docs/tlslite.integration.smtp_tls.rst create mode 100644 docs/tlslite.integration.tlsasyncdispatchermixin.rst create mode 100644 docs/tlslite.integration.tlssocketservermixin.rst create mode 100644 docs/tlslite.integration.xmlrpcserver.rst create mode 100644 docs/tlslite.integration.xmlrpctransport.rst create mode 100644 docs/tlslite.keyexchange.rst create mode 100644 docs/tlslite.mathtls.rst create mode 100644 docs/tlslite.messages.rst create mode 100644 docs/tlslite.messagesocket.rst create mode 100644 docs/tlslite.recordlayer.rst create mode 100644 docs/tlslite.rst create mode 100644 docs/tlslite.session.rst create mode 100644 docs/tlslite.sessioncache.rst create mode 100644 docs/tlslite.tlsconnection.rst create mode 100644 docs/tlslite.tlsrecordlayer.rst create mode 100644 docs/tlslite.utils.aes.rst create mode 100644 docs/tlslite.utils.aesgcm.rst create mode 100644 docs/tlslite.utils.asn1parser.rst create mode 100644 docs/tlslite.utils.chacha.rst create mode 100644 docs/tlslite.utils.chacha20_poly1305.rst create mode 100644 docs/tlslite.utils.cipherfactory.rst create mode 100644 docs/tlslite.utils.codec.rst create mode 100644 docs/tlslite.utils.compat.rst create mode 100644 docs/tlslite.utils.constanttime.rst create mode 100644 docs/tlslite.utils.cryptomath.rst create mode 100644 docs/tlslite.utils.datefuncs.rst create mode 100644 docs/tlslite.utils.dns_utils.rst create mode 100644 docs/tlslite.utils.ecc.rst create mode 100644 docs/tlslite.utils.keyfactory.rst create mode 100644 docs/tlslite.utils.lists.rst create mode 100644 docs/tlslite.utils.openssl_aes.rst create mode 100644 docs/tlslite.utils.openssl_rc4.rst create mode 100644 docs/tlslite.utils.openssl_rsakey.rst create mode 100644 docs/tlslite.utils.openssl_tripledes.rst create mode 100644 docs/tlslite.utils.pem.rst create mode 100644 docs/tlslite.utils.poly1305.rst create mode 100644 docs/tlslite.utils.pycrypto_aes.rst create mode 100644 docs/tlslite.utils.pycrypto_aesgcm.rst create mode 100644 docs/tlslite.utils.pycrypto_rc4.rst create mode 100644 docs/tlslite.utils.pycrypto_rsakey.rst create mode 100644 docs/tlslite.utils.pycrypto_tripledes.rst create mode 100644 docs/tlslite.utils.python_aes.rst create mode 100644 docs/tlslite.utils.python_aesgcm.rst create mode 100644 docs/tlslite.utils.python_chacha20_poly1305.rst create mode 100644 docs/tlslite.utils.python_rc4.rst create mode 100644 docs/tlslite.utils.python_rsakey.rst create mode 100644 docs/tlslite.utils.rc4.rst create mode 100644 docs/tlslite.utils.rijndael.rst create mode 100644 docs/tlslite.utils.rsakey.rst create mode 100644 docs/tlslite.utils.rst create mode 100644 docs/tlslite.utils.tackwrapper.rst create mode 100644 docs/tlslite.utils.tlshashlib.rst create mode 100644 docs/tlslite.utils.tripledes.rst create mode 100644 docs/tlslite.utils.x25519.rst create mode 100644 docs/tlslite.verifierdb.rst create mode 100644 docs/tlslite.x509.rst create mode 100644 docs/tlslite.x509certchain.rst diff --git a/.gitignore b/.gitignore index ffa11409..cb85c5a6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,5 @@ coverage.xml pylint_report.txt build/ -docs/ +docs/_build/ htmlcov/ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..1e21bbc8 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,225 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " epub3 to make an epub3" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + @echo " dummy to check syntax errors of document sources" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/tlslite-ng.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/tlslite-ng.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/tlslite-ng" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/tlslite-ng" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +.PHONY: dummy +dummy: + $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy + @echo + @echo "Build finished. Dummy builder generates no files." diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..8d9a24a1 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,340 @@ +# -*- coding: utf-8 -*- +# +# tlslite-ng documentation build configuration file, created by +# sphinx-quickstart on Thu Jul 20 13:51:42 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'tlslite-ng' +copyright = u'2017, Hubert Kario' +author = u'Hubert Kario' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'0.7' +# The full version, including alpha/beta/rc tags. +release = u'0.7.0-alpha9' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +# html_title = u'tlslite-ng v0.7.0-alpha9' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +# html_logo = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or +# 32x32 pixels large. +# +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +# html_domain_indices = True + +# If false, no index is generated. +# +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'tlslite-ngdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'tlslite-ng.tex', u'tlslite-ng Documentation', + u'Hubert Kario', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# +# latex_use_parts = False + +# If true, show page references after internal links. +# +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# It false, will not define \strong, \code, itleref, \crossref ... but only +# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added +# packages. +# +# latex_keep_old_macro_names = True + +# If false, no module index is generated. +# +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'tlslite-ng', u'tlslite-ng Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'tlslite-ng', u'tlslite-ng Documentation', + author, 'tlslite-ng', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..121e8c5d --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,20 @@ +.. tlslite-ng documentation master file, created by + sphinx-quickstart on Thu Jul 20 13:51:42 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to tlslite-ng's documentation! +====================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..45a97b76 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,281 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. epub3 to make an epub3 + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + echo. dummy to check syntax errors of document sources + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\tlslite-ng.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\tlslite-ng.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "epub3" ( + %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +if "%1" == "dummy" ( + %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. Dummy builder generates no files. + goto end +) + +:end diff --git a/docs/modules.rst b/docs/modules.rst new file mode 100644 index 00000000..9e1fba8d --- /dev/null +++ b/docs/modules.rst @@ -0,0 +1,7 @@ +tlslite +======= + +.. toctree:: + :maxdepth: 4 + + tlslite diff --git a/docs/tlslite.api.rst b/docs/tlslite.api.rst new file mode 100644 index 00000000..58defd2f --- /dev/null +++ b/docs/tlslite.api.rst @@ -0,0 +1,8 @@ +tlslite.api module +================== + +.. automodule:: tlslite.api + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.basedb.rst b/docs/tlslite.basedb.rst new file mode 100644 index 00000000..76b5f2d7 --- /dev/null +++ b/docs/tlslite.basedb.rst @@ -0,0 +1,9 @@ +tlslite.basedb module +===================== + +.. automodule:: tlslite.basedb + :members: + :special-members: __contains__, __delitem__, __getitem__, __init__, + __setitem__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.bufferedsocket.rst b/docs/tlslite.bufferedsocket.rst new file mode 100644 index 00000000..f5b9661f --- /dev/null +++ b/docs/tlslite.bufferedsocket.rst @@ -0,0 +1,8 @@ +tlslite.bufferedsocket module +============================= + +.. automodule:: tlslite.bufferedsocket + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.checker.rst b/docs/tlslite.checker.rst new file mode 100644 index 00000000..48cf7cec --- /dev/null +++ b/docs/tlslite.checker.rst @@ -0,0 +1,8 @@ +tlslite.checker module +====================== + +.. automodule:: tlslite.checker + :members: + :special-members: __init__, __call__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.constants.rst b/docs/tlslite.constants.rst new file mode 100644 index 00000000..92ab9a26 --- /dev/null +++ b/docs/tlslite.constants.rst @@ -0,0 +1,7 @@ +tlslite.constants module +======================== + +.. automodule:: tlslite.constants + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.defragmenter.rst b/docs/tlslite.defragmenter.rst new file mode 100644 index 00000000..50a531f6 --- /dev/null +++ b/docs/tlslite.defragmenter.rst @@ -0,0 +1,8 @@ +tlslite.defragmenter module +=========================== + +.. automodule:: tlslite.defragmenter + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.dh.rst b/docs/tlslite.dh.rst new file mode 100644 index 00000000..be547bbe --- /dev/null +++ b/docs/tlslite.dh.rst @@ -0,0 +1,8 @@ +tlslite.dh module +================= + +.. automodule:: tlslite.dh + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.errors.rst b/docs/tlslite.errors.rst new file mode 100644 index 00000000..e221c920 --- /dev/null +++ b/docs/tlslite.errors.rst @@ -0,0 +1,8 @@ +tlslite.errors module +===================== + +.. automodule:: tlslite.errors + :members: + :special-members: __init__, __str__, __repr__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.extensions.rst b/docs/tlslite.extensions.rst new file mode 100644 index 00000000..98120eb7 --- /dev/null +++ b/docs/tlslite.extensions.rst @@ -0,0 +1,8 @@ +tlslite.extensions module +========================= + +.. automodule:: tlslite.extensions + :members: + :special-members: __init__, __repr__, __str__, __eq__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.handshakehashes.rst b/docs/tlslite.handshakehashes.rst new file mode 100644 index 00000000..c344a626 --- /dev/null +++ b/docs/tlslite.handshakehashes.rst @@ -0,0 +1,8 @@ +tlslite.handshakehashes module +============================== + +.. automodule:: tlslite.handshakehashes + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.handshakehelpers.rst b/docs/tlslite.handshakehelpers.rst new file mode 100644 index 00000000..e3293a2a --- /dev/null +++ b/docs/tlslite.handshakehelpers.rst @@ -0,0 +1,7 @@ +tlslite.handshakehelpers module +=============================== + +.. automodule:: tlslite.handshakehelpers + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.handshakesettings.rst b/docs/tlslite.handshakesettings.rst new file mode 100644 index 00000000..8cbbc8c7 --- /dev/null +++ b/docs/tlslite.handshakesettings.rst @@ -0,0 +1,8 @@ +tlslite.handshakesettings module +================================ + +.. automodule:: tlslite.handshakesettings + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.integration.asyncstatemachine.rst b/docs/tlslite.integration.asyncstatemachine.rst new file mode 100644 index 00000000..16c27c6e --- /dev/null +++ b/docs/tlslite.integration.asyncstatemachine.rst @@ -0,0 +1,8 @@ +tlslite.integration.asyncstatemachine module +============================================ + +.. automodule:: tlslite.integration.asyncstatemachine + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.integration.clienthelper.rst b/docs/tlslite.integration.clienthelper.rst new file mode 100644 index 00000000..6743b331 --- /dev/null +++ b/docs/tlslite.integration.clienthelper.rst @@ -0,0 +1,8 @@ +tlslite.integration.clienthelper module +======================================= + +.. automodule:: tlslite.integration.clienthelper + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.integration.httptlsconnection.rst b/docs/tlslite.integration.httptlsconnection.rst new file mode 100644 index 00000000..54df44c4 --- /dev/null +++ b/docs/tlslite.integration.httptlsconnection.rst @@ -0,0 +1,8 @@ +tlslite.integration.httptlsconnection module +============================================ + +.. automodule:: tlslite.integration.httptlsconnection + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.integration.imap4_tls.rst b/docs/tlslite.integration.imap4_tls.rst new file mode 100644 index 00000000..82c836e4 --- /dev/null +++ b/docs/tlslite.integration.imap4_tls.rst @@ -0,0 +1,8 @@ +tlslite.integration.imap4_tls module +==================================== + +.. automodule:: tlslite.integration.imap4_tls + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.integration.pop3_tls.rst b/docs/tlslite.integration.pop3_tls.rst new file mode 100644 index 00000000..a61a60f3 --- /dev/null +++ b/docs/tlslite.integration.pop3_tls.rst @@ -0,0 +1,8 @@ +tlslite.integration.pop3_tls module +=================================== + +.. automodule:: tlslite.integration.pop3_tls + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.integration.rst b/docs/tlslite.integration.rst new file mode 100644 index 00000000..de8a4fd7 --- /dev/null +++ b/docs/tlslite.integration.rst @@ -0,0 +1,25 @@ +tlslite.integration package +=========================== + +.. automodule:: tlslite.integration + :members: + :special-members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + + tlslite.integration.asyncstatemachine + tlslite.integration.clienthelper + tlslite.integration.httptlsconnection + tlslite.integration.imap4_tls + tlslite.integration.pop3_tls + tlslite.integration.smtp_tls + tlslite.integration.tlsasyncdispatchermixin + tlslite.integration.tlssocketservermixin + tlslite.integration.xmlrpcserver + tlslite.integration.xmlrpctransport + diff --git a/docs/tlslite.integration.smtp_tls.rst b/docs/tlslite.integration.smtp_tls.rst new file mode 100644 index 00000000..2b032fdc --- /dev/null +++ b/docs/tlslite.integration.smtp_tls.rst @@ -0,0 +1,8 @@ +tlslite.integration.smtp_tls module +=================================== + +.. automodule:: tlslite.integration.smtp_tls + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.integration.tlsasyncdispatchermixin.rst b/docs/tlslite.integration.tlsasyncdispatchermixin.rst new file mode 100644 index 00000000..936c2948 --- /dev/null +++ b/docs/tlslite.integration.tlsasyncdispatchermixin.rst @@ -0,0 +1,8 @@ +tlslite.integration.tlsasyncdispatchermixin module +================================================== + +.. automodule:: tlslite.integration.tlsasyncdispatchermixin + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.integration.tlssocketservermixin.rst b/docs/tlslite.integration.tlssocketservermixin.rst new file mode 100644 index 00000000..10fc2ec2 --- /dev/null +++ b/docs/tlslite.integration.tlssocketservermixin.rst @@ -0,0 +1,8 @@ +tlslite.integration.tlssocketservermixin module +=============================================== + +.. automodule:: tlslite.integration.tlssocketservermixin + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.integration.xmlrpcserver.rst b/docs/tlslite.integration.xmlrpcserver.rst new file mode 100644 index 00000000..10d51206 --- /dev/null +++ b/docs/tlslite.integration.xmlrpcserver.rst @@ -0,0 +1,8 @@ +tlslite.integration.xmlrpcserver module +======================================= + +.. automodule:: tlslite.integration.xmlrpcserver + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.integration.xmlrpctransport.rst b/docs/tlslite.integration.xmlrpctransport.rst new file mode 100644 index 00000000..5116cb87 --- /dev/null +++ b/docs/tlslite.integration.xmlrpctransport.rst @@ -0,0 +1,8 @@ +tlslite.integration.xmlrpctransport module +========================================== + +.. automodule:: tlslite.integration.xmlrpctransport + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.keyexchange.rst b/docs/tlslite.keyexchange.rst new file mode 100644 index 00000000..0dfe506b --- /dev/null +++ b/docs/tlslite.keyexchange.rst @@ -0,0 +1,8 @@ +tlslite.keyexchange module +========================== + +.. automodule:: tlslite.keyexchange + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.mathtls.rst b/docs/tlslite.mathtls.rst new file mode 100644 index 00000000..66798f5d --- /dev/null +++ b/docs/tlslite.mathtls.rst @@ -0,0 +1,8 @@ +tlslite.mathtls module +====================== + +.. automodule:: tlslite.mathtls + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.messages.rst b/docs/tlslite.messages.rst new file mode 100644 index 00000000..884e0c5d --- /dev/null +++ b/docs/tlslite.messages.rst @@ -0,0 +1,8 @@ +tlslite.messages module +======================= + +.. automodule:: tlslite.messages + :members: + :special-members: __init__, __repr__, __str__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.messagesocket.rst b/docs/tlslite.messagesocket.rst new file mode 100644 index 00000000..bd09c971 --- /dev/null +++ b/docs/tlslite.messagesocket.rst @@ -0,0 +1,8 @@ +tlslite.messagesocket module +============================ + +.. automodule:: tlslite.messagesocket + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.recordlayer.rst b/docs/tlslite.recordlayer.rst new file mode 100644 index 00000000..1628e44a --- /dev/null +++ b/docs/tlslite.recordlayer.rst @@ -0,0 +1,8 @@ +tlslite.recordlayer module +========================== + +.. automodule:: tlslite.recordlayer + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.rst b/docs/tlslite.rst new file mode 100644 index 00000000..45a7b7e7 --- /dev/null +++ b/docs/tlslite.rst @@ -0,0 +1,47 @@ +tlslite package +=============== + +.. automodule:: tlslite + :members: + :special-members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + + tlslite.integration + tlslite.utils + +Submodules +---------- + +.. toctree:: + + tlslite.api + tlslite.basedb + tlslite.bufferedsocket + tlslite.checker + tlslite.constants + tlslite.defragmenter + tlslite.dh + tlslite.errors + tlslite.extensions + tlslite.handshakehashes + tlslite.handshakehelpers + tlslite.handshakesettings + tlslite.keyexchange + tlslite.mathtls + tlslite.messages + tlslite.messagesocket + tlslite.recordlayer + tlslite.session + tlslite.sessioncache + tlslite.tlsconnection + tlslite.tlsrecordlayer + tlslite.verifierdb + tlslite.x509 + tlslite.x509certchain + diff --git a/docs/tlslite.session.rst b/docs/tlslite.session.rst new file mode 100644 index 00000000..07d94b1b --- /dev/null +++ b/docs/tlslite.session.rst @@ -0,0 +1,8 @@ +tlslite.session module +====================== + +.. automodule:: tlslite.session + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.sessioncache.rst b/docs/tlslite.sessioncache.rst new file mode 100644 index 00000000..4a8d7e05 --- /dev/null +++ b/docs/tlslite.sessioncache.rst @@ -0,0 +1,8 @@ +tlslite.sessioncache module +=========================== + +.. automodule:: tlslite.sessioncache + :members: + :special-members: __init__, __getitem__, __setitem__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.tlsconnection.rst b/docs/tlslite.tlsconnection.rst new file mode 100644 index 00000000..f3f8fd71 --- /dev/null +++ b/docs/tlslite.tlsconnection.rst @@ -0,0 +1,8 @@ +tlslite.tlsconnection module +============================ + +.. automodule:: tlslite.tlsconnection + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.tlsrecordlayer.rst b/docs/tlslite.tlsrecordlayer.rst new file mode 100644 index 00000000..9d86c644 --- /dev/null +++ b/docs/tlslite.tlsrecordlayer.rst @@ -0,0 +1,8 @@ +tlslite.tlsrecordlayer module +============================= + +.. automodule:: tlslite.tlsrecordlayer + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.aes.rst b/docs/tlslite.utils.aes.rst new file mode 100644 index 00000000..2e3e7ee2 --- /dev/null +++ b/docs/tlslite.utils.aes.rst @@ -0,0 +1,8 @@ +tlslite.utils.aes module +======================== + +.. automodule:: tlslite.utils.aes + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.aesgcm.rst b/docs/tlslite.utils.aesgcm.rst new file mode 100644 index 00000000..eeed660a --- /dev/null +++ b/docs/tlslite.utils.aesgcm.rst @@ -0,0 +1,8 @@ +tlslite.utils.aesgcm module +=========================== + +.. automodule:: tlslite.utils.aesgcm + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.asn1parser.rst b/docs/tlslite.utils.asn1parser.rst new file mode 100644 index 00000000..5f608fe5 --- /dev/null +++ b/docs/tlslite.utils.asn1parser.rst @@ -0,0 +1,8 @@ +tlslite.utils.asn1parser module +=============================== + +.. automodule:: tlslite.utils.asn1parser + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.chacha.rst b/docs/tlslite.utils.chacha.rst new file mode 100644 index 00000000..a12ecdce --- /dev/null +++ b/docs/tlslite.utils.chacha.rst @@ -0,0 +1,8 @@ +tlslite.utils.chacha module +=========================== + +.. automodule:: tlslite.utils.chacha + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.chacha20_poly1305.rst b/docs/tlslite.utils.chacha20_poly1305.rst new file mode 100644 index 00000000..c0bd0ba2 --- /dev/null +++ b/docs/tlslite.utils.chacha20_poly1305.rst @@ -0,0 +1,8 @@ +tlslite.utils.chacha20_poly1305 module +====================================== + +.. automodule:: tlslite.utils.chacha20_poly1305 + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.cipherfactory.rst b/docs/tlslite.utils.cipherfactory.rst new file mode 100644 index 00000000..bd020406 --- /dev/null +++ b/docs/tlslite.utils.cipherfactory.rst @@ -0,0 +1,8 @@ +tlslite.utils.cipherfactory module +================================== + +.. automodule:: tlslite.utils.cipherfactory + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.codec.rst b/docs/tlslite.utils.codec.rst new file mode 100644 index 00000000..5301f700 --- /dev/null +++ b/docs/tlslite.utils.codec.rst @@ -0,0 +1,8 @@ +tlslite.utils.codec module +========================== + +.. automodule:: tlslite.utils.codec + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.compat.rst b/docs/tlslite.utils.compat.rst new file mode 100644 index 00000000..22047a60 --- /dev/null +++ b/docs/tlslite.utils.compat.rst @@ -0,0 +1,8 @@ +tlslite.utils.compat module +=========================== + +.. automodule:: tlslite.utils.compat + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.constanttime.rst b/docs/tlslite.utils.constanttime.rst new file mode 100644 index 00000000..9a96a1f1 --- /dev/null +++ b/docs/tlslite.utils.constanttime.rst @@ -0,0 +1,8 @@ +tlslite.utils.constanttime module +================================= + +.. automodule:: tlslite.utils.constanttime + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.cryptomath.rst b/docs/tlslite.utils.cryptomath.rst new file mode 100644 index 00000000..65ac5ba9 --- /dev/null +++ b/docs/tlslite.utils.cryptomath.rst @@ -0,0 +1,8 @@ +tlslite.utils.cryptomath module +=============================== + +.. automodule:: tlslite.utils.cryptomath + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.datefuncs.rst b/docs/tlslite.utils.datefuncs.rst new file mode 100644 index 00000000..e99e3765 --- /dev/null +++ b/docs/tlslite.utils.datefuncs.rst @@ -0,0 +1,8 @@ +tlslite.utils.datefuncs module +============================== + +.. automodule:: tlslite.utils.datefuncs + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.dns_utils.rst b/docs/tlslite.utils.dns_utils.rst new file mode 100644 index 00000000..118e3e05 --- /dev/null +++ b/docs/tlslite.utils.dns_utils.rst @@ -0,0 +1,8 @@ +tlslite.utils.dns_utils module +============================== + +.. automodule:: tlslite.utils.dns_utils + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.ecc.rst b/docs/tlslite.utils.ecc.rst new file mode 100644 index 00000000..1a1157bc --- /dev/null +++ b/docs/tlslite.utils.ecc.rst @@ -0,0 +1,8 @@ +tlslite.utils.ecc module +======================== + +.. automodule:: tlslite.utils.ecc + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.keyfactory.rst b/docs/tlslite.utils.keyfactory.rst new file mode 100644 index 00000000..03730053 --- /dev/null +++ b/docs/tlslite.utils.keyfactory.rst @@ -0,0 +1,8 @@ +tlslite.utils.keyfactory module +=============================== + +.. automodule:: tlslite.utils.keyfactory + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.lists.rst b/docs/tlslite.utils.lists.rst new file mode 100644 index 00000000..6eca7c0e --- /dev/null +++ b/docs/tlslite.utils.lists.rst @@ -0,0 +1,8 @@ +tlslite.utils.lists module +========================== + +.. automodule:: tlslite.utils.lists + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.openssl_aes.rst b/docs/tlslite.utils.openssl_aes.rst new file mode 100644 index 00000000..74a92716 --- /dev/null +++ b/docs/tlslite.utils.openssl_aes.rst @@ -0,0 +1,8 @@ +tlslite.utils.openssl_aes module +================================ + +.. automodule:: tlslite.utils.openssl_aes + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.openssl_rc4.rst b/docs/tlslite.utils.openssl_rc4.rst new file mode 100644 index 00000000..ab55c6c2 --- /dev/null +++ b/docs/tlslite.utils.openssl_rc4.rst @@ -0,0 +1,8 @@ +tlslite.utils.openssl_rc4 module +================================ + +.. automodule:: tlslite.utils.openssl_rc4 + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.openssl_rsakey.rst b/docs/tlslite.utils.openssl_rsakey.rst new file mode 100644 index 00000000..267f549e --- /dev/null +++ b/docs/tlslite.utils.openssl_rsakey.rst @@ -0,0 +1,8 @@ +tlslite.utils.openssl_rsakey module +=================================== + +.. automodule:: tlslite.utils.openssl_rsakey + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.openssl_tripledes.rst b/docs/tlslite.utils.openssl_tripledes.rst new file mode 100644 index 00000000..a436b537 --- /dev/null +++ b/docs/tlslite.utils.openssl_tripledes.rst @@ -0,0 +1,8 @@ +tlslite.utils.openssl_tripledes module +====================================== + +.. automodule:: tlslite.utils.openssl_tripledes + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.pem.rst b/docs/tlslite.utils.pem.rst new file mode 100644 index 00000000..8a7d06ef --- /dev/null +++ b/docs/tlslite.utils.pem.rst @@ -0,0 +1,8 @@ +tlslite.utils.pem module +======================== + +.. automodule:: tlslite.utils.pem + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.poly1305.rst b/docs/tlslite.utils.poly1305.rst new file mode 100644 index 00000000..8176bfff --- /dev/null +++ b/docs/tlslite.utils.poly1305.rst @@ -0,0 +1,8 @@ +tlslite.utils.poly1305 module +============================= + +.. automodule:: tlslite.utils.poly1305 + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.pycrypto_aes.rst b/docs/tlslite.utils.pycrypto_aes.rst new file mode 100644 index 00000000..ad32a314 --- /dev/null +++ b/docs/tlslite.utils.pycrypto_aes.rst @@ -0,0 +1,8 @@ +tlslite.utils.pycrypto_aes module +================================= + +.. automodule:: tlslite.utils.pycrypto_aes + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.pycrypto_aesgcm.rst b/docs/tlslite.utils.pycrypto_aesgcm.rst new file mode 100644 index 00000000..7d98ab54 --- /dev/null +++ b/docs/tlslite.utils.pycrypto_aesgcm.rst @@ -0,0 +1,8 @@ +tlslite.utils.pycrypto_aesgcm module +==================================== + +.. automodule:: tlslite.utils.pycrypto_aesgcm + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.pycrypto_rc4.rst b/docs/tlslite.utils.pycrypto_rc4.rst new file mode 100644 index 00000000..f218905b --- /dev/null +++ b/docs/tlslite.utils.pycrypto_rc4.rst @@ -0,0 +1,8 @@ +tlslite.utils.pycrypto_rc4 module +================================= + +.. automodule:: tlslite.utils.pycrypto_rc4 + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.pycrypto_rsakey.rst b/docs/tlslite.utils.pycrypto_rsakey.rst new file mode 100644 index 00000000..a4d5b89b --- /dev/null +++ b/docs/tlslite.utils.pycrypto_rsakey.rst @@ -0,0 +1,8 @@ +tlslite.utils.pycrypto_rsakey module +==================================== + +.. automodule:: tlslite.utils.pycrypto_rsakey + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.pycrypto_tripledes.rst b/docs/tlslite.utils.pycrypto_tripledes.rst new file mode 100644 index 00000000..32ce21ce --- /dev/null +++ b/docs/tlslite.utils.pycrypto_tripledes.rst @@ -0,0 +1,8 @@ +tlslite.utils.pycrypto_tripledes module +======================================= + +.. automodule:: tlslite.utils.pycrypto_tripledes + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.python_aes.rst b/docs/tlslite.utils.python_aes.rst new file mode 100644 index 00000000..0c364555 --- /dev/null +++ b/docs/tlslite.utils.python_aes.rst @@ -0,0 +1,8 @@ +tlslite.utils.python_aes module +=============================== + +.. automodule:: tlslite.utils.python_aes + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.python_aesgcm.rst b/docs/tlslite.utils.python_aesgcm.rst new file mode 100644 index 00000000..ee9e31af --- /dev/null +++ b/docs/tlslite.utils.python_aesgcm.rst @@ -0,0 +1,8 @@ +tlslite.utils.python_aesgcm module +================================== + +.. automodule:: tlslite.utils.python_aesgcm + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.python_chacha20_poly1305.rst b/docs/tlslite.utils.python_chacha20_poly1305.rst new file mode 100644 index 00000000..61c43349 --- /dev/null +++ b/docs/tlslite.utils.python_chacha20_poly1305.rst @@ -0,0 +1,8 @@ +tlslite.utils.python_chacha20_poly1305 module +============================================= + +.. automodule:: tlslite.utils.python_chacha20_poly1305 + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.python_rc4.rst b/docs/tlslite.utils.python_rc4.rst new file mode 100644 index 00000000..9d619409 --- /dev/null +++ b/docs/tlslite.utils.python_rc4.rst @@ -0,0 +1,8 @@ +tlslite.utils.python_rc4 module +=============================== + +.. automodule:: tlslite.utils.python_rc4 + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.python_rsakey.rst b/docs/tlslite.utils.python_rsakey.rst new file mode 100644 index 00000000..b050e163 --- /dev/null +++ b/docs/tlslite.utils.python_rsakey.rst @@ -0,0 +1,8 @@ +tlslite.utils.python_rsakey module +================================== + +.. automodule:: tlslite.utils.python_rsakey + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.rc4.rst b/docs/tlslite.utils.rc4.rst new file mode 100644 index 00000000..4498b083 --- /dev/null +++ b/docs/tlslite.utils.rc4.rst @@ -0,0 +1,8 @@ +tlslite.utils.rc4 module +======================== + +.. automodule:: tlslite.utils.rc4 + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.rijndael.rst b/docs/tlslite.utils.rijndael.rst new file mode 100644 index 00000000..e9e7b09c --- /dev/null +++ b/docs/tlslite.utils.rijndael.rst @@ -0,0 +1,8 @@ +tlslite.utils.rijndael module +============================= + +.. automodule:: tlslite.utils.rijndael + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.rsakey.rst b/docs/tlslite.utils.rsakey.rst new file mode 100644 index 00000000..2235b804 --- /dev/null +++ b/docs/tlslite.utils.rsakey.rst @@ -0,0 +1,8 @@ +tlslite.utils.rsakey module +=========================== + +.. automodule:: tlslite.utils.rsakey + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.rst b/docs/tlslite.utils.rst new file mode 100644 index 00000000..fa1005ba --- /dev/null +++ b/docs/tlslite.utils.rst @@ -0,0 +1,53 @@ +tlslite.utils package +===================== + +.. automodule:: tlslite.utils + :members: + :special-members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + + tlslite.utils.aes + tlslite.utils.aesgcm + tlslite.utils.asn1parser + tlslite.utils.chacha + tlslite.utils.chacha20_poly1305 + tlslite.utils.cipherfactory + tlslite.utils.codec + tlslite.utils.compat + tlslite.utils.constanttime + tlslite.utils.cryptomath + tlslite.utils.datefuncs + tlslite.utils.dns_utils + tlslite.utils.ecc + tlslite.utils.keyfactory + tlslite.utils.lists + tlslite.utils.openssl_aes + tlslite.utils.openssl_rc4 + tlslite.utils.openssl_rsakey + tlslite.utils.openssl_tripledes + tlslite.utils.pem + tlslite.utils.poly1305 + tlslite.utils.pycrypto_aes + tlslite.utils.pycrypto_aesgcm + tlslite.utils.pycrypto_rc4 + tlslite.utils.pycrypto_rsakey + tlslite.utils.pycrypto_tripledes + tlslite.utils.python_aes + tlslite.utils.python_aesgcm + tlslite.utils.python_chacha20_poly1305 + tlslite.utils.python_rc4 + tlslite.utils.python_rsakey + tlslite.utils.rc4 + tlslite.utils.rijndael + tlslite.utils.rsakey + tlslite.utils.tackwrapper + tlslite.utils.tlshashlib + tlslite.utils.tripledes + tlslite.utils.x25519 + diff --git a/docs/tlslite.utils.tackwrapper.rst b/docs/tlslite.utils.tackwrapper.rst new file mode 100644 index 00000000..0921e22b --- /dev/null +++ b/docs/tlslite.utils.tackwrapper.rst @@ -0,0 +1,8 @@ +tlslite.utils.tackwrapper module +================================ + +.. automodule:: tlslite.utils.tackwrapper + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.tlshashlib.rst b/docs/tlslite.utils.tlshashlib.rst new file mode 100644 index 00000000..517bf58e --- /dev/null +++ b/docs/tlslite.utils.tlshashlib.rst @@ -0,0 +1,8 @@ +tlslite.utils.tlshashlib module +=============================== + +.. automodule:: tlslite.utils.tlshashlib + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.tripledes.rst b/docs/tlslite.utils.tripledes.rst new file mode 100644 index 00000000..cece70d6 --- /dev/null +++ b/docs/tlslite.utils.tripledes.rst @@ -0,0 +1,8 @@ +tlslite.utils.tripledes module +============================== + +.. automodule:: tlslite.utils.tripledes + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.x25519.rst b/docs/tlslite.utils.x25519.rst new file mode 100644 index 00000000..54556327 --- /dev/null +++ b/docs/tlslite.utils.x25519.rst @@ -0,0 +1,8 @@ +tlslite.utils.x25519 module +=========================== + +.. automodule:: tlslite.utils.x25519 + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.verifierdb.rst b/docs/tlslite.verifierdb.rst new file mode 100644 index 00000000..8a0a3a80 --- /dev/null +++ b/docs/tlslite.verifierdb.rst @@ -0,0 +1,8 @@ +tlslite.verifierdb module +========================= + +.. automodule:: tlslite.verifierdb + :members: + :special-members: __init__, __setitem__, __getitem__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.x509.rst b/docs/tlslite.x509.rst new file mode 100644 index 00000000..3479a217 --- /dev/null +++ b/docs/tlslite.x509.rst @@ -0,0 +1,8 @@ +tlslite.x509 module +=================== + +.. automodule:: tlslite.x509 + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.x509certchain.rst b/docs/tlslite.x509certchain.rst new file mode 100644 index 00000000..d5898359 --- /dev/null +++ b/docs/tlslite.x509certchain.rst @@ -0,0 +1,8 @@ +tlslite.x509certchain module +============================ + +.. automodule:: tlslite.x509certchain + :members: + :special-members: __init__ + :undoc-members: + :show-inheritance: From 92271deb5ecdac423c3a95c079cfe162c8d9d5ef Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 21 Jul 2017 19:54:42 +0200 Subject: [PATCH 473/574] convert the documentation to sphinx format --- tlslite/basedb.py | 32 +- tlslite/bufferedsocket.py | 4 +- tlslite/checker.py | 37 +- tlslite/constants.py | 162 +++---- tlslite/defragmenter.py | 10 +- tlslite/dh.py | 10 +- tlslite/errors.py | 110 +++-- tlslite/extensions.py | 404 ++++++++-------- tlslite/handshakehashes.py | 16 +- tlslite/handshakehelpers.py | 5 +- tlslite/handshakesettings.py | 182 +++---- tlslite/integration/asyncstatemachine.py | 21 +- tlslite/integration/clienthelper.py | 55 ++- tlslite/integration/httptlsconnection.py | 84 ++-- tlslite/integration/imap4_tls.py | 56 +-- tlslite/integration/pop3_tls.py | 55 ++- tlslite/integration/smtp_tls.py | 46 +- .../integration/tlsasyncdispatchermixin.py | 28 +- tlslite/integration/tlssocketservermixin.py | 8 +- tlslite/integration/xmlrpcserver.py | 16 +- tlslite/integration/xmlrpctransport.py | 59 +-- tlslite/keyexchange.py | 30 +- tlslite/mathtls.py | 16 +- tlslite/messages.py | 445 +++++++++--------- tlslite/messagesocket.py | 51 +- tlslite/recordlayer.py | 94 ++-- tlslite/session.py | 46 +- tlslite/sessioncache.py | 16 +- tlslite/tlsconnection.py | 428 ++++++++--------- tlslite/tlsrecordlayer.py | 222 ++++----- tlslite/utils/asn1parser.py | 32 +- tlslite/utils/cipherfactory.py | 52 +- tlslite/utils/codec.py | 142 +++--- tlslite/utils/constanttime.py | 82 ++-- tlslite/utils/cryptomath.py | 5 + tlslite/utils/dns_utils.py | 5 +- tlslite/utils/keyfactory.py | 68 ++- tlslite/utils/lists.py | 11 +- tlslite/utils/rsakey.py | 208 ++++---- tlslite/utils/x25519.py | 20 +- tlslite/verifierdb.py | 53 +-- tlslite/x509.py | 50 +- tlslite/x509certchain.py | 26 +- 43 files changed, 1790 insertions(+), 1712 deletions(-) diff --git a/tlslite/basedb.py b/tlslite/basedb.py index 7ec45672..b12c7794 100644 --- a/tlslite/basedb.py +++ b/tlslite/basedb.py @@ -24,9 +24,10 @@ def __init__(self, filename, type): self.lock = threading.Lock() def create(self): - """Create a new on-disk database. + """ + Create a new on-disk database. - @raise anydbm.error: If there's a problem creating the database. + :raises anydbm.error: If there's a problem creating the database. """ if self.filename: self.db = anydbm.open(self.filename, "n") #raises anydbm.error @@ -36,10 +37,11 @@ def create(self): self.db = {} def open(self): - """Open a pre-existing on-disk database. + """ + Open a pre-existing on-disk database. - @raise anydbm.error: If there's a problem opening the database. - @raise ValueError: If the database is not of the right type. + :raises anydbm.error: If there's a problem opening the database. + :raises ValueError: If the database is not of the right type. """ if not self.filename: raise ValueError("Can only open on-disk databases") @@ -89,15 +91,14 @@ def __delitem__(self, username): self.lock.release() def __contains__(self, username): - """Check if the database contains the specified username. - - @type username: str - @param username: The username to check for. + """ + Check if the database contains the specified username. - @rtype: bool - @return: True if the database contains the username, False - otherwise. + :param str username: The username to check for. + :rtype: bool + :returns: True if the database contains the username, False + otherwise. """ if self.db == None: raise AssertionError("DB not open") @@ -113,10 +114,11 @@ def check(self, username, param): return self._checkItem(value, username, param) def keys(self): - """Return a list of usernames in the database. + """ + Return a list of usernames in the database. - @rtype: list - @return: The usernames in the database. + :rtype: list + :returns: The usernames in the database. """ if self.db == None: raise AssertionError("DB not open") diff --git a/tlslite/bufferedsocket.py b/tlslite/bufferedsocket.py index 4694a26f..ec579bd7 100644 --- a/tlslite/bufferedsocket.py +++ b/tlslite/bufferedsocket.py @@ -16,8 +16,8 @@ class BufferedSocket(object): Not multithread safe. - @type buffer_writes: boolean - @ivar buffer_writes: whether to buffer data writes, False by default + :vartype buffer_writes: boolean + :ivar buffer_writes: whether to buffer data writes, False by default """ def __init__(self, socket): diff --git a/tlslite/checker.py b/tlslite/checker.py index 4f2ee820..255c988a 100644 --- a/tlslite/checker.py +++ b/tlslite/checker.py @@ -9,35 +9,36 @@ class Checker(object): - """This class is passed to a handshake function to check the other + """ + This class is passed to a handshake function to check the other party's certificate chain. If a handshake function completes successfully, but the Checker judges the other party's certificate chain to be missing or inadequate, a subclass of - L{tlslite.errors.TLSAuthenticationError} will be raised. + :py:class:`tlslite.errors.TLSAuthenticationError` + will be raised. Currently, the Checker can check an X.509 chain. """ - def __init__(self, + def __init__(self, x509Fingerprint=None, checkResumedSession=False): - """Create a new Checker instance. + """ + Create a new Checker instance. You must pass in one of these argument combinations: - x509Fingerprint - @type x509Fingerprint: str - @param x509Fingerprint: A hex-encoded X.509 end-entity - fingerprint which the other party's end-entity certificate must - match. + :param str x509Fingerprint: A hex-encoded X.509 end-entity + fingerprint which the other party's end-entity certificate must + match. - @type checkResumedSession: bool - @param checkResumedSession: If resumed sessions should be - checked. This defaults to False, on the theory that if the - session was checked once, we don't need to bother - re-checking it. + :param bool checkResumedSession: If resumed sessions should be + checked. This defaults to False, on the theory that if the + session was checked once, we don't need to bother + re-checking it. """ self.x509Fingerprint = x509Fingerprint @@ -49,11 +50,11 @@ def __call__(self, connection): When a Checker is passed to a handshake function, this will be called at the end of the function. - @type connection: L{tlslite.tlsconnection.TLSConnection} - @param connection: The TLSConnection to examine. + :param tlslite.tlsconnection.TLSConnection connection: The + TLSConnection to examine. - @raise tlslite.errors.TLSAuthenticationError: If the other - party's certificate chain is missing or bad. + :raises tlslite.errors.TLSAuthenticationError: If the other + party's certificate chain is missing or bad. """ if not self.checkResumedSession and connection.resumed: return @@ -74,4 +75,4 @@ def __call__(self, connection): elif chain: raise TLSAuthenticationTypeError() else: - raise TLSNoAuthenticationError() \ No newline at end of file + raise TLSNoAuthenticationError() diff --git a/tlslite/constants.py b/tlslite/constants.py index 28a8917e..ef93615f 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -329,30 +329,33 @@ class AlertLevel(TLSEnum): class AlertDescription(TLSEnum): """ - @cvar bad_record_mac: A TLS record failed to decrypt properly. + :cvar bad_record_mac: A TLS record failed to decrypt properly. - If this occurs during a SRP handshake it most likely - indicates a bad password. It may also indicate an implementation - error, or some tampering with the data in transit. + If this occurs during a SRP handshake it most likely + indicates a bad password. It may also indicate an implementation + error, or some tampering with the data in transit. - This alert will be signalled by the server if the SRP password is bad. It - may also be signalled by the server if the SRP username is unknown to the - server, but it doesn't wish to reveal that fact. + This alert will be signalled by the server if the SRP password is bad. + It + may also be signalled by the server if the SRP username is unknown to + the + server, but it doesn't wish to reveal that fact. - @cvar handshake_failure: A problem occurred while handshaking. + :cvar handshake_failure: A problem occurred while handshaking. - This typically indicates a lack of common ciphersuites between client and - server, or some other disagreement (about SRP parameters or key sizes, - for example). + This typically indicates a lack of common ciphersuites between client + and + server, or some other disagreement (about SRP parameters or key sizes, + for example). - @cvar protocol_version: The other party's SSL/TLS version was unacceptable. + :cvar protocol_version: The other party's SSL/TLS version was unacceptable. - This indicates that the client and server couldn't agree on which version - of SSL or TLS to use. - - @cvar user_canceled: The handshake is being cancelled for some reason. + This indicates that the client and server couldn't agree on which + version + of SSL or TLS to use. + :cvar user_canceled: The handshake is being cancelled for some reason. """ close_notify = 0 @@ -394,32 +397,32 @@ class CipherSuite: """ Numeric values of ciphersuites and ciphersuite types - @cvar tripleDESSuites: ciphersuties which use 3DES symmetric cipher in CBC - mode - @cvar aes128Suites: ciphersuites which use AES symmetric cipher in CBC mode - with 128 bit key - @cvar aes256Suites: ciphersuites which use AES symmetric cipher in CBC mode - with 128 bit key - @cvar rc4Suites: ciphersuites which use RC4 symmetric cipher with 128 bit - key - @cvar shaSuites: ciphersuites which use SHA-1 HMAC integrity mechanism - and protocol default Pseudo Random Function - @cvar sha256Suites: ciphersuites which use SHA-256 HMAC integrity mechanism - and SHA-256 Pseudo Random Function - @cvar md5Suites: ciphersuites which use MD-5 HMAC integrity mechanism and - protocol default Pseudo Random Function - @cvar srpSuites: ciphersuites which use Secure Remote Password (SRP) key - exchange protocol - @cvar srpCertSuites: ciphersuites which use Secure Remote Password (SRP) - key exchange protocol with RSA server authentication - @cvar srpAllSuites: all SRP ciphersuites, pure SRP and with RSA based - server authentication - @cvar certSuites: ciphersuites which use RSA key exchange with RSA server - authentication - @cvar certAllSuites: ciphersuites which use RSA server authentication - @cvar anonSuites: ciphersuites which use anonymous Finite Field - Diffie-Hellman key exchange - @cvar ietfNames: dictionary with string names of the ciphersuites + :cvar tripleDESSuites: ciphersuties which use 3DES symmetric cipher in CBC + mode + :cvar aes128Suites: ciphersuites which use AES symmetric cipher in CBC mode + with 128 bit key + :cvar aes256Suites: ciphersuites which use AES symmetric cipher in CBC mode + with 128 bit key + :cvar rc4Suites: ciphersuites which use RC4 symmetric cipher with 128 bit + key + :cvar shaSuites: ciphersuites which use SHA-1 HMAC integrity mechanism + and protocol default Pseudo Random Function + :cvar sha256Suites: ciphersuites which use SHA-256 HMAC integrity mechanism + and SHA-256 Pseudo Random Function + :cvar md5Suites: ciphersuites which use MD-5 HMAC integrity mechanism and + protocol default Pseudo Random Function + :cvar srpSuites: ciphersuites which use Secure Remote Password (SRP) key + exchange protocol + :cvar srpCertSuites: ciphersuites which use Secure Remote Password (SRP) + key exchange protocol with RSA server authentication + :cvar srpAllSuites: all SRP ciphersuites, pure SRP and with RSA based + server authentication + :cvar certSuites: ciphersuites which use RSA key exchange with RSA server + authentication + :cvar certAllSuites: ciphersuites which use RSA server authentication + :cvar anonSuites: ciphersuites which use anonymous Finite Field + Diffie-Hellman key exchange + :cvar ietfNames: dictionary with string names of the ciphersuites """ ietfNames = {} @@ -443,31 +446,31 @@ class CipherSuite: SSL_CK_DES_192_EDE3_CBC_WITH_MD5 = 0x0700C0 ietfNames[0x0700C0] = 'SSL_CK_DES_192_EDE3_CBC_WITH_MD5' - # SSL2 ciphersuites which use RC4 symmetric cipher + #: SSL2 ciphersuites which use RC4 symmetric cipher ssl2rc4 = [] ssl2rc4.append(SSL_CK_RC4_128_WITH_MD5) ssl2rc4.append(SSL_CK_RC4_128_EXPORT40_WITH_MD5) - # SSL2 ciphersuites which use RC2 symmetric cipher + #: SSL2 ciphersuites which use RC2 symmetric cipher ssl2rc2 = [] ssl2rc2.append(SSL_CK_RC2_128_CBC_WITH_MD5) ssl2rc2.append(SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5) - # SSL2 ciphersuites which use IDEA symmetric cipher + #: SSL2 ciphersuites which use IDEA symmetric cipher ssl2idea = [SSL_CK_IDEA_128_CBC_WITH_MD5] - # SSL2 ciphersuites which use (single) DES symmetric cipher + #: SSL2 ciphersuites which use (single) DES symmetric cipher ssl2des = [SSL_CK_DES_64_CBC_WITH_MD5] - # SSL2 ciphersuites which use 3DES symmetric cipher + #: SSL2 ciphersuites which use 3DES symmetric cipher ssl2_3des = [SSL_CK_DES_192_EDE3_CBC_WITH_MD5] - # SSL2 ciphersuites which encrypt only part (40 bits) of the key + #: SSL2 ciphersuites which encrypt only part (40 bits) of the key ssl2export = [] ssl2export.append(SSL_CK_RC4_128_EXPORT40_WITH_MD5) ssl2export.append(SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5) - # SSL2 ciphersuties which use 128 bit key + #: SSL2 ciphersuties which use 128 bit key ssl2_128Key = [] ssl2_128Key.append(SSL_CK_RC4_128_WITH_MD5) ssl2_128Key.append(SSL_CK_RC4_128_EXPORT40_WITH_MD5) @@ -475,10 +478,10 @@ class CipherSuite: ssl2_128Key.append(SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5) ssl2_128Key.append(SSL_CK_IDEA_128_CBC_WITH_MD5) - # SSL2 ciphersuites which use 64 bit key + #: SSL2 ciphersuites which use 64 bit key ssl2_64Key = [SSL_CK_DES_64_CBC_WITH_MD5] - # SSL2 ciphersuites which use 192 bit key + #: SSL2 ciphersuites which use 192 bit key ssl2_192Key = [SSL_CK_DES_192_EDE3_CBC_WITH_MD5] # @@ -687,7 +690,7 @@ class CipherSuite: # Define cipher suite families below # - # 3DES CBC ciphers + #: 3DES CBC ciphers tripleDESSuites = [] tripleDESSuites.append(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA) # unsupp tripleDESSuites.append(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA) # unsupported @@ -700,7 +703,7 @@ class CipherSuite: tripleDESSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) tripleDESSuites.append(TLS_ECDH_ANON_WITH_3DES_EDE_CBC_SHA) - # AES-128 CBC ciphers + #: AES-128 CBC ciphers aes128Suites = [] aes128Suites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) aes128Suites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) @@ -720,7 +723,7 @@ class CipherSuite: aes128Suites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) aes128Suites.append(TLS_ECDH_ANON_WITH_AES_128_CBC_SHA) - # AES-256 CBC ciphers + #: AES-256 CBC ciphers aes256Suites = [] aes256Suites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA) aes256Suites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) @@ -740,7 +743,7 @@ class CipherSuite: aes256Suites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) aes256Suites.append(TLS_ECDH_ANON_WITH_AES_256_CBC_SHA) - # AES-128 GCM ciphers + #: AES-128 GCM ciphers aes128GcmSuites = [] aes128GcmSuites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) aes128GcmSuites.append(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) @@ -750,7 +753,7 @@ class CipherSuite: aes128GcmSuites.append(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) # unsupp aes128GcmSuites.append(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) - # AES-256-GCM ciphers (implicit SHA384, see sha384PrfSuites) + #: AES-256-GCM ciphers (implicit SHA384, see sha384PrfSuites) aes256GcmSuites = [] aes256GcmSuites.append(TLS_RSA_WITH_AES_256_GCM_SHA384) aes256GcmSuites.append(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) @@ -760,17 +763,17 @@ class CipherSuite: aes256GcmSuites.append(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) # unsupported aes256GcmSuites.append(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) - # CHACHA20 cipher, 00'th IETF draft (implicit POLY1305 authenticator) + #: CHACHA20 cipher, 00'th IETF draft (implicit POLY1305 authenticator) chacha20draft00Suites = [] chacha20draft00Suites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00) chacha20draft00Suites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00) - # CHACHA20 cipher (implicit POLY1305 authenticator, SHA256 PRF) + #: CHACHA20 cipher (implicit POLY1305 authenticator, SHA256 PRF) chacha20Suites = [] chacha20Suites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) chacha20Suites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) - # RC4 128 stream cipher + #: RC4 128 stream cipher rc4Suites = [] rc4Suites.append(TLS_ECDHE_RSA_WITH_RC4_128_SHA) rc4Suites.append(TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) # unsupported @@ -781,7 +784,7 @@ class CipherSuite: rc4Suites.append(TLS_RSA_WITH_RC4_128_MD5) rc4Suites.append(TLS_ECDH_ANON_WITH_RC4_128_SHA) - # no encryption + #: no encryption nullSuites = [] nullSuites.append(TLS_RSA_WITH_NULL_MD5) nullSuites.append(TLS_RSA_WITH_NULL_SHA) @@ -792,7 +795,7 @@ class CipherSuite: nullSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) nullSuites.append(TLS_ECDH_ANON_WITH_NULL_SHA) - # SHA-1 HMAC, protocol default PRF + #: SHA-1 HMAC, protocol default PRF shaSuites = [] shaSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) shaSuites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) @@ -837,7 +840,7 @@ class CipherSuite: shaSuites.append(TLS_ECDH_ANON_WITH_RC4_128_SHA) shaSuites.append(TLS_ECDH_ANON_WITH_NULL_SHA) - # SHA-256 HMAC, SHA-256 PRF + #: SHA-256 HMAC, SHA-256 PRF sha256Suites = [] sha256Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA256) sha256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA256) @@ -851,42 +854,42 @@ class CipherSuite: sha256Suites.append(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) # unsupported sha256Suites.append(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) - # SHA-384 HMAC, SHA-384 PRF + #: SHA-384 HMAC, SHA-384 PRF sha384Suites = [] sha384Suites.append(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384) # unsupported sha384Suites.append(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384) # unsupported sha384Suites.append(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) # unsupported sha384Suites.append(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) - # stream cipher construction + #: stream cipher construction streamSuites = [] streamSuites.extend(rc4Suites) streamSuites.extend(nullSuites) - # AEAD integrity, any PRF + #: AEAD integrity, any PRF aeadSuites = [] aeadSuites.extend(aes128GcmSuites) aeadSuites.extend(aes256GcmSuites) aeadSuites.extend(chacha20Suites) aeadSuites.extend(chacha20draft00Suites) - # TLS1.2 with SHA384 PRF + #: TLS1.2 with SHA384 PRF sha384PrfSuites = [] sha384PrfSuites.extend(sha384Suites) sha384PrfSuites.extend(aes256GcmSuites) - # MD-5 HMAC, protocol default PRF + #: MD-5 HMAC, protocol default PRF md5Suites = [] md5Suites.append(TLS_DH_ANON_WITH_RC4_128_MD5) md5Suites.append(TLS_RSA_WITH_RC4_128_MD5) md5Suites.append(TLS_RSA_WITH_NULL_MD5) - # SSL3, TLS1.0, TLS1.1 and TLS1.2 compatible ciphers + #: SSL3, TLS1.0, TLS1.1 and TLS1.2 compatible ciphers ssl3Suites = [] ssl3Suites.extend(shaSuites) ssl3Suites.extend(md5Suites) - # TLS1.2 specific ciphersuites + #: TLS1.2 specific ciphersuites tls12Suites = [] tls12Suites.extend(sha256Suites) tls12Suites.extend(sha384Suites) @@ -960,7 +963,7 @@ def _filterSuites(suites, settings, version=None): return [s for s in suites if s in macSuites and s in cipherSuites and s in keyExchangeSuites] - # SRP key exchange + #: SRP key exchange, no certificate base authentication srpSuites = [] srpSuites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA) srpSuites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA) @@ -971,7 +974,7 @@ def getSrpSuites(cls, settings, version=None): """Return SRP cipher suites matching settings""" return cls._filterSuites(CipherSuite.srpSuites, settings, version) - # SRP key exchange, RSA authentication + #: SRP key exchange, RSA authentication srpCertSuites = [] srpCertSuites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA) srpCertSuites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA) @@ -982,6 +985,7 @@ def getSrpCertSuites(cls, settings, version=None): """Return SRP cipher suites that use server certificates""" return cls._filterSuites(CipherSuite.srpCertSuites, settings, version) + #: All that use SRP key exchange srpAllSuites = srpSuites + srpCertSuites @classmethod @@ -989,7 +993,7 @@ def getSrpAllSuites(cls, settings, version=None): """Return all SRP cipher suites matching settings""" return cls._filterSuites(CipherSuite.srpAllSuites, settings, version) - # RSA key exchange, RSA authentication + #: RSA key exchange, RSA authentication certSuites = [] certSuites.append(TLS_RSA_WITH_AES_256_GCM_SHA384) certSuites.append(TLS_RSA_WITH_AES_128_GCM_SHA256) @@ -1009,7 +1013,7 @@ def getCertSuites(cls, settings, version=None): """Return ciphers with RSA authentication matching settings""" return cls._filterSuites(CipherSuite.certSuites, settings, version) - # FFDHE key exchange, RSA authentication + #: FFDHE key exchange, RSA authentication dheCertSuites = [] dheCertSuites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) dheCertSuites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_draft_00) @@ -1026,7 +1030,7 @@ def getDheCertSuites(cls, settings, version=None): """Provide authenticated DHE ciphersuites matching settings""" return cls._filterSuites(CipherSuite.dheCertSuites, settings, version) - # ECDHE key exchange, RSA authentication + #: ECDHE key exchange, RSA authentication ecdheCertSuites = [] ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_draft_00) @@ -1045,10 +1049,10 @@ def getEcdheCertSuites(cls, settings, version=None): """Provide authenticated ECDHE ciphersuites matching settings""" return cls._filterSuites(CipherSuite.ecdheCertSuites, settings, version) - # RSA authentication + #: RSA authentication certAllSuites = srpCertSuites + certSuites + dheCertSuites + ecdheCertSuites - # ECDHE key exchange, ECDSA authentication + #: ECDHE key exchange, ECDSA authentication ecdheEcdsaSuites = [] ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) @@ -1060,7 +1064,7 @@ def getEcdheCertSuites(cls, settings, version=None): ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_RC4_128_SHA) ecdheEcdsaSuites.append(TLS_ECDHE_ECDSA_WITH_NULL_SHA) - # anon FFDHE key exchange + #: anon FFDHE key exchange anonSuites = [] anonSuites.append(TLS_DH_ANON_WITH_AES_256_GCM_SHA384) anonSuites.append(TLS_DH_ANON_WITH_AES_128_GCM_SHA256) @@ -1078,7 +1082,7 @@ def getAnonSuites(cls, settings, version=None): dhAllSuites = dheCertSuites + anonSuites - # anon ECDHE key exchange + #: anon ECDHE key exchange ecdhAnonSuites = [] ecdhAnonSuites.append(TLS_ECDH_ANON_WITH_AES_256_CBC_SHA) ecdhAnonSuites.append(TLS_ECDH_ANON_WITH_AES_128_CBC_SHA) @@ -1091,7 +1095,7 @@ def getEcdhAnonSuites(cls, settings, version=None): """Provide anonymous ECDH ciphersuites matching settings""" return cls._filterSuites(CipherSuite.ecdhAnonSuites, settings, version) - # all ciphersuites which use ephemeral ECDH key exchange + #: all ciphersuites which use ephemeral ECDH key exchange ecdhAllSuites = ecdheEcdsaSuites + ecdheCertSuites + ecdhAnonSuites @staticmethod diff --git a/tlslite/defragmenter.py b/tlslite/defragmenter.py index 0bd61072..de6b56a4 100644 --- a/tlslite/defragmenter.py +++ b/tlslite/defragmenter.py @@ -19,11 +19,11 @@ class Defragmenter(object): Supports messages with given size (like Alerts) or with a length header in specific place (like Handshake messages). - @ivar priorities: order in which messages from given types should be - returned. - @ivar buffers: data buffers for message types - @ivar decoders: functions which check buffers if a message of given type - is complete + :ivar priorities: order in which messages from given types should be + returned. + :ivar buffers: data buffers for message types + :ivar decoders: functions which check buffers if a message of given type + is complete """ def __init__(self): diff --git a/tlslite/dh.py b/tlslite/dh.py index 2266706b..922d1ee6 100644 --- a/tlslite/dh.py +++ b/tlslite/dh.py @@ -12,8 +12,8 @@ def parseBinary(data): """ Parse DH parameters from ASN.1 DER encoded binary string. - @param data: DH parameters - @rtype: tuple of int + :param bytes data: DH parameters + :rtype: tuple of int """ parser = ASN1Parser(data) @@ -29,9 +29,9 @@ def parse(data): The string can either by PEM or DER encoded - @param data: DH parameters - @rtype: tuple of int - @return: generator and prime + :param bytes data: DH parameters + :rtype: tuple of int + :returns: generator and prime """ try: return parseBinary(data) diff --git a/tlslite/errors.py b/tlslite/errors.py index 393ffd56..ece80fdf 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -4,41 +4,43 @@ # # See the LICENSE file for legal information regarding use of this file. -"""Exception classes. -@sort: TLSError, TLSAbruptCloseError, TLSAlert, TLSLocalAlert, TLSRemoteAlert, -TLSAuthenticationError, TLSNoAuthenticationError, TLSAuthenticationTypeError, -TLSFingerprintError, TLSAuthorizationError, TLSValidationError, TLSFaultError, -TLSUnsupportedError -""" +"""Exception classes.""" import socket from .constants import AlertDescription, AlertLevel class BaseTLSException(Exception): + """ + Metaclass for TLS Lite exceptions. - """Metaclass for TLS Lite exceptions. - - Look to L{TLSError} for exceptions that should be caught by tlslite + Look to :py:class:`tlslite.errors.TLSError` for exceptions that should be + caught by tlslite consumers """ pass + class EncryptionError(BaseTLSException): - """Base class for exceptions thrown while encrypting""" + """Base class for exceptions thrown while encrypting.""" + + pass -class TLSError(BaseTLSException): +class TLSError(BaseTLSException): """Base class for all TLS Lite exceptions.""" - + def __str__(self): - """"At least print out the Exception time for str(...).""" - return repr(self) + """At least print out the Exception time for str(...).""" + return repr(self) + class TLSClosedConnectionError(TLSError, socket.error): """An attempt was made to use the connection after it was closed.""" + pass + class TLSAbruptCloseError(TLSError): """The socket was closed without a proper TLS shutdown. @@ -48,10 +50,13 @@ class TLSAbruptCloseError(TLSError): to truncate the connection. It could also signify a misbehaving TLS implementation, or a random network failure. """ + pass + class TLSAlert(TLSError): """A TLS alert has been signalled.""" + pass _descriptionStr = {\ @@ -82,20 +87,22 @@ class TLSAlert(TLSError): AlertDescription.no_renegotiation: "no_renegotiation",\ AlertDescription.unknown_psk_identity: "unknown_psk_identity"} + class TLSLocalAlert(TLSAlert): """A TLS alert has been signalled by the local implementation. - @type description: int - @ivar description: Set to one of the constants in - L{tlslite.constants.AlertDescription} + :vartype description: int + :ivar description: Set to one of the constants in + :py:class:`tlslite.constants.AlertDescription` - @type level: int - @ivar level: Set to one of the constants in - L{tlslite.constants.AlertLevel} + :vartype level: int + :ivar level: Set to one of the constants in + :py:class:`tlslite.constants.AlertLevel` - @type message: str - @ivar message: Description of what went wrong. + :vartype message: str + :ivar message: Description of what went wrong. """ + def __init__(self, alert, message=None): self.description = alert.description self.level = alert.level @@ -110,17 +117,20 @@ def __str__(self): else: return alertStr + class TLSRemoteAlert(TLSAlert): - """A TLS alert has been signalled by the remote implementation. + """ + A TLS alert has been signalled by the remote implementation. - @type description: int - @ivar description: Set to one of the constants in - L{tlslite.constants.AlertDescription} + :vartype description: int + :ivar description: Set to one of the constants in + :py:class:`tlslite.constants.AlertDescription` - @type level: int - @ivar level: Set to one of the constants in - L{tlslite.constants.AlertLevel} + :vartype level: int + :ivar level: Set to one of the constants in + :py:class:`tlslite.constants.AlertLevel` """ + def __init__(self, alert): self.description = alert.description self.level = alert.level @@ -131,103 +141,125 @@ def __str__(self): alertStr = str(self.description) return alertStr + class TLSAuthenticationError(TLSError): - """The handshake succeeded, but the other party's authentication + """ + The handshake succeeded, but the other party's authentication was inadequate. This exception will only be raised when a - L{tlslite.Checker.Checker} has been passed to a handshake function. + :py:class:`tlslite.Checker.Checker` has been passed to a handshake + function. The Checker will be invoked once the handshake completes, and if the Checker objects to how the other party authenticated, a subclass of this exception will be raised. """ + pass + class TLSNoAuthenticationError(TLSAuthenticationError): """The Checker was expecting the other party to authenticate with a certificate chain, but this did not occur.""" + pass + class TLSAuthenticationTypeError(TLSAuthenticationError): """The Checker was expecting the other party to authenticate with a different type of certificate chain.""" + pass + class TLSFingerprintError(TLSAuthenticationError): """The Checker was expecting the other party to authenticate with a certificate chain that matches a different fingerprint.""" + pass + class TLSAuthorizationError(TLSAuthenticationError): """The Checker was expecting the other party to authenticate with a certificate chain that has a different authorization.""" + pass + class TLSValidationError(TLSAuthenticationError): """The Checker has determined that the other party's certificate chain is invalid.""" + def __init__(self, msg, info=None): # Include a dict containing info about this validation failure TLSAuthenticationError.__init__(self, msg) self.info = info + class TLSFaultError(TLSError): """The other party responded incorrectly to an induced fault. This exception will only occur during fault testing, when a - TLSConnection's fault variable is set to induce some sort of + :py:class:`tlslite.tlsconnection.TLSConnection`'s fault variable is + set to induce some sort of faulty behavior, and the other party doesn't respond appropriately. """ + pass class TLSUnsupportedError(TLSError): """The implementation doesn't support the requested (or required) capabilities.""" + pass + class TLSInternalError(TLSError): """The internal state of object is unexpected or invalid. Caused by incorrect use of API. """ + pass -class TLSProtocolException(BaseTLSException): +class TLSProtocolException(BaseTLSException): """Exceptions used internally for handling errors in received messages""" pass -class TLSIllegalParameterException(TLSProtocolException): +class TLSIllegalParameterException(TLSProtocolException): """Parameters specified in message were incorrect or invalid""" pass -class TLSRecordOverflow(TLSProtocolException): +class TLSRecordOverflow(TLSProtocolException): """The received record size was too big""" pass -class TLSDecryptionFailed(TLSProtocolException): +class TLSDecryptionFailed(TLSProtocolException): """Decryption of data was unsuccessful""" pass -class TLSBadRecordMAC(TLSProtocolException): +class TLSBadRecordMAC(TLSProtocolException): """Bad MAC (or padding in case of mac-then-encrypt)""" pass + class TLSInsufficientSecurity(TLSProtocolException): """Parameters selected by user are too weak""" pass + class TLSUnknownPSKIdentity(TLSProtocolException): """The PSK or SRP identity is unknown""" @@ -245,21 +277,25 @@ class MaskTooLongError(EncryptionError): pass + class MessageTooLongError(EncryptionError): """The message passed into function is too long""" pass + class EncodingError(EncryptionError): """An error appeared while encoding""" pass + class InvalidSignature(EncryptionError): """Verification function found invalid signature""" pass + class UnknownRSAType(EncryptionError): """Unknown RSA algorithm type passed""" diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 74b08403..5136571a 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -18,7 +18,7 @@ class TLSExtension(object): This class handles the generic information about TLS extensions used by both sides of connection in Client Hello and Server Hello messages. - See U{RFC 4366} for more info. + See https://tools.ietf.org/html/rfc4366 for more info. It is used as a base class for specific users and as a way to store extensions that are not implemented in library. @@ -26,43 +26,44 @@ class TLSExtension(object): To implement a new extension you will need to create a new class which calls this class contructor (__init__), usually specifying just the extType parameter. The other methods which need to be implemented are: - L{extData}, L{create}, L{parse} and L{__repr__}. If the parser can be used + `extData`, `create`, `parse` and `__repr__`. If the parser can be used for client and optionally server extensions, the extension constructor - should be added to L{_universalExtensions}. Otherwise, when the client and + should be added to `_universalExtensions`. Otherwise, when the client and server extensions have completely different forms, you should add client - form to the L{_universalExtensions} and the server form to - L{_serverExtensions}. Since the server MUST NOT send extensions not + form to the `_universalExtensions` and the server form to + `_serverExtensions`. Since the server MUST NOT send extensions not advertised by client, there are no purely server-side extensions. But if the client side extension is just marked by presence and has no payload, - the client side (thus the L{_universalExtensions} may be skipped, then - the L{TLSExtension} class will be used for implementing it. See + the client side (thus the `_universalExtensions` may be skipped, then + the `TLSExtension` class will be used for implementing it. See end of the file for type-to-constructor bindings. - Though please note that subclassing for the purpose of parsing extensions - is not an officially supported part of API (just as underscores in their - names would indicate. + .. note:: Subclassing for the purpose of parsing extensions + is not an officially supported part of API (just as underscores in + their + names would indicate). - @type extType: int - @ivar extType: a 2^16-1 limited integer specifying the type of the + :vartype extType: int + :ivar extType: a 2^16-1 limited integer specifying the type of the extension that it contains, e.g. 0 indicates server name extension - @type extData: bytearray - @ivar extData: a byte array containing the value of the extension as + :vartype extData: bytearray + :ivar extData: a byte array containing the value of the extension as to be written on the wire - @type serverType: boolean - @ivar serverType: indicates that the extension was parsed with ServerHello + :vartype serverType: boolean + :ivar serverType: indicates that the extension was parsed with ServerHello specific parser, otherwise it used universal or ClientHello specific parser - @type _universalExtensions: dict - @cvar _universalExtensions: dictionary with concrete implementations of + :vartype _universalExtensions: dict + :cvar _universalExtensions: dictionary with concrete implementations of specific TLS extensions where key is the numeric value of the extension ID. Contains ClientHello version of extensions or universal implementations - @type _serverExtensions: dict - @cvar _serverExtensions: dictionary with concrete implementations of + :vartype _serverExtensions: dict + :cvar _serverExtensions: dictionary with concrete implementations of specific TLS extensions where key is the numeric value of the extension ID. Includes only those extensions that require special handlers for ServerHello versions. @@ -75,14 +76,14 @@ def __init__(self, server=False, extType=None): """ Creates a generic TLS extension. - You'll need to use L{create} or L{parse} methods to create an extension + You'll need to use :py:meth:`create` or :py:meth:`parse` methods to + create an extension that is actually usable. - @type server: boolean - @param server: whether to select ClientHello or ServerHello version + :param bool server: whether to select ClientHello or ServerHello + version for parsing - @type extType: int - @param extType: type of extension encoded as an integer, to be used + :param int extType: type of extension encoded as an integer, to be used by subclasses """ self.extType = extType @@ -99,7 +100,7 @@ def extData(self): common to all extension. In other words, without the extension ID and overall extension length. - @rtype: bytearray + :rtype: bytearray """ return self._extData @@ -119,19 +120,18 @@ def create(self, *args, **kwargs): The extension can carry arbitrary data and have arbitrary payload, can be used in client hello or server hello messages. - The legacy calling method uses two arguments - the extType and data. + The legacy calling method uses two arguments - the `extType` and + `data`. If the new calling method is used, only one argument is passed in - - data. + `data`. Child classes need to override this method so that it is possible to set values for all fields used by the extension. - @type extType: int - @param extType: if int: type of the extension encoded as an integer - between M{0} and M{2^16-1} - @type data: bytearray - @param data: raw data representing extension on the wire - @rtype: L{TLSExtension} + :param int extType: if int: type of the extension encoded as an integer + between `0` and `2^16-1` + :param bytearray data: raw data representing extension on the wire + :rtype: TLSExtension """ # old style if len(args) + len(kwargs) == 2: @@ -149,12 +149,12 @@ def write(self): Note that child classes in general don't need to override this method. - @rtype: bytearray - @return: An array of bytes formatted as is supposed to be written on + :rtype: bytearray + :returns: An array of bytes formatted as is supposed to be written on the wire, including the extension_type, length and the extension data - @raise AssertionError: when the object was not initialized + :raises AssertionError: when the object was not initialized """ assert self.extType is not None @@ -180,15 +180,14 @@ def parse(self, p): extension from on the wire data. Note that child class parsers will not receive the generic header of the extension, but just a parser with the payload. In other words, the method should be the exact - reverse of the L{extData} property. + reverse of the `extData` property. - @type p: L{tlslite.util.codec.Parser} - @param p: data to be parsed + :param tlslite.util.codec.Parser p: data to be parsed - @raise SyntaxError: when the size of the passed element doesn't match - the internal representation + :raises SyntaxError: when the size of the passed element doesn't match + the internal representation - @rtype: L{TLSExtension} + :rtype: TLSExtension """ extType = p.get(2) extLength = p.get(2) @@ -230,7 +229,7 @@ def __repr__(self): Child classes should override this method to support more appropriate string rendering of the extension. - @rtype: str + :rtype: str """ return "TLSExtension(extType={0!r}, extData={1!r},"\ " serverType={2!r})".format(self.extType, self.extData, @@ -255,7 +254,7 @@ def __init__(self, elemLength, lengthLength, fieldName, extType): def extData(self): """Return raw data encoding of the extension - @rtype: bytearray + :rtype: bytearray """ if self._internalList is None: return bytearray(0) @@ -269,8 +268,7 @@ def extData(self): def create(self, values): """Set the list to specified values - @type values: list of int - @param values: list of values to save + :param list values: list of values to save """ self._internalList = values return self @@ -279,8 +277,8 @@ def parse(self, parser): """ Deserialise extension from on-the-wire data - @type parser: L{Parser} - @rtype: Extension + :param tlslite.utils.codec.Parser parser: data + :rtype: Extension """ if parser.getRemainingLength() == 0: self._internalList = None @@ -326,8 +324,8 @@ class SNIExtension(TLSExtension): opaque byte strings, in case of DNS host names (records of type 0) they are UTF-8 encoded domain names (without the ending dot). - @type hostNames: tuple of bytearrays - @ivar hostNames: tuple of hostnames (server name records of type 0) + :vartype hostNames: tuple of bytearrays + :ivar hostNames: tuple of hostnames (server name records of type 0) advertised in the extension. Note that it may not include all names from client hello as the client can advertise other types. Also note that while it's not possible to change the returned array in place, it @@ -342,20 +340,20 @@ class SNIExtension(TLSExtension): sni_extension.hostNames = names - @type serverNames: list of L{ServerName} - @ivar serverNames: list of all names advertised in extension. - L{ServerName} is a namedtuple with two elements, the first + :vartype serverNames: list of :py:class:`ServerName` + :ivar serverNames: list of all names advertised in extension. + :py:class:`ServerName` is a namedtuple with two elements, the first element (type) defines the type of the name (encoded as int) while the other (name) is a bytearray that carries the value. - Known types are defined in L{tlslite.constants.NameType}. + Known types are defined in :py:class:`tlslite.constants.NameType`. The list will be empty if the on the wire extension had and empty list while it will be None if the extension was empty. - @type extType: int - @ivar extType: numeric type of SNIExtension, i.e. 0 + :vartype extType: int + :ivar extType: numeric type of SNIExtension, i.e. 0 - @type extData: bytearray - @ivar extData: raw representation of the extension + :vartype extData: bytearray + :ivar extData: raw representation of the extension """ ServerName = namedtuple('ServerName', 'name_type name') @@ -364,7 +362,7 @@ def __init__(self): """ Create an instance of SNIExtension. - See also: L{create} and L{parse}. + See also: :py:meth:`create` and :py:meth:`parse`. """ super(SNIExtension, self).__init__(extType=ExtensionType.server_name) self.serverNames = None @@ -373,7 +371,7 @@ def __repr__(self): """ Return programmer-readable representation of extension - @rtype: str + :rtype: str """ return "SNIExtension(serverNames={0!r})".format(self.serverNames) @@ -382,24 +380,22 @@ def create(self, hostname=None, hostNames=None, serverNames=None): Initializes an instance with provided hostname, host names or raw server names. - Any of the parameters may be None, in that case the list inside the - extension won't be defined, if either hostNames or serverNames is - an empty list, then the extension will define a list of lenght 0. + Any of the parameters may be `None`, in that case the list inside the + extension won't be defined, if either `hostNames` or `serverNames` is + an empty list, then the extension will define a list of length 0. If multiple parameters are specified at the same time, then the resulting list of names will be concatenated in order of hostname, hostNames and serverNames last. - @type hostname: bytearray - @param hostname: raw UTF-8 encoding of the host name + :param bytearray hostname: raw UTF-8 encoding of the host name - @type hostNames: list of bytearrays - @param hostNames: list of raw UTF-8 encoded host names + :param list hostNames: list of raw UTF-8 encoded host names - @type serverNames: list of L{ServerName} - @param serverNames: pairs of name_type and name encoded as a namedtuple + :param list serverNames: pairs of name_type and name encoded as a + namedtuple - @rtype: L{SNIExtension} + :rtype: SNIExtension """ if hostname is None and hostNames is None and serverNames is None: self.serverNames = None @@ -425,7 +421,7 @@ def create(self, hostname=None, hostNames=None, serverNames=None): def hostNames(self): """ Returns a simulated list of hostNames from the extension. - @rtype: tuple of bytearrays + :rtype: tuple of bytearrays """ # because we can't simulate assignments to array elements we return # an immutable type @@ -438,13 +434,13 @@ def hostNames(self): @hostNames.setter def hostNames(self, hostNames): """ Removes all host names from the extension and replaces them by - names in X{hostNames} parameter. + names in `hostNames` parameter. - Newly added parameters will be added at the I{beginning} of the list + Newly added parameters will be added at the beginning of the list of extensions. - @type hostNames: iterable of bytearrays - @param hostNames: host names to replace the old server names of type 0 + :param iterable hostNames: host names (bytearrays) to replace the + old server names of type 0 """ self.serverNames = \ @@ -455,17 +451,19 @@ def hostNames(self, hostNames): @hostNames.deleter def hostNames(self): - """ Remove all host names from extension, leaves other name types - unmodified + """ + Remove all host names from extension, leaves other name types + unmodified. """ self.serverNames = [x for x in self.serverNames if \ x.name_type != NameType.host_name] @property def extData(self): - """ raw encoding of extension data, without type and length header + """ + Raw encoding of extension data, without type and length header. - @rtype: bytearray + :rtype: bytearray """ if self.serverNames is None: return bytearray(0) @@ -483,10 +481,12 @@ def extData(self): return w.bytes def write(self): - """ Returns encoded extension, as encoded on the wire + """ + Returns encoded extension, as encoded on the wire - @rtype: bytearray - @return: an array of bytes formatted as they are supposed to be written + :rtype: bytearray + :returns: an array of bytes formatted as they are supposed to be + written on the wire, including the type, length and extension data """ @@ -505,11 +505,10 @@ def parse(self, p): The parser should not include the type or length of extension! - @type p: L{tlslite.util.codec.Parser} - @param p: data to be parsed + :param tlslite.util.codec.Parser p: data to be parsed - @rtype: L{SNIExtension} - @raise SyntaxError: when the internal sizes don't match the attached + :rtype: SNIExtension + :raises SyntaxError: when the internal sizes don't match the attached data """ if p.getRemainingLength() == 0: @@ -535,21 +534,21 @@ class ClientCertTypeExtension(VarListExtension): See RFC 6091. - @type extType: int - @ivar extType: numeric type of Certificate Type extension, i.e. 9 + :vartype extType: int + :ivar extType: numeric type of Certificate Type extension, i.e. 9 - @type extData: bytearray - @ivar extData: raw representation of the extension data + :vartype extData: bytearray + :ivar extData: raw representation of the extension data - @type certTypes: list of int - @ivar certTypes: list of certificate type identifiers (each one byte long) + :vartype certTypes: list of int + :ivar certTypes: list of certificate type identifiers (each one byte long) """ def __init__(self): """ Create an instance of ClientCertTypeExtension - See also: L{create} and L{parse} + See also: :py:meth:`create` and :py:meth:`parse` """ super(ClientCertTypeExtension, self).__init__(1, 1, 'certTypes', \ ExtensionType.cert_type) @@ -559,21 +558,21 @@ class ServerCertTypeExtension(TLSExtension): This class handles the Certificate Type extension (variant sent by server) defined in RFC 6091. - @type extType: int - @ivar extType: binary type of Certificate Type extension, i.e. 9 + :vartype extType: int + :ivar extType: binary type of Certificate Type extension, i.e. 9 - @type extData: bytearray - @ivar extData: raw representation of the extension data + :vartype extData: bytearray + :ivar extData: raw representation of the extension data - @type cert_type: int - @ivar cert_type: the certificate type selected by server + :vartype cert_type: int + :ivar cert_type: the certificate type selected by server """ def __init__(self): """ Create an instance of ServerCertTypeExtension - See also: L{create} and L{parse} + See also: :py:meth:`create` and :py:meth:`parse` """ super(ServerCertTypeExtension, self).__init__(server=True, \ extType=ExtensionType.cert_type) @@ -582,7 +581,7 @@ def __init__(self): def __repr__(self): """ Return programmer-centric description of object - @rtype: str + :rtype: str """ return "ServerCertTypeExtension(cert_type={0!r})".format(self.cert_type) @@ -591,7 +590,7 @@ def extData(self): """ Return the raw encoding of the extension data - @rtype: bytearray + :rtype: bytearray """ if self.cert_type is None: return bytearray(0) @@ -604,8 +603,7 @@ def extData(self): def create(self, val): """Create an instance for sending the extension to client. - @type val: int - @param val: selected type of certificate + :param int val: selected type of certificate """ self.cert_type = val return self @@ -613,8 +611,7 @@ def create(self, val): def parse(self, p): """Parse the extension from on the wire format - @type p: L{Parser} - @param p: parser with data + :param Parser p: parser with data """ self.cert_type = p.get(1) if p.getRemainingLength() > 0: @@ -627,21 +624,21 @@ class SRPExtension(TLSExtension): This class handles the Secure Remote Password protocol TLS extension defined in RFC 5054. - @type extType: int - @ivar extType: numeric type of SRPExtension, i.e. 12 + :vartype extType: int + :ivar extType: numeric type of SRPExtension, i.e. 12 - @type extData: bytearray - @ivar extData: raw representation of extension data + :vartype extData: bytearray + :ivar extData: raw representation of extension data - @type identity: bytearray - @ivar identity: UTF-8 encoding of user name + :vartype identity: bytearray + :ivar identity: UTF-8 encoding of user name """ def __init__(self): """ Create an instance of SRPExtension - See also: L{create} and L{parse} + See also: :py:meth:`create` and :py:meth:`parse` """ super(SRPExtension, self).__init__(extType=ExtensionType.srp) @@ -651,7 +648,7 @@ def __repr__(self): """ Return programmer-centric description of extension - @rtype: str + :rtype: str """ return "SRPExtension(identity={0!r})".format(self.identity) @@ -660,7 +657,7 @@ def extData(self): """ Return raw data encoding of the extension - @rtype: bytearray + :rtype: bytearray """ if self.identity is None: @@ -675,11 +672,11 @@ def extData(self): def create(self, identity=None): """ Create and instance of SRPExtension with specified protocols - @type identity: bytearray - @param identity: UTF-8 encoded identity (user name) to be provided + :param bytearray identity: UTF-8 encoded identity (user name) to be + provided to user. MUST be shorter than 2^8-1. - @raise ValueError: when the identity lenght is longer than 2^8-1 + :raises ValueError: when the identity lenght is longer than 2^8-1 """ if identity is None: @@ -695,12 +692,11 @@ def parse(self, p): """ Parse the extension from on the wire format - @type p: L{tlslite.util.codec.Parser} - @param p: data to be parsed + :param Parser p: data to be parsed - @raise SyntaxError: when the data is internally inconsistent + :raises SyntaxError: when the data is internally inconsistent - @rtype: L{SRPExtension} + :rtype: SRPExtension """ self.identity = p.getVarBytes(1) @@ -711,21 +707,21 @@ class NPNExtension(TLSExtension): """ This class handles the unofficial Next Protocol Negotiation TLS extension. - @type protocols: list of bytearrays - @ivar protocols: list of protocol names supported by the server + :vartype protocols: list of bytearrays + :ivar protocols: list of protocol names supported by the server - @type extType: int - @ivar extType: numeric type of NPNExtension, i.e. 13172 + :vartype extType: int + :ivar extType: numeric type of NPNExtension, i.e. 13172 - @type extData: bytearray - @ivar extData: raw representation of extension data + :vartype extData: bytearray + :ivar extData: raw representation of extension data """ def __init__(self): """ Create an instance of NPNExtension - See also: L{create} and L{parse} + See also: :py:meth:`create` and :py:meth:`parse` """ super(NPNExtension, self).__init__(extType=ExtensionType.supports_npn) @@ -735,7 +731,7 @@ def __repr__(self): """ Create programmer-readable version of representation - @rtype: str + :rtype: str """ return "NPNExtension(protocols={0!r})".format(self.protocols) @@ -743,7 +739,7 @@ def __repr__(self): def extData(self): """ Return the raw data encoding of the extension - @rtype: bytearray + :rtype: bytearray """ if self.protocols is None: return bytearray(0) @@ -758,8 +754,7 @@ def extData(self): def create(self, protocols=None): """ Create an instance of NPNExtension with specified protocols - @type protocols: list of bytearray - @param protocols: list of protocol names that are supported + :param list protocols: list of protocol names that are supported """ self.protocols = protocols return self @@ -767,13 +762,12 @@ def create(self, protocols=None): def parse(self, p): """ Parse the extension from on the wire format - @type p: L{tlslite.util.codec.Parser} - @param p: data to be parsed + :param Parser p: data to be parsed - @raise SyntaxError: when the size of the passed element doesn't match + :raises SyntaxError: when the size of the passed element doesn't match the internal representation - @rtype: L{NPNExtension} + :rtype: NPNExtension """ self.protocols = [] @@ -787,11 +781,11 @@ class TACKExtension(TLSExtension): This class handles the server side TACK extension (see draft-perrin-tls-tack-02). - @type tacks: list - @ivar tacks: list of L{TACK}'s supported by server + :vartype tacks: list + :ivar tacks: list of TACK's supported by server - @type activation_flags: int - @ivar activation_flags: activation flags for the tacks + :vartype activation_flags: int + :ivar activation_flags: activation flags for the tacks """ class TACK(object): @@ -813,7 +807,7 @@ def __repr__(self): """ Return programmmer readable representation of TACK object - @rtype: str + :rtype: str """ return "TACK(public_key={0!r}, min_generation={1!r}, "\ "generation={2!r}, expiration={3!r}, target_hash={4!r}, "\ @@ -839,7 +833,7 @@ def write(self): """ Convert the TACK into on the wire format - @rtype: bytearray + :rtype: bytearray """ w = Writer() if len(self.public_key) != 64: @@ -860,11 +854,10 @@ def parse(self, p): """ Parse the TACK from on the wire format - @type p: L{tlslite.util.codec.Parser} - @param p: data to be parsed + :param Parser p: data to be parsed - @rtype: L{TACK} - @raise SyntaxError: when the internal sizes don't match the + :rtype: TACK + :raises SyntaxError: when the internal sizes don't match the provided data """ @@ -904,7 +897,7 @@ def __init__(self): """ Create an instance of TACKExtension - See also: L{create} and L{parse} + See also: :py:meth:`create` and :py:meth`parse` """ super(TACKExtension, self).__init__(extType=ExtensionType.tack) @@ -915,7 +908,7 @@ def __repr__(self): """ Create a programmer readable representation of TACK extension - @rtype: str + :rtype: str """ return "TACKExtension(activation_flags={0!r}, tacks={1!r})".format( self.activation_flags, self.tacks) @@ -925,7 +918,7 @@ def extData(self): """ Return the raw data encoding of the extension - @rtype: bytearray + :rtype: bytearray """ w2 = Writer() for t in self.tacks: @@ -941,7 +934,7 @@ def create(self, tacks, activation_flags): """ Initialize the instance of TACKExtension - @rtype: TACKExtension + :rtype: TACKExtension """ self.tacks = tacks @@ -952,10 +945,9 @@ def parse(self, p): """ Parse the extension from on the wire format - @type p: L{tlslite.util.codec.Parser} - @param p: data to be parsed + :param Parser p: data to be parsed - @rtype: L{TACKExtension} + :rtype: TACKExtension """ self.tacks = [] @@ -974,8 +966,8 @@ class SupportedGroupsExtension(VarListExtension): See RFC4492, RFC7027 and RFC-ietf-tls-negotiated-ff-dhe-10 - @type groups: int - @ivar groups: list of groups that the client supports + :vartype groups: int + :ivar groups: list of groups that the client supports """ def __init__(self): @@ -989,8 +981,8 @@ class ECPointFormatsExtension(VarListExtension): See RFC4492. - @type formats: list of int - @ivar formats: list of point formats supported by peer + :vartype formats: list of int + :ivar formats: list of point formats supported by peer """ def __init__(self): @@ -1021,7 +1013,7 @@ def extData(self): """ Return raw encoding of the extension - @rtype: bytearray + :rtype: bytearray """ if self.sigalgs is None: return bytearray(0) @@ -1035,9 +1027,8 @@ def create(self, sigalgs): """ Set the list of supported algorithm types - @type sigalgs: list of tuples - @param sigalgs: list of pairs of a hash algorithm and signature - algorithm + :param list sigalgs: list of pairs of a hash algorithm and signature + algorithm """ self.sigalgs = sigalgs return self @@ -1046,8 +1037,8 @@ def parse(self, parser): """ Deserialise extension from on the wire data - @type parser: L{Parser} - @rtype: SignatureAlgorithmsExtension + :type Parser parser: data + :rtype: SignatureAlgorithmsExtension """ if parser.getRemainingLength() == 0: self.sigalgs = None @@ -1083,7 +1074,7 @@ def extData(self): """ Return raw encoding of the extension. - @rtype: bytearray + :rtype: bytearray """ return self.paddingData @@ -1091,8 +1082,7 @@ def create(self, size): """ Set the padding size and create null byte padding of defined size. - @type size: int - @param size: required padding size in bytes + :param int size: required padding size in bytes """ self.paddingData = bytearray(size) return self @@ -1101,13 +1091,12 @@ def parse(self, p): """ Deserialise extension from on the wire data. - @type p: L{tlslite.util.codec.Parser} - @param p: data to be parsed + :param Parser p: data to be parsed - @raise SyntaxError: when the size of the passed element doesn't match - the internal representation + :raises SyntaxError: when the size of the passed element doesn't match + the internal representation - @rtype: L{TLSExtension} + :rtype: TLSExtension """ self.paddingData = p.getFixBytes(p.getRemainingLength()) return self @@ -1131,7 +1120,7 @@ def extData(self): """ Return raw encoding of the extension. - @rtype: bytearray + :rtype: bytearray """ if self.renegotiated_connection is None: return bytearray(0) @@ -1144,7 +1133,7 @@ def create(self, renegotiated_connection): """ Set the finished message payload from previous connection. - @type renegotiated_connection: bytearray + :param bytearray renegotiated_connection: data """ self.renegotiated_connection = renegotiated_connection return self @@ -1153,10 +1142,9 @@ def parse(self, parser): """ Deserialise extension from on the wire data. - @type parser: L{tlslite.util.codec.Parser} - @param parser: data to be parsed + :param Parser parser: data to be parsed - @rtype: L{RenegotiationInfoExtension} + :rtype: RenegotiationInfoExtension """ if parser.getRemainingLength() == 0: self.renegotiated_connection = None @@ -1170,21 +1158,21 @@ class ALPNExtension(TLSExtension): """ Handling of Application Layer Protocol Negotiation extension from RFC 7301. - @type protocol_names: list of bytearrays - @ivar protocol_names: list of protocol names acceptable or selected by peer + :vartype protocol_names: list of bytearrays + :ivar protocol_names: list of protocol names acceptable or selected by peer - @type extType: int - @ivar extType: numberic type of ALPNExtension, i.e. 16 + :vartype extType: int + :ivar extType: numberic type of ALPNExtension, i.e. 16 - @type extData: bytearray - @ivar extData: raw encoding of the extension data + :vartype extData: bytearray + :ivar extData: raw encoding of the extension data """ def __init__(self): """ Create instance of ALPNExtension - See also: L{create} and L{parse} + See also: :py:meth:`create` and :py:meth:`parse` """ super(ALPNExtension, self).__init__(extType=ExtensionType.alpn) @@ -1194,7 +1182,7 @@ def __repr__(self): """ Create programmer-readable representation of object - @rtype: str + :rtype: str """ return "ALPNExtension(protocol_names={0!r})".format(self.protocol_names) @@ -1203,7 +1191,7 @@ def extData(self): """ Return encoded payload of the extension - @rtype: bytearray + :rtype: bytearray """ if self.protocol_names is None: return bytearray(0) @@ -1223,8 +1211,7 @@ def create(self, protocol_names=None): """ Create an instance of ALPNExtension with specified protocols - @type protocols: list of bytearray - @param protocols: list of protocol names that are to be sent + :param list protocols: list of protocol names that are to be sent """ self.protocol_names = protocol_names return self @@ -1233,13 +1220,12 @@ def parse(self, parser): """ Parse the extension from on the wire format - @type parser: L{tlslite.util.codec.Parser} - @param parser: data to be parsed as extension + :param Parser parser: data to be parsed as extension - @raise SyntaxError: when the encoding of the extension is self + :raises SyntaxError: when the encoding of the extension is self inconsistent - @rtype: L{ALPNExtension} + :rtype: ALPNExtension """ self.protocol_names = [] parser.startLengthCheck(2) @@ -1256,15 +1242,15 @@ class StatusRequestExtension(TLSExtension): """ Handling of the Certificate Status Request extension from RFC 6066. - @type status_type: int - @ivar status_type: type of the status request + :vartype status_type: int + :ivar status_type: type of the status request - @type responder_id_list: list of bytearray - @ivar responder_id_list: list of DER encoded OCSP responder identifiers + :vartype responder_id_list: list of bytearray + :ivar responder_id_list: list of DER encoded OCSP responder identifiers that the client trusts - @type request_extensions: bytearray - @ivar request_extensions: DER encoded list of OCSP extensions, as defined + :vartype request_extensions: bytearray + :ivar request_extensions: DER encoded list of OCSP extensions, as defined in RFC 2560 """ @@ -1280,7 +1266,7 @@ def __repr__(self): """ Create programmer-readable representation of object - @rtype: str + :rtype: str """ return ("StatusRequestExtension(status_type={0}, " "responder_id_list={1!r}, " @@ -1293,7 +1279,7 @@ def extData(self): """ Return encoded payload of the extension. - @rtype: bytearray + :rtype: bytearray """ if self.status_type is None: return bytearray() @@ -1317,15 +1303,14 @@ def create(self, status_type=CertificateStatusType.ocsp, """ Create an instance of StatusRequestExtension with specified options. - @type status_type: int - @param status_type: type of status returned + :param int status_type: type of status returned - @type responder_id_list: list - @param responder_id_list: list of encoded OCSP responder identifiers + :param list responder_id_list: list of encoded OCSP responder + identifiers that the client trusts - @type request_extensions: bytearray - @param request_extensions: DER encoding of requested OCSP extensions + :param bytearray request_extensions: DER encoding of requested OCSP + extensions """ self.status_type = status_type self.responder_id_list = list(responder_id_list) @@ -1336,10 +1321,9 @@ def parse(self, parser): """ Parse the extension from on the wire format. - @type parser: L{tlslite.util.codec.Parser} - @param parser: data to be parsed as extension + :param Parser parser: data to be parsed as extension - @rtype: L{StatusRequestExtension} + :rtype: StatusRequestExtension """ # handling of server side message if parser.getRemainingLength() == 0: diff --git a/tlslite/handshakehashes.py b/tlslite/handshakehashes.py index 666abbc1..55d88fcf 100644 --- a/tlslite/handshakehashes.py +++ b/tlslite/handshakehashes.py @@ -27,10 +27,9 @@ def __init__(self): def update(self, data): """ - Add L{data} to hash input. + Add `data` to hash input. - @type data: bytearray - @param data: serialized TLS handshake message + :param bytearray data: serialized TLS handshake message """ text = compat26Str(data) self._handshakeMD5.update(text) @@ -46,8 +45,7 @@ def digest(self, digest=None): Used for Finished and CertificateVerify messages. - @type digest: str - @param digest: name of digest to return + :param str digest: name of digest to return """ if digest is None: return self._handshakeMD5.digest() + self._handshakeSHA.digest() @@ -72,10 +70,8 @@ def digestSSL(self, masterSecret, label): Used for Finished and CertificateVerify messages. - @type masterSecret: bytearray - @param masterSecret: value of the master secret - @type label: bytearray - @param label: label to include in the calculation + :param bytearray masterSecret: value of the master secret + :param bytearray label: label to include in the calculation """ #pylint: disable=maybe-no-member imacMD5 = self._handshakeMD5.copy() @@ -102,7 +98,7 @@ def copy(self): Return a copy of the object with all the hashes in the same state as the source object. - @rtype: HandshakeHashes + :rtype: HandshakeHashes """ other = HandshakeHashes() other._handshakeMD5 = self._handshakeMD5.copy() diff --git a/tlslite/handshakehelpers.py b/tlslite/handshakehelpers.py index c935fc8e..a13a9ea8 100644 --- a/tlslite/handshakehelpers.py +++ b/tlslite/handshakehelpers.py @@ -11,8 +11,6 @@ class HandshakeHelpers(object): """ This class encapsulates helper functions to be used with a TLS handshake. - - @sort: alignUsingPaddingExtension """ @staticmethod @@ -20,8 +18,7 @@ def alignClientHelloPadding(clientHello): """ Align ClientHello using the Padding extension to 512 bytes at least. - @type clientHello: ClientHello - @param clientHello: ClientHello to be aligned + :param ClientHello clientHello: ClientHello to be aligned """ # Check clientHello size if padding extension should be added # we want to add the extension even when using just SSLv3 diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index fd8e39fa..dc0ae11b 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -38,115 +38,129 @@ "ffdhe8192"] class HandshakeSettings(object): - """This class encapsulates various parameters that can be used with + """ + This class encapsulates various parameters that can be used with a TLS handshake. - @sort: minKeySize, maxKeySize, cipherNames, macNames, certificateTypes, - minVersion, maxVersion - @type minKeySize: int - @ivar minKeySize: The minimum bit length for asymmetric keys. + :vartype minKeySize: int + :ivar minKeySize: The minimum bit length for asymmetric keys. + + If the other party tries to use SRP, RSA, or Diffie-Hellman + parameters smaller than this length, an alert will be + signalled. The default is 1023. + + + :vartype maxKeySize: int + :ivar maxKeySize: The maximum bit length for asymmetric keys. + + If the other party tries to use SRP, RSA, or Diffie-Hellman + parameters larger than this length, an alert will be signalled. + The default is 8193. + + :vartype cipherNames: list + :ivar cipherNames: The allowed ciphers. - If the other party tries to use SRP, RSA, or Diffie-Hellman - parameters smaller than this length, an alert will be - signalled. The default is 1023. + The allowed values in this list are 'chacha20-poly1305', 'aes256gcm', + 'aes128gcm', 'aes256', 'aes128', '3des', 'chacha20-poly1305_draft00', + 'null' and + 'rc4'. If these settings are used with a client handshake, they + determine the order of the ciphersuites offered in the ClientHello + message. - @type maxKeySize: int - @ivar maxKeySize: The maximum bit length for asymmetric keys. + If these settings are used with a server handshake, the server will + choose whichever ciphersuite matches the earliest entry in this + list. - If the other party tries to use SRP, RSA, or Diffie-Hellman - parameters larger than this length, an alert will be signalled. - The default is 8193. + .. note:: If '3des' is used in this list, but TLS Lite can't find an + add-on library that supports 3DES, then '3des' will be silently + removed. - @type cipherNames: list - @ivar cipherNames: The allowed ciphers. + The default value is list that excludes 'rc4', 'null' and + 'chacha20-poly1305_draft00'. - The allowed values in this list are 'aes256', 'aes128', '3des', and - 'rc4'. If these settings are used with a client handshake, they - determine the order of the ciphersuites offered in the ClientHello - message. + :vartype macNames: list + :ivar macNames: The allowed MAC algorithms. - If these settings are used with a server handshake, the server will - choose whichever ciphersuite matches the earliest entry in this - list. + The allowed values in this list are 'sha384', 'sha256', 'aead', 'sha' + and 'md5'. - NOTE: If '3des' is used in this list, but TLS Lite can't find an - add-on library that supports 3DES, then '3des' will be silently - removed. + The default value is list that excludes 'md5'. - The default value is ['rc4', 'aes256', 'aes128', '3des']. + :vartype certificateTypes: list + :ivar certificateTypes: The allowed certificate types. - @type macNames: list - @ivar macNames: The allowed MAC algorithms. - - The allowed values in this list are 'sha' and 'md5'. - - The default value is ['sha']. + The only allowed certificate type is 'x509'. This list is only used + with a + client handshake. The client will advertise to the server which + certificate + types are supported, and will check that the server uses one of the + appropriate types. - @type certificateTypes: list - @ivar certificateTypes: The allowed certificate types. + :vartype minVersion: tuple + :ivar minVersion: The minimum allowed SSL/TLS version. - The only allowed certificate type is 'x509'. This list is only used with a - client handshake. The client will advertise to the server which certificate - types are supported, and will check that the server uses one of the - appropriate types. + This variable can be set to (3, 0) for SSL 3.0, (3, 1) for TLS 1.0, + (3, 2) for + TLS 1.1, or (3, 3) for TLS 1.2. If the other party wishes to use a + lower + version, a protocol_version alert will be signalled. The default is + (3, 1). + :vartype maxVersion: tuple + :ivar maxVersion: The maximum allowed SSL/TLS version. - @type minVersion: tuple - @ivar minVersion: The minimum allowed SSL/TLS version. + This variable can be set to (3, 0) for SSL 3.0, (3, 1) for TLS 1.0, + (3, 2) for TLS 1.1, or (3, 3) for TLS 1.2. If the other party wishes + to use a + higher version, a protocol_version alert will be signalled. The + default is (3, 3). - This variable can be set to (3,0) for SSL 3.0, (3,1) for TLS 1.0, (3,2) for - TLS 1.1, or (3,3) for TLS 1.2. If the other party wishes to use a lower - version, a protocol_version alert will be signalled. The default is (3,1). + .. warning:: Some servers may (improperly) reject clients which offer + support + for TLS 1.1 or higher. In this case, try lowering maxVersion to + (3, 1). - @type maxVersion: tuple - @ivar maxVersion: The maximum allowed SSL/TLS version. + :vartype useExperimentalTackExtension: bool + :ivar useExperimentalTackExtension: Whether to enabled TACK support. - This variable can be set to (3,0) for SSL 3.0, (3,1) for TLS 1.0, (3,2) for - TLS 1.1, or (3,3) for TLS 1.2. If the other party wishes to use a higher - version, a protocol_version alert will be signalled. The default is (3,3). - (WARNING: Some servers may (improperly) reject clients which offer support - for TLS 1.1. In this case, try lowering maxVersion to (3,1)). - - @type useExperimentalTackExtension: bool - @ivar useExperimentalTackExtension: Whether to enabled TACK support. - - Note that TACK support is not standardized by IETF and uses a temporary - TLS Extension number, so should NOT be used in production software. + Note that TACK support is not standardized by IETF and uses a temporary + TLS Extension number, so should NOT be used in production software. - @type sendFallbackSCSV: bool - @ivar sendFallbackSCSV: Whether to, as a client, send FALLBACK_SCSV. + :vartype sendFallbackSCSV: bool + :ivar sendFallbackSCSV: Whether to, as a client, send FALLBACK_SCSV. - @type rsaSigHashes: list - @ivar rsaSigHashes: List of hashes supported (and advertised as such) for - TLS 1.2 signatures over Server Key Exchange or Certificate Verify with - RSA signature algorithm. + :vartype rsaSigHashes: list + :ivar rsaSigHashes: List of hashes supported (and advertised as such) for + TLS 1.2 signatures over Server Key Exchange or Certificate Verify with + RSA signature algorithm. - The list is sorted from most wanted to least wanted algorithm. + The list is sorted from most wanted to least wanted algorithm. - The allowed hashes are: "md5", "sha1", "sha224", "sha256", - "sha384" and "sha512". The default list does not include md5. + The allowed hashes are: "md5", "sha1", "sha224", "sha256", + "sha384" and "sha512". The default list does not include md5. - @type eccCurves: list - @ivar eccCurves: List of named curves that are to be supported + :vartype eccCurves: list + :ivar eccCurves: List of named curves that are to be supported - @type useEncryptThenMAC: bool - @ivar useEncryptThenMAC: whether to support the encrypt then MAC extension - from RFC 7366. True by default. + :vartype useEncryptThenMAC: bool + :ivar useEncryptThenMAC: whether to support the encrypt then MAC extension + from RFC 7366. True by default. - @type useExtendedMasterSecret: bool - @ivar useExtendedMasterSecret: whether to support the extended master - secret calculation from RFC 7627. True by default. + :vartype useExtendedMasterSecret: bool + :ivar useExtendedMasterSecret: whether to support the extended master + secret calculation from RFC 7627. True by default. - @type requireExtendedMasterSecret: bool - @ivar requireExtendedMasterSecret: whether to require negotiation of - extended master secret calculation for successful connection. Requires - useExtendedMasterSecret to be set to true. False by default. + :vartype requireExtendedMasterSecret: bool + :ivar requireExtendedMasterSecret: whether to require negotiation of + extended master secret calculation for successful connection. Requires + useExtendedMasterSecret to be set to true. False by default. - @type defaultCurve: str - @ivar defaultCurve: curve that will be used by server in case the client - did not advertise support for any curves. It does not have to be the - first curve for eccCurves and may be distinct from curves from that list. + :vartype defaultCurve: str + :ivar defaultCurve: curve that will be used by server in case the client + did not advertise support for any curves. It does not have to be the + first curve for eccCurves and may be distinct from curves from that + list. """ def __init__(self): self.minKeySize = 1023 @@ -275,9 +289,9 @@ def validate(self): Validate the settings, filter out unsupported ciphersuites and return a copy of object. Does not modify the original object. - @rtype: HandshakeSettings - @return: a self-consistent copy of settings - @raise ValueError: when settings are invalid, insecure or unsupported. + :rtype: HandshakeSettings + :returns: a self-consistent copy of settings + :raises ValueError: when settings are invalid, insecure or unsupported. """ other = HandshakeSettings() other.minKeySize = self.minKeySize diff --git a/tlslite/integration/asyncstatemachine.py b/tlslite/integration/asyncstatemachine.py index 262bc25e..249482c5 100644 --- a/tlslite/integration/asyncstatemachine.py +++ b/tlslite/integration/asyncstatemachine.py @@ -76,8 +76,8 @@ def wantsReadEvent(self): operation wants to read from the socket. If an operation is not active, this returns None. - @rtype: bool or None - @return: If the state machine wants to read. + :rtype: bool or None + :returns: If the state machine wants to read. """ if self.result != None: return self.result == 0 @@ -90,8 +90,8 @@ def wantsWriteEvent(self): operation wants to write to the socket. If an operation is not active, this returns None. - @rtype: bool or None - @return: If the state machine wants to write. + :rtype: bool or None + :returns: If the state machine wants to write. """ if self.result != None: return self.result == 1 @@ -194,10 +194,10 @@ def _doWriteOp(self): def setHandshakeOp(self, handshaker): """Start a handshake operation. - @type handshaker: generator - @param handshaker: A generator created by using one of the - asynchronous handshake functions (i.e. handshakeServerAsync, or - handshakeClientxxx(..., async=True). + :param generator handshaker: A generator created by using one of the + asynchronous handshake functions (i.e. + :py:meth:`~.TLSConnection.handshakeServerAsync` , or + handshakeClientxxx(..., async=True). """ try: self._checkAssert(0) @@ -211,7 +211,7 @@ def setServerHandshakeOp(self, **args): """Start a handshake operation. The arguments passed to this function will be forwarded to - L{tlslite.tlsconnection.TLSConnection.handshakeServerAsync}. + :py:obj:`~tlslite.tlsconnection.TLSConnection.handshakeServerAsync`. """ handshaker = self.tlsConnection.handshakeServerAsync(**args) self.setHandshakeOp(handshaker) @@ -230,8 +230,7 @@ def setCloseOp(self): def setWriteOp(self, writeBuffer): """Start a write operation. - @type writeBuffer: str - @param writeBuffer: The string to transmit. + :param str writeBuffer: The string to transmit. """ try: self._checkAssert(0) diff --git a/tlslite/integration/clienthelper.py b/tlslite/integration/clienthelper.py index 7e886a1c..9155ac23 100644 --- a/tlslite/integration/clienthelper.py +++ b/tlslite/integration/clienthelper.py @@ -26,6 +26,7 @@ def __init__(self, """ For client authentication, use one of these argument combinations: + - username, password (SRP) - certChain, privateKey (certificate) @@ -33,6 +34,7 @@ def __init__(self, implicit mutual authentication performed by SRP, or you can do certificate-based server authentication with one of these argument combinations: + - x509Fingerprint Certificate-based server authentication is compatible with @@ -43,43 +45,40 @@ def __init__(self, performed only when this class needs to connect with the server. Then you should be prepared to handle TLS-specific exceptions. See the client handshake functions in - L{tlslite.TLSConnection.TLSConnection} for details on which + :py:class:`~tlslite.tlsconnection.TLSConnection` for details on which exceptions might be raised. - @type username: str - @param username: SRP username. Requires the - 'password' argument. + :param str username: SRP username. Requires the + 'password' argument. - @type password: str - @param password: SRP password for mutual authentication. - Requires the 'username' argument. + :param str password: SRP password for mutual authentication. + Requires the 'username' argument. - @type certChain: L{tlslite.x509certchain.X509CertChain} - @param certChain: Certificate chain for client authentication. - Requires the 'privateKey' argument. Excludes the SRP arguments. + :param X509CertChain certChain: Certificate chain for client + authentication. + Requires the 'privateKey' argument. Excludes the SRP arguments. - @type privateKey: L{tlslite.utils.rsakey.RSAKey} - @param privateKey: Private key for client authentication. - Requires the 'certChain' argument. Excludes the SRP arguments. + :param RSAKey privateKey: Private key for client authentication. + Requires the 'certChain' argument. Excludes the SRP arguments. - @type checker: L{tlslite.checker.Checker} - @param checker: Callable object called after handshaking to - evaluate the connection and raise an Exception if necessary. + :param Checker checker: Callable object called after handshaking to + evaluate the connection and raise an Exception if necessary. - @type settings: L{tlslite.handshakesettings.HandshakeSettings} - @param settings: Various settings which can be used to control - the ciphersuites, certificate types, and SSL/TLS versions - offered by the client. + :type settings: HandshakeSettings + :param settings: Various settings which can be used to control + the ciphersuites, certificate types, and SSL/TLS versions + offered by the client. - @type anon: bool - @param anon: set to True if the negotiation should advertise only - anonymous TLS ciphersuites. Mutually exclusive with client certificate - authentication or SRP authentication + :param bool anon: set to True if the negotiation should advertise only + anonymous TLS ciphersuites. Mutually exclusive with client + certificate + authentication or SRP authentication - @type host: str or None - @param host: the hostname that the connection is made to. Can be an - IP address (in which case the SNI extension won't be sent). Can - include the port (in which case the port will be stripped and ignored). + :type host: str or None + :param host: the hostname that the connection is made to. Can be an + IP address (in which case the SNI extension won't be sent). Can + include the port (in which case the port will be stripped and + ignored). """ self.username = None diff --git a/tlslite/integration/httptlsconnection.py b/tlslite/integration/httptlsconnection.py index 0fdc135f..c9221500 100644 --- a/tlslite/integration/httptlsconnection.py +++ b/tlslite/integration/httptlsconnection.py @@ -21,27 +21,29 @@ class HTTPTLSConnection(httplib.HTTPConnection, ClientHelper): """This class extends L{httplib.HTTPConnection} to support TLS.""" - def __init__(self, host, port=None, strict=None, + def __init__(self, host, port=None, strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None, username=None, password=None, certChain=None, privateKey=None, checker=None, settings=None, - ignoreAbruptClose=False, + ignoreAbruptClose=False, anon=False): """Create a new HTTPTLSConnection. For client authentication, use one of these argument combinations: - - username, password (SRP) - - certChain, privateKey (certificate) + + - username, password (SRP) + - certChain, privateKey (certificate) For server authentication, you can either rely on the implicit mutual authentication performed by SRP or you can do certificate-based server authentication with one of these argument combinations: - - x509Fingerprint + + - x509Fingerprint Certificate-based server authentication is compatible with SRP or certificate-based client authentication. @@ -51,45 +53,45 @@ def __init__(self, host, port=None, strict=None, performed only when this class needs to connect with the server. Thus you should be prepared to handle TLS-specific exceptions when calling methods inherited from - L{httplib.HTTPConnection} such as request(), connect(), and + :py:class:`httplib.HTTPConnection` such as request(), connect(), and send(). See the client handshake functions in - L{tlslite.TLSConnection.TLSConnection} for details on which + :py:class:`~tlslite.tlsconnection.TLSConnection` for details on which exceptions might be raised. - @type host: str - @param host: Server to connect to. - - @type port: int - @param port: Port to connect to. - - @type username: str - @param username: SRP username. Requires the - 'password' argument. - - @type password: str - @param password: SRP password for mutual authentication. - Requires the 'username' argument. - - @type certChain: L{tlslite.x509certchain.X509CertChain} or - @param certChain: Certificate chain for client authentication. - Requires the 'privateKey' argument. Excludes the SRP arguments. - - @type privateKey: L{tlslite.utils.rsakey.RSAKey} - @param privateKey: Private key for client authentication. - Requires the 'certChain' argument. Excludes the SRP arguments. - - @type checker: L{tlslite.checker.Checker} - @param checker: Callable object called after handshaking to - evaluate the connection and raise an Exception if necessary. - - @type settings: L{tlslite.handshakesettings.HandshakeSettings} - @param settings: Various settings which can be used to control - the ciphersuites, certificate types, and SSL/TLS versions - offered by the client. - - @type ignoreAbruptClose: bool - @param ignoreAbruptClose: ignore the TLSAbruptCloseError on - unexpected hangup. + :type host: str + :param host: Server to connect to. + + :type port: int + :param port: Port to connect to. + + :type username: str + :param username: SRP username. Requires the + 'password' argument. + + :type password: str + :param password: SRP password for mutual authentication. + Requires the 'username' argument. + + :type certChain: ~tlslite.x509certchain.X509CertChain + :param certChain: Certificate chain for client authentication. + Requires the 'privateKey' argument. Excludes the SRP arguments. + + :type privateKey: ~tlslite.utils.rsakey.RSAKey + :param privateKey: Private key for client authentication. + Requires the 'certChain' argument. Excludes the SRP arguments. + + :type checker: ~tlslite.checker.Checker + :param checker: Callable object called after handshaking to + evaluate the connection and raise an Exception if necessary. + + :type settings: ~tlslite.handshakesettings.HandshakeSettings + :param settings: Various settings which can be used to control + the ciphersuites, certificate types, and SSL/TLS versions + offered by the client. + + :type ignoreAbruptClose: bool + :param ignoreAbruptClose: ignore the TLSAbruptCloseError on + unexpected hangup. """ if source_address: httplib.HTTPConnection.__init__(self, diff --git a/tlslite/integration/imap4_tls.py b/tlslite/integration/imap4_tls.py index 4703a316..3254b543 100644 --- a/tlslite/integration/imap4_tls.py +++ b/tlslite/integration/imap4_tls.py @@ -12,7 +12,7 @@ IMAP4_TLS_PORT = 993 class IMAP4_TLS(IMAP4, ClientHelper): - """This class extends L{imaplib.IMAP4} with TLS support.""" + """This class extends :py:class:`imaplib.IMAP4` with TLS support.""" def __init__(self, host = '', port = IMAP4_TLS_PORT, username=None, password=None, @@ -23,6 +23,7 @@ def __init__(self, host = '', port = IMAP4_TLS_PORT, For client authentication, use one of these argument combinations: + - username, password (SRP) - certChain, privateKey (certificate) @@ -30,6 +31,7 @@ def __init__(self, host = '', port = IMAP4_TLS_PORT, implicit mutual authentication performed by SRP or you can do certificate-based server authentication with one of these argument combinations: + - x509Fingerprint Certificate-based server authentication is compatible with @@ -37,39 +39,39 @@ def __init__(self, host = '', port = IMAP4_TLS_PORT, The caller should be prepared to handle TLS-specific exceptions. See the client handshake functions in - L{tlslite.TLSConnection.TLSConnection} for details on which + :py:class:`~tlslite.tlsconnection.TLSConnection` for details on which exceptions might be raised. - @type host: str - @param host: Server to connect to. + :type host: str + :param host: Server to connect to. + + :type port: int + :param port: Port to connect to. - @type port: int - @param port: Port to connect to. + :type username: str + :param username: SRP username. Requires the + 'password' argument. - @type username: str - @param username: SRP username. Requires the - 'password' argument. + :type password: str + :param password: SRP password for mutual authentication. + Requires the 'username' argument. - @type password: str - @param password: SRP password for mutual authentication. - Requires the 'username' argument. + :type certChain: ~tlslite.x509certchain.X509CertChain + :param certChain: Certificate chain for client authentication. + Requires the 'privateKey' argument. Excludes the SRP arguments. - @type certChain: L{tlslite.x509certchain.X509CertChain} - @param certChain: Certificate chain for client authentication. - Requires the 'privateKey' argument. Excludes the SRP arguments. + :type privateKey: ~tlslite.utils.rsakey.RSAKey + :param privateKey: Private key for client authentication. + Requires the 'certChain' argument. Excludes the SRP arguments. - @type privateKey: L{tlslite.utils.rsakey.RSAKey} - @param privateKey: Private key for client authentication. - Requires the 'certChain' argument. Excludes the SRP arguments. - - @type checker: L{tlslite.checker.Checker} - @param checker: Callable object called after handshaking to - evaluate the connection and raise an Exception if necessary. + :type checker: ~tlslite.checker.Checker + :param checker: Callable object called after handshaking to + evaluate the connection and raise an Exception if necessary. - @type settings: L{tlslite.handshakesettings.HandshakeSettings} - @param settings: Various settings which can be used to control - the ciphersuites, certificate types, and SSL/TLS versions - offered by the client. + :type settings: ~tlslite.handshakesettings.HandshakeSettings + :param settings: Various settings which can be used to control + the ciphersuites, certificate types, and SSL/TLS versions + offered by the client. """ ClientHelper.__init__(self, @@ -93,4 +95,4 @@ def open(self, host = '', port = IMAP4_TLS_PORT): self.sock.connect((host, port)) self.sock = TLSConnection(self.sock) ClientHelper._handshake(self, self.sock) - self.file = self.sock.makefile('rb') \ No newline at end of file + self.file = self.sock.makefile('rb') diff --git a/tlslite/integration/pop3_tls.py b/tlslite/integration/pop3_tls.py index 64f6124e..814e4945 100644 --- a/tlslite/integration/pop3_tls.py +++ b/tlslite/integration/pop3_tls.py @@ -9,7 +9,7 @@ from tlslite.integration.clienthelper import ClientHelper class POP3_TLS(POP3, ClientHelper): - """This class extends L{poplib.POP3} with TLS support.""" + """This class extends :py:class:`poplib.POP3` with TLS support.""" def __init__(self, host, port = POP3_SSL_PORT, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, @@ -21,6 +21,7 @@ def __init__(self, host, port = POP3_SSL_PORT, For client authentication, use one of these argument combinations: + - username, password (SRP) - certChain, privateKey (certificate) @@ -28,6 +29,7 @@ def __init__(self, host, port = POP3_SSL_PORT, implicit mutual authentication performed by SRP or you can do certificate-based server authentication with one of these argument combinations: + - x509Fingerprint Certificate-based server authentication is compatible with @@ -35,38 +37,39 @@ def __init__(self, host, port = POP3_SSL_PORT, The caller should be prepared to handle TLS-specific exceptions. See the client handshake functions in - L{tlslite.TLSConnection.TLSConnection} for details on which + :py:class:`~tlslite.tlsconnection.TLSConnection` + for details on which exceptions might be raised. - @type host: str - @param host: Server to connect to. + :type host: str + :param host: Server to connect to. + + :type port: int + :param port: Port to connect to. - @type port: int - @param port: Port to connect to. + :type username: str + :param username: SRP username. - @type username: str - @param username: SRP username. - - @type password: str - @param password: SRP password for mutual authentication. - Requires the 'username' argument. + :type password: str + :param password: SRP password for mutual authentication. + Requires the 'username' argument. - @type certChain: L{tlslite.x509certchain.X509CertChain} - @param certChain: Certificate chain for client authentication. - Requires the 'privateKey' argument. Excludes the SRP argument. + :type certChain: ~tlslite.x509certchain.X509CertChain + :param certChain: Certificate chain for client authentication. + Requires the 'privateKey' argument. Excludes the SRP argument. - @type privateKey: L{tlslite.utils.rsakey.RSAKey} - @param privateKey: Private key for client authentication. - Requires the 'certChain' argument. Excludes the SRP argument. + :type privateKey: ~tlslite.utils.rsakey.RSAKey + :param privateKey: Private key for client authentication. + Requires the 'certChain' argument. Excludes the SRP argument. - @type checker: L{tlslite.checker.Checker} - @param checker: Callable object called after handshaking to - evaluate the connection and raise an Exception if necessary. + :type checker: ~tlslite.checker.Checker + :param checker: Callable object called after handshaking to + evaluate the connection and raise an Exception if necessary. - @type settings: L{tlslite.handshakesettings.HandshakeSettings} - @param settings: Various settings which can be used to control - the ciphersuites, certificate types, and SSL/TLS versions - offered by the client. + :type settings: ~tlslite.handshakesettings.HandshakeSettings + :param settings: Various settings which can be used to control + the ciphersuites, certificate types, and SSL/TLS versions + offered by the client. """ self.host = host self.port = port @@ -81,4 +84,4 @@ def __init__(self, host, port = POP3_SSL_PORT, self.sock = connection self.file = self.sock.makefile('rb') self._debugging = 0 - self.welcome = self._getresp() \ No newline at end of file + self.welcome = self._getresp() diff --git a/tlslite/integration/smtp_tls.py b/tlslite/integration/smtp_tls.py index d4214732..1c94d0ee 100644 --- a/tlslite/integration/smtp_tls.py +++ b/tlslite/integration/smtp_tls.py @@ -8,7 +8,7 @@ from tlslite.integration.clienthelper import ClientHelper class SMTP_TLS(SMTP): - """This class extends L{smtplib.SMTP} with TLS support.""" + """This class extends :py:class:`smtplib.SMTP` with TLS support.""" def starttls(self, username=None, password=None, @@ -22,6 +22,7 @@ def starttls(self, For client authentication, use one of these argument combinations: + - username, password (SRP) - certChain, privateKey (certificate) @@ -29,6 +30,7 @@ def starttls(self, implicit mutual authentication performed by SRP or you can do certificate-based server authentication with one of these argument combinations: + - x509Fingerprint Certificate-based server authentication is compatible with @@ -36,33 +38,33 @@ def starttls(self, The caller should be prepared to handle TLS-specific exceptions. See the client handshake functions in - L{tlslite.TLSConnection.TLSConnection} for details on which + :py:class:`~tlslite.tlsconnection.TLSConnection` for details on which exceptions might be raised. - @type username: str - @param username: SRP username. Requires the - 'password' argument. + :type username: str + :param username: SRP username. Requires the + 'password' argument. - @type password: str - @param password: SRP password for mutual authentication. - Requires the 'username' argument. + :type password: str + :param password: SRP password for mutual authentication. + Requires the 'username' argument. - @type certChain: L{tlslite.x509certchain.X509CertChain} - @param certChain: Certificate chain for client authentication. - Requires the 'privateKey' argument. Excludes the SRP arguments. + :type certChain: ~tlslite.x509certchain.X509CertChain + :param certChain: Certificate chain for client authentication. + Requires the 'privateKey' argument. Excludes the SRP arguments. - @type privateKey: L{tlslite.utils.rsakey.RSAKey} - @param privateKey: Private key for client authentication. - Requires the 'certChain' argument. Excludes the SRP arguments. + :type privateKey: ~tlslite.utils.rsakey.RSAKey + :param privateKey: Private key for client authentication. + Requires the 'certChain' argument. Excludes the SRP arguments. - @type checker: L{tlslite.checker.Checker} - @param checker: Callable object called after handshaking to - evaluate the connection and raise an Exception if necessary. + :type checker: ~tlslite.checker.Checker + :param checker: Callable object called after handshaking to + evaluate the connection and raise an Exception if necessary. - @type settings: L{tlslite.handshakesettings.HandshakeSettings} - @param settings: Various settings which can be used to control - the ciphersuites, certificate types, and SSL/TLS versions - offered by the client. + :type settings: ~tlslite.handshakesettings.HandshakeSettings + :param settings: Various settings which can be used to control + the ciphersuites, certificate types, and SSL/TLS versions + offered by the client. """ (resp, reply) = self.docmd("STARTTLS") if resp == 220: @@ -75,4 +77,4 @@ def starttls(self, helper._handshake(conn) self.sock = conn self.file = conn.makefile('rb') - return (resp, reply) \ No newline at end of file + return (resp, reply) diff --git a/tlslite/integration/tlsasyncdispatchermixin.py b/tlslite/integration/tlsasyncdispatchermixin.py index dc325966..3cade322 100644 --- a/tlslite/integration/tlsasyncdispatchermixin.py +++ b/tlslite/integration/tlsasyncdispatchermixin.py @@ -13,20 +13,22 @@ class TLSAsyncDispatcherMixIn(AsyncStateMachine): - """This class can be "mixed in" with an - L{asyncore.dispatcher} to add TLS support. + """ + This class can be "mixed in" with an + :py:class:`asyncore.dispatcher` to add TLS support. This class essentially sits between the dispatcher and the select loop, intercepting events and only calling the dispatcher when applicable. - In the case of handle_read(), a read operation will be activated, + In the case of :py:meth:`handle_read`, a read operation will be activated, and when it completes, the bytes will be placed in a buffer where - the dispatcher can retrieve them by calling recv(), and the - dispatcher's handle_read() will be called. + the dispatcher can retrieve them by calling :py:meth:`recv`, and the + dispatcher's :py:meth:`handle_read` will be called. - In the case of handle_write(), the dispatcher's handle_write() will - be called, and when it calls send(), a write operation will be + In the case of :py:meth:`handle_write`, the dispatcher's + :py:meth:`handle_write` will + be called, and when it calls :py:meth:`send`, a write operation will be activated. To use this class, you must combine it with an asyncore.dispatcher, @@ -34,15 +36,16 @@ class TLSAsyncDispatcherMixIn(AsyncStateMachine): Below is an example of using this class with medusa. This class is mixed in with http_channel to create http_tls_channel. Note: + 1. the mix-in is listed first in the inheritance list 2. the input buffer size must be at least 16K, otherwise the - dispatcher might not read all the bytes from the TLS layer, - leaving some bytes in limbo. + dispatcher might not read all the bytes from the TLS layer, + leaving some bytes in limbo. 3. IE seems to have a problem receiving a whole HTTP response in a - single TLS record, so HTML pages containing '\\r\\n\\r\\n' won't - be displayed on IE. + single TLS record, so HTML pages containing '\\r\\n\\r\\n' won't + be displayed on IE. Add the following text into 'start_medusa.py', in the 'HTTP Server' section:: @@ -70,12 +73,11 @@ def __init__ (self, server, conn, addr): hs.channel_class = http_tls_channel If the TLS layer raises an exception, the exception will be caught - in asyncore.dispatcher, which will call close() on this class. The + in asyncore.dispatcher, which will call :py:meth:`close` on this class. The TLS layer always closes the TLS connection before raising an exception, so the close operation will complete right away, causing asyncore.dispatcher.close() to be called, which closes the socket and removes this instance from the asyncore loop. - """ diff --git a/tlslite/integration/tlssocketservermixin.py b/tlslite/integration/tlssocketservermixin.py index 8e2182f6..a990df13 100644 --- a/tlslite/integration/tlssocketservermixin.py +++ b/tlslite/integration/tlssocketservermixin.py @@ -7,12 +7,12 @@ class TLSSocketServerMixIn: """ - This class can be mixed in with any L{SocketServer.TCPServer} to + This class can be mixed in with any :py:class:`SocketServer.TCPServer` to add TLS support. To use this class, define a new class that inherits from it and - some L{SocketServer.TCPServer} (with the mix-in first). Then - implement the handshake() method, doing some sort of server + some :py:class:`SocketServer.TCPServer` (with the mix-in first). Then + implement the :py:meth:`handshake` method, doing some sort of server handshake on the connection argument. If the handshake method returns True, the RequestHandler will be triggered. Below is a complete example of a threaded HTTPS server:: @@ -59,4 +59,4 @@ def finish_request(self, sock, client_address): #Implement this method to do some form of handshaking. Return True #if the handshake finishes properly and the request is authorized. def handshake(self, tlsConnection): - raise NotImplementedError() \ No newline at end of file + raise NotImplementedError() diff --git a/tlslite/integration/xmlrpcserver.py b/tlslite/integration/xmlrpcserver.py index c4f40cd9..b9cd66f8 100644 --- a/tlslite/integration/xmlrpcserver.py +++ b/tlslite/integration/xmlrpcserver.py @@ -4,7 +4,8 @@ # # See the LICENSE file for legal information regarding use of this file. -"""xmlrpcserver.py - simple XML RPC server supporting TLS""" +"""xmlrpcserver.py - simple XML RPC server supporting TLS.""" + try: from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler except ImportError: @@ -14,19 +15,20 @@ class TLSXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): - """XMLRPCRequestHandler using TLS""" - + """XMLRPCRequestHandler using TLS.""" + # Redefine the setup method (see SocketServer.StreamRequestHandler) def setup(self): + """Setup the connection for TLS.""" self.connection = self.request if getattr(self, 'timeout', None) is not None: # Python 2.7 self.connection.settimeout(self.timeout) self.rfile = self.connection.makefile('rb', self.rbufsize) self.wfile = self.connection.makefile('wb', self.wbufsize) - + def do_POST(self): - """Handles the HTTPS POST request.""" + """Handle the HTTPS POST request.""" SimpleXMLRPCRequestHandler.do_POST(self) try: # shut down the connection @@ -37,7 +39,7 @@ def do_POST(self): class TLSXMLRPCServer(TLSSocketServerMixIn, SimpleXMLRPCServer): - """Simple XML-RPC server using TLS""" + """Simple XML-RPC server using TLS.""" def __init__(self, addr, *args, **kwargs): if not args and not 'requestHandler' in kwargs: @@ -46,7 +48,7 @@ def __init__(self, addr, *args, **kwargs): class MultiPathTLSXMLRPCServer(TLSXMLRPCServer): - """Multipath XML-RPC Server using TLS""" + """Multipath XML-RPC Server using TLS.""" def __init__(self, addr, *args, **kwargs): TLSXMLRPCServer.__init__(addr, *args, **kwargs) diff --git a/tlslite/integration/xmlrpctransport.py b/tlslite/integration/xmlrpctransport.py index de7fc5af..7ed53f0c 100644 --- a/tlslite/integration/xmlrpctransport.py +++ b/tlslite/integration/xmlrpctransport.py @@ -34,9 +34,11 @@ def __init__(self, use_datetime=0, checker=None, settings=None, ignoreAbruptClose=False): - """Create a new XMLRPCTransport. + """ + Create a new XMLRPCTransport. - An instance of this class can be passed to L{xmlrpclib.ServerProxy} + An instance of this class can be passed to + :py:class:`xmlrpclib.ServerProxy` to use TLS with XML-RPC calls:: from tlslite import XMLRPCTransport @@ -47,6 +49,7 @@ def __init__(self, use_datetime=0, For client authentication, use one of these argument combinations: + - username, password (SRP) - certChain, privateKey (certificate) @@ -54,6 +57,7 @@ def __init__(self, use_datetime=0, implicit mutual authentication performed by SRP or you can do certificate-based server authentication with one of these argument combinations: + - x509Fingerprint Certificate-based server authentication is compatible with @@ -63,41 +67,41 @@ def __init__(self, use_datetime=0, simply stores these arguments for later. The handshake is performed only when this class needs to connect with the server. Thus you should be prepared to handle TLS-specific - exceptions when calling methods of L{xmlrpclib.ServerProxy}. See the + exceptions when calling methods of :py:class:`xmlrpclib.ServerProxy`. + See the client handshake functions in - L{tlslite.TLSConnection.TLSConnection} for details on which + :py:class:`~tlslite.tlsconnection.TLSConnection` for details on which exceptions might be raised. - @type username: str - @param username: SRP username. Requires the - 'password' argument. + :type username: str + :param username: SRP username. Requires the + 'password' argument. - @type password: str - @param password: SRP password for mutual authentication. - Requires the 'username' argument. + :type password: str + :param password: SRP password for mutual authentication. + Requires the 'username' argument. - @type certChain: L{tlslite.x509certchain.X509CertChain} - @param certChain: Certificate chain for client authentication. - Requires the 'privateKey' argument. Excludes the SRP arguments. + :type certChain: ~tlslite.x509certchain.X509CertChain + :param certChain: Certificate chain for client authentication. + Requires the 'privateKey' argument. Excludes the SRP arguments. - @type privateKey: L{tlslite.utils.rsakey.RSAKey} - @param privateKey: Private key for client authentication. - Requires the 'certChain' argument. Excludes the SRP arguments. + :type privateKey: ~tlslite.utils.rsakey.RSAKey + :param privateKey: Private key for client authentication. + Requires the 'certChain' argument. Excludes the SRP arguments. - @type checker: L{tlslite.checker.Checker} - @param checker: Callable object called after handshaking to - evaluate the connection and raise an Exception if necessary. + :type checker: ~tlslite.checker.Checker + :param checker: Callable object called after handshaking to + evaluate the connection and raise an Exception if necessary. - @type settings: L{tlslite.handshakesettings.HandshakeSettings} - @param settings: Various settings which can be used to control - the ciphersuites, certificate types, and SSL/TLS versions - offered by the client. + :type settings: ~tlslite.handshakesettings.HandshakeSettings + :param settings: Various settings which can be used to control + the ciphersuites, certificate types, and SSL/TLS versions + offered by the client. - @type ignoreAbruptClose: bool - @param ignoreAbruptClose: ignore the TLSAbruptCloseError on - unexpected hangup. + :type ignoreAbruptClose: bool + :param ignoreAbruptClose: ignore the TLSAbruptCloseError on + unexpected hangup. """ - # self._connection is new in python 2.7, since we're using it here, # we'll add this ourselves too, just in case we're pre-2.7 self._connection = (None, None) @@ -110,6 +114,7 @@ def __init__(self, use_datetime=0, settings) def make_connection(self, host): + """Make a connection to `host`. Reuse keepalive connections.""" # return an existing connection if possible. This allows # HTTP/1.1 keep-alive. if self._connection and host == self._connection[0]: diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 90f87128..4e0d7e38 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -112,8 +112,8 @@ def signServerKeyExchange(self, serverKeyExchange, sigHash=None): """ Sign a server key exchange using default or specified algorithm - @type sigHash: str - @param sigHash: name of the signature hash to be used for signing + :type sigHash: str + :param sigHash: name of the signature hash to be used for signing """ if self.serverHello.server_version < (3, 3): hashBytes = serverKeyExchange.hash(self.clientHello.random, @@ -227,15 +227,17 @@ def makeCertificateVerify(version, handshakeHashes, validSigAlgs, clientRandom, serverRandom): """Create a Certificate Verify message - @param version: protocol version in use - @param handshakeHashes: the running hash of all handshake messages - @param validSigAlgs: acceptable signature algorithms for client side, - applicable only to TLSv1.2 (or later) - @param certificateRequest: the server provided Certificate Request - message - @param premasterSecret: the premaster secret, needed only for SSLv3 - @param clientRandom: client provided random value, needed only for SSLv3 - @param serverRandom: server provided random value, needed only for SSLv3 + :param version: protocol version in use + :param handshakeHashes: the running hash of all handshake messages + :param validSigAlgs: acceptable signature algorithms for client side, + applicable only to TLSv1.2 (or later) + :param certificateRequest: the server provided Certificate Request + message + :param premasterSecret: the premaster secret, needed only for SSLv3 + :param clientRandom: client provided random value, needed only for + SSLv3 + :param serverRandom: server provided random value, needed only for + SSLv3 """ signatureAlgorithm = None # in TLS 1.2 we must decide which algorithm to use for signing @@ -451,11 +453,11 @@ def __init__(self, cipherSuite, clientHello, serverHello, privateKey, """ Create helper object for Diffie-Hellamn key exchange. - @param dhParams: Diffie-Hellman parameters that will be used by + :param dhParams: Diffie-Hellman parameters that will be used by server. First element of the tuple is the generator, the second is the prime. If not specified it will use a secure set (currently a 2048-bit safe prime). - @type dhParams: 2-element tuple of int + :type dhParams: 2-element tuple of int """ super(DHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, serverHello, dhParams, @@ -476,7 +478,7 @@ def _non_zero_check(value): """ Verify using constant time operation that the bytearray is not zero - @raises TLSIllegalParameterException: if the value is all zero + :raises TLSIllegalParameterException: if the value is all zero """ summa = 0 for i in value: diff --git a/tlslite/mathtls.py b/tlslite/mathtls.py index ed8b6f25..6b88d982 100644 --- a/tlslite/mathtls.py +++ b/tlslite/mathtls.py @@ -426,8 +426,8 @@ def paramStrength(param): field (DSA, DH) or integer factorisation cryptography (RSA) when provided with the prime defining the field or the modulus of the public key. - @param param: prime or modulus - @type param: int + :param param: prime or modulus + :type param: int """ size = numBits(param) if size < 512: @@ -558,12 +558,12 @@ def calcFinished(version, masterSecret, cipherSuite, handshakeHashes, isClient): """Calculate the Handshake protocol Finished value - @param version: TLS protocol version tuple - @param masterSecret: negotiated master secret of the connection - @param cipherSuite: negotiated cipher suite of the connection, - @param handshakeHashes: running hash of the handshake messages - @param isClient: whether the calculation should be performed for message - sent by client (True) or by server (False) side of connection + :param version: TLS protocol version tuple + :param masterSecret: negotiated master secret of the connection + :param cipherSuite: negotiated cipher suite of the connection, + :param handshakeHashes: running hash of the handshake messages + :param isClient: whether the calculation should be performed for message + sent by client (True) or by server (False) side of connection """ assert version in ((3, 0), (3, 1), (3, 2), (3, 3)) if version == (3,0): diff --git a/tlslite/messages.py b/tlslite/messages.py index caf71a84..6249e609 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -87,11 +87,11 @@ class RecordHeader2(RecordHeader): """ SSLv2 record header. - @type padding: int - @ivar padding: number of bytes added at end of message to make it multiple - of block cipher size - @type securityEscape: boolean - @ivar securityEscape: whether the record contains a security escape message + :vartype padding: int + :ivar padding: number of bytes added at end of message to make it multiple + of block cipher size + :vartype securityEscape: boolean + :ivar securityEscape: whether the record contains a security escape message """ def __init__(self): @@ -155,10 +155,10 @@ def __init__(self, contentType, data): """ Initialize object with specified contentType and data. - @type contentType: int - @param contentType: TLS record layer content type of associated data - @type data: bytearray - @param data: data + :type contentType: int + :param contentType: TLS record layer content type of associated data + :type data: bytearray + :param data: data """ self.contentType = contentType self.data = data @@ -231,7 +231,10 @@ def postWrite(self, w): class HelloMessage(HandshakeMsg): - """Class for sharing code between L{ClientHello} and L{ServerHello}""" + """ + Class for sharing code between :py:class:`ClientHello` and + :py:class:`ServerHello`. + """ def __init__(self, *args, **kwargs): """Initialize object.""" @@ -242,8 +245,8 @@ def getExtension(self, extType): """ Return extension of given type if present, None otherwise. - @rtype: L{tlslite.extensions.TLSExtension} - @raise TLSInternalError: when there are multiple extensions of the + :rtype: ~tlslite.extensions.TLSExtension + :raises TLSInternalError: when there are multiple extensions of the same type """ if self.extensions is None: @@ -262,8 +265,8 @@ def addExtension(self, ext): """ Add extension to internal list of extensions. - @type ext: TLSExtension - @param ext: extension object to add to list + :type ext: TLSExtension + :param ext: extension object to add to list """ if self.extensions is None: self.extensions = [] @@ -288,10 +291,10 @@ def _addOrRemoveExt(self, extType, add): """ Remove or add an empty extension of given type. - @type extType: int - @param extType: numeric id of extension to add or remove - @type add: boolean - @param add: whether to add (True) or remove (False) the extension + :type extType: int + :param extType: numeric id of extension to add or remove + :type add: boolean + :param add: whether to add (True) or remove (False) the extension """ if add: self._addExt(extType) @@ -303,25 +306,25 @@ class ClientHello(HelloMessage): """ Class for handling the ClientHello SSLv2/SSLv3/TLS message. - @type certificate_types: list - @ivar certificate_types: list of supported certificate types (deprecated) - - @type srp_username: bytearray - @ivar srp_username: name of the user in SRP extension (deprecated) + :vartype certificate_types: list + :ivar certificate_types: list of supported certificate types + (deprecated) + :vartype srp_username: bytearray + :ivar srp_username: name of the user in SRP extension (deprecated) - @type supports_npn: boolean - @ivar supports_npn: NPN extension presence (deprecated) + :vartype supports_npn: boolean + :ivar supports_npn: NPN extension presence (deprecated) - @type tack: boolean - @ivar tack: TACK extension presence (deprecated) + :vartype tack: boolean + :ivar tack: TACK extension presence (deprecated) - @type server_name: bytearray - @ivar server_name: first host_name (type 0) present in SNI extension + :vartype server_name: bytearray + :ivar server_name: first host_name (type 0) present in SNI extension (deprecated) - @type extensions: list of L{TLSExtension} - @ivar extensions: list of TLS extensions parsed from wire or to send, see - L{TLSExtension} and child classes for exact examples + :vartype extensions: list of :py:class:`TLSExtension` + :ivar extensions: list of TLS extensions parsed from wire or to send, see + :py:class:`TLSExtension` and child classes for exact examples """ def __init__(self, ssl2=False): @@ -337,7 +340,7 @@ def __str__(self): """ Return human readable representation of Client Hello. - @rtype: str + :rtype: str """ if self.session_id.count(bytearray(b'\x00')) == len(self.session_id)\ and len(self.session_id) != 0: @@ -359,7 +362,7 @@ def __repr__(self): """ Return machine readable representation of Client Hello. - @rtype: str + :rtype: str """ return "ClientHello(ssl2={0}, client_version=({1[0]}.{1[1]}), "\ "random={2!r}, session_id={3!r}, cipher_suites={4!r}, "\ @@ -373,7 +376,8 @@ def certificate_types(self): """ Return the list of certificate types supported. - @deprecated: use extensions field to get the extension for inspection + .. deprecated:: 0.5 + use extensions field to get the extension for inspection """ cert_type = self.getExtension(ExtensionType.cert_type) if cert_type is None: @@ -388,12 +392,12 @@ def certificate_types(self, val): """ Set list of supported certificate types. - Sets the list of supported types to list given in L{val} if the + Sets the list of supported types to list given in :py:obj:`val` if the cert_type extension is present. Creates the extension and places it last in the list otherwise. - @type val: list - @param val: list of supported certificate types by client encoded as + :type val: list + :param val: list of supported certificate types by client encoded as single byte integers """ cert_type = self.getExtension(ExtensionType.cert_type) @@ -409,7 +413,8 @@ def srp_username(self): """ Return username for the SRP. - @deprecated: use extensions field to get the extension for inspection + .. deprecated:: 0.5 + use extensions field to get the extension for inspection """ srp_ext = self.getExtension(ExtensionType.srp) @@ -423,8 +428,8 @@ def srp_username(self, name): """ Set the username for SRP. - @type name: bytearray - @param name: UTF-8 encoded username + :type name: bytearray + :param name: UTF-8 encoded username """ srp_ext = self.getExtension(ExtensionType.srp) @@ -439,8 +444,10 @@ def tack(self): """ Return whether the client supports TACK. - @rtype: boolean - @deprecated: use extensions field to get the extension for inspection + .. deprecated:: 0.5 + use extensions field to get the extension for inspection + + :rtype: boolean """ return self.getExtension(ExtensionType.tack) is not None @@ -449,8 +456,8 @@ def tack(self, present): """ Create or delete the TACK extension. - @type present: boolean - @param present: True will create extension while False will remove + :type present: boolean + :param present: True will create extension while False will remove extension from client hello """ self._addOrRemoveExt(ExtensionType.tack, present) @@ -460,8 +467,10 @@ def supports_npn(self): """ Return whether client supports NPN extension. - @rtype: boolean - @deprecated: use extensions field to get the extension for inspection + .. deprecated:: 0.5 + use extensions field to get the extension for inspection + + :rtype: boolean """ return self.getExtension(ExtensionType.supports_npn) is not None @@ -470,8 +479,8 @@ def supports_npn(self, present): """ Create or delete the NPN extension. - @type present: boolean - @param present: selects whatever to create or remove the extension + :type present: boolean + :param present: selects whatever to create or remove the extension from list of supported ones """ self._addOrRemoveExt(ExtensionType.supports_npn, present) @@ -481,8 +490,10 @@ def server_name(self): """ Return first host_name present in SNI extension. - @rtype: bytearray - @deprecated: use extensions field to get the extension for inspection + .. deprecated:: 0.5 + use extensions field to get the extension for inspection + + :rtype: bytearray """ sni_ext = self.getExtension(ExtensionType.server_name) if sni_ext is None: @@ -498,8 +509,8 @@ def server_name(self, hostname): """ Set the first host_name present in SNI extension. - @type hostname: bytearray - @param hostname: name of the host_name to set + :type hostname: bytearray + :param hostname: name of the host_name to set """ sni_ext = self.getExtension(ExtensionType.server_name) if sni_ext is None: @@ -517,43 +528,44 @@ def create(self, version, random, session_id, cipher_suites, """ Create a ClientHello message for sending. - @type version: tuple - @param version: the highest supported TLS version encoded as two int + :type version: tuple + :param version: the highest supported TLS version encoded as two int tuple - @type random: bytearray - @param random: client provided random value, in old versions of TLS + :type random: bytearray + :param random: client provided random value, in old versions of TLS (before 1.2) the first 32 bits should include system time, also used as the "challenge" field in SSLv2 - @type session_id: bytearray - @param session_id: ID of session, set when doing session resumption + :type session_id: bytearray + :param session_id: ID of session, set when doing session resumption - @type cipher_suites: list - @param cipher_suites: list of ciphersuites advertised as supported + :type cipher_suites: list + :param cipher_suites: list of ciphersuites advertised as supported - @type certificate_types: list - @param certificate_types: list of supported certificate types, uses + :type certificate_types: list + :param certificate_types: list of supported certificate types, uses TLS extension for signalling, as such requires TLS1.0 to work - @type srpUsername: bytearray - @param srpUsername: utf-8 encoded username for SRP, TLS extension + :type srpUsername: bytearray + :param srpUsername: utf-8 encoded username for SRP, TLS extension - @type tack: boolean - @param tack: whatever to advertise support for TACK, TLS extension + :type tack: boolean + :param tack: whatever to advertise support for TACK, TLS extension - @type supports_npn: boolean - @param supports_npn: whatever to advertise support for NPN, TLS + :type supports_npn: boolean + :param supports_npn: whatever to advertise support for NPN, TLS extension - @type serverName: bytearray - @param serverName: the hostname to request in server name indication + :type serverName: bytearray + :param serverName: the hostname to request in server name indication extension, TLS extension. Note that SNI allows to set multiple - hostnames and values that are not hostnames, use L{SNIExtension} - together with L{extensions} to use it. + hostnames and values that are not hostnames, use + :py:class:`~.extensions.SNIExtension` + together with :py:obj:`extensions` to use it. - @type extensions: list of L{TLSExtension} - @param extensions: list of extensions to advertise + :type extensions: list of :py:class:`~.extensions.TLSExtension` + :param extensions: list of extensions to advertise """ self.client_version = version self.random = random @@ -662,33 +674,34 @@ class ServerHello(HelloMessage): """ Handling of Server Hello messages. - @type server_version: tuple - @ivar server_version: protocol version encoded as two int tuple + :vartype server_version: tuple + :ivar server_version: protocol version encoded as two int tuple - @type random: bytearray - @ivar random: server random value + :vartype random: bytearray + :ivar random: server random value - @type session_id: bytearray - @ivar session_id: session identifier for resumption + :vartype session_id: bytearray + :ivar session_id: session identifier for resumption - @type cipher_suite: int - @ivar cipher_suite: server selected cipher_suite + :vartype cipher_suite: int + :ivar cipher_suite: server selected cipher_suite - @type compression_method: int - @ivar compression_method: server selected compression method + :vartype compression_method: int + :ivar compression_method: server selected compression method - @type next_protos: list of bytearray - @ivar next_protos: list of advertised protocols in NPN extension + :vartype next_protos: list of bytearray + :ivar next_protos: list of advertised protocols in NPN extension - @type next_protos_advertised: list of bytearray - @ivar next_protos_advertised: list of protocols advertised in NPN extension + :vartype next_protos_advertised: list of bytearray + :ivar next_protos_advertised: list of protocols advertised in NPN extension - @type certificate_type: int - @ivar certificate_type: certificate type selected by server + :vartype certificate_type: int + :ivar certificate_type: certificate type selected by server - @type extensions: list - @ivar extensions: list of TLS extensions present in server_hello message, - see L{TLSExtension} and child classes for exact examples + :vartype extensions: list + :ivar extensions: list of TLS extensions present in server_hello message, + see :py:class:`~.extensions.TLSExtension` and child classes for exact + examples """ def __init__(self): @@ -749,7 +762,7 @@ def certificate_type(self): """ Return the certificate type selected by server. - @rtype: int + :rtype: int """ cert_type = self.getExtension(ExtensionType.cert_type) if cert_type is None: @@ -763,8 +776,8 @@ def certificate_type(self, val): """ Set the certificate type supported. - @type val: int - @param val: type of certificate + :type val: int + :param val: type of certificate """ # XXX backwards compatibility, 0 means x.509 and should not be sent if val == 0 or val is None: @@ -782,7 +795,7 @@ def next_protos(self): """ Return the advertised protocols in NPN extension. - @rtype: list of bytearrays + :rtype: list of bytearrays """ npn_ext = self.getExtension(ExtensionType.supports_npn) @@ -796,8 +809,8 @@ def next_protos(self, val): """ Set the advertised protocols in NPN extension. - @type val: list - @param val: list of protocols to advertise as UTF-8 encoded names + :type val: list + :param val: list of protocols to advertise as UTF-8 encoded names """ if val is None: return @@ -818,7 +831,7 @@ def next_protos_advertised(self): """ Return the advertised protocols in NPN extension. - @rtype: list of bytearrays + :rtype: list of bytearrays """ return self.next_protos @@ -827,8 +840,8 @@ def next_protos_advertised(self, val): """ Set the advertised protocols in NPN extension. - @type val: list - @param val: list of protocols to advertise as UTF-8 encoded names + :type val: list + :param val: list of protocols to advertise as UTF-8 encoded names """ self.next_protos = val @@ -895,24 +908,24 @@ class ServerHello2(HandshakeMsg): """ SERVER-HELLO message from SSLv2. - @type session_id_hit: int - @ivar session_id_hit: non zero if the client provided session ID was + :vartype session_id_hit: int + :ivar session_id_hit: non zero if the client provided session ID was matched in server's session cache - @type certificate_type: int - @ivar certificate_type: type of certificate sent + :vartype certificate_type: int + :ivar certificate_type: type of certificate sent - @type server_version: tuple of ints - @ivar server_version: protocol version selected by server + :vartype server_version: tuple of ints + :ivar server_version: protocol version selected by server - @type certificate: bytearray - @ivar certificate: certificate sent by server + :vartype certificate: bytearray + :ivar certificate: certificate sent by server - @type ciphers: array of int - @ivar ciphers: list of ciphers supported by server + :vartype ciphers: array of int + :ivar ciphers: list of ciphers supported by server - @type session_id: bytearray - @ivar session_id: idendifier of negotiated session + :vartype session_id: bytearray + :ivar session_id: idendifier of negotiated session """ def __init__(self): @@ -1078,54 +1091,54 @@ class ServerKeyExchange(HandshakeMsg): """ Handling TLS Handshake protocol Server Key Exchange messages. - @type cipherSuite: int - @cvar cipherSuite: id of ciphersuite selected in Server Hello message - @type srp_N: int - @cvar srp_N: SRP protocol prime - @type srp_N_len: int - @cvar srp_N_len: length of srp_N in bytes - @type srp_g: int - @cvar srp_g: SRP protocol generator - @type srp_g_len: int - @cvar srp_g_len: length of srp_g in bytes - @type srp_s: bytearray - @cvar srp_s: SRP protocol salt value - @type srp_B: int - @cvar srp_B: SRP protocol server public value - @type srp_B_len: int - @cvar srp_B_len: length of srp_B in bytes - @type dh_p: int - @cvar dh_p: FFDHE protocol prime - @type dh_p_len: int - @cvar dh_p_len: length of dh_p in bytes - @type dh_g: int - @cvar dh_g: FFDHE protocol generator - @type dh_g_len: int - @type dh_g_len: length of dh_g in bytes - @type dh_Ys: int - @cvar dh_Ys: FFDH protocol server key share - @type dh_Ys_len: int - @cvar dh_Ys_len: length of dh_Ys in bytes - @type curve_type: int - @cvar curve_type: Type of curve used (explicit, named, etc.) - @type named_curve: int - @cvar named_curve: TLS ID of named curve - @type ecdh_Ys: bytearray - @cvar ecdh_Ys: ECDH protocol encoded point key share - @type signature: bytearray - @cvar signature: signature performed over the parameters by server - @type hashAlg: int - @cvar hashAlg: id of hash algorithm used for signature - @type signAlg: int - @cvar signAlg: id of signature algorithm used for signature + :vartype cipherSuite: int + :cvar cipherSuite: id of ciphersuite selected in Server Hello message + :vartype srp_N: int + :cvar srp_N: SRP protocol prime + :vartype srp_N_len: int + :cvar srp_N_len: length of srp_N in bytes + :vartype srp_g: int + :cvar srp_g: SRP protocol generator + :vartype srp_g_len: int + :cvar srp_g_len: length of srp_g in bytes + :vartype srp_s: bytearray + :cvar srp_s: SRP protocol salt value + :vartype srp_B: int + :cvar srp_B: SRP protocol server public value + :vartype srp_B_len: int + :cvar srp_B_len: length of srp_B in bytes + :vartype dh_p: int + :cvar dh_p: FFDHE protocol prime + :vartype dh_p_len: int + :cvar dh_p_len: length of dh_p in bytes + :vartype dh_g: int + :cvar dh_g: FFDHE protocol generator + :vartype dh_g_len: int + :cvar dh_g_len: length of dh_g in bytes + :vartype dh_Ys: int + :cvar dh_Ys: FFDH protocol server key share + :vartype dh_Ys_len: int + :cvar dh_Ys_len: length of dh_Ys in bytes + :vartype curve_type: int + :cvar curve_type: Type of curve used (explicit, named, etc.) + :vartype named_curve: int + :cvar named_curve: TLS ID of named curve + :vartype ecdh_Ys: bytearray + :cvar ecdh_Ys: ECDH protocol encoded point key share + :vartype signature: bytearray + :cvar signature: signature performed over the parameters by server + :vartype hashAlg: int + :cvar hashAlg: id of hash algorithm used for signature + :vartype signAlg: int + :cvar signAlg: id of signature algorithm used for signature """ def __init__(self, cipherSuite, version): """ Initialise Server Key Exchange for reading or writing. - @type cipherSuite: int - @param cipherSuite: id of ciphersuite selected by server + :type cipherSuite: int + :param cipherSuite: id of ciphersuite selected by server """ HandshakeMsg.__init__(self, HandshakeType.server_key_exchange) self.cipherSuite = cipherSuite @@ -1202,10 +1215,10 @@ def createECDH(self, curve_type, named_curve=None, point=None): def parse(self, parser): """ - Deserialise message from L{Parser}. + Deserialise message from :py:class:`Parser`. - @type parser: L{Parser} - @param parser: parser to read data from + :type parser: Parser + :param parser: parser to read data from """ parser.startLengthCheck(3) if self.cipherSuite in CipherSuite.srpAllSuites: @@ -1245,7 +1258,7 @@ def writeParams(self): """ Serialise the key exchange parameters. - @rtype: bytearray + :rtype: bytearray """ writer = Writer() if self.cipherSuite in CipherSuite.srpAllSuites: @@ -1276,7 +1289,7 @@ def write(self): """ Serialise complete message. - @rtype: bytearray + :rtype: bytearray """ writer = Writer() writer.bytes += self.writeParams() @@ -1292,7 +1305,7 @@ def hash(self, clientRandom, serverRandom): """ Calculate hash of parameters to sign. - @rtype: bytearray + :rtype: bytearray """ bytesToHash = clientRandom + serverRandom + self.writeParams() if self.version >= (3, 3): @@ -1333,29 +1346,29 @@ class ClientKeyExchange(HandshakeMsg): """ Handling of TLS Handshake protocol ClientKeyExchange message. - @type cipherSuite: int - @ivar cipherSuite: the cipher suite id used for the connection - @type version: tuple(int, int) - @ivar version: TLS protocol version used for the connection - @type srp_A: int - @ivar srp_A: SRP protocol client answer value - @type dh_Yc: int - @ivar dh_Yc: client Finite Field Diffie-Hellman protocol key share - @type ecdh_Yc: bytearray - @ivar ecdh_Yc: encoded curve coordinates - @type encryptedPreMasterSecret: bytearray - @ivar encryptedPreMasterSecret: client selected PremMaster secret encrypted - with server public key (from certificate) + :vartype cipherSuite: int + :ivar cipherSuite: the cipher suite id used for the connection + :vartype version: tuple(int, int) + :ivar version: TLS protocol version used for the connection + :vartype srp_A: int + :ivar srp_A: SRP protocol client answer value + :vartype dh_Yc: int + :ivar dh_Yc: client Finite Field Diffie-Hellman protocol key share + :vartype ecdh_Yc: bytearray + :ivar ecdh_Yc: encoded curve coordinates + :vartype encryptedPreMasterSecret: bytearray + :ivar encryptedPreMasterSecret: client selected PremMaster secret encrypted + with server public key (from certificate) """ def __init__(self, cipherSuite, version=None): """ Initialise ClientKeyExchange for reading or writing. - @type cipherSuite: int - @param cipherSuite: id of the ciphersuite selected by server - @type version: tuple(int, int) - @param version: protocol version selected by server + :type cipherSuite: int + :param cipherSuite: id of the ciphersuite selected by server + :type version: tuple(int, int) + :param version: protocol version selected by server """ HandshakeMsg.__init__(self, HandshakeType.client_key_exchange) self.cipherSuite = cipherSuite @@ -1371,9 +1384,9 @@ def createSRP(self, srp_A): returns self - @type srp_A: int - @param srp_A: client SRP answer - @rtype: L{ClientKeyExchange} + :type srp_A: int + :param srp_A: client SRP answer + :rtype: ClientKeyExchange """ self.srp_A = srp_A return self @@ -1384,8 +1397,8 @@ def createRSA(self, encryptedPreMasterSecret): returns self - @type encryptedPreMasterSecret: bytearray - @rtype: L{ClientKeyExchange} + :type encryptedPreMasterSecret: bytearray + :rtype: ClientKeyExchange """ self.encryptedPreMasterSecret = encryptedPreMasterSecret return self @@ -1396,8 +1409,8 @@ def createDH(self, dh_Yc): returns self - @type dh_Yc: int - @rtype: L{ClientKeyExchange} + :type dh_Yc: int + :rtype: ClientKeyExchange """ self.dh_Yc = dh_Yc return self @@ -1408,20 +1421,20 @@ def createECDH(self, ecdh_Yc): returns self - @type ecdh_Yc: bytearray - @rtype: L{ClientKeyExchange} + :type ecdh_Yc: bytearray + :rtype: ClientKeyExchange """ self.ecdh_Yc = ecdh_Yc return self def parse(self, parser): """ - Deserialise the message from L{Parser}, + Deserialise the message from :py:class:`Parser`, returns self - @type parser: L{Parser} - @rtype: L{ClientKeyExchange} + :type parser: Parser + :rtype: ClientKeyExchange """ parser.startLengthCheck(3) if self.cipherSuite in CipherSuite.srpAllSuites: @@ -1447,7 +1460,7 @@ def write(self): """ Serialise the object. - @rtype: bytearray + :rtype: bytearray """ w = Writer() if self.cipherSuite in CipherSuite.srpAllSuites: @@ -1472,18 +1485,18 @@ class ClientMasterKey(HandshakeMsg): """ Handling of SSLv2 CLIENT-MASTER-KEY message. - @type cipher: int - @ivar cipher: negotiated cipher + :vartype cipher: int + :ivar cipher: negotiated cipher - @type clear_key: bytearray - @ivar clear_key: the part of master secret key that is sent in clear for - export cipher suites + :vartype clear_key: bytearray + :ivar clear_key: the part of master secret key that is sent in clear for + export cipher suites - @type encrypted_key: bytearray - @ivar encrypted_key: (part of) master secret encrypted using server key + :vartype encrypted_key: bytearray + :ivar encrypted_key: (part of) master secret encrypted using server key - @type key_argument: bytearray - @ivar key_argument: additional key argument for block ciphers + :vartype key_argument: bytearray + :ivar key_argument: additional key argument for block ciphers """ def __init__(self): @@ -1538,7 +1551,7 @@ def __init__(self, version): """ Create message. - @param version: TLS protocol version in use + :param version: TLS protocol version in use """ HandshakeMsg.__init__(self, HandshakeType.certificate_verify) self.version = version @@ -1549,9 +1562,9 @@ def create(self, signature, signatureAlgorithm=None): """ Provide data for serialisation of message. - @param signature: signature carried in the message - @param signatureAlgorithm: signature algorithm used to make the - signature (TLSv1.2 only) + :param signature: signature carried in the message + :param signatureAlgorithm: signature algorithm used to make the + signature (TLSv1.2 only) """ self.signatureAlgorithm = signatureAlgorithm self.signature = signature @@ -1561,7 +1574,7 @@ def parse(self, parser): """ Deserialize message from parser. - @param parser: parser with data to read + :param parser: parser with data to read """ parser.startLengthCheck(3) if self.version >= (3, 3): @@ -1574,7 +1587,7 @@ def write(self): """ Serialize the data to bytearray. - @rtype: bytearray + :rtype: bytearray """ writer = Writer() if self.version >= (3, 3): @@ -1686,8 +1699,8 @@ class ClientFinished(SSL2Finished): """ Handling of SSLv2 CLIENT-FINISHED message. - @type verify_data: bytearray - @ivar verify_data: payload of the message, should be the CONNECTION-ID + :vartype verify_data: bytearray + :ivar verify_data: payload of the message, should be the CONNECTION-ID """ def __init__(self): @@ -1698,8 +1711,8 @@ class ServerFinished(SSL2Finished): """ Handling of SSLv2 SERVER-FINISHED message. - @type verify_data: bytearray - @ivar verify_data: payload of the message, should be SESSION-ID + :vartype verify_data: bytearray + :ivar verify_data: payload of the message, should be SESSION-ID """ def __init__(self): @@ -1712,11 +1725,11 @@ class CertificateStatus(HandshakeMsg): Handling of the handshake protocol message that includes the OCSP staple. - @type status_type: int - @ivar status_type: type of response returned + :vartype status_type: int + :ivar status_type: type of response returned - @type ocsp: bytearray - @ivar ocsp: OCSPResponse from RFC 2560 + :vartype ocsp: bytearray + :ivar ocsp: OCSPResponse from RFC 2560 """ def __init__(self): diff --git a/tlslite/messagesocket.py b/tlslite/messagesocket.py index 67c55e6a..84ae5171 100644 --- a/tlslite/messagesocket.py +++ b/tlslite/messagesocket.py @@ -27,27 +27,27 @@ class MessageSocket(RecordLayer): This class tries to provide a useful abstraction for handling Handshake protocol messages. - @type recordSize: int - @ivar recordSize: maximum size of records sent through socket. Messages - bigger than this size will be fragmented to smaller chunks. Setting it - to higher value than the default 2^14 will make the implementation - non RFC compliant and likely not interoperable with other peers. - - @type defragmenter: L{Defragmenter} - @ivar defragmenter: defragmenter used for read records - - @type unfragmentedDataTypes: tuple - @ivar unfragmentedDataTypes: data types which will be passed as-read, - TLS application_data by default + :vartype recordSize: int + :ivar recordSize: maximum size of records sent through socket. Messages + bigger than this size will be fragmented to smaller chunks. Setting it + to higher value than the default 2^14 will make the implementation + non RFC compliant and likely not interoperable with other peers. + + :vartype defragmenter: Defragmenter + :ivar defragmenter: defragmenter used for read records + + :vartype unfragmentedDataTypes: tuple + :ivar unfragmentedDataTypes: data types which will be passed as-read, + TLS application_data by default """ def __init__(self, sock, defragmenter): """Apply TLS Record Layer abstraction to raw network socket. - @type sock: L{socket.socket} - @param sock: network socket to wrap - @type defragmenter: L{Defragmenter} - @param defragmenter: defragmenter to apply on the records read + :type sock: socket.socket + :param sock: network socket to wrap + :type defragmenter: Defragmenter + :param defragmenter: defragmenter to apply on the records read """ super(MessageSocket, self).__init__(sock) @@ -65,9 +65,10 @@ def recvMessage(self): Read next message in queue will return a 0 or 1 if the read is blocking, a tuple of - L{RecordHeader3} and L{Parser} in case a message was received. + :py:class:`RecordHeader3` and :py:class:`Parser` in case a message was + received. - @rtype: generator + :rtype: generator """ while True: while True: @@ -96,7 +97,7 @@ def recvMessage(self): self._lastRecordVersion = header.version def recvMessageBlocking(self): - """Blocking variant of L{recvMessage}""" + """Blocking variant of :py:meth:`recvMessage`.""" for res in self.recvMessage(): if res in (0, 1): pass @@ -110,7 +111,7 @@ def flush(self): Will fragment the messages and write them in as little records as possible. - @rtype: generator + :rtype: generator """ while len(self._sendBuffer) > 0: recordPayload = self._sendBuffer[:self.recordSize] @@ -123,7 +124,7 @@ def flush(self): self._sendBufferType = None def flushBlocking(self): - """Blocking variant of L{flush}""" + """Blocking variant of :py:meth:`flush`.""" for _ in self.flush(): pass @@ -137,7 +138,7 @@ def queueMessage(self, msg): If the message is of different type as messages in queue, the queue is flushed and then the message is queued. - @rtype: generator + :rtype: generator """ if self._sendBufferType is None: self._sendBufferType = msg.contentType @@ -154,7 +155,7 @@ def queueMessage(self, msg): self._sendBuffer += msg.write() def queueMessageBlocking(self, msg): - """Blocking variant of L{queueMessage}""" + """Blocking variant of :py:meth:`queueMessage`.""" for _ in self.queueMessage(msg): pass @@ -171,7 +172,7 @@ def sendMessage(self, msg): Use the sendRecord() message if you want to send a message outside the queue, or a message of zero size. - @rtype: generator + :rtype: generator """ for res in self.queueMessage(msg): yield res @@ -180,6 +181,6 @@ def sendMessage(self, msg): yield res def sendMessageBlocking(self, msg): - """Blocking variant of L{sendMessage}""" + """Blocking variant of :py:meth:`sendMessage`.""" for _ in self.sendMessage(msg): pass diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 25a4812f..6446a49f 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -28,7 +28,7 @@ def __init__(self, sock): """ Assign socket to wrapper - @type sock: socket.socket + :type sock: socket.socket """ self.sock = sock self.version = (0, 0) @@ -37,9 +37,9 @@ def _sockSendAll(self, data): """ Send all data through socket - @type data: bytearray - @param data: data to send - @raise socket.error: when write to socket failed + :type data: bytearray + :param data: data to send + :raises socket.error: when write to socket failed """ while 1: try: @@ -59,11 +59,11 @@ def send(self, msg, padding=0): """ Send the message through socket. - @type msg: bytearray - @param msg: TLS message to send - @type padding: int - @param padding: amount of padding to specify for SSLv2 - @raise socket.error: when write to socket failed + :type msg: bytearray + :param msg: TLS message to send + :type padding: int + :param padding: amount of padding to specify for SSLv2 + :raises socket.error: when write to socket failed """ data = msg.write() @@ -84,10 +84,10 @@ def _sockRecvAll(self, length): """ Read exactly the amount of bytes specified in L{length} from raw socket. - @rtype: generator - @return: generator that will return 0 or 1 in case the socket is non - blocking and would block and bytearray in case the read finished - @raise TLSAbruptCloseError: when the socket closed + :rtype: generator + :returns: generator that will return 0 or 1 in case the socket is non + blocking and would block and bytearray in case the read finished + :raises TLSAbruptCloseError: when the socket closed """ buf = bytearray(0) @@ -172,17 +172,17 @@ def recv(self): """ Read a single record from socket, handle SSLv2 and SSLv3 record layer - @rtype: generator - @return: generator that returns 0 or 1 in case the read would be + :rtype: generator + :returns: generator that returns 0 or 1 in case the read would be blocking or a tuple containing record header (object) and record data (bytearray) read from socket - @raise socket.error: In case of network error - @raise TLSAbruptCloseError: When the socket was closed on the other - side in middle of record receiving - @raise TLSRecordOverflow: When the received record was longer than - allowed by TLS - @raise TLSIllegalParameterException: When the record header was - malformed + :raises socket.error: In case of network error + :raises TLSAbruptCloseError: When the socket was closed on the other + side in middle of record receiving + :raises TLSRecordOverflow: When the received record was longer than + allowed by TLS + :raises TLSIllegalParameterException: When the record header was + malformed """ record = None for record in self._recvHeader(): @@ -234,12 +234,12 @@ class RecordLayer(object): """ Implementation of TLS record layer protocol - @ivar version: the TLS version to use (tuple encoded as on the wire) - @ivar sock: underlying socket - @ivar client: whether the connection should use encryption - @ivar encryptThenMAC: use the encrypt-then-MAC mechanism for record - integrity - @ivar handshake_finished: used in SSL2, True if handshake protocol is over + :ivar version: the TLS version to use (tuple encoded as on the wire) + :ivar sock: underlying socket + :ivar client: whether the connection should use encryption + :ivar encryptThenMAC: use the encrypt-then-MAC mechanism for record + integrity + :ivar handshake_finished: used in SSL2, True if handshake protocol is over """ def __init__(self, sock): @@ -279,8 +279,8 @@ def getCipherName(self): """ Return the name of the bulk cipher used by this connection - @rtype: str - @return: The name of the cipher, like 'aes128', 'rc4', etc. + :rtype: str + :returns: The name of the cipher, like 'aes128', 'rc4', etc. """ if self._writeState.encContext is None: return None @@ -292,8 +292,8 @@ def getCipherImplementation(self): 'python' for tlslite internal implementation, 'openssl' for M2crypto and 'pycrypto' for pycrypto - @rtype: str - @return: Name of cipher implementation used, None if not initialised + :rtype: str + :returns: Name of cipher implementation used, None if not initialised """ if self._writeState.encContext is None: return None @@ -454,8 +454,8 @@ def sendRecord(self, msg): Note that if the message was not fragmented to below 2**14 bytes it will be rejected by the other connection side. - @param msg: TLS message to send - @type msg: ApplicationData, HandshakeMessage, etc. + :param msg: TLS message to send + :type msg: ApplicationData, HandshakeMessage, etc. """ data = msg.write() contentType = msg.contentType @@ -561,8 +561,8 @@ def _macThenDecrypt(self, recordType, buf): """ Check MAC of data, then decrypt and remove padding - @raise TLSBadRecordMAC: when the mac value is invalid - @raise TLSDecryptionFailed: when the data to decrypt has invalid size + :raises TLSBadRecordMAC: when the mac value is invalid + :raises TLSDecryptionFailed: when the data to decrypt has invalid size """ if self._readState.macContext: macLength = self._readState.macContext.digest_size @@ -685,11 +685,11 @@ def recvRecord(self): """ Read, decrypt and check integrity of a single record - @rtype: tuple - @return: message header and decrypted message payload - @raise TLSDecryptionFailed: when decryption of data failed - @raise TLSBadRecordMAC: when record has bad MAC or padding - @raise socket.error: when reading from socket was unsuccessful + :rtype: tuple + :returns: message header and decrypted message payload + :raises TLSDecryptionFailed: when decryption of data failed + :raises TLSBadRecordMAC: when record has bad MAC or padding + :raises socket.error: when reading from socket was unsuccessful """ result = None for result in self._recordSocket.recv(): @@ -731,8 +731,10 @@ def changeWriteState(self): """ Change the cipher state to the pending one for write operations. - This should be done only once after a call to L{calcPendingStates} was - performed and directly after sending a L{ChangeCipherSpec} message. + This should be done only once after a call to + :py:meth:`calcPendingStates` was + performed and directly after sending a :py:class:`ChangeCipherSpec` + message. """ if self.version in ((0, 2), (2, 0)): # in SSLv2 sequence numbers carry over from plaintext to encrypted @@ -745,8 +747,10 @@ def changeReadState(self): """ Change the cipher state to the pending one for read operations. - This should be done only once after a call to L{calcPendingStates} was - performed and directly after receiving a L{ChangeCipherSpec} message. + This should be done only once after a call to + :py:meth:`calcPendingStates` was + performed and directly after receiving a :py:class:`ChangeCipherSpec` + message. """ if self.version in ((0, 2), (2, 0)): # in SSLv2 sequence numbers carry over from plaintext to encrypted diff --git a/tlslite/session.py b/tlslite/session.py index 19ff985c..aaacabf9 100644 --- a/tlslite/session.py +++ b/tlslite/session.py @@ -25,31 +25,31 @@ class Session(object): they can create a new connection based on an old session without the overhead of a full handshake. - The session for a L{tlslite.TLSConnection.TLSConnection} can be + The session for a :py:class:`~tlslite.tlsconnection.TLSConnection` can be retrieved from the connection's 'session' attribute. - @type srpUsername: str - @ivar srpUsername: The client's SRP username (or None). + :vartype srpUsername: str + :ivar srpUsername: The client's SRP username (or None). - @type clientCertChain: L{tlslite.x509certchain.X509CertChain} - @ivar clientCertChain: The client's certificate chain (or None). + :vartype clientCertChain: ~tlslite.x509certchain.X509CertChain + :ivar clientCertChain: The client's certificate chain (or None). - @type serverCertChain: L{tlslite.x509certchain.X509CertChain} - @ivar serverCertChain: The server's certificate chain (or None). + :vartype serverCertChain: ~tlslite.x509certchain.X509CertChain + :ivar serverCertChain: The server's certificate chain (or None). - @type tackExt: L{tack.structures.TackExtension.TackExtension} - @ivar tackExt: The server's TackExtension (or None). + :vartype tackExt: tack.structures.TackExtension.TackExtension + :ivar tackExt: The server's TackExtension (or None). - @type tackInHelloExt: L{bool} - @ivar tackInHelloExt:True if a TACK was presented via TLS Extension. + :vartype tackInHelloExt: bool + :ivar tackInHelloExt: True if a TACK was presented via TLS Extension. - @type encryptThenMAC: bool - @ivar encryptThenMAC: True if connection uses CBC cipher in - encrypt-then-MAC mode + :vartype encryptThenMAC: bool + :ivar encryptThenMAC: True if connection uses CBC cipher in + encrypt-then-MAC mode - @type appProto: bytearray - @ivar appProto: name of the negotiated application level protocol, None - if not negotiated + :vartype appProto: bytearray + :ivar appProto: name of the negotiated application level protocol, None + if not negotiated """ def __init__(self): @@ -106,8 +106,8 @@ def _clone(self): def valid(self): """If this session can be used for session resumption. - @rtype: bool - @return: If this session can be used for session resumption. + :rtype: bool + :returns: If this session can be used for session resumption. """ return self.resumable and self.sessionID @@ -131,15 +131,15 @@ def getBreakSigs(self): def getCipherName(self): """Get the name of the cipher used with this connection. - @rtype: str - @return: The name of the cipher used with this connection. + :rtype: str + :returns: The name of the cipher used with this connection. """ return CipherSuite.canonicalCipherName(self.cipherSuite) def getMacName(self): """Get the name of the HMAC hash algo used with this connection. - @rtype: str - @return: The name of the HMAC hash algo used with this connection. + :rtype: str + :returns: The name of the HMAC hash algo used with this connection. """ return CipherSuite.canonicalMacName(self.cipherSuite) diff --git a/tlslite/sessioncache.py b/tlslite/sessioncache.py index d1f1b055..ac54e49e 100644 --- a/tlslite/sessioncache.py +++ b/tlslite/sessioncache.py @@ -29,14 +29,14 @@ class SessionCache(object): def __init__(self, maxEntries=10000, maxAge=14400): """Create a new SessionCache. - @type maxEntries: int - @param maxEntries: The maximum size of the cache. When this - limit is reached, the oldest sessions will be deleted as - necessary to make room for new ones. The default is 10000. - - @type maxAge: int - @param maxAge: The number of seconds before a session expires - from the cache. The default is 14400 (i.e. 4 hours).""" + :type maxEntries: int + :param maxEntries: The maximum size of the cache. When this + limit is reached, the oldest sessions will be deleted as + necessary to make room for new ones. The default is 10000. + + :type maxAge: int + :param maxAge: The number of seconds before a session expires + from the cache. The default is 14400 (i.e. 4 hours).""" self.lock = threading.Lock() diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 45447723..ba036129 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -53,17 +53,17 @@ class TLSConnection(TLSRecordLayer): not use the asynchronous functions directly, but should use some framework like asyncore or Twisted which TLS Lite integrates with (see - L{tlslite.integration.tlsasyncdispatchermixin.TLSAsyncDispatcherMixIn}). + :py:class:`~.integration.tlsasyncdispatchermixin.TLSAsyncDispatcherMixIn`). """ def __init__(self, sock): """Create a new TLSConnection instance. - @param sock: The socket data will be transmitted on. The - socket should already be connected. It may be in blocking or - non-blocking mode. + :param sock: The socket data will be transmitted on. The + socket should already be connected. It may be in blocking or + non-blocking mode. - @type sock: L{socket.socket} + :type sock: socket.socket """ TLSRecordLayer.__init__(self, sock) self.serverSigAlg = None @@ -76,11 +76,11 @@ def __init__(self, sock): def keyingMaterialExporter(self, label, length=20): """Return keying material as described in RFC 5705 - @type label: bytearray - @param label: label to be provided for the exporter + :type label: bytearray + :param label: label to be provided for the exporter - @type length: int - @param length: number of bytes of the keying material to export + :type length: int + :param length: number of bytes of the keying material to export """ if label in (b'server finished', b'client finished', b'master secret', b'key expansion'): @@ -114,7 +114,7 @@ def handshakeClientAnonymous(self, session=None, settings=None, This function performs an SSL or TLS handshake using an anonymous Diffie Hellman ciphersuite. - + Like any handshake function, this can be called on a closed TLS connection, or on a TLS connection that is already open. If called on an open connection it performs a re-handshake. @@ -125,42 +125,42 @@ def handshakeClientAnonymous(self, session=None, settings=None, If an exception is raised, the connection will have been automatically closed (if it was ever open). - @type session: L{tlslite.Session.Session} - @param session: A TLS session to attempt to resume. If the - resumption does not succeed, a full handshake will be - performed. - - @type settings: L{tlslite.HandshakeSettings.HandshakeSettings} - @param settings: Various settings which can be used to control - the ciphersuites, certificate types, and SSL/TLS versions - offered by the client. - - @type checker: L{tlslite.Checker.Checker} - @param checker: A Checker instance. This instance will be - invoked to examine the other party's authentication - credentials, if the handshake completes succesfully. - - @type serverName: string - @param serverName: The ServerNameIndication TLS Extension. - - @type async: bool - @param async: If False, this function will block until the - handshake is completed. If True, this function will return a - generator. Successive invocations of the generator will - return 0 if it is waiting to read from the socket, 1 if it is - waiting to write to the socket, or will raise StopIteration if - the handshake operation is completed. - - @rtype: None or an iterable - @return: If 'async' is True, a generator object will be - returned. - - @raise socket.error: If a socket error occurs. - @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed - without a preceding alert. - @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. - @raise tlslite.errors.TLSAuthenticationError: If the checker - doesn't like the other party's authentication credentials. + :type session: ~tlslite.session.Session + :param session: A TLS session to attempt to resume. If the + resumption does not succeed, a full handshake will be + performed. + + :type settings: ~tlslite.handshakesettings.HandshakeSettings + :param settings: Various settings which can be used to control + the ciphersuites, certificate types, and SSL/TLS versions + offered by the client. + + :type checker: ~tlslite.checker.Checker + :param checker: A Checker instance. This instance will be + invoked to examine the other party's authentication + credentials, if the handshake completes succesfully. + + :type serverName: string + :param serverName: The ServerNameIndication TLS Extension. + + :type async: bool + :param async: If False, this function will block until the + handshake is completed. If True, this function will return a + generator. Successive invocations of the generator will + return 0 if it is waiting to read from the socket, 1 if it is + waiting to write to the socket, or will raise StopIteration if + the handshake operation is completed. + + :rtype: None or an iterable + :returns: If 'async' is True, a generator object will be + returned. + + :raises socket.error: If a socket error occurs. + :raises tlslite.errors.TLSAbruptCloseError: If the socket is closed + without a preceding alert. + :raises tlslite.errors.TLSAlert: If a TLS alert is signalled. + :raises tlslite.errors.TLSAuthenticationError: If the checker + doesn't like the other party's authentication credentials. """ handshaker = self._handshakeClientAsync(anonParams=(True), session=session, @@ -191,53 +191,53 @@ def handshakeClientSRP(self, username, password, session=None, If an exception is raised, the connection will have been automatically closed (if it was ever open). - @type username: bytearray - @param username: The SRP username. - - @type password: bytearray - @param password: The SRP password. - - @type session: L{tlslite.session.Session} - @param session: A TLS session to attempt to resume. This - session must be an SRP session performed with the same username - and password as were passed in. If the resumption does not - succeed, a full SRP handshake will be performed. - - @type settings: L{tlslite.handshakesettings.HandshakeSettings} - @param settings: Various settings which can be used to control - the ciphersuites, certificate types, and SSL/TLS versions - offered by the client. - - @type checker: L{tlslite.checker.Checker} - @param checker: A Checker instance. This instance will be - invoked to examine the other party's authentication - credentials, if the handshake completes succesfully. - - @type reqTack: bool - @param reqTack: Whether or not to send a "tack" TLS Extension, - requesting the server return a TackExtension if it has one. - - @type serverName: string - @param serverName: The ServerNameIndication TLS Extension. - - @type async: bool - @param async: If False, this function will block until the - handshake is completed. If True, this function will return a - generator. Successive invocations of the generator will - return 0 if it is waiting to read from the socket, 1 if it is - waiting to write to the socket, or will raise StopIteration if - the handshake operation is completed. - - @rtype: None or an iterable - @return: If 'async' is True, a generator object will be - returned. - - @raise socket.error: If a socket error occurs. - @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed - without a preceding alert. - @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. - @raise tlslite.errors.TLSAuthenticationError: If the checker - doesn't like the other party's authentication credentials. + :type username: bytearray + :param username: The SRP username. + + :type password: bytearray + :param password: The SRP password. + + :type session: ~tlslite.session.Session + :param session: A TLS session to attempt to resume. This + session must be an SRP session performed with the same username + and password as were passed in. If the resumption does not + succeed, a full SRP handshake will be performed. + + :type settings: ~tlslite.handshakesettings.HandshakeSettings + :param settings: Various settings which can be used to control + the ciphersuites, certificate types, and SSL/TLS versions + offered by the client. + + :type checker: ~tlslite.checker.Checker + :param checker: A Checker instance. This instance will be + invoked to examine the other party's authentication + credentials, if the handshake completes succesfully. + + :type reqTack: bool + :param reqTack: Whether or not to send a "tack" TLS Extension, + requesting the server return a TackExtension if it has one. + + :type serverName: string + :param serverName: The ServerNameIndication TLS Extension. + + :type async: bool + :param async: If False, this function will block until the + handshake is completed. If True, this function will return a + generator. Successive invocations of the generator will + return 0 if it is waiting to read from the socket, 1 if it is + waiting to write to the socket, or will raise StopIteration if + the handshake operation is completed. + + :rtype: None or an iterable + :returns: If 'async' is True, a generator object will be + returned. + + :raises socket.error: If a socket error occurs. + :raises tlslite.errors.TLSAbruptCloseError: If the socket is closed + without a preceding alert. + :raises tlslite.errors.TLSAlert: If a TLS alert is signalled. + :raises tlslite.errors.TLSAuthenticationError: If the checker + doesn't like the other party's authentication credentials. """ # TODO add deprecation warning if isinstance(username, str): @@ -285,63 +285,63 @@ def handshakeClientCert(self, certChain=None, privateKey=None, If an exception is raised, the connection will have been automatically closed (if it was ever open). - @type certChain: L{tlslite.x509certchain.X509CertChain} - @param certChain: The certificate chain to be used if the - server requests client authentication. - - @type privateKey: L{tlslite.utils.rsakey.RSAKey} - @param privateKey: The private key to be used if the server - requests client authentication. - - @type session: L{tlslite.session.Session} - @param session: A TLS session to attempt to resume. If the - resumption does not succeed, a full handshake will be - performed. - - @type settings: L{tlslite.handshakesettings.HandshakeSettings} - @param settings: Various settings which can be used to control - the ciphersuites, certificate types, and SSL/TLS versions - offered by the client. - - @type checker: L{tlslite.checker.Checker} - @param checker: A Checker instance. This instance will be - invoked to examine the other party's authentication - credentials, if the handshake completes succesfully. - - @type nextProtos: list of strings. - @param nextProtos: A list of upper layer protocols ordered by - preference, to use in the Next-Protocol Negotiation Extension. - - @type reqTack: bool - @param reqTack: Whether or not to send a "tack" TLS Extension, - requesting the server return a TackExtension if it has one. - - @type serverName: string - @param serverName: The ServerNameIndication TLS Extension. - - @type async: bool - @param async: If False, this function will block until the - handshake is completed. If True, this function will return a - generator. Successive invocations of the generator will - return 0 if it is waiting to read from the socket, 1 if it is - waiting to write to the socket, or will raise StopIteration if - the handshake operation is completed. - - @type alpn: list of bytearrays - @param alpn: protocol names to advertise to server as supported by - client in the Application Layer Protocol Negotiation extension. - Example items in the array include b'http/1.1' or b'h2'. - - @rtype: None or an iterable - @return: If 'async' is True, a generator object will be - returned. - - @raise socket.error: If a socket error occurs. - @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed - without a preceding alert. - @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. - @raise tlslite.errors.TLSAuthenticationError: If the checker - doesn't like the other party's authentication credentials. + :type certChain: ~tlslite.x509certchain.X509CertChain + :param certChain: The certificate chain to be used if the + server requests client authentication. + + :type privateKey: ~tlslite.utils.rsakey.RSAKey + :param privateKey: The private key to be used if the server + requests client authentication. + + :type session: ~tlslite.session.Session + :param session: A TLS session to attempt to resume. If the + resumption does not succeed, a full handshake will be + performed. + + :type settings: ~tlslite.handshakesettings.HandshakeSettings + :param settings: Various settings which can be used to control + the ciphersuites, certificate types, and SSL/TLS versions + offered by the client. + + :type checker: ~tlslite.checker.Checker + :param checker: A Checker instance. This instance will be + invoked to examine the other party's authentication + credentials, if the handshake completes succesfully. + + :type nextProtos: list of str + :param nextProtos: A list of upper layer protocols ordered by + preference, to use in the Next-Protocol Negotiation Extension. + + :type reqTack: bool + :param reqTack: Whether or not to send a "tack" TLS Extension, + requesting the server return a TackExtension if it has one. + + :type serverName: string + :param serverName: The ServerNameIndication TLS Extension. + + :type async: bool + :param async: If False, this function will block until the + handshake is completed. If True, this function will return a + generator. Successive invocations of the generator will + return 0 if it is waiting to read from the socket, 1 if it is + waiting to write to the socket, or will raise StopIteration if + the handshake operation is completed. + + :type alpn: list of bytearrays + :param alpn: protocol names to advertise to server as supported by + client in the Application Layer Protocol Negotiation extension. + Example items in the array include b'http/1.1' or b'h2'. + + :rtype: None or an iterable + :returns: If 'async' is True, a generator object will be + returned. + + :raises socket.error: If a socket error occurs. + :raises tlslite.errors.TLSAbruptCloseError: If the socket is closed + without a preceding alert. + :raises tlslite.errors.TLSAlert: If a TLS alert is signalled. + :raises tlslite.errors.TLSAuthenticationError: If the checker + doesn't like the other party's authentication credentials. """ handshaker = \ self._handshakeClientAsync(certParams=(certChain, privateKey), @@ -1123,71 +1123,71 @@ def handshakeServer(self, verifierDB=None, If an exception is raised, the connection will have been automatically closed (if it was ever open). - @type verifierDB: L{tlslite.verifierdb.VerifierDB} - @param verifierDB: A database of SRP password verifiers - associated with usernames. If the client performs an SRP - handshake, the session's srpUsername attribute will be set. - - @type certChain: L{tlslite.x509certchain.X509CertChain} - @param certChain: The certificate chain to be used if the - client requests server certificate authentication. - - @type privateKey: L{tlslite.utils.rsakey.RSAKey} - @param privateKey: The private key to be used if the client - requests server certificate authentication. - - @type reqCert: bool - @param reqCert: Whether to request client certificate - authentication. This only applies if the client chooses server - certificate authentication; if the client chooses SRP - authentication, this will be ignored. If the client - performs a client certificate authentication, the sessions's - clientCertChain attribute will be set. - - @type sessionCache: L{tlslite.sessioncache.SessionCache} - @param sessionCache: An in-memory cache of resumable sessions. - The client can resume sessions from this cache. Alternatively, - if the client performs a full handshake, a new session will be - added to the cache. - - @type settings: L{tlslite.handshakesettings.HandshakeSettings} - @param settings: Various settings which can be used to control - the ciphersuites and SSL/TLS version chosen by the server. - - @type checker: L{tlslite.checker.Checker} - @param checker: A Checker instance. This instance will be - invoked to examine the other party's authentication - credentials, if the handshake completes succesfully. - - @type reqCAs: list of L{bytearray} of unsigned bytes - @param reqCAs: A collection of DER-encoded DistinguishedNames that - will be sent along with a certificate request. This does not affect - verification. - - @type nextProtos: list of strings. - @param nextProtos: A list of upper layer protocols to expose to the - clients through the Next-Protocol Negotiation Extension, - if they support it. - - @type alpn: list of bytearrays - @param alpn: names of application layer protocols supported. - Note that it will be used instead of NPN if both were advertised by - client. - - @type sni: bytearray - @param sni: expected virtual name hostname. - - @raise socket.error: If a socket error occurs. - @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed - without a preceding alert. - @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. - @raise tlslite.errors.TLSAuthenticationError: If the checker - doesn't like the other party's authentication credentials. + :type verifierDB: ~tlslite.verifierdb.VerifierDB + :param verifierDB: A database of SRP password verifiers + associated with usernames. If the client performs an SRP + handshake, the session's srpUsername attribute will be set. + + :type certChain: ~tlslite.x509certchain.X509CertChain + :param certChain: The certificate chain to be used if the + client requests server certificate authentication. + + :type privateKey: ~tlslite.utils.rsakey.RSAKey + :param privateKey: The private key to be used if the client + requests server certificate authentication. + + :type reqCert: bool + :param reqCert: Whether to request client certificate + authentication. This only applies if the client chooses server + certificate authentication; if the client chooses SRP + authentication, this will be ignored. If the client + performs a client certificate authentication, the sessions's + clientCertChain attribute will be set. + + :type sessionCache: ~tlslite.sessioncache.SessionCache + :param sessionCache: An in-memory cache of resumable sessions. + The client can resume sessions from this cache. Alternatively, + if the client performs a full handshake, a new session will be + added to the cache. + + :type settings: ~tlslite.handshakesettings.HandshakeSettings + :param settings: Various settings which can be used to control + the ciphersuites and SSL/TLS version chosen by the server. + + :type checker: ~tlslite.checker.Checker + :param checker: A Checker instance. This instance will be + invoked to examine the other party's authentication + credentials, if the handshake completes succesfully. + + :type reqCAs: list of bytearray + :param reqCAs: A collection of DER-encoded DistinguishedNames that + will be sent along with a certificate request. This does not affect + verification. + + :type nextProtos: list of str + :param nextProtos: A list of upper layer protocols to expose to the + clients through the Next-Protocol Negotiation Extension, + if they support it. + + :type alpn: list of bytearray + :param alpn: names of application layer protocols supported. + Note that it will be used instead of NPN if both were advertised by + client. + + :type sni: bytearray + :param sni: expected virtual name hostname. + + :raises socket.error: If a socket error occurs. + :raises tlslite.errors.TLSAbruptCloseError: If the socket is closed + without a preceding alert. + :raises tlslite.errors.TLSAlert: If a TLS alert is signalled. + :raises tlslite.errors.TLSAuthenticationError: If the checker + doesn't like the other party's authentication credentials. """ for result in self.handshakeServerAsync(verifierDB, certChain, privateKey, reqCert, sessionCache, settings, - checker, reqCAs, - tacks=tacks, activationFlags=activationFlags, + checker, reqCAs, + tacks=tacks, activationFlags=activationFlags, nextProtos=nextProtos, anon=anon, alpn=alpn, sni=sni): pass @@ -1207,8 +1207,8 @@ def handshakeServerAsync(self, verifierDB=None, waiting to write to the socket, or it will raise StopIteration if the handshake operation is complete. - @rtype: iterable - @return: A generator; see above for details. + :rtype: iterable + :returns: A generator; see above for details. """ handshaker = self._handshakeServerAsyncHelper(\ verifierDB=verifierDB, certChain=certChain, diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index f1e077af..b83b8b3b 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -30,79 +30,79 @@ class TLSRecordLayer(object): """ This class handles data transmission for a TLS connection. - Its only subclass is L{tlslite.TLSConnection.TLSConnection}. We've + Its only subclass is :py:class:`~tlslite.tlsconnection.TLSConnection`. + We've separated the code in this class from TLSConnection to make things more readable. - @type sock: socket.socket - @ivar sock: The underlying socket object. - - @type session: L{tlslite.Session.Session} - @ivar session: The session corresponding to this connection. - - Due to TLS session resumption, multiple connections can correspond - to the same underlying session. - - @type version: tuple - @ivar version: The TLS version being used for this connection. - - (3,0) means SSL 3.0, and (3,1) means TLS 1.0. - - @type closed: bool - @ivar closed: If this connection is closed. - - @type resumed: bool - @ivar resumed: If this connection is based on a resumed session. - - @type allegedSrpUsername: str or None - @ivar allegedSrpUsername: This is set to the SRP username - asserted by the client, whether the handshake succeeded or not. - If the handshake fails, this can be inspected to determine - if a guessing attack is in progress against a particular user - account. - - @type closeSocket: bool - @ivar closeSocket: If the socket should be closed when the - connection is closed, defaults to True (writable). - - If you set this to True, TLS Lite will assume the responsibility of - closing the socket when the TLS Connection is shutdown (either - through an error or through the user calling close()). The default - is False. - - @type ignoreAbruptClose: bool - @ivar ignoreAbruptClose: If an abrupt close of the socket should - raise an error (writable). - - If you set this to True, TLS Lite will not raise a - L{tlslite.errors.TLSAbruptCloseError} exception if the underlying - socket is unexpectedly closed. Such an unexpected closure could be - caused by an attacker. However, it also occurs with some incorrect - TLS implementations. - - You should set this to True only if you're not worried about an - attacker truncating the connection, and only if necessary to avoid - spurious errors. The default is False. - - @type encryptThenMAC: bool - @ivar encryptThenMAC: Whether the connection uses the encrypt-then-MAC - construct for CBC cipher suites, will be False also if connection uses - RC4 or AEAD. - - @type recordSize: int - @ivar recordSize: maimum size of data to be sent in a single record layer - message. Note that after encryption is established (generally after - handshake protocol has finished) the actual amount of data written to - network socket will be larger because of the record layer header, padding - or encryption overhead. It can be set to low value (so that there is no - fragmentation on Ethernet, IP and TCP level) at the beginning of - connection to reduce latency and set to protocol max (2**14) to maximise - throughput after sending few kiB of data. Setting to values greater than - 2**14 will cause the connection to be dropped by RFC compliant peers. - - @sort: __init__, read, readAsync, write, writeAsync, close, closeAsync, - getCipherImplementation, getCipherName + :vartype sock: socket.socket + :ivar sock: The underlying socket object. + + :vartype session: ~tlslite.Session.Session + :ivar session: The session corresponding to this connection. + Due to TLS session resumption, multiple connections can correspond + to the same underlying session. + + :vartype version: tuple + :ivar version: The TLS version being used for this connection. + (3,0) means SSL 3.0, and (3,1) means TLS 1.0. + + :vartype closed: bool + :ivar closed: If this connection is closed. + + :vartype resumed: bool + :ivar resumed: If this connection is based on a resumed session. + + :vartype allegedSrpUsername: str or None + :ivar allegedSrpUsername: This is set to the SRP username + asserted by the client, whether the handshake succeeded or not. + If the handshake fails, this can be inspected to determine + if a guessing attack is in progress against a particular user + account. + + :vartype closeSocket: bool + :ivar closeSocket: If the socket should be closed when the + connection is closed, defaults to True (writable). + + If you set this to True, TLS Lite will assume the responsibility of + closing the socket when the TLS Connection is shutdown (either + through an error or through the user calling close()). The default + is False. + + :vartype ignoreAbruptClose: bool + :ivar ignoreAbruptClose: If an abrupt close of the socket should + raise an error (writable). + + If you set this to True, TLS Lite will not raise a + :py:class:`~tlslite.errors.TLSAbruptCloseError` exception if the + underlying + socket is unexpectedly closed. Such an unexpected closure could be + caused by an attacker. However, it also occurs with some incorrect + TLS implementations. + + You should set this to True only if you're not worried about an + attacker truncating the connection, and only if necessary to avoid + spurious errors. The default is False. + + :vartype encryptThenMAC: bool + :ivar encryptThenMAC: Whether the connection uses the encrypt-then-MAC + construct for CBC cipher suites, will be False also if connection uses + RC4 or AEAD. + + :vartype recordSize: int + :ivar recordSize: maimum size of data to be sent in a single record layer + message. Note that after encryption is established (generally after + handshake protocol has finished) the actual amount of data written to + network socket will be larger because of the record layer header, + padding + or encryption overhead. It can be set to low value (so that there is no + fragmentation on Ethernet, IP and TCP level) at the beginning of + connection to reduce latency and set to protocol max (2**14) to + maximise + throughput after sending few kiB of data. Setting to values greater + than + 2**14 will cause the connection to be dropped by RFC compliant peers. """ def __init__(self, sock): @@ -202,21 +202,21 @@ def read(self, max=None, min=1): If an exception is raised, the connection will have been automatically closed. - @type max: int - @param max: The maximum number of bytes to return. + :type max: int + :param max: The maximum number of bytes to return. - @type min: int - @param min: The minimum number of bytes to return + :type min: int + :param min: The minimum number of bytes to return - @rtype: str - @return: A string of no more than 'max' bytes, and no fewer - than 'min' (unless the connection has been closed, in which - case fewer than 'min' bytes may be returned). + :rtype: str + :returns: A string of no more than 'max' bytes, and no fewer + than 'min' (unless the connection has been closed, in which + case fewer than 'min' bytes may be returned). - @raise socket.error: If a socket error occurs. - @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed - without a preceding alert. - @raise tlslite.errors.TLSAlert: If a TLS alert is signalled. + :raises socket.error: If a socket error occurs. + :raises tlslite.errors.TLSAbruptCloseError: If the socket is closed + without a preceding alert. + :raises tlslite.errors.TLSAlert: If a TLS alert is signalled. """ for result in self.readAsync(max, min): pass @@ -231,8 +231,8 @@ def readAsync(self, max=None, min=1): to write to the socket, or a string if the read operation has completed. - @rtype: iterable - @return: A generator; see above for details. + :rtype: iterable + :returns: A generator; see above for details. """ try: while len(self._readBuffer) len(self.bytes): raise SyntaxError() @@ -292,10 +292,10 @@ def getFixBytes(self, lengthBytes): """ Read a string of bytes encoded in 'lengthBytes' bytes. - @type lengthBytes: int - @param lengthBytes: number of bytes to return + :type lengthBytes: int + :param lengthBytes: number of bytes to return - @rtype: bytearray + :rtype: bytearray """ if self.index + lengthBytes > len(self.bytes): raise SyntaxError() @@ -307,11 +307,11 @@ def getVarBytes(self, lengthLength): """ Read a variable length string with a fixed length. - @type lengthLength: int - @param lengthLength: number of bytes in which the length of the string - is encoded in + :type lengthLength: int + :param lengthLength: number of bytes in which the length of the string + is encoded in - @rtype: bytearray + :rtype: bytearray """ lengthBytes = self.get(lengthLength) return self.getFixBytes(lengthBytes) @@ -320,13 +320,13 @@ def getFixList(self, length, lengthList): """ Read a list of static length with same-sized ints. - @type length: int - @param length: size in bytes of a single element in list + :type length: int + :param length: size in bytes of a single element in list - @type lengthList: int - @param lengthList: number of elements in list + :type lengthList: int + :param lengthList: number of elements in list - @rtype: list of int + :rtype: list of int """ l = [0] * lengthList for x in range(lengthList): @@ -337,13 +337,13 @@ def getVarList(self, length, lengthLength): """ Read a variable length list of same-sized integers. - @type length: int - @param length: size in bytes of a single element + :type length: int + :param length: size in bytes of a single element - @type lengthLength: int - @param lengthLength: size of the encoded length of the list + :type lengthLength: int + :param lengthLength: size of the encoded length of the list - @rtype: list of int + :rtype: list of int """ lengthList = self.get(lengthLength) if lengthList % length != 0: @@ -358,16 +358,16 @@ def getVarTupleList(self, elemLength, elemNum, lengthLength): """ Read a variable length list of same sized tuples. - @type elemLength: int - @param elemLength: length in bytes of single tuple element + :type elemLength: int + :param elemLength: length in bytes of single tuple element - @type elemNum: int - @param elemNum: number of elements in tuple + :type elemNum: int + :param elemNum: number of elements in tuple - @type lengthLength: int - @param lengthLength: length in bytes of the list length variable + :type lengthLength: int + :param lengthLength: length in bytes of the list length variable - @rtype: list of tuple of int + :rtype: list of tuple of int """ lengthList = self.get(lengthLength) if lengthList % (elemLength * elemNum) != 0: @@ -385,8 +385,8 @@ def startLengthCheck(self, lengthLength): """ Read length of struct and start a length check for parsing. - @type lengthLength: int - @param lengthLength: number of bytes in which the length is encoded + :type lengthLength: int + :param lengthLength: number of bytes in which the length is encoded """ self.lengthCheck = self.get(lengthLength) self.indexCheck = self.index @@ -395,8 +395,8 @@ def setLengthCheck(self, length): """ Set length of struct and start a length check for parsing. - @type length: int - @param length: expected size of parsed struct in bytes + :type length: int + :param length: expected size of parsed struct in bytes """ self.lengthCheck = length self.indexCheck = self.index diff --git a/tlslite/utils/constanttime.py b/tlslite/utils/constanttime.py index c68077fb..60322c14 100644 --- a/tlslite/utils/constanttime.py +++ b/tlslite/utils/constanttime.py @@ -12,11 +12,11 @@ def ct_lt_u32(val_a, val_b): """ Returns 1 if val_a < val_b, 0 otherwise. Constant time. - @type val_a: int - @type val_b: int - @param val_a: an unsigned integer representable as a 32 bit value - @param val_b: an unsigned integer representable as a 32 bit value - @rtype: int + :type val_a: int + :type val_b: int + :param val_a: an unsigned integer representable as a 32 bit value + :param val_b: an unsigned integer representable as a 32 bit value + :rtype: int """ val_a &= 0xffffffff val_b &= 0xffffffff @@ -27,11 +27,11 @@ def ct_gt_u32(val_a, val_b): """ Return 1 if val_a > val_b, 0 otherwise. Constant time. - @type val_a: int - @type val_b: int - @param val_a: an unsigned integer representable as a 32 bit value - @param val_b: an unsigned integer representable as a 32 bit value - @rtype: int + :type val_a: int + :type val_b: int + :param val_a: an unsigned integer representable as a 32 bit value + :param val_b: an unsigned integer representable as a 32 bit value + :rtype: int """ return ct_lt_u32(val_b, val_a) @@ -39,11 +39,11 @@ def ct_le_u32(val_a, val_b): """ Return 1 if val_a <= val_b, 0 otherwise. Constant time. - @type val_a: int - @type val_b: int - @param val_a: an unsigned integer representable as a 32 bit value - @param val_b: an unsigned integer representable as a 32 bit value - @rtype: int + :type val_a: int + :type val_b: int + :param val_a: an unsigned integer representable as a 32 bit value + :param val_b: an unsigned integer representable as a 32 bit value + :rtype: int """ return 1 ^ ct_gt_u32(val_a, val_b) @@ -59,9 +59,9 @@ def ct_isnonzero_u32(val): """ Returns 1 if val is != 0, 0 otherwise. Constant time. - @type val: int - @param val: an unsigned integer representable as a 32 bit value - @rtype: int + :type val: int + :param val: an unsigned integer representable as a 32 bit value + :rtype: int """ val &= 0xffffffff return (val|(-val&0xffffffff)) >> 31 @@ -70,11 +70,11 @@ def ct_neq_u32(val_a, val_b): """ Return 1 if val_a != val_b, 0 otherwise. Constant time. - @type val_a: int - @type val_b: int - @param val_a: an unsigned integer representable as a 32 bit value - @param val_b: an unsigned integer representable as a 32 bit value - @rtype: int + :type val_a: int + :type val_b: int + :param val_a: an unsigned integer representable as a 32 bit value + :param val_b: an unsigned integer representable as a 32 bit value + :rtype: int """ val_a &= 0xffffffff val_b &= 0xffffffff @@ -85,11 +85,11 @@ def ct_eq_u32(val_a, val_b): """ Return 1 if val_a == val_b, 0 otherwise. Constant time. - @type val_a: int - @type val_b: int - @param val_a: an unsigned integer representable as a 32 bit value - @param val_b: an unsigned integer representable as a 32 bit value - @rtype: int + :type val_a: int + :type val_b: int + :param val_a: an unsigned integer representable as a 32 bit value + :param val_b: an unsigned integer representable as a 32 bit value + :rtype: int """ return 1 ^ ct_neq_u32(val_a, val_b) @@ -97,24 +97,24 @@ def ct_check_cbc_mac_and_pad(data, mac, seqnumBytes, contentType, version): """ Check CBC cipher HMAC and padding. Close to constant time. - @type data: bytearray - @param data: data with HMAC value to test and padding + :type data: bytearray + :param data: data with HMAC value to test and padding - @type mac: hashlib mac - @param mac: empty HMAC, initialised with a key + :type mac: hashlib mac + :param mac: empty HMAC, initialised with a key - @type seqnumBytes: bytearray - @param seqnumBytes: TLS sequence number, used as input to HMAC + :type seqnumBytes: bytearray + :param seqnumBytes: TLS sequence number, used as input to HMAC - @type contentType: int - @param contentType: a single byte, used as input to HMAC + :type contentType: int + :param contentType: a single byte, used as input to HMAC - @type version: tuple of int - @param version: a tuple of two ints, used as input to HMAC and to guide - checking of padding + :type version: tuple of int + :param version: a tuple of two ints, used as input to HMAC and to guide + checking of padding - @rtype: boolean - @return: True if MAC and pad is ok, False otherwise + :rtype: boolean + :returns: True if MAC and pad is ok, False otherwise """ assert version in ((3, 0), (3, 1), (3, 2), (3, 3)) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index 65de786d..7af23d4b 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -121,6 +121,11 @@ def HKDF_expand(PRK, info, L, algorithm): # ************************************************************************** def bytesToNumber(b, endian="big"): + """ + Convert a number stored in bytearray to an integer. + + By default assumes big-endian encoding of the number. + """ if endian == "big": return int(b2a_hex(b), 16) elif endian == "little": diff --git a/tlslite/utils/dns_utils.py b/tlslite/utils/dns_utils.py index 1ae8756e..1c4fa298 100644 --- a/tlslite/utils/dns_utils.py +++ b/tlslite/utils/dns_utils.py @@ -11,8 +11,9 @@ def is_valid_hostname(hostname): """ Check if the parameter is a valid hostname. - @type hostname: str or bytearray - @rtype: boolean + :type hostname: str or bytearray + :param hostname: string to check + :rtype: boolean """ try: if not isinstance(hostname, str): diff --git a/tlslite/utils/keyfactory.py b/tlslite/utils/keyfactory.py index 1ee338f3..2e31fd91 100644 --- a/tlslite/utils/keyfactory.py +++ b/tlslite/utils/keyfactory.py @@ -1,9 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -"""Factory functions for asymmetric cryptography. -@sort: generateRSAKey, parsePEMKey, parseAsPublicKey -""" +"""Factory functions for asymmetric cryptography.""" from .compat import * @@ -24,11 +22,11 @@ def generateRSAKey(bits, implementations=["openssl", "python"]): """Generate an RSA key with the specified bit length. - @type bits: int - @param bits: Desired bit length of the new key's modulus. + :type bits: int + :param bits: Desired bit length of the new key's modulus. - @rtype: L{tlslite.utils.rsakey.RSAKey} - @return: A new RSA private key. + :rtype: ~tlslite.utils.rsakey.RSAKey + :returns: A new RSA private key. """ for implementation in implementations: if implementation == "openssl" and cryptomath.m2cryptoLoaded: @@ -71,29 +69,29 @@ def parsePEMKey(s, private=False, public=False, passwordCallback=None, and M2Crypto are installed. In this case, passwordCallback will be invoked to query the user for the password. - @type s: str - @param s: A string containing a PEM-encoded public or private key. + :type s: str + :param s: A string containing a PEM-encoded public or private key. - @type private: bool - @param private: If True, a L{SyntaxError} will be raised if the - private key component is not present. + :type private: bool + :param private: If True, a :py:class:`SyntaxError` will be raised if the + private key component is not present. - @type public: bool - @param public: If True, the private key component (if present) will - be discarded, so this function will always return a public key. + :type public: bool + :param public: If True, the private key component (if present) will + be discarded, so this function will always return a public key. - @type passwordCallback: callable - @param passwordCallback: This function will be called, with no - arguments, if the PEM-encoded private key is password-encrypted. - The callback should return the password string. If the password is - incorrect, SyntaxError will be raised. If no callback is passed - and the key is password-encrypted, a prompt will be displayed at - the console. + :type passwordCallback: callable + :param passwordCallback: This function will be called, with no + arguments, if the PEM-encoded private key is password-encrypted. + The callback should return the password string. If the password is + incorrect, SyntaxError will be raised. If no callback is passed + and the key is password-encrypted, a prompt will be displayed at + the console. - @rtype: L{tlslite.utils.RSAKey.RSAKey} - @return: An RSA key. + :rtype: ~tlslite.utils.rsakey.RSAKey + :returns: An RSA key. - @raise SyntaxError: If the key is not properly formatted. + :raises SyntaxError: If the key is not properly formatted. """ for implementation in implementations: if implementation == "openssl" and cryptomath.m2cryptoLoaded: @@ -127,26 +125,26 @@ def _parseKeyHelper(key, private, public): def parseAsPublicKey(s): """Parse a PEM-formatted public key. - @type s: str - @param s: A string containing a PEM-encoded public or private key. + :type s: str + :param s: A string containing a PEM-encoded public or private key. - @rtype: L{tlslite.utils.rsakey.RSAKey} - @return: An RSA public key. + :rtype: ~tlslite.utils.rsakey.RSAKey + :returns: An RSA public key. - @raise SyntaxError: If the key is not properly formatted. + :raises SyntaxError: If the key is not properly formatted. """ return parsePEMKey(s, public=True) def parsePrivateKey(s): """Parse a PEM-formatted private key. - @type s: str - @param s: A string containing a PEM-encoded private key. + :type s: str + :param s: A string containing a PEM-encoded private key. - @rtype: L{tlslite.utils.rsakey.RSAKey} - @return: An RSA private key. + :rtype: ~tlslite.utils.rsakey.RSAKey + :returns: An RSA private key. - @raise SyntaxError: If the key is not properly formatted. + :raises SyntaxError: If the key is not properly formatted. """ return parsePEMKey(s, private=True) diff --git a/tlslite/utils/lists.py b/tlslite/utils/lists.py index a921518b..3020470f 100644 --- a/tlslite/utils/lists.py +++ b/tlslite/utils/lists.py @@ -8,15 +8,16 @@ def getFirstMatching(values, matches): """ - Return the first element in values that is also in matches. + Return the first element in :py:obj:`values` that is also in + :py:obj:`matches`. Return None if values is None, empty or no element in values is also in matches. - @type values: collections.abc.Iterable - @param values: list of items to look through, can be None - @type matches: collections.abc.Container - @param matches: list of items to check against + :type values: collections.abc.Iterable + :param values: list of items to look through, can be None + :type matches: collections.abc.Container + :param matches: list of items to check against """ assert matches is not None if not values: diff --git a/tlslite/utils/rsakey.py b/tlslite/utils/rsakey.py index 87ccdc06..5c8c675b 100644 --- a/tlslite/utils/rsakey.py +++ b/tlslite/utils/rsakey.py @@ -13,14 +13,14 @@ class RSAKey(object): """This is an abstract base class for RSA keys. Particular implementations of RSA keys, such as - L{openssl_rsakey.OpenSSL_RSAKey}, - L{python_rsakey.Python_RSAKey}, and - L{pycrypto_rsakey.PyCrypto_RSAKey}, + :py:class:`~.openssl_rsakey.OpenSSL_RSAKey`, + :py:class:`~.python_rsakey.Python_RSAKey`, and + :py:class:`~.pycrypto_rsakey.PyCrypto_RSAKey`, inherit from this. To create or parse an RSA key, don't use one of these classes directly. Instead, use the factory functions in - L{tlslite.utils.keyfactory}. + :py:class:`~tlslite.utils.keyfactory`. """ def __init__(self, n=0, e=0): @@ -28,25 +28,25 @@ def __init__(self, n=0, e=0): If n and e are passed in, the new key will be initialized. - @type n: int - @param n: RSA modulus. + :type n: int + :param n: RSA modulus. - @type e: int - @param e: RSA public exponent. + :type e: int + :param e: RSA public exponent. """ raise NotImplementedError() def __len__(self): """Return the length of this key in bits. - @rtype: int + :rtype: int """ return numBits(self.n) def hasPrivateKey(self): """Return whether or not this key has a private component. - @rtype: bool + :rtype: bool """ raise NotImplementedError() @@ -57,23 +57,23 @@ def hashAndSign(self, bytes, rsaScheme='PKCS1', hAlg='sha1', sLen=0): a PKCS1 or PSS signature on the passed-in data with selected hash algorithm. - @type bytes: str or L{bytearray} of unsigned bytes - @param bytes: The value which will be hashed and signed. + :type bytes: str or bytearray + :param bytes: The value which will be hashed and signed. - @type rsaScheme: str - @param rsaScheme: The type of RSA scheme that will be applied, + :type rsaScheme: str + :param rsaScheme: The type of RSA scheme that will be applied, "PKCS1" for RSASSA-PKCS#1 v1.5 signature and "PSS" for RSASSA-PSS with MGF1 signature method - @type hAlg: str - @param hAlg: The hash algorithm that will be used + :type hAlg: str + :param hAlg: The hash algorithm that will be used - @type sLen: int - @param sLen: The length of intended salt value, applicable only + :type sLen: int + :param sLen: The length of intended salt value, applicable only for RSASSA-PSS signatures - @rtype: L{bytearray} of unsigned bytes. - @return: A PKCS1 or PSS signature on the passed-in data. + :rtype: bytearray + :returns: A PKCS1 or PSS signature on the passed-in data. """ rsaScheme = rsaScheme.lower() hAlg = hAlg.lower() @@ -88,26 +88,26 @@ def hashAndVerify(self, sigBytes, bytes, rsaScheme='PKCS1', hAlg='sha1', This verifies a PKCS1 or PSS signature on the passed-in data with selected hash algorithm. - @type sigBytes: L{bytearray} of unsigned bytes - @param sigBytes: A PKCS1 or PSS signature. + :type sigBytes: bytearray + :param sigBytes: A PKCS1 or PSS signature. - @type bytes: str or L{bytearray} of unsigned bytes - @param bytes: The value which will be hashed and verified. + :type bytes: str or bytearray + :param bytes: The value which will be hashed and verified. - @type rsaScheme: str - @param rsaScheme: The type of RSA scheme that will be applied, + :type rsaScheme: str + :param rsaScheme: The type of RSA scheme that will be applied, "PKCS1" for RSASSA-PKCS#1 v1.5 signature and "PSS" for RSASSA-PSS with MGF1 signature method - @type hAlg: str - @param hAlg: The hash algorithm that will be used + :type hAlg: str + :param hAlg: The hash algorithm that will be used - @type sLen: int - @param sLen: The length of intended salt value, applicable only + :type sLen: int + :param sLen: The length of intended salt value, applicable only for RSASSA-PSS signatures - @rtype: bool - @return: Whether the signature matches the passed-in data. + :rtype: bool + :returns: Whether the signature matches the passed-in data. """ rsaScheme = rsaScheme.lower() hAlg = hAlg.lower() @@ -120,14 +120,14 @@ def MGF1(self, mgfSeed, maskLen, hAlg): This generates mask based on passed-in seed and output maskLen. - @type mgfSeed: L{bytearray} - @param mgfSeed: Seed from which mask will be generated. + :type mgfSeed: bytearray + :param mgfSeed: Seed from which mask will be generated. - @type maskLen: int - @param maskLen: Wished length of the mask, in octets + :type maskLen: int + :param maskLen: Wished length of the mask, in octets - @rtype: L{bytearray} - @return: Mask + :rtype: bytearray + :returns: Mask """ hashLen = getattr(hashlib, hAlg)().digest_size if maskLen > (2 ** 32) * hashLen: @@ -144,17 +144,17 @@ def EMSA_PSS_encode(self, mHash, emBits, hAlg, sLen=0): This encodes the message using selected hash algorithm - @type mHash: bytearray - @param mHash: Hash of message to be encoded + :type mHash: bytearray + :param mHash: Hash of message to be encoded - @type emBits: int - @param emBits: maximal length of returned EM + :type emBits: int + :param emBits: maximal length of returned EM - @type hAlg: str - @param hAlg: hash algorithm to be used + :type hAlg: str + :param hAlg: hash algorithm to be used - @type sLen: int - @param sLen: length of salt""" + :type sLen: int + :param sLen: length of salt""" hashLen = getattr(hashlib, hAlg)().digest_size emLen = divceil(emBits, 8) if emLen < hashLen + sLen + 2: @@ -178,14 +178,14 @@ def RSASSA_PSS_sign(self, mHash, hAlg, sLen=0): This signs the message using selected hash algorithm - @type mHash: bytearray - @param mHash: Hash of message to be signed + :type mHash: bytearray + :param mHash: Hash of message to be signed - @type hAlg: str - @param hAlg: hash algorithm to be used + :type hAlg: str + :param hAlg: hash algorithm to be used - @type sLen: int - @param sLen: length of salt""" + :type sLen: int + :param sLen: length of salt""" EM = self.EMSA_PSS_encode(mHash, numBits(self.n) - 1, hAlg, sLen) m = bytesToNumber(EM) if m >= self.n: @@ -199,20 +199,20 @@ def EMSA_PSS_verify(self, mHash, EM, emBits, hAlg, sLen=0): This verifies the signature in encoded message - @type mHash: bytearray - @param mHash: Hash of the original not signed message + :type mHash: bytearray + :param mHash: Hash of the original not signed message - @type EM: bytearray - @param EM: Encoded message + :type EM: bytearray + :param EM: Encoded message - @type emBits: int - @param emBits: Length of the encoded message in bits + :type emBits: int + :param emBits: Length of the encoded message in bits - @type hAlg: str - @param hAlg: hash algorithm to be used + :type hAlg: str + :param hAlg: hash algorithm to be used - @type sLen: int - @param sLen: Length of salt + :type sLen: int + :param sLen: Length of salt """ hashLen = getattr(hashlib, hAlg)().digest_size emLen = divceil(emBits, 8) @@ -252,17 +252,17 @@ def RSASSA_PSS_verify(self, mHash, S, hAlg, sLen=0): This verifies the signature in the signed message - @type mHash: bytearray - @param mHash: Hash of original message + :type mHash: bytearray + :param mHash: Hash of original message - @type S: bytearray - @param S: Signed message + :type S: bytearray + :param S: Signed message - @type hAlg: str - @param hAlg: Hash algorithm to be used + :type hAlg: str + :param hAlg: Hash algorithm to be used - @type sLen: int - @param sLen: Length of salt + :type sLen: int + :param sLen: Length of salt """ if len(bytearray(S)) != len(numberToByteArray(self.n)): raise InvalidSignature("Invalid signature") @@ -294,24 +294,24 @@ def sign(self, bytes, padding='pkcs1', hashAlg=None, saltLen=None): This requires the key to have a private component. It performs a PKCS1 signature on the passed-in data. - @type bytes: L{bytearray} of unsigned bytes - @param bytes: The value which will be signed. + :type bytes: bytearray + :param bytes: The value which will be signed. - @type padding: str - @param padding: name of the rsa padding mode to use, supported: - "pkcs1" for RSASSA-PKCS1_1_5 and "pss" for RSASSA-PSS. + :type padding: str + :param padding: name of the rsa padding mode to use, supported: + "pkcs1" for RSASSA-PKCS1_1_5 and "pss" for RSASSA-PSS. - @type hashAlg: str - @param hashAlg: name of hash to be encoded using the PKCS#1 prefix - for "pkcs1" padding or the hash used for MGF1 in "pss". Parameter - is mandatory for "pss" padding. + :type hashAlg: str + :param hashAlg: name of hash to be encoded using the PKCS#1 prefix + for "pkcs1" padding or the hash used for MGF1 in "pss". Parameter + is mandatory for "pss" padding. - @type saltLen: int - @param saltLen: length of salt used for the PSS padding. Default - is the length of the hash output used. + :type saltLen: int + :param saltLen: length of salt used for the PSS padding. Default + is the length of the hash output used. - @rtype: L{bytearray} of unsigned bytes. - @return: A PKCS1 signature on the passed-in data. + :rtype: bytearray + :returns: A PKCS1 signature on the passed-in data. """ padding = padding.lower() if padding == 'pkcs1': @@ -342,14 +342,14 @@ def verify(self, sigBytes, bytes, padding='pkcs1', hashAlg=None, This verifies a PKCS1 signature on the passed-in data. - @type sigBytes: L{bytearray} of unsigned bytes - @param sigBytes: A PKCS1 signature. + :type sigBytes: bytearray + :param sigBytes: A PKCS1 signature. - @type bytes: L{bytearray} of unsigned bytes - @param bytes: The value which will be verified. + :type bytes: bytearray + :param bytes: The value which will be verified. - @rtype: bool - @return: Whether the signature matches the passed-in data. + :rtype: bool + :returns: Whether the signature matches the passed-in data. """ if padding == "pkcs1" and hashAlg == 'sha1': # Try it with/without the embedded NULL @@ -377,11 +377,11 @@ def encrypt(self, bytes): This performs PKCS1 encryption of the passed-in data. - @type bytes: L{bytearray} of unsigned bytes - @param bytes: The value which will be encrypted. + :type bytes: bytearray + :param bytes: The value which will be encrypted. - @rtype: L{bytearray} of unsigned bytes. - @return: A PKCS1 encryption of the passed-in data. + :rtype: bytearray + :returns: A PKCS1 encryption of the passed-in data. """ paddedBytes = self._addPKCS1Padding(bytes, 2) m = bytesToNumber(paddedBytes) @@ -397,12 +397,12 @@ def decrypt(self, encBytes): This requires the key to have a private component. It performs PKCS1 decryption of the passed-in data. - @type encBytes: L{bytearray} of unsigned bytes - @param encBytes: The value which will be decrypted. + :type encBytes: bytearray + :param encBytes: The value which will be decrypted. - @rtype: L{bytearray} of unsigned bytes or None. - @return: A PKCS1 decryption of the passed-in data or None if - the data is not properly formatted. + :rtype: bytearray or None + :returns: A PKCS1 decryption of the passed-in data or None if + the data is not properly formatted. """ if not self.hasPrivateKey(): raise AssertionError() @@ -434,23 +434,23 @@ def acceptsPassword(self): """Return True if the write() method accepts a password for use in encrypting the private key. - @rtype: bool + :rtype: bool """ raise NotImplementedError() def write(self, password=None): """Return a string containing the key. - @rtype: str - @return: A string describing the key, in whichever format (PEM) - is native to the implementation. + :rtype: str + :returns: A string describing the key, in whichever format (PEM) + is native to the implementation. """ raise NotImplementedError() def generate(bits): """Generate a new key with the specified bit length. - @rtype: L{tlslite.utils.RSAKey.RSAKey} + :rtype: ~tlslite.utils.RSAKey.RSAKey """ raise NotImplementedError() generate = staticmethod(generate) diff --git a/tlslite/utils/x25519.py b/tlslite/utils/x25519.py index ed3a87aa..334daad4 100644 --- a/tlslite/utils/x25519.py +++ b/tlslite/utils/x25519.py @@ -53,13 +53,13 @@ def x25519(k, u): """ Perform point multiplication on X25519 curve. - @type k: bytearray - @param k: random secret value (multiplier), should be 32 byte long + :type k: bytearray + :param k: random secret value (multiplier), should be 32 byte long - @type u: bytearray - @param u: curve generator or the other party key share + :type u: bytearray + :param u: curve generator or the other party key share - @rtype: bytearray + :rtype: bytearray """ bits = 255 k = decodeScalar22519(k) @@ -81,13 +81,13 @@ def x448(k, u): """ Perform point multiplication on X448 curve. - @type k: bytearray - @param k: random secret value (multiplier), should be 56 bytes long + :type k: bytearray + :param k: random secret value (multiplier), should be 56 bytes long - @type u: bytearray - @param u: curve generator or the other party key share + :type u: bytearray + :param u: curve generator or the other party key share - @rtype: bytearray + :rtype: bytearray """ bits = 448 k = decodeScalar448(k) diff --git a/tlslite/verifierdb.py b/tlslite/verifierdb.py index 46c46fd8..0711170c 100644 --- a/tlslite/verifierdb.py +++ b/tlslite/verifierdb.py @@ -20,11 +20,11 @@ class VerifierDB(BaseDB): def __init__(self, filename=None): """Create a new VerifierDB instance. - @type filename: str - @param filename: Filename for an on-disk database, or None for - an in-memory database. If the filename already exists, follow - this with a call to open(). To create a new on-disk database, - follow this with a call to create(). + :type filename: str + :param filename: Filename for an on-disk database, or None for + an in-memory database. If the filename already exists, follow + this with a call to open(). To create a new on-disk database, + follow this with a call to create(). """ BaseDB.__init__(self, filename, b"verifier") @@ -39,15 +39,15 @@ def _getItem(self, username, valueStr): def __setitem__(self, username, verifierEntry): """Add a verifier entry to the database. - @type username: str - @param username: The username to associate the verifier with. - Must be less than 256 characters in length. Must not already - be in the database. + :type username: str + :param username: The username to associate the verifier with. + Must be less than 256 characters in length. Must not already + be in the database. - @type verifierEntry: tuple - @param verifierEntry: The verifier entry to add. Use - L{tlslite.verifierdb.VerifierDB.makeVerifier} to create a - verifier entry. + :type verifierEntry: tuple + :param verifierEntry: The verifier entry to add. Use + :py:meth:`~tlslite.verifierdb.VerifierDB.makeVerifier` to create a + verifier entry. """ BaseDB.__setitem__(self, username, verifierEntry) @@ -69,25 +69,25 @@ def _checkItem(self, value, username, param): v = powMod(g, x, N) return (verifier == v) - + @staticmethod def makeVerifier(username, password, bits): """Create a verifier entry which can be stored in a VerifierDB. - @type username: str - @param username: The username for this verifier. Must be less - than 256 characters in length. + :type username: str + :param username: The username for this verifier. Must be less + than 256 characters in length. - @type password: str - @param password: The password for this verifier. + :type password: str + :param password: The password for this verifier. - @type bits: int - @param bits: This values specifies which SRP group parameters - to use. It must be one of (1024, 1536, 2048, 3072, 4096, 6144, - 8192). Larger values are more secure but slower. 2048 is a - good compromise between safety and speed. + :type bits: int + :param bits: This values specifies which SRP group parameters + to use. It must be one of (1024, 1536, 2048, 3072, 4096, 6144, + 8192). Larger values are more secure but slower. 2048 is a + good compromise between safety and speed. - @rtype: tuple - @return: A tuple which may be stored in a VerifierDB. + :rtype: tuple + :returns: A tuple which may be stored in a VerifierDB. """ if isinstance(username, str): usernameBytes = bytearray(username, "utf-8") @@ -98,4 +98,3 @@ def makeVerifier(username, password, bits): else: passwordBytes = bytearray(password) return mathtls.makeVerifier(usernameBytes, passwordBytes, bits) - makeVerifier = staticmethod(makeVerifier) diff --git a/tlslite/x509.py b/tlslite/x509.py index 3245406a..1161b888 100644 --- a/tlslite/x509.py +++ b/tlslite/x509.py @@ -13,48 +13,50 @@ class X509(object): - """This class represents an X.509 certificate. + """ + This class represents an X.509 certificate. - @type bytes: L{bytearray} of unsigned bytes - @ivar bytes: The DER-encoded ASN.1 certificate + :vartype bytes: bytearray + :ivar bytes: The DER-encoded ASN.1 certificate - @type publicKey: L{tlslite.utils.rsakey.RSAKey} - @ivar publicKey: The subject public key from the certificate. + :vartype publicKey: ~tlslite.utils.rsakey.RSAKey + :ivar publicKey: The subject public key from the certificate. - @type subject: L{bytearray} of unsigned bytes - @ivar subject: The DER-encoded ASN.1 subject distinguished name. + :vartype subject: bytearray + :ivar subject: The DER-encoded ASN.1 subject distinguished name. - @type certAlg: str - @ivar certAlg: algorithm of the public key, "rsa" for RSASSA-PKCS#1 v1.5 - and "rsa-pss" for RSASSA-PSS + :vartype certAlg: str + :ivar certAlg: algorithm of the public key, "rsa" for RSASSA-PKCS#1 v1.5 + and "rsa-pss" for RSASSA-PSS """ def __init__(self): + """Create empty certificate object.""" self.bytes = bytearray(0) self.publicKey = None self.subject = None self.certAlg = None def parse(self, s): - """Parse a PEM-encoded X.509 certificate. - - @type s: str - @param s: A PEM-encoded X.509 certificate (i.e. a base64-encoded - certificate wrapped with "-----BEGIN CERTIFICATE-----" and - "-----END CERTIFICATE-----" tags). """ + Parse a PEM-encoded X.509 certificate. + :type s: str + :param s: A PEM-encoded X.509 certificate (i.e. a base64-encoded + certificate wrapped with "-----BEGIN CERTIFICATE-----" and + "-----END CERTIFICATE-----" tags). + """ bytes = dePem(s, "CERTIFICATE") self.parseBinary(bytes) return self def parseBinary(self, bytes): - """Parse a DER-encoded X.509 certificate. - - @type bytes: str or L{bytearray} of unsigned bytes - @param bytes: A DER-encoded X.509 certificate. """ + Parse a DER-encoded X.509 certificate. + :type bytes: str or L{bytearray} of unsigned bytes + :param bytes: A DER-encoded X.509 certificate. + """ self.bytes = bytearray(bytes) p = ASN1Parser(bytes) @@ -120,14 +122,16 @@ def parseBinary(self, bytes): self.publicKey = _createPublicRSAKey(n, e) def getFingerprint(self): - """Get the hex-encoded fingerprint of this certificate. + """ + Get the hex-encoded fingerprint of this certificate. - @rtype: str - @return: A hex-encoded fingerprint. + :rtype: str + :returns: A hex-encoded fingerprint. """ return b2a_hex(SHA1(self.bytes)) def writeBytes(self): + """Serialise object to a DER encoded string.""" return self.bytes diff --git a/tlslite/x509certchain.py b/tlslite/x509certchain.py index 2a592b6d..d7ca81f4 100644 --- a/tlslite/x509certchain.py +++ b/tlslite/x509certchain.py @@ -11,19 +11,19 @@ class X509CertChain(object): """This class represents a chain of X.509 certificates. - @type x509List: list - @ivar x509List: A list of L{tlslite.x509.X509} instances, - starting with the end-entity certificate and with every - subsequent certificate certifying the previous. + :vartype x509List: list + :ivar x509List: A list of :py:class:`tlslite.x509.X509` instances, + starting with the end-entity certificate and with every + subsequent certificate certifying the previous. """ def __init__(self, x509List=None): """Create a new X509CertChain. - @type x509List: list - @param x509List: A list of L{tlslite.x509.X509} instances, - starting with the end-entity certificate and with every - subsequent certificate certifying the previous. + :type x509List: list + :param x509List: A list of :py:class:`tlslite.x509.X509` instances, + starting with the end-entity certificate and with every + subsequent certificate certifying the previous. """ if x509List: self.x509List = x509List @@ -46,14 +46,14 @@ def parsePemList(self, s): def getNumCerts(self): """Get the number of certificates in this chain. - @rtype: int + :rtype: int """ return len(self.x509List) def getEndEntityPublicKey(self): """Get the public key from the end-entity certificate. - @rtype: L{tlslite.utils.rsakey.RSAKey} + :rtype: ~tlslite.utils.rsakey.RSAKey` """ if self.getNumCerts() == 0: raise AssertionError() @@ -62,13 +62,13 @@ def getEndEntityPublicKey(self): def getFingerprint(self): """Get the hex-encoded fingerprint of the end-entity certificate. - @rtype: str - @return: A hex-encoded fingerprint. + :rtype: str + :returns: A hex-encoded fingerprint. """ if self.getNumCerts() == 0: raise AssertionError() return self.x509List[0].getFingerprint() - + def checkTack(self, tack): if self.x509List: tlsCert = TlsCertificate(self.x509List[0].bytes) From 48e839b42fd660e497423496de5ebf304d1cbfb2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 24 Jul 2017 20:13:51 +0200 Subject: [PATCH 474/574] package building with Sphinx docs --- CONTRIBUTING.md | 15 ++++----------- MANIFEST.in | 4 ++-- Makefile | 7 ++++--- README.md | 7 +++---- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ff5f608..99741642 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,8 +27,7 @@ The list goes as follows: * git * GNU make * pip -* epydoc (you need to [patch](https://sourceforge.net/p/epydoc/bugs/342/) - the `epydoc/docparser.py`!) +* sphinx The python module dependencies are as follows: @@ -46,12 +45,9 @@ On Fedora they can be installed using: ``` dnf install python-ecdsa python3-ecdsa pylint python3-pylint python-diff-cover \ python3-diff-cover python-coverage python3-coverage python2-hypothesis \ - python3-hypothesis python3-libs python-unittest2 python-mock epydoc + python3-hypothesis python3-libs python-unittest2 python-mock python3-sphinx ``` -Then edit `epydoc/docparser.py` using -[patch](https://sourceforge.net/p/epydoc/bugs/342/) - On RHEL 7 you will need to enable [EPEL](https://fedoraproject.org/wiki/EPEL), and install [pip](https://pip.pypa.io/en/stable/installing/) for Python3, after which you can install the dependencies using: @@ -59,14 +55,11 @@ after which you can install the dependencies using: ``` yum install python-ecdsa python34-ecdsa pylint \ python-coverage python34-coverage python2-hypothesis \ - python34-libs python-unittest2 python-mock python-pip -pip2 install diff-cover epydoc + python34-libs python-unittest2 python-mock python-pip python-sphinx +pip2 install diff-cover pip3 install hypothesis diff-cover pylint ``` -Then edit `epydoc/docparser.py` using -[patch](https://sourceforge.net/p/epydoc/bugs/342/) - Optional module dependencies: * tackpy diff --git a/MANIFEST.in b/MANIFEST.in index 340da696..43fa1934 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ recursive-include tests * -recursive-include docs * +recursive-include docs/_build/html * include LICENSE include README include Makefile -include MANIFEST.in \ No newline at end of file +include MANIFEST.in diff --git a/Makefile b/Makefile index 304f143a..1ba0c144 100644 --- a/Makefile +++ b/Makefile @@ -27,12 +27,13 @@ clean: rm -rf unit_tests/*.pyc rm -rf unit_tests/__pycache__ rm -rf dist - rm -rf docs rm -rf build rm -f MANIFEST + $(MAKE) -C docs clean +.PHONY : docs docs: - epydoc --html -v --introspect-only -o docs --graph all tlslite + $(MAKE) -C docs html dist: docs ./setup.py sdist @@ -66,7 +67,7 @@ ifndef PYTHON3 cd tests/ && PYTHONPATH=.. python ./tlstest.py client localhost:4433 . endif endif - epydoc --check --fail-on-error -v tlslite + $(MAKE) -C docs dummy pylint --msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" tlslite > pylint_report.txt || : diff-quality --violations=pylint --fail-under=90 pylint_report.txt ifdef COVERAGE2 diff --git a/README.md b/README.md index eeef7fa9..6087fdb5 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,9 @@ python socket interface or as a backend for multiple other libraries. tlslite-ng is pure python, however it can use other libraries for faster crypto operations. tlslite-ng integrates with several stdlib neworking libraries. -API documentation is available in the 'docs' directory of the PyPI package -or can be automatically generated using `make docs` with Epydoc installed. +API documentation is available in the `docs/_build/html` directory of the PyPI +package +or can be automatically generated using `make docs` with Sphinx installed. If you have questions or feedback, feel free to contact me. Issues and pull requests can also be submitted through github issue tracking system, see @@ -75,8 +76,6 @@ details. Currently it is distributed under Gnu LGPLv2 license. -Thanks to Edward Loper for Epydoc, which generated the API docs. - 3 Installation =============== From c414494cb2a0d81d83ca8f69026884a6fbf28de8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 24 Jul 2017 20:15:44 +0200 Subject: [PATCH 475/574] update acknowledgements --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6087fdb5..1b69fccd 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,8 @@ Implemented features of TLS include: 2 Licenses/Acknowledgements ============================ -tlslite-ng is a fork of TLS Lite. TLS Lite was written (mostly) by Trevor +tlslite-ng is a fork of TLS Lite, it is currently maintained and developed by +Hubert Kario. TLS Lite was written (mostly) by Trevor Perrin. It includes code from Bram Cohen, Google, Kees Bos, Sam Rushing, Dimitris Moraitis, Marcelo Fernandez, Martin von Loewis, Dave Baggett, Yngve N. Pettersen (ported by Paul Sokolovsky), Mirko Dziadzka, David Benjamin, From 6e9855dea2b3b1e2bf9070534dd921a054faa365 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 24 Jul 2017 20:22:22 +0200 Subject: [PATCH 476/574] release v0.7.0-beta1 --- README | 1 + README.md | 4 +++- docs/conf.py | 2 +- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README b/README index e383ad87..5c65e7c0 100644 --- a/README +++ b/README @@ -18,6 +18,7 @@ Functionality implemented include: - secp256r1, secp384r1, secp521r1, secp256k1, secp224r1 and secp192r1 curves for ECDHE_RSA key exchange (support for last two depends on the version of ecdsa library used) + - x25519 and x448 curves for ECDHE_RSA key exchage (RFC 7748. RFC 4492bis) - anonymous DHE key exchange - anonymous ECDH key exchange - NULL encryption ciphersuites diff --git a/README.md b/README.md index 1b69fccd..6ad68499 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.7.0-alpha9 2017-07-20 +tlslite-ng version 0.7.0-beta1 2017-07-24 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -59,6 +59,7 @@ Implemented features of TLS include: * padding extension * keying material exporter * RSA-PSS signatures in TLSv1.2, RSA-PSS in certificates (TLSv1.3 extension) +* X25519 and X448 ECDHE key exchange * (experimental) TACK extension 2 Licenses/Acknowledgements @@ -612,6 +613,7 @@ encrypt-then-MAC mode for CBC ciphers. * Allow negotiation of ECDHE ciphersuites even if client doesn't advertise any curves, default to P-256 curve support, support configuring it. * Stricter checks on received SNI (server_name) extension +* Support for x25519 and x448 curve for ECDHE 0.6.0 - 2016-09-07 diff --git a/docs/conf.py b/docs/conf.py index 8d9a24a1..e2695628 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,7 +61,7 @@ # The short X.Y version. version = u'0.7' # The full version, including alpha/beta/rc tags. -release = u'0.7.0-alpha9' +release = u'0.7.0-beta1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index b4cd0fb5..bebb5a3e 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ README = f.read() setup(name="tlslite-ng", - version="0.7.0-alpha9", + version="0.7.0-beta1", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index a2a8d22b..b0731128 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.7.0-alpha9 +@version: 0.7.0-beta1 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index b4b4a5d3..df59b5b1 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.7.0-alpha9" +__version__ = "0.7.0-beta1" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 5dffb87e2eebb49f8d4456a97e9551d59b928733 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 26 Jul 2017 19:29:05 +0200 Subject: [PATCH 477/574] add repr() support to SignatureAlgorithmsExtension --- tlslite/extensions.py | 24 +++++++++++++++++++++++- unit_tests/test_tlslite_extensions.py | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 5136571a..7c20712d 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -9,7 +9,8 @@ from __future__ import generators from .utils.codec import Writer, Parser from collections import namedtuple -from .constants import NameType, ExtensionType, CertificateStatusType +from .constants import NameType, ExtensionType, CertificateStatusType, \ + SignatureAlgorithm, HashAlgorithm, SignatureScheme from .errors import TLSInternalError class TLSExtension(object): @@ -1008,6 +1009,27 @@ def __init__(self): signature_algorithms) self.sigalgs = None + def _repr_sigalgs(self): + """Return a text representation of sigalgs field.""" + if self.sigalgs is None: + return "None" + else: + values = [] + for alg in self.sigalgs: + name = SignatureScheme.toRepr(alg) + if name is None: + name = "({0}, {1})".format(HashAlgorithm.toStr(alg[0]), + SignatureAlgorithm. + toStr(alg[1])) + values.append(name) + + return "[{0}]".format(", ".join(values)) + + def __repr__(self): + """Return a text representation of the extension.""" + return "SignatureAlgorithmsExtension(sigalgs={0})".format( + self._repr_sigalgs()) + @property def extData(self): """ diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 1aaefe3d..bfa2d4a8 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -1392,6 +1392,24 @@ def test_parse_with_extra_data_at_end(self): with self.assertRaises(SyntaxError): ext.parse(parser) + def test___repr__(self): + ext = SignatureAlgorithmsExtension().create([(HashAlgorithm.sha1, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha256, + SignatureAlgorithm.rsa), + (HashAlgorithm.sha384, + SignatureAlgorithm.dsa)]) + + self.assertEqual(repr(ext), + "SignatureAlgorithmsExtension(" + "sigalgs=[rsa_pkcs1_sha1, rsa_pkcs1_sha256, (sha384, dsa)])") + + def test___repr___with_none(self): + ext = SignatureAlgorithmsExtension() + + self.assertEqual(repr(ext), "SignatureAlgorithmsExtension(" + "sigalgs=None)") + class TestPaddingExtension(unittest.TestCase): def test__init__(self): ext = PaddingExtension() From 48983fd22257e5a3e6b37e34d2d022e50b1e1b49 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 31 Jul 2017 16:59:38 +0200 Subject: [PATCH 478/574] test for regression introduced by 18160a23de04 The DHE code expects parsing of an empty string to return zero, as previous code did. Without it, the error handling for missing key share is incorrect. --- unit_tests/test_tlslite_utils_cryptomath.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index deb37e81..2f281136 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -443,3 +443,11 @@ def test_very_long_numbers_little_endian(self): def test_with_unknown_type(self): with self.assertRaises(ValueError): bytesToNumber(bytearray(b'\xf0'), "middle") + + @unittest.expectedFailure + def test_with_empty_string(self): + self.assertEqual(0, bytesToNumber(b'')) + + @unittest.expectedFailure + def test_with_empty_string_little_endian(self): + self.assertEqual(0, bytesToNumber(b'', "little")) From 8e0ee728b0768d0e4b8557e9c3fcaac71beb500a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 31 Jul 2017 17:03:00 +0200 Subject: [PATCH 479/574] fix regression introduced in 18160a23de04 correctly handle FFDHE key shares with key share being an empty string --- tlslite/utils/cryptomath.py | 6 ++++++ unit_tests/test_tlslite_utils_cryptomath.py | 2 -- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index 7af23d4b..521866f5 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -126,6 +126,12 @@ def bytesToNumber(b, endian="big"): By default assumes big-endian encoding of the number. """ + # if string is empty, consider it to be representation of zero + # while it may be a bit unorthodox, it is the inverse of numberToByteArray + # with default parameters + if not b: + return 0 + if endian == "big": return int(b2a_hex(b), 16) elif endian == "little": diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index 2f281136..53e95343 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -444,10 +444,8 @@ def test_with_unknown_type(self): with self.assertRaises(ValueError): bytesToNumber(bytearray(b'\xf0'), "middle") - @unittest.expectedFailure def test_with_empty_string(self): self.assertEqual(0, bytesToNumber(b'')) - @unittest.expectedFailure def test_with_empty_string_little_endian(self): self.assertEqual(0, bytesToNumber(b'', "little")) From d9869e63d9c3df98327d3f18de01e047165a1300 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 31 Jul 2017 19:02:19 +0200 Subject: [PATCH 480/574] correctly handle missing ecdh key share from CKE message --- tlslite/errors.py | 6 ++++++ tlslite/keyexchange.py | 11 +++++++---- tlslite/tlsconnection.py | 19 ++++++++++++++++++- unit_tests/test_tlslite_keyexchange.py | 18 +++++++++++++++++- 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/tlslite/errors.py b/tlslite/errors.py index ece80fdf..759fbe8f 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -236,6 +236,12 @@ class TLSIllegalParameterException(TLSProtocolException): pass +class TLSDecodeError(TLSProtocolException): + """The received message encoding does not match specification.""" + + pass + + class TLSRecordOverflow(TLSProtocolException): """The received record size was too big""" diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 4e0d7e38..46cf4aa6 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -7,7 +7,8 @@ from .mathtls import goodGroupParameters, makeK, makeU, makeX, \ calcMasterSecret, paramStrength, RFC7919_GROUPS from .errors import TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ - TLSIllegalParameterException, TLSDecryptionFailed, TLSInternalError + TLSIllegalParameterException, TLSDecryptionFailed, TLSInternalError, \ + TLSDecodeError from .messages import ServerKeyExchange, ClientKeyExchange, CertificateVerify from .constants import SignatureAlgorithm, HashAlgorithm, CipherSuite, \ ExtensionType, GroupName, ECCurveType, SignatureScheme @@ -542,9 +543,11 @@ def makeServerKeyExchange(self, sigHash=None): def processClientKeyExchange(self, clientKeyExchange): """Calculate premaster secret from previously generated SKE and CKE""" - if self.group_id in [GroupName.x25519, GroupName.x448]: - ecdhYc = clientKeyExchange.ecdh_Yc + ecdhYc = clientKeyExchange.ecdh_Yc + if not ecdhYc: + raise TLSDecodeError("No key share") + if self.group_id in [GroupName.x25519, GroupName.x448]: if self.group_id == GroupName.x25519: if len(ecdhYc) != X25519_ORDER_SIZE: raise TLSIllegalParameterException("Invalid key share") @@ -558,7 +561,7 @@ def processClientKeyExchange(self, clientKeyExchange): else: curveName = GroupName.toRepr(self.group_id) try: - ecdhYc = decodeX962Point(clientKeyExchange.ecdh_Yc, + ecdhYc = decodeX962Point(ecdhYc, getCurveByName(curveName)) # TODO update python-ecdsa library to raise something more on point except AssertionError: diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index ba036129..17648586 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1882,6 +1882,10 @@ def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, for result in self._sendError(AlertDescription.illegal_parameter, "Suspicious A value"): yield result + except TLSDecodeError as alert: + for result in self._sendError(AlertDescription.decode_error, + str(alert)): + yield result yield premasterSecret @@ -1976,6 +1980,10 @@ def _serverCertKeyExchange(self, clientHello, serverHello, for result in self._sendError(AlertDescription.illegal_parameter, str(alert)): yield result + except TLSDecodeError as alert: + for result in self._sendError(AlertDescription.decode_error, + str(alert)): + yield result #Get and check CertificateVerify, if relevant self._certificate_verify_handshake_hash = self._handshake_hash.copy() @@ -2064,7 +2072,16 @@ def _serverAnonKeyExchange(self, serverHello, keyExchange, cipherSuite): else: break cke = result - premasterSecret = keyExchange.processClientKeyExchange(cke) + try: + premasterSecret = keyExchange.processClientKeyExchange(cke) + except TLSIllegalParameterException as alert: + for result in self._sendError(AlertDescription.illegal_parameter, + str(alert)): + yield result + except TLSDecodeError as alert: + for result in self._sendError(AlertDescription.decode_error, + str(alert)): + yield result yield premasterSecret diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index bf8b5866..58d7b7f4 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -23,7 +23,7 @@ SignatureScheme from tlslite.errors import TLSLocalAlert, TLSIllegalParameterException, \ TLSDecryptionFailed, TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ - TLSInternalError + TLSInternalError, TLSDecodeError from tlslite.x509 import X509 from tlslite.x509certchain import X509CertChain from tlslite.utils.keyfactory import parsePEMKey @@ -1247,6 +1247,22 @@ def test_ECDHE_key_exchange_with_invalid_CKE(self): with self.assertRaises(TLSIllegalParameterException): self.keyExchange.processClientKeyExchange(cln_key_ex) + def test_ECDHE_key_exchange_with_empty_value_in_CKE(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + + KeyExchange.verifyServerKeyExchange(srv_key_ex, + self.srv_pub_key, + self.client_hello.random, + self.server_hello.random, + [(HashAlgorithm.sha1, + SignatureAlgorithm.rsa)]) + + cln_key_ex = ClientKeyExchange(self.cipher_suite, (3, 3)) + cln_key_ex.createECDH(bytearray()) + + with self.assertRaises(TLSDecodeError): + self.keyExchange.processClientKeyExchange(cln_key_ex) + def test_ECDHE_key_exchange_with_missing_curves(self): self.client_hello.extensions = [SNIExtension().create(bytearray(b"a"))] From 663364b99e79912913c8aadfeb36ad6cfa930533 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 31 Jul 2017 20:28:21 +0200 Subject: [PATCH 481/574] Release 0.7.0 --- README.md | 6 +++--- docs/conf.py | 2 +- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6ad68499..cd633694 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.7.0-beta1 2017-07-24 +tlslite-ng version 0.7.0 2017-07-31 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -586,7 +586,7 @@ encrypt-then-MAC mode for CBC ciphers. 12 History =========== -0.7.0 - in-dev +0.7.0 - 2017-07-31 * enable and add missing definitions of TLS_ECDHE_RSA_WITH_RC4_128_SHA and TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA @@ -611,7 +611,7 @@ encrypt-then-MAC mode for CBC ciphers. follow RFC recommendations with regards to session resumption, reject non-empty * Allow negotiation of ECDHE ciphersuites even if client doesn't advertise - any curves, default to P-256 curve support, support configuring it. + any curves, default to P-256 curve support, support configuring the default * Stricter checks on received SNI (server_name) extension * Support for x25519 and x448 curve for ECDHE diff --git a/docs/conf.py b/docs/conf.py index e2695628..22953818 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,7 +61,7 @@ # The short X.Y version. version = u'0.7' # The full version, including alpha/beta/rc tags. -release = u'0.7.0-beta1' +release = u'0.7.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index bebb5a3e..49a4d52d 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ README = f.read() setup(name="tlslite-ng", - version="0.7.0-beta1", + version="0.7.0", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index b0731128..f773d7c9 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.7.0-beta1 +@version: 0.7.0 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index df59b5b1..93694580 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.7.0-beta1" +__version__ = "0.7.0" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 4d60332890bf255441b92e98cb04afc13ec9ee19 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 1 Aug 2017 15:25:58 +0200 Subject: [PATCH 482/574] fix long for pycrypto PyCrypto on Python 2.7 expects longs not only for initialisation of RSA keys but also for RSA operations --- tlslite/utils/pycrypto_rsakey.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tlslite/utils/pycrypto_rsakey.py b/tlslite/utils/pycrypto_rsakey.py index 05953b5e..b8ffaf4e 100644 --- a/tlslite/utils/pycrypto_rsakey.py +++ b/tlslite/utils/pycrypto_rsakey.py @@ -29,12 +29,10 @@ def hasPrivateKey(self): return self.rsa.has_private() def _rawPrivateKeyOp(self, m): - c = self.rsa.decrypt((m,)) - return c + return self.rsa.decrypt((compatLong(m),)) def _rawPublicKeyOp(self, c): - m = self.rsa.encrypt(c, None)[0] - return m + return self.rsa.encrypt(compatLong(c), None)[0] def generate(bits): key = PyCrypto_RSAKey() From d70409eadd2be378d177a3779c7c05e69c12d96b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 2 Aug 2017 18:04:20 +0200 Subject: [PATCH 483/574] add test coverage for Certificate message --- unit_tests/test_tlslite_messages.py | 122 +++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 9255a6fa..7232f4ef 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -10,7 +10,8 @@ from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ - ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus + ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus, \ + Certificate from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ @@ -19,6 +20,26 @@ from tlslite.extensions import SNIExtension, ClientCertTypeExtension, \ SRPExtension, TLSExtension, NPNExtension from tlslite.errors import TLSInternalError +from tlslite.x509 import X509 +from tlslite.x509certchain import X509CertChain + + +srv_raw_certificate = str( + "-----BEGIN CERTIFICATE-----\n" + "MIIB9jCCAV+gAwIBAgIJAMyn9DpsTG55MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV\n" + "BAMMCWxvY2FsaG9zdDAeFw0xNTAxMjExNDQzMDFaFw0xNTAyMjAxNDQzMDFaMBQx\n" + "EjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n" + "0QkEeakSyV/LMtTeARdRtX5pdbzVuUuqOIdz3lg7YOyRJ/oyLTPzWXpKxr//t4FP\n" + "QvYsSJiVOlPk895FNu6sNF/uJQyQGfFWYKkE6fzFifQ6s9kssskFlL1DVI/dD/Zn\n" + "7sgzua2P1SyLJHQTTs1MtMb170/fX2EBPkDz+2kYKN0CAwEAAaNQME4wHQYDVR0O\n" + "BBYEFJtvXbRmxRFXYVMOPH/29pXCpGmLMB8GA1UdIwQYMBaAFJtvXbRmxRFXYVMO\n" + "PH/29pXCpGmLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAkOgC7LP/\n" + "Rd6uJXY28HlD2K+/hMh1C3SRT855ggiCMiwstTHACGgNM+AZNqt6k8nSfXc6k1gw\n" + "5a7SGjzkWzMaZC3ChBeCzt/vIAGlMyXeqTRhjTCdc/ygRv3NPrhUKKsxUYyXRk5v\n" + "g/g6MwxzXfQP3IyFu3a9Jia/P89Z1rQCNRY=\n" + "-----END CERTIFICATE-----\n" + ) + class TestMessage(unittest.TestCase): def test___init__(self): @@ -2454,5 +2475,104 @@ def test_write(self): b'\xbc\xaa')) +class TestCertificate(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.x509_cert = X509().parse(srv_raw_certificate) + cls.der_cert = cls.x509_cert.writeBytes() + + def test___init__(self): + cert = Certificate(CertificateType.x509) + + self.assertIsNotNone(cert) + self.assertIsInstance(cert, Certificate) + + def test_write_empty(self): + cert = Certificate(CertificateType.x509) + cert.create(X509CertChain()) + + self.assertEqual(cert.write(), + bytearray(b'\x0b' # type of message - certificate + b'\x00\x00\x03' # length of message - 3 bytes + b'\x00\x00\x00')) # length of the list of certs + + def test_write_none(self): + cert = Certificate(CertificateType.x509) + cert.create(None) + + self.assertEqual(cert.write(), + bytearray(b'\x0b' + b'\x00\x00\x03' # length of message - 3 bytes + b'\x00\x00\x00')) # length of the list of certs + + def test_parse_empty(self): + cert = Certificate(CertificateType.x509) + parser = Parser( + bytearray(# b'\x0b' # type of message - certificate + b'\x00\x00\x03' # length of message - 3 bytes + b'\x00\x00\x00')) # length of the list of certs + cert = cert.parse(parser) + + self.assertIsNone(cert.certChain) + + def test_parse_empty_with_leftover_byte(self): + cert = Certificate(CertificateType.x509) + parser = Parser( + bytearray(# b'\x0b' # type of message - certificate + b'\x00\x00\x04' # length of message - 3 bytes + b'\x00\x00\x00' # length of the list of certs + b'\x00')) # extra byte + with self.assertRaises(SyntaxError): + cert.parse(parser) + + def test_write_with_cert(self): + cert = Certificate(CertificateType.x509) + cert.create(X509CertChain([self.x509_cert])) + + # just sanity check + self.assertEqual(len(self.der_cert), 0x0001fa) + # verify that the message is encoded correctly + self.assertEqual(cert.write(), + bytearray(b'\x0b' + # type of message - certificate + b'\x00\x02\x00' + # length of handshake message + b'\x00\x01\xfd' + # length of array of certificates + b'\x00\x01\xfa' + # length of the first certificate + self.der_cert)) + + def test_parse_with_cert(self): + cert = Certificate(CertificateType.x509) + parser = Parser( + bytearray(#b'\x0b' + # type of message - certificate + b'\x00\x02\x00' + # length of handshake message + b'\x00\x01\xfd' + # length of array of certificates + b'\x00\x01\xfa' + # length of the first certificate + self.der_cert)) + + cert = cert.parse(parser) + self.assertIsNotNone(cert.certChain) + self.assertIsInstance(cert.certChain, X509CertChain) + self.assertEqual(len(cert.certChain.x509List), 1) + self.assertEqual(cert.certChain.x509List[0].writeBytes(), + self.der_cert) + + def test_parse_with_openpgp_type(self): + cert = Certificate(CertificateType.openpgp) + + parser = Parser( + bytearray(# b'\x0b' # type of message - certificate + b'\x00\x00\x03' # length of message - 3 bytes + b'\x00\x00\x00')) # length of the list of certs + + with self.assertRaises(AssertionError): + cert.parse(parser) + + def test_write_with_openpgp_type(self): + cert = Certificate(CertificateType.openpgp) + + with self.assertRaises(AssertionError): + cert.write() + + if __name__ == '__main__': unittest.main() From 5060d46daa597c1f43d737400d092dca74ded1c6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 2 Aug 2017 18:24:44 +0200 Subject: [PATCH 484/574] test coverage for Finished --- unit_tests/test_tlslite_messages.py | 63 ++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 7232f4ef..918adf16 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -11,7 +11,7 @@ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus, \ - Certificate + Certificate, Finished from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ @@ -2367,6 +2367,67 @@ def test___repr__(self): self.assertEqual("ServerHelloDone()", repr(shd)) + +class TestFinished(unittest.TestCase): + def test___init__(self): + finished = Finished((3, 3)) + + self.assertIsNotNone(finished) + self.assertEqual(finished.handshakeType, 20) + + def test_parse_ssl2(self): + finished = Finished((2, 0)) + + parser = Parser(bytearray(b'\x00\x00\x24' + + b'\x0f' * 0x24)) + + with self.assertRaises(AssertionError): + finished.parse(parser) + + def test_parse_ssl3(self): + finished = Finished((3, 0)) + + parser = Parser(bytearray(b'\x00\x00\x24' + + b'\x0f' * 0x24)) + + finished = finished.parse(parser) + + self.assertEqual(finished.verify_data, bytearray(b'\x0f' * 36)) + + def test_write_ssl3(self): + finished = Finished((3, 0)) + + finished = finished.create(bytearray(b'\x03' * 36)) + + self.assertEqual(finished.write(), + bytearray(b'\x14' + b'\x00\x00\x24' + b'\x03' * 36)) + + def test_parse_tls_1_2(self): + finished = Finished((3, 3)) + + parser = Parser(bytearray(b'\x00\x00\x0c' + b'\x04' * 12)) + + finished = finished.parse(parser) + + self.assertEqual(finished.verify_data, bytearray(b'\x04' * 12)) + + def test_parse_tls_1_2_with_invalid_number_of_bytes(self): + finished = Finished((3, 3)) + + parser = Parser(bytearray(b'\x00\x00\x0d' + b'\x04' * 13)) + + with self.assertRaises(SyntaxError): + finished.parse(parser) + + def test_write_tls_1_2(self): + finished = Finished((3, 3)) + + finished = finished.create(bytearray(b'\x04' * 12)) + + self.assertEqual(finished.write(), + bytearray(b'\x14' + b'\x00\x00\x0c' + b'\x04' * 12)) + + class TestClientFinished(unittest.TestCase): def test___init__(self): fin = ClientFinished() From eb0794f3c62d41518edc1897e7a298b60725aaf9 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 17:14:44 +0200 Subject: [PATCH 485/574] add test coverage for addExtension of HelloMessage --- unit_tests/test_tlslite_messages.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 918adf16..d289ef87 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -11,12 +11,12 @@ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus, \ - Certificate, Finished + Certificate, Finished, HelloMessage from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ HashAlgorithm, SignatureAlgorithm, ECCurveType, GroupName, \ - SSL2HandshakeType, CertificateStatusType + SSL2HandshakeType, CertificateStatusType, HandshakeType from tlslite.extensions import SNIExtension, ClientCertTypeExtension, \ SRPExtension, TLSExtension, NPNExtension from tlslite.errors import TLSInternalError @@ -53,6 +53,23 @@ def test_write(self): self.assertEqual(bytearray(10), msg.write()) + +class TestHelloMessage(unittest.TestCase): + def test___init__(self): + msg = HelloMessage(HandshakeType.client_hello) + + self.assertIsInstance(msg, HelloMessage) + + def test_addExtension(self): + msg = HelloMessage(HandshakeType.client_hello) + msg.extensions = [] + + msg.addExtension(SNIExtension().create(bytearray(b'example.com'))) + + self.assertEqual(msg.extensions, + [SNIExtension().create(bytearray(b'example.com'))]) + + class TestClientHello(unittest.TestCase): def test___init__(self): client_hello = ClientHello() From e3c34c8c01bb9a30a72a1cf7e01394958763684f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 17:21:38 +0200 Subject: [PATCH 486/574] make the sslv2 client hello test case names unique --- unit_tests/test_tlslite_messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index d289ef87..00ab0d6b 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -622,7 +622,7 @@ def test_server_name_other_than_dns_name(self): self.assertEqual(client_hello.server_name, bytearray(0)) - def test_parse_with_SSLv2_client_hello(self): + def test_parse_with_SSLv2_client_hello_and_short_random(self): parser = Parser(bytearray( # length and type is handled by hello protocol parser #b'\x80\x2e' + # length - 46 bytes From 4f6fca8d7092694799f159b41e0c582da267e6a1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 17:50:02 +0200 Subject: [PATCH 487/574] show that re-setting certificate_type in ServerHello to 0 works incorrectly --- unit_tests/test_tlslite_messages.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 00ab0d6b..6903ff91 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -841,6 +841,20 @@ def test_create(self): self.assertEqual(None, server_hello.tackExt) self.assertEqual(None, server_hello.next_protos_advertised) + @unittest.expectedFailure + def test_certificate_type_update_to_x509(self): + server_hello = ServerHello().create( + (1,1), # server version + bytearray(b'\x00'*31+b'\x01'), # random + bytearray(0), # session id + 4, # cipher suite + 1, # certificate type + None, # TACK ext + None) # next protos advertised + + server_hello.certificate_type = CertificateType.x509 + self.assertEqual(CertificateType.x509, server_hello.certificate_type) + def test_create_with_minimal_options(self): server_hello = ServerHello().create( (3, 3), # server version From c6ebf6fdb83af69983ffd65256ba229e5ec10eaf Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 17:51:28 +0200 Subject: [PATCH 488/574] fix handling of the certificate_type set to 0 --- tlslite/messages.py | 12 ++++++++---- unit_tests/test_tlslite_messages.py | 1 - 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 6249e609..5e1c2d48 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -779,15 +779,19 @@ def certificate_type(self, val): :type val: int :param val: type of certificate """ - # XXX backwards compatibility, 0 means x.509 and should not be sent - if val == 0 or val is None: - return - cert_type = self.getExtension(ExtensionType.cert_type) if cert_type is None: + # XXX backwards compatibility, 0 means x.509 and should not be sent + if val == CertificateType.x509 or val is None: + return ext = ServerCertTypeExtension().create(val) self.addExtension(ext) else: + if val == CertificateType.x509 or val is None: + # XXX backwards compatibility, 0 means x.509 and should not be + # sent + self._removeExt(ExtensionType.cert_type) + return cert_type.cert_type = val @property diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 6903ff91..01a232f8 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -841,7 +841,6 @@ def test_create(self): self.assertEqual(None, server_hello.tackExt) self.assertEqual(None, server_hello.next_protos_advertised) - @unittest.expectedFailure def test_certificate_type_update_to_x509(self): server_hello = ServerHello().create( (1,1), # server version From a8f4283cebf3f81d6f0b7a3979c6297e6f90bacf Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 17:54:22 +0200 Subject: [PATCH 489/574] more test coverage for certificate_type --- unit_tests/test_tlslite_messages.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 01a232f8..87c0754e 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -854,6 +854,20 @@ def test_certificate_type_update_to_x509(self): server_hello.certificate_type = CertificateType.x509 self.assertEqual(CertificateType.x509, server_hello.certificate_type) + def test_certificate_type_set_to_raw(self): + server_hello = ServerHello().create( + (1,1), # server version + bytearray(b'\x00'*31+b'\x01'), # random + bytearray(0), # session id + 4, # cipher suite + 1, # certificate type + None, # TACK ext + None) # next protos advertised + + server_hello.certificate_type = 2 + self.assertEqual(2, server_hello.certificate_type) + + def test_create_with_minimal_options(self): server_hello = ServerHello().create( (3, 3), # server version From a7addb47616f273d92de4d08f9cb6eb23b5d417e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 17:58:29 +0200 Subject: [PATCH 490/574] test case for ServerHello without extensions list --- unit_tests/test_tlslite_messages.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 87c0754e..ceea19a3 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1030,6 +1030,20 @@ def test_parse_with_cert_type_extension(self): server_hello = ServerHello().parse(p) self.assertEqual(1, server_hello.certificate_type) + def test_parse_with_no_extensions(self): + p = Parser(bytearray( + b'\x00\x00\x26' + # length - 45 bytes + b'\x03\x03' + # version - TLS 1.2 + b'\x01'*31 + b'\x02' + # random + b'\x00' + # session id length + b'\x00\x9d' + # cipher suite + b'\x00' # compression method (none) + )) + + server_hello = ServerHello().parse(p) + self.assertIsNone(server_hello.extensions) + + def test_parse_with_bad_cert_type_extension(self): p = Parser(bytearray( b'\x00\x00\x2e' + # length - 46 bytes From 1300e9d984543ad94f43641fed77d082a2b868e1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 18:25:51 +0200 Subject: [PATCH 491/574] test ServerKeyExchange with rsa_pss signature --- unit_tests/test_tlslite_messages.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index ceea19a3..3bd4861b 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -16,7 +16,8 @@ from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ HashAlgorithm, SignatureAlgorithm, ECCurveType, GroupName, \ - SSL2HandshakeType, CertificateStatusType, HandshakeType + SSL2HandshakeType, CertificateStatusType, HandshakeType, \ + SignatureScheme from tlslite.extensions import SNIExtension, ClientCertTypeExtension, \ SRPExtension, TLSExtension, NPNExtension from tlslite.errors import TLSInternalError @@ -2211,6 +2212,21 @@ def test_hash(self): b'\x8e?YW\xcd\xad\xc6\x83\x91\x1d.fe,\x17y' + b'=\xc4T\x89')) + def test_hash_with_rsa_pss_sha256(self): + ske = ServerKeyExchange( + CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + (3, 3)) + + ske.createDH(dh_p=31, dh_g=2, dh_Ys=16) + ske.hashAlg, ske.signAlg = SignatureScheme.rsa_pss_sha256 + + hash1 = ske.hash(bytearray(32), bytearray(32)) + + self.assertEqual(hash1, + bytearray(b'^\xfe\x0e\x8f\xd2\x87\x8e/%\xbeK9\xb7$' + b'\x93\x9a\x81TW\nI\xff\xb2\xbeo\xaf\x90' + b'\xa7\xfb\xe1sM')) + def test_hash_with_invalid_ciphersuite(self): ske = ServerKeyExchange(0, (3, 1)) From f024371b16930a8e3abe30b47b71e39c3c41d5fb Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 18:29:28 +0200 Subject: [PATCH 492/574] test coverage for ServerHelloDone --- unit_tests/test_tlslite_messages.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 3bd4861b..2fd940dd 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -2436,6 +2436,32 @@ def test___init__(self): self.assertIsNotNone(shd) + def test_create(self): + shd = ServerHelloDone().create() + + self.assertIsInstance(shd, ServerHelloDone) + + def test_parse(self): + p = Parser(bytearray(b'\x00\x00\x00')) + shd = ServerHelloDone() + + shd = shd.parse(p) + + self.assertIsInstance(shd, ServerHelloDone) + + def test_parse_with_payload(self): + p = Parser(bytearray(b'\x00\x00\x01\x00')) + shd = ServerHelloDone() + + with self.assertRaises(SyntaxError): + shd.parse(p) + + def test_write(self): + shd = ServerHelloDone() + + self.assertEqual(bytearray(b'\x0e\x00\x00\x00'), + shd.write()) + def test___repr__(self): shd = ServerHelloDone() From 854b56c4850cc7f692c41dd6c1521709548dd309 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 18:44:01 +0200 Subject: [PATCH 493/574] test coverage for ServerHello next_protos --- unit_tests/test_tlslite_messages.py | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 2fd940dd..cbe809bc 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -842,6 +842,37 @@ def test_create(self): self.assertEqual(None, server_hello.tackExt) self.assertEqual(None, server_hello.next_protos_advertised) + def test_next_protos_reset(self): + server_hello = ServerHello().create( + (1,1), # server version + bytearray(b'\x00'*31+b'\x02'), # random + bytearray(0), # session id + 4, # cipher suite + 0, # certificate type + None, # TACK ext + [bytearray(b'http/1.1')]) # next protos advertised + + self.assertEqual(server_hello.next_protos, [bytearray(b'http/1.1')]) + server_hello.next_protos = [bytearray(b'spdy/3'), + bytearray(b'http/1.1')] + self.assertEqual([bytearray(b'spdy/3'), bytearray(b'http/1.1')], + server_hello.next_protos) + + @unittest.expectedFailure + def test_next_protos_reset_to_None(self): + server_hello = ServerHello().create( + (1,1), # server version + bytearray(b'\x00'*31+b'\x02'), # random + bytearray(0), # session id + 4, # cipher suite + 0, # certificate type + None, # TACK ext + [bytearray(b'http/1.1')]) # next protos advertised + + self.assertEqual(server_hello.next_protos, [bytearray(b'http/1.1')]) + server_hello.next_protos = None + self.assertIsNone(server_hello.next_protos) + def test_certificate_type_update_to_x509(self): server_hello = ServerHello().create( (1,1), # server version From 94c31723959ea11be53b96e522aab61b7a18d4d3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 18:47:08 +0200 Subject: [PATCH 494/574] fix re-setting next_protos to None --- tlslite/messages.py | 11 ++++++++--- unit_tests/test_tlslite_messages.py | 1 - 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 5e1c2d48..5b9aad36 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -816,18 +816,23 @@ def next_protos(self, val): :type val: list :param val: list of protocols to advertise as UTF-8 encoded names """ - if val is None: - return - else: + if val is not None: # convinience function, make sure the values are properly encoded val = [bytearray(x) for x in val] npn_ext = self.getExtension(ExtensionType.supports_npn) if npn_ext is None: + if val is None: + # XXX: do not send empty extension + return ext = NPNExtension().create(val) self.addExtension(ext) else: + if val is None: + # XXX: do not send empty extension + self._removeExt(ExtensionType.supports_npn) + return npn_ext.protocols = val @property diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index cbe809bc..05af663d 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -858,7 +858,6 @@ def test_next_protos_reset(self): self.assertEqual([bytearray(b'spdy/3'), bytearray(b'http/1.1')], server_hello.next_protos) - @unittest.expectedFailure def test_next_protos_reset_to_None(self): server_hello = ServerHello().create( (1,1), # server version From f31d32455bf025d6c8421fbbbf8258d8f6ccb320 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 19:50:47 +0200 Subject: [PATCH 495/574] coverage for ServerHello.__str__ with extensions --- unit_tests/test_tlslite_messages.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 05af663d..ec07c0f9 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1180,6 +1180,25 @@ def test___str__(self): "compression method(0)", str(server_hello)) + def test___str___with_extension(self): + server_hello = ServerHello() + server_hello = server_hello.create( + (3,0), + bytearray(b'\x00'*32), + bytearray(b'\x01\x20'), + 34500, + 0, + None, + None, + extensions=[SNIExtension().create(bytearray(b'test'))]) + + self.assertEqual("server_hello,length(55),version(3.0),random(...)," + "session ID(bytearray(b'\\x01 ')),cipher(0x86c4)," + "compression method(0),extensions[SNIExtension(" + "serverNames=[ServerName(name_type=0, " + "name=bytearray(b'test'))])]", + str(server_hello)) + def test___repr__(self): server_hello = ServerHello() server_hello = server_hello.create( From 581757178d3eb0c135f80c609118b584c807138a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 19:06:02 +0200 Subject: [PATCH 496/574] test coverage for ChangeCipherSpec --- unit_tests/test_tlslite_messages.py | 32 ++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index ec07c0f9..75d23051 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -11,7 +11,7 @@ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus, \ - Certificate, Finished, HelloMessage + Certificate, Finished, HelloMessage, ChangeCipherSpec from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ @@ -2784,5 +2784,35 @@ def test_write_with_openpgp_type(self): cert.write() +class TestChangeCipherSpec(unittest.TestCase): + def test___init__(self): + ccs = ChangeCipherSpec() + + self.assertIsNotNone(ccs) + self.assertEqual(ccs.type, 1) + + def test_create(self): + ccs = ChangeCipherSpec().create() + + self.assertIsNotNone(ccs) + self.assertIsInstance(ccs, ChangeCipherSpec) + + self.assertEqual(ccs.type, 1) + + def test_write(self): + ccs = ChangeCipherSpec().create() + + self.assertEqual(bytearray(b'\x01'), ccs.write()) + + def test_parse(self): + parser = Parser(bytearray(b'\x01')) + + ccs = ChangeCipherSpec() + ccs = ccs.parse(parser) + + self.assertIsInstance(ccs, ChangeCipherSpec) + self.assertEqual(ccs.type, 1) + + if __name__ == '__main__': unittest.main() From 4dc70fb4596afd7824db32156bd1d524b3ade413 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 19:15:49 +0200 Subject: [PATCH 497/574] test coverage for NextProtocol message --- unit_tests/test_tlslite_messages.py | 42 ++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 75d23051..d1297077 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -11,7 +11,7 @@ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus, \ - Certificate, Finished, HelloMessage, ChangeCipherSpec + Certificate, Finished, HelloMessage, ChangeCipherSpec, NextProtocol from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ @@ -2814,5 +2814,45 @@ def test_parse(self): self.assertEqual(ccs.type, 1) +class TestNextProtocol(unittest.TestCase): + def test___init__(self): + np = NextProtocol() + + self.assertIsNotNone(np) + self.assertIsNone(np.next_proto) + + def test_create(self): + np = NextProtocol().create(bytearray(b'test')) + + self.assertIsInstance(np, NextProtocol) + self.assertEqual(np.next_proto, bytearray(b'test')) + + def test_write(self): + np = NextProtocol().create(bytearray(b'test')) + + self.assertEqual(bytearray(b'\x43' + # type - NPN + b'\x00\x00\x20' + # length - 32 bytes + b'\x04' + # value length + b'test' + # value + b'\x1a' + # length of padding + b'\x00' * 0x1a # padding + ), + np.write()) + + def test_parse(self): + parser = Parser(bytearray(#b'\x43' + # type - NPN + b'\x00\x00\x20' + # length - 32 bytes + b'\x04' + # value length + b'test' + # value + b'\x1a' + # length of padding + b'\x00' * 0x1a # padding + )) + np = NextProtocol() + np = np.parse(parser) + + self.assertIsInstance(np, NextProtocol) + self.assertEqual(np.next_proto, bytearray(b'test')) + + if __name__ == '__main__': unittest.main() From 10dec1334b5c2832b785e5901541241f8743e9a6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 19:22:21 +0200 Subject: [PATCH 498/574] test coverage for ApplicationData --- unit_tests/test_tlslite_messages.py | 40 ++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index d1297077..b3b2a7d5 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -11,7 +11,8 @@ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus, \ - Certificate, Finished, HelloMessage, ChangeCipherSpec, NextProtocol + Certificate, Finished, HelloMessage, ChangeCipherSpec, NextProtocol, \ + ApplicationData from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ @@ -2854,5 +2855,42 @@ def test_parse(self): self.assertEqual(np.next_proto, bytearray(b'test')) +class TestApplicationData(unittest.TestCase): + def test___init__(self): + app_data = ApplicationData() + + self.assertIsNotNone(app_data) + self.assertEqual(bytearray(0), app_data.bytes) + + def test_create(self): + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertIsInstance(app_data, ApplicationData) + self.assertEqual(bytearray(b'test'), app_data.bytes) + + def test_write(self): + app_data = ApplicationData().create(bytearray(b'test')) + + self.assertEqual(bytearray(b'test'), app_data.write()) + + def test_parse(self): + parser = Parser(bytearray(b'test2')) + app_data = ApplicationData() + + app_data = app_data.parse(parser) + + self.assertIsInstance(app_data, ApplicationData) + self.assertEqual(app_data.bytes, bytearray(b'test2')) + + def test_splitFirstByte(self): + app_data = ApplicationData().create(bytearray(b'test')) + + app_data1 = app_data.splitFirstByte() + + self.assertIsInstance(app_data1, ApplicationData) + self.assertEqual(app_data1.bytes, bytearray(b't')) + self.assertEqual(app_data.bytes, bytearray(b'est')) + + if __name__ == '__main__': unittest.main() From a0979f533aa911a7fa1f10942f8fe597890c7b08 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 8 Aug 2017 19:30:23 +0200 Subject: [PATCH 499/574] exclude XXX from fixme checks XXX is a mark that "There be dragons", in other words warning for future programmers working on code, not something we can fix. --- .codeclimate.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index e71671ad..886d8dc3 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -16,6 +16,12 @@ engines: - python fixme: enabled: true + config: + strings: + - TODO + - FIXME + - HACK + - BUG checks: bug: enabled: true From c3a58b9f44328348093acc4be5a13e22f9040250 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 14 Aug 2017 17:58:03 +0200 Subject: [PATCH 500/574] remove code duplication in ServerHello --- tlslite/messages.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 5b9aad36..0a205c28 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -779,19 +779,16 @@ def certificate_type(self, val): :type val: int :param val: type of certificate """ + if val == CertificateType.x509 or val is None: + # XXX backwards compatibility, x509 value should not be sent + self._removeExt(ExtensionType.cert_type) + return + cert_type = self.getExtension(ExtensionType.cert_type) if cert_type is None: - # XXX backwards compatibility, 0 means x.509 and should not be sent - if val == CertificateType.x509 or val is None: - return ext = ServerCertTypeExtension().create(val) self.addExtension(ext) else: - if val == CertificateType.x509 or val is None: - # XXX backwards compatibility, 0 means x.509 and should not be - # sent - self._removeExt(ExtensionType.cert_type) - return cert_type.cert_type = val @property @@ -816,23 +813,20 @@ def next_protos(self, val): :type val: list :param val: list of protocols to advertise as UTF-8 encoded names """ - if val is not None: + if val is None: + # XXX: do not send empty extension + self._removeExt(ExtensionType.supports_npn) + return + else: # convinience function, make sure the values are properly encoded val = [bytearray(x) for x in val] npn_ext = self.getExtension(ExtensionType.supports_npn) if npn_ext is None: - if val is None: - # XXX: do not send empty extension - return ext = NPNExtension().create(val) self.addExtension(ext) else: - if val is None: - # XXX: do not send empty extension - self._removeExt(ExtensionType.supports_npn) - return npn_ext.protocols = val @property From 62d766e0328e2c5a40b8bc87c3813131a14dcfa2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 11 Aug 2017 12:26:38 +0200 Subject: [PATCH 501/574] increase the minimal mass for code to be duplicate because we are using generator expressions for asynchronous operation, there is a lot of code like this: for result in : if result in (0, 1): yield result else: break while indeed code like this is similar to each other, it's also smallest amount that will work correctly. It is also slightly above (34) the default for diplication in codeclimate (32). --- .codeclimate.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 886d8dc3..e036b6ab 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -13,7 +13,9 @@ engines: - 2c398389f33ea2572edefc5370ed49c0 config: languages: - - python + python: + python_version: 3 + mass_threshold: 35 fixme: enabled: true config: From 231eed2672ddcd6e5db1cb103faaced6f2009d8f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 16 Aug 2017 16:06:30 +0200 Subject: [PATCH 502/574] add TLS 1.3 specific ciphers --- tlslite/constants.py | 35 +++++++++++++++++++- unit_tests/test_tlslite_constants.py | 48 ++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index ef93615f..4c57ff8e 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -553,6 +553,14 @@ class CipherSuite: TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF ietfNames[0x00FF] = 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV' + # TLS 1.3 ciphersuites + TLS_AES_128_GCM_SHA256 = 0x1301 + ietfNames[0x1301] = 'TLS_AES_128_GCM_SHA256' + TLS_AES_256_GCM_SHA384 = 0x1302 + ietfNames[0x1302] = 'TLS_AES_256_GCM_SHA384' + TLS_CHACHA20_POLY1305_SHA256 = 0x1303 + ietfNames[0x1303] = 'TLS_CHACHA20_POLY1305_SHA256' + # RFC 7507 - Fallback Signaling Cipher Suite Value for Preventing Protocol # Downgrade Attacks TLS_FALLBACK_SCSV = 0x5600 @@ -752,6 +760,7 @@ class CipherSuite: aes128GcmSuites.append(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256) # unsupp aes128GcmSuites.append(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) # unsupp aes128GcmSuites.append(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) + aes128GcmSuites.append(TLS_AES_128_GCM_SHA256) #: AES-256-GCM ciphers (implicit SHA384, see sha384PrfSuites) aes256GcmSuites = [] @@ -762,6 +771,7 @@ class CipherSuite: aes256GcmSuites.append(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384) # unsupp aes256GcmSuites.append(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) # unsupported aes256GcmSuites.append(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) + aes256GcmSuites.append(TLS_AES_256_GCM_SHA384) #: CHACHA20 cipher, 00'th IETF draft (implicit POLY1305 authenticator) chacha20draft00Suites = [] @@ -772,6 +782,7 @@ class CipherSuite: chacha20Suites = [] chacha20Suites.append(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) chacha20Suites.append(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) + chacha20Suites.append(TLS_CHACHA20_POLY1305_SHA256) #: RC4 128 stream cipher rc4Suites = [] @@ -895,14 +906,29 @@ class CipherSuite: tls12Suites.extend(sha384Suites) tls12Suites.extend(aeadSuites) + #: TLS1.3 specific ciphersuites + tls13Suites = [] + + # TLS 1.3 suites are not a superset of TLS 1.2 suites, but they + # use the same mechanism (AEAD), so we need to remove TLS 1.3 items + # from the TLS 1.2 list + tls13Suites.append(TLS_AES_256_GCM_SHA384) + tls12Suites.remove(TLS_AES_256_GCM_SHA384) + tls13Suites.append(TLS_AES_128_GCM_SHA256) + tls12Suites.remove(TLS_AES_128_GCM_SHA256) + tls13Suites.append(TLS_CHACHA20_POLY1305_SHA256) + tls12Suites.remove(TLS_CHACHA20_POLY1305_SHA256) + @staticmethod def filterForVersion(suites, minVersion, maxVersion): """Return a copy of suites without ciphers incompatible with version""" includeSuites = set([]) if (3, 0) <= minVersion <= (3, 3): includeSuites.update(CipherSuite.ssl3Suites) - if maxVersion == (3, 3): + if maxVersion >= (3, 3) and minVersion <= (3, 3): includeSuites.update(CipherSuite.tls12Suites) + if maxVersion > (3, 3): + includeSuites.update(CipherSuite.tls13Suites) return [s for s in suites if s in includeSuites] @staticmethod @@ -945,6 +971,8 @@ def _filterSuites(suites, settings, version=None): cipherSuites += CipherSuite.nullSuites keyExchangeSuites = [] + if version >= (3, 4): + keyExchangeSuites += CipherSuite.tls13Suites if "rsa" in keyExchangeNames: keyExchangeSuites += CipherSuite.certSuites if "dhe_rsa" in keyExchangeNames: @@ -963,6 +991,11 @@ def _filterSuites(suites, settings, version=None): return [s for s in suites if s in macSuites and s in cipherSuites and s in keyExchangeSuites] + @classmethod + def getTLS13Suites(cls, settings, version=None): + """Return cipher suites that are TLS 1.3 specific.""" + return cls._filterSuites(CipherSuite.tls13Suites, settings, version) + #: SRP key exchange, no certificate base authentication srpSuites = [] srpSuites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA) diff --git a/unit_tests/test_tlslite_constants.py b/unit_tests/test_tlslite_constants.py index e1a65838..152c69fb 100644 --- a/unit_tests/test_tlslite_constants.py +++ b/unit_tests/test_tlslite_constants.py @@ -9,6 +9,7 @@ except ImportError: import unittest +from tlslite.handshakesettings import HandshakeSettings from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ ContentType, AlertDescription, AlertLevel, HandshakeType, GroupName, \ TLSEnum, SignatureScheme @@ -113,12 +114,13 @@ def test_filterForVersion_with_unknown_ciphers(self): self.assertEqual(filtered, []) - def test_filterForVersion_with_TLS_1_2_ciphers(self): + def test_filterForVersion_with_TLS_1_1(self): suites = [CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_RSA_WITH_RC4_128_MD5, CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, - CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256] + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite.TLS_AES_128_GCM_SHA256] filtered = CipherSuite.filterForVersion(suites, (3, 2), (3, 2)) @@ -127,6 +129,48 @@ def test_filterForVersion_with_TLS_1_2_ciphers(self): CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_RSA_WITH_RC4_128_MD5]) + def test_filterForVersion_with_TLS_1_2(self): + suites = [CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_RC4_128_MD5, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite.TLS_AES_128_GCM_SHA256] + + filtered = CipherSuite.filterForVersion(suites, (3, 3), (3, 3)) + + self.assertEqual(filtered, + [CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_RC4_128_MD5, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256]) + + def test_filterForVersion_with_TLS_1_3(self): + suites = [CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_AES_128_GCM_SHA256] + + filtered = CipherSuite.filterForVersion(suites, (3, 4), (3, 4)) + + self.assertEqual(filtered, + [CipherSuite.TLS_AES_128_GCM_SHA256]) + + def test_getTLS13Suites(self): + hs = HandshakeSettings() + hs.maxVersion = (3, 4) + self.assertEqual(CipherSuite.getTLS13Suites(hs), + [CipherSuite.TLS_AES_256_GCM_SHA384, + CipherSuite.TLS_AES_128_GCM_SHA256, + CipherSuite.TLS_CHACHA20_POLY1305_SHA256]) + + def test_getTLS13Suites_with_TLS1_2(self): + hs = HandshakeSettings() + hs.maxVersion = (3, 4) + self.assertEqual(CipherSuite.getTLS13Suites(hs, (3, 3)), + []) + class TestSignatureScheme(unittest.TestCase): def test_toRepr_with_valid_value(self): From 73108dfc202cb5e973616afd6ce64059a052f5ca Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 9 Aug 2017 17:09:39 +0200 Subject: [PATCH 503/574] Supported Versions extension from TLS 1.3 --- tlslite/constants.py | 1 + tlslite/extensions.py | 68 ++++++++++++++++++++++++++- unit_tests/test_tlslite_extensions.py | 59 ++++++++++++++++++++++- 3 files changed, 126 insertions(+), 2 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 4c57ff8e..558fac9a 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -132,6 +132,7 @@ class ExtensionType(TLSEnum): client_hello_padding = 21 # RFC 7685 encrypt_then_mac = 22 # RFC 7366 extended_master_secret = 23 # RFC 7627 + supported_versions = 43 # TLS 1.3 supports_npn = 13172 tack = 0xF300 renegotiation_info = 0xff01 # RFC 5746 diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 7c20712d..88fec197 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -529,6 +529,71 @@ def parse(self, p): return self + +class SupportedVersionsExtension(TLSExtension): + """ + This class handles the SupportedVersion extensions used in TLS 1.3. + + See draft-ietf-tls-tls13. + + :vartype extType: int + :ivar extType: numeric type of the Supported Versions extension, i.e. 43 + + :vartype extData: bytearray + :ivar extData: raw representation of the extension data + + :vartype versions: list of tuples + :ivar versions: list of supported protocol versions; each tuple has two + one byte long integers + """ + + def __init__(self): + """Create an instance of SupportedVersionsExtension.""" + super(SupportedVersionsExtension, self).__init__(extType= + ExtensionType. + supported_versions) + self.versions = None + + @property + def extData(self): + """ + Return raw encoding of the extension + + :rtype: bytearray + """ + if self.versions is None: + return bytearray(0) + + writer = Writer() + # elelements 1 byte each, overall length encoded in 1 byte + writer.addVarTupleSeq(self.versions, 1, 1) + return writer.bytes + + def create(self, versions): + """ + Set the list of supported version identifiers + + :param list versions: list of 2-element tuples that specifiy the + protocol identifiers + """ + self.versions = versions + return self + + def parse(self, parser): + """ + Deserialise extension from on the wire data + + :param Parser parser: data to be parsed + :rtype SupportedVersionsExtension + """ + self.versions = parser.getVarTupleList(1, 2, 1) + + if parser.getRemainingLength() != 0: + raise SyntaxError() + + return self + + class ClientCertTypeExtension(VarListExtension): """ This class handles the (client variant of) Certificate Type extension @@ -1378,7 +1443,8 @@ def parse(self, parser): ExtensionType.alpn: ALPNExtension, ExtensionType.supports_npn: NPNExtension, ExtensionType.client_hello_padding: PaddingExtension, - ExtensionType.renegotiation_info: RenegotiationInfoExtension} + ExtensionType.renegotiation_info: RenegotiationInfoExtension, + ExtensionType.supported_versions: SupportedVersionsExtension} TLSExtension._serverExtensions = \ { diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index bfa2d4a8..12ff2550 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -12,7 +12,8 @@ SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\ TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\ SignatureAlgorithmsExtension, PaddingExtension, VarListExtension, \ - RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension + RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension, \ + SupportedVersionsExtension from tlslite.utils.codec import Parser from tlslite.constants import NameType, ExtensionType, GroupName,\ ECPointFormat, HashAlgorithm, SignatureAlgorithm, \ @@ -1711,5 +1712,61 @@ def test_parse_with_trailing_data(self): with self.assertRaises(SyntaxError): self.ext.parse(parser) + +class TestSupportedVersionsExtension(unittest.TestCase): + def test___init__(self): + ext = SupportedVersionsExtension() + + self.assertIsNotNone(ext) + self.assertIsNone(ext.versions) + self.assertEqual(bytearray(0), ext.extData) + self.assertEqual(43, ext.extType) + + def test_create(self): + ext = SupportedVersionsExtension() + + ext = ext.create([(3, 1), (3, 2)]) + + self.assertEqual([(3, 1), (3, 2)], ext.versions) + + def test_extData(self): + ext = SupportedVersionsExtension() + + ext = ext.create([(3, 3), (3, 4)]) + + self.assertEqual(ext.extData, bytearray(b'\x04' # overall length + b'\x03\x03' # first item + b'\x03\x04')) # second item + + def test_parse(self): + ext = TLSExtension() + + p = Parser(bytearray( + b'\x00\x2b' # type of ext + b'\x00\x05' # length + b'\x04' # length of array inside + b'\x03\x03' # first item + b'\x03\x04')) # second item + + ext = ext.parse(p) + + self.assertIsInstance(ext, SupportedVersionsExtension) + self.assertEqual([(3, 3), (3, 4)], ext.versions) + + def test_parse_with_trailing_data(self): + ext = TLSExtension() + + p = Parser(bytearray( + b'\x00\x2b' # type of ext + b'\x00\x06' # length + b'\x04' # length of array inside + b'\x03\x03' # first item + b'\x03\x04' # second item + b'\x00')) # trailing byte + + with self.assertRaises(SyntaxError): + ext.parse(p) + + if __name__ == '__main__': unittest.main() From 8a79706ddc31789d8b7fe7e31708f79388f8a882 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 9 Aug 2017 17:18:33 +0200 Subject: [PATCH 504/574] introduce abstract type for extensions that are a list of tuples Both Signature Algorithms and Protocol Versions (from TLS1.3) have the same format, so allow for future code sharing between them --- tlslite/extensions.py | 163 +++++++++++++++++++++----- unit_tests/test_tlslite_extensions.py | 94 ++++++++++++++- 2 files changed, 228 insertions(+), 29 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 88fec197..1e075a07 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -236,38 +236,39 @@ def __repr__(self): " serverType={2!r})".format(self.extType, self.extData, self.serverType) -class VarListExtension(TLSExtension): + +class ListExtension(TLSExtension): """ - Abstract extension for handling extensions comprised only of a value list + Abstract class for extensions that deal with single list in payload. Extension for handling arbitrary extensions comprising of just a list of same-sized elementes inside an array """ - def __init__(self, elemLength, lengthLength, fieldName, extType): - super(VarListExtension, self).__init__(extType=extType) + def __init__(self, fieldName, extType): + """ + Create instance of the class. + + :param str fieldName: name of the field to store the list that is + the payload + :type int extType: numerical ID of the extension + """ + super(ListExtension, self).__init__(extType=extType) self._fieldName = fieldName self._internalList = None - self._elemLength = elemLength - self._lengthLength = lengthLength @property def extData(self): - """Return raw data encoding of the extension + """ + Return raw data encoding of the extension. :rtype: bytearray """ - if self._internalList is None: - return bytearray(0) - - writer = Writer() - writer.addVarSeq(self._internalList, - self._elemLength, - self._lengthLength) - return writer.bytes + raise NotImplementedError("Abstract class") def create(self, values): - """Set the list to specified values + """ + Set the list to specified values. :param list values: list of values to save """ @@ -276,21 +277,15 @@ def create(self, values): def parse(self, parser): """ - Deserialise extension from on-the-wire data + Deserialise extension from on-the-wire data. :param tlslite.utils.codec.Parser parser: data :rtype: Extension """ - if parser.getRemainingLength() == 0: - self._internalList = None - return self - - self._internalList = parser.getVarList(self._elemLength, - self._lengthLength) - return self + raise NotImplementedError("Abstract class") def __getattr__(self, name): - """Return the special field name value""" + """Return the special field name value.""" if name == '_fieldName': raise AttributeError("type object '{0}' has no attribute '{1}'"\ .format(self.__class__.__name__, name)) @@ -300,20 +295,132 @@ def __getattr__(self, name): .format(self.__class__.__name__, name)) def __setattr__(self, name, value): - """Set the special field value""" + """Set the special field value.""" if name == '_fieldName': - super(VarListExtension, self).__setattr__(name, value) + super(ListExtension, self).__setattr__(name, value) return if hasattr(self, '_fieldName') and name == self._fieldName: self._internalList = value return - super(VarListExtension, self).__setattr__(name, value) + super(ListExtension, self).__setattr__(name, value) def __repr__(self): + """Return human readable representation of the extension.""" return "{0}({1}={2!r})".format(self.__class__.__name__, self._fieldName, self._internalList) + +class VarListExtension(ListExtension): + """ + Abstract extension for handling extensions comprised of uniform value list. + + Extension for handling arbitrary extensions comprising of just a list + of same-sized elementes inside an array + """ + + def __init__(self, elemLength, lengthLength, fieldName, extType): + super(VarListExtension, self).__init__(fieldName, extType=extType) + self._elemLength = elemLength + self._lengthLength = lengthLength + + @property + def extData(self): + """ + Return raw data encoding of the extension. + + :rtype: bytearray + """ + if self._internalList is None: + return bytearray(0) + + writer = Writer() + writer.addVarSeq(self._internalList, + self._elemLength, + self._lengthLength) + return writer.bytes + + def parse(self, parser): + """ + Deserialise extension from on-the-wire data. + + :param tlslite.utils.codec.Parser parser: data + :rtype: Extension + """ + if parser.getRemainingLength() == 0: + self._internalList = None + return self + + self._internalList = parser.getVarList(self._elemLength, + self._lengthLength) + + if parser.getRemainingLength(): + raise SyntaxError() + + return self + + +class VarSeqListExtension(ListExtension): + """ + Abstract extension for handling extensions comprised of tuple list. + + Extension for handling arbitrary extensions comprising of a single list + of same-sized elements in same-sized tuples + """ + + def __init__(self, elemLength, elemNum, lengthLength, fieldName, extType): + """ + Create a handler for extension that has a list of tuples as payload. + + :param int elemLength: number of bytes needed to encode single element + of a tuple + :param int elemNum: number of elements in a tuple + :param int lengthLength: number of bytes needed to encode overall + length of the list + :param str fieldName: name of the field storing the list of elements + :param int extType: numerical ID of the extension encoded + """ + super(VarSeqListExtension, self).__init__(fieldName, extType=extType) + self._elemLength = elemLength + self._elemNum = elemNum + self._lengthLength = lengthLength + + @property + def extData(self): + """ + Return raw data encoding of the extension. + + :rtype: bytearray + """ + if self._internalList is None: + return bytearray(0) + + writer = Writer() + writer.addVarTupleSeq(self._internalList, + self._elemLength, + self._lengthLength) + return writer.bytes + + def parse(self, parser): + """ + Deserialise extension from on-the-wire data. + + :param tlslite.utils.codec.Parser parser: data + :rtype: Extension + """ + if parser.getRemainingLength() == 0: + self._internalList = None + return self + + self._internalList = parser.getVarTupleList(self._elemLength, + self._elemNum, + self._lengthLength) + if parser.getRemainingLength(): + raise SyntaxError() + + return self + + class SNIExtension(TLSExtension): """ Class for handling Server Name Indication (server_name) extension from diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 12ff2550..a561fb42 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -13,7 +13,7 @@ TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\ SignatureAlgorithmsExtension, PaddingExtension, VarListExtension, \ RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension, \ - SupportedVersionsExtension + SupportedVersionsExtension, VarSeqListExtension, ListExtension from tlslite.utils.codec import Parser from tlslite.constants import NameType, ExtensionType, GroupName,\ ECPointFormat, HashAlgorithm, SignatureAlgorithm, \ @@ -347,6 +347,21 @@ def test___repr__(self): "extData=bytearray(b'\\x00\\x00'), serverType=False)", repr(ext)) + +class TestListExtension(unittest.TestCase): + def setUp(self): + self.ext = ListExtension('groups', 0) + + def test_extData(self): + with self.assertRaises(NotImplementedError): + _ = self.ext.extData + + def test_parse(self): + p = Parser(bytearray(0)) + with self.assertRaises(NotImplementedError): + self.ext.parse(p) + + class TestVarListExtension(unittest.TestCase): def setUp(self): self.ext = VarListExtension(1, 1, 'groups', 42) @@ -369,6 +384,72 @@ def test_get_non_existant_attribute(self): self.assertEqual(str(e.exception), "type object 'VarListExtension' has no attribute 'gruppen'") + +class TestVarSeqListExtension(unittest.TestCase): + def setUp(self): + self.ext = VarSeqListExtension(2, 2, 1, 'values', 42) + + def test___init__(self): + self.assertIsNotNone(self.ext) + + def test_get_attribute(self): + self.assertIsNone(self.ext.values) + + def test_set_attribute(self): + self.ext.values = [(2, 3), (3, 4), (7, 9)] + + self.assertEqual(self.ext.values, [(2, 3), (3, 4), (7, 9)]) + + def test_get_non_existant_attribute(self): + with self.assertRaises(AttributeError) as e: + val = self.ext.value + + self.assertEqual(str(e.exception), + "type object 'VarSeqListExtension' has no attribute 'value'") + + def test_empty_extData(self): + self.assertEqual(self.ext.extData, bytearray()) + + def test_extData(self): + self.ext.create([(2, 3), (10, 1)]) + + self.assertEqual(self.ext.extData, + bytearray(#b'\x00\x2a' # ID + #b'\x00\x09' # ext length + b'\x08' # array length + b'\x00\x02\x00\x03' # first tuple + b'\x00\x0a\x00\x01')) # second tuple + + def test_parse(self): + p = Parser(bytearray(#b'\x00\x2a' # ID + #b'\x00\x09' # ext length + b'\x08' # array length + b'\x00\x02\x00\x03' # first tuple + b'\x00\x0a\x00\x01')) # second tuple + + self.ext = self.ext.parse(p) + + self.assertEqual(self.ext.values, [(2, 3), (10, 1)]) + + def test_parse_with_trailing_data(self): + p = Parser(bytearray(#b'\x00\x2a' # ID + #b'\x00\x0a' # ext length + b'\x08' # array length + b'\x00\x02\x00\x03' # first tuple + b'\x00\x0a\x00\x01' # second tuple + b'\x00')) # trailing byte + + with self.assertRaises(SyntaxError): + self.ext.parse(p) + + def test_parse_empty(self): + p = Parser(bytearray(0)) + + self.ext = self.ext.parse(p) + + self.assertIsNone(self.ext.values) + + class TestSNIExtension(unittest.TestCase): def test___init__(self): server_name = SNIExtension() @@ -1292,6 +1373,17 @@ def test_parse_with_empty_data(self): self.assertEqual(ext.extType, ExtensionType.supported_groups) self.assertIsNone(ext.groups) + def test_parse_with_trailing_data(self): + parser = Parser(bytearray( + b'\x00\x04' + # length of extension list array + b'\x00\x13' + # secp192r1 + b'\x00\x15' + # secp224r1 + b'\x00' # trailing byte + )) + + with self.assertRaises(SyntaxError): + SupportedGroupsExtension().parse(parser) + def test_parse_with_empty_array(self): parser = Parser(bytearray(2)) From c44c2309fdd35e4288c3036f03559c2b3b7e8576 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 27 Jul 2017 14:35:57 +0200 Subject: [PATCH 505/574] make Signature Algorithms and Supported Versions share code --- tlslite/extensions.py | 97 ++++--------------------------------------- 1 file changed, 8 insertions(+), 89 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 1e075a07..e494bcbd 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -637,7 +637,7 @@ def parse(self, p): return self -class SupportedVersionsExtension(TLSExtension): +class SupportedVersionsExtension(VarSeqListExtension): """ This class handles the SupportedVersion extensions used in TLS 1.3. @@ -656,49 +656,11 @@ class SupportedVersionsExtension(TLSExtension): def __init__(self): """Create an instance of SupportedVersionsExtension.""" - super(SupportedVersionsExtension, self).__init__(extType= + super(SupportedVersionsExtension, self).__init__(1, 2, 1, + "versions", + extType= ExtensionType. supported_versions) - self.versions = None - - @property - def extData(self): - """ - Return raw encoding of the extension - - :rtype: bytearray - """ - if self.versions is None: - return bytearray(0) - - writer = Writer() - # elelements 1 byte each, overall length encoded in 1 byte - writer.addVarTupleSeq(self.versions, 1, 1) - return writer.bytes - - def create(self, versions): - """ - Set the list of supported version identifiers - - :param list versions: list of 2-element tuples that specifiy the - protocol identifiers - """ - self.versions = versions - return self - - def parse(self, parser): - """ - Deserialise extension from on the wire data - - :param Parser parser: data to be parsed - :rtype SupportedVersionsExtension - """ - self.versions = parser.getVarTupleList(1, 2, 1) - - if parser.getRemainingLength() != 0: - raise SyntaxError() - - return self class ClientCertTypeExtension(VarListExtension): @@ -1163,8 +1125,7 @@ def __init__(self): super(ECPointFormatsExtension, self).__init__(1, 1, 'formats', \ ExtensionType.ec_point_formats) -class SignatureAlgorithmsExtension(TLSExtension): - +class SignatureAlgorithmsExtension(VarSeqListExtension): """ Client side list of supported signature algorithms. @@ -1176,10 +1137,11 @@ class SignatureAlgorithmsExtension(TLSExtension): def __init__(self): """Create instance of class""" - super(SignatureAlgorithmsExtension, self).__init__(extType= + super(SignatureAlgorithmsExtension, self).__init__(1, 2, 2, + 'sigalgs', + extType= ExtensionType. signature_algorithms) - self.sigalgs = None def _repr_sigalgs(self): """Return a text representation of sigalgs field.""" @@ -1202,49 +1164,6 @@ def __repr__(self): return "SignatureAlgorithmsExtension(sigalgs={0})".format( self._repr_sigalgs()) - @property - def extData(self): - """ - Return raw encoding of the extension - - :rtype: bytearray - """ - if self.sigalgs is None: - return bytearray(0) - - writer = Writer() - # elements 1 byte each, overall length encoded in 2 bytes - writer.addVarTupleSeq(self.sigalgs, 1, 2) - return writer.bytes - - def create(self, sigalgs): - """ - Set the list of supported algorithm types - - :param list sigalgs: list of pairs of a hash algorithm and signature - algorithm - """ - self.sigalgs = sigalgs - return self - - def parse(self, parser): - """ - Deserialise extension from on the wire data - - :type Parser parser: data - :rtype: SignatureAlgorithmsExtension - """ - if parser.getRemainingLength() == 0: - self.sigalgs = None - return self - - self.sigalgs = parser.getVarTupleList(1, 2, 2) - - if parser.getRemainingLength() != 0: - raise SyntaxError() - - return self - class PaddingExtension(TLSExtension): """ From 3104c0e5c74aaa75836fede2589fe9281790de87 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 27 Jul 2017 17:13:55 +0200 Subject: [PATCH 506/574] Client side Key Share extension --- tlslite/constants.py | 1 + tlslite/extensions.py | 113 +++++++++++++++++++++++++- unit_tests/test_tlslite_extensions.py | 112 ++++++++++++++++++++++++- 3 files changed, 223 insertions(+), 3 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 558fac9a..7589dffe 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -132,6 +132,7 @@ class ExtensionType(TLSEnum): client_hello_padding = 21 # RFC 7685 encrypt_then_mac = 22 # RFC 7366 extended_master_secret = 23 # RFC 7627 + key_share = 40 # TLS 1.3 supported_versions = 43 # TLS 1.3 supports_npn = 13172 tack = 0xF300 diff --git a/tlslite/extensions.py b/tlslite/extensions.py index e494bcbd..4de5f479 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -1457,6 +1457,116 @@ def parse(self, parser): return self +class KeyShareEntry(object): + """Handler for of the item of the Key Share extension.""" + + def __init__(self): + """Initialise the object.""" + self.group = None + self.key_exchange = None + self.private = None + + def create(self, group, key_exchange, private=None): + """ + Initialise the Key Share Entry from Key Share extension. + + :param int group: ID of the key share + :param bytearray key_exchange: value of the key share + :param object private: private value for the given share (won't be + encoded during serialisation) + :rtype: KeyShareEntry + """ + self.group = group + self.key_exchange = key_exchange + self.private = private + return self + + def parse(self, parser): + """ + Parse the value from on the wire format. + + :param Parser parser: data to be parsed as extension + + :rtype: KeyShareEntry + """ + self.group = parser.get(2) + self.key_exchange = parser.getVarBytes(2) + return self + + def write(self, writer): + """ + Write the on the wire representation of the item to writer. + + :param Writer writer: buffer to write the data to + """ + writer.addTwo(self.group) + writer.addTwo(len(self.key_exchange)) + writer.bytes += self.key_exchange + + +class ClientKeyShareExtension(TLSExtension): + """ + Class for handling the Client Hello version of the Key Share extension. + + Extension for sending the key shares to server + """ + + def __init__(self): + """Create instance of the object.""" + super(ClientKeyShareExtension, self).__init__(extType=ExtensionType. + key_share) + self.client_shares = None + + @property + def extData(self): + """ + Return the on the wire raw encoding of the extension + + :rtype: bytearray + """ + shares = Writer() + for share in self.client_shares: + share.write(shares) + + w = Writer() + w.addTwo(len(shares.bytes)) + w.bytes += shares.bytes + + return w.bytes + + def create(self, client_shares): + """Set the advertised client shares in the extension.""" + self.client_shares = client_shares + return self + + def parse(self, parser): + """ + Parse the extension from on the wire format + + :param Parser parser: data to be parsed + + :raises SyntaxError: when the data does not match the definition + + :rtype: ClientKeyShareExtension + """ + if not parser.getRemainingLength(): + self.client_shares = None + return self + + self.client_shares = [] + parser.startLengthCheck(2) + + while not parser.atLengthCheck(): + self.client_shares.append(KeyShareEntry().parse(parser)) + + parser.stopLengthCheck() + + if parser.getRemainingLength(): + raise SyntaxError("Trailing data in client Key Share extension") + + return self + + TLSExtension._universalExtensions = \ { ExtensionType.server_name: SNIExtension, @@ -1470,7 +1580,8 @@ def parse(self, parser): ExtensionType.supports_npn: NPNExtension, ExtensionType.client_hello_padding: PaddingExtension, ExtensionType.renegotiation_info: RenegotiationInfoExtension, - ExtensionType.supported_versions: SupportedVersionsExtension} + ExtensionType.supported_versions: SupportedVersionsExtension, + ExtensionType.key_share: ClientKeyShareExtension} TLSExtension._serverExtensions = \ { diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index a561fb42..1646581a 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -8,13 +8,21 @@ import unittest2 as unittest except ImportError: import unittest +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call + from tlslite.extensions import TLSExtension, SNIExtension, NPNExtension,\ SRPExtension, ClientCertTypeExtension, ServerCertTypeExtension,\ TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\ SignatureAlgorithmsExtension, PaddingExtension, VarListExtension, \ RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension, \ - SupportedVersionsExtension, VarSeqListExtension, ListExtension -from tlslite.utils.codec import Parser + SupportedVersionsExtension, VarSeqListExtension, ListExtension, \ + ClientKeyShareExtension, KeyShareEntry +from tlslite.utils.codec import Parser, Writer from tlslite.constants import NameType, ExtensionType, GroupName,\ ECPointFormat, HashAlgorithm, SignatureAlgorithm, \ CertificateStatusType @@ -1860,5 +1868,105 @@ def test_parse_with_trailing_data(self): ext.parse(p) +class TestKeyShareEntry(unittest.TestCase): + def setUp(self): + self.kse = KeyShareEntry() + + def test___init__(self): + self.assertIsNotNone(self.kse) + + def test_parse(self): + p = Parser(bytearray(b'\x00\x12' # group ID + b'\x00\x02' # share length + b'\x01\x01')) # key share + + self.kse = self.kse.parse(p) + + self.assertEqual(self.kse.group, 18) + self.assertEqual(self.kse.key_exchange, bytearray(b'\x01\x01')) + + def test_write(self): + w = Writer() + + self.kse.group = 18 + self.kse.key_exchange = bytearray(b'\x01\x01') + + self.kse.write(w) + + self.assertEqual(w.bytes, bytearray(b'\x00\x12' # group ID + b'\x00\x02' # share length + b'\x01\x01')) # key share + + +class TestKeyShareExtension(unittest.TestCase): + def setUp(self): + self.cks = ClientKeyShareExtension() + + def test___init__(self): + self.assertIsNotNone(self.cks) + + def test_create(self): + entry = mock.Mock() + self.cks = self.cks.create([entry]) + + self.assertIs(self.cks.client_shares[0], entry) + + def test_extData(self): + entries = [KeyShareEntry().create(10, bytearray(b'\x12\x13\x14')), + KeyShareEntry().create(12, bytearray(b'\x02'))] + self.cks = self.cks.create(entries) + + self.assertEqual(self.cks.extData, bytearray( + b'\x00\x0c' # list length + b'\x00\x0a' # ID of first entry + b'\x00\x03' # length of share of first entry + b'\x12\x13\x14' # value of share of first entry + b'\x00\x0c' # ID of second entry + b'\x00\x01' # length of share of second entry + b'\x02')) # Value of share of second entry + + def test_parse(self): + p = Parser(bytearray( + b'\x00\x0c' # list length + b'\x00\x0a' # ID of first entry + b'\x00\x03' # length of share of first entry + b'\x12\x13\x14' # value of share of first entry + b'\x00\x0c' # ID of second entry + b'\x00\x01' # length of share of second entry + b'\x02')) # Value of share of second entry + + self.cks = self.cks.parse(p) + + self.assertEqual(len(self.cks.client_shares), 2) + self.assertIsInstance(self.cks.client_shares[0], KeyShareEntry) + self.assertEqual(self.cks.client_shares[0].group, 10) + self.assertEqual(self.cks.client_shares[0].key_exchange, + bytearray(b'\x12\x13\x14')) + self.assertIsInstance(self.cks.client_shares[1], KeyShareEntry) + self.assertEqual(self.cks.client_shares[1].group, 12) + self.assertEqual(self.cks.client_shares[1].key_exchange, + bytearray(b'\x02')) + + def test_parse_missing_list(self): + p = Parser(bytearray()) + + self.cks = self.cks.parse(p) + + self.assertIsNone(self.cks.client_shares) + + def test_parse_empty_list(self): + p = Parser(bytearray(b'\x00\x00')) + + self.cks = self.cks.parse(p) + + self.assertEqual([], self.cks.client_shares) + + def test_parse_with_trailing_data(self): + p = Parser(bytearray(b'\x00\x00\x01')) + + with self.assertRaises(SyntaxError): + self.cks.parse(p) + + if __name__ == '__main__': unittest.main() From 00feb459500af4f8bb84ae7032296c521a4ebdc6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 9 Aug 2017 18:49:50 +0200 Subject: [PATCH 507/574] server side Key Share extension --- tlslite/extensions.py | 52 ++++++++++++++++++- unit_tests/test_tlslite_extensions.py | 74 ++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 4de5f479..a4febdfa 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -1567,6 +1567,55 @@ def parse(self, parser): return self +class ServerKeyShareExtension(TLSExtension): + """ + Class for handling the Server Hello variant of the Key Share extension. + + Extension for sending the key shares to client + """ + + def __init__(self): + """Create instance of the object.""" + super(ServerKeyShareExtension, self).__init__(extType=ExtensionType. + key_share, + server=True) + self.server_share = None + + def create(self, server_share): + """Set the advertised server share in the extension.""" + self.server_share = server_share + return self + + @property + def extData(self): + """Serialise the payload of the extension""" + if self.server_share is None: + return bytearray(0) + + w = Writer() + self.server_share.write(w) + return w.bytes + + def parse(self, parser): + """ + Parse the extension from on the wire format. + + :param Parser parser: data to be parsed + + :rtype: ServerKeyShareExtension + """ + if not parser.getRemainingLength(): + self.server_share = None + return self + + self.server_share = KeyShareEntry().parse(parser) + + if parser.getRemainingLength(): + raise SyntaxError("Trailing data in server Key Share extension") + + return self + + TLSExtension._universalExtensions = \ { ExtensionType.server_name: SNIExtension, @@ -1586,4 +1635,5 @@ def parse(self, parser): TLSExtension._serverExtensions = \ { ExtensionType.cert_type: ServerCertTypeExtension, - ExtensionType.tack: TACKExtension} + ExtensionType.tack: TACKExtension, + ExtensionType.key_share: ServerKeyShareExtension} diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 1646581a..5ea2ac87 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -21,7 +21,7 @@ SignatureAlgorithmsExtension, PaddingExtension, VarListExtension, \ RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension, \ SupportedVersionsExtension, VarSeqListExtension, ListExtension, \ - ClientKeyShareExtension, KeyShareEntry + ClientKeyShareExtension, KeyShareEntry, ServerKeyShareExtension from tlslite.utils.codec import Parser, Writer from tlslite.constants import NameType, ExtensionType, GroupName,\ ECPointFormat, HashAlgorithm, SignatureAlgorithm, \ @@ -1968,5 +1968,77 @@ def test_parse_with_trailing_data(self): self.cks.parse(p) +class TestServerKeyShareExtension(unittest.TestCase): + def setUp(self): + self.ext = ServerKeyShareExtension() + + def test__init__(self): + self.assertIsNotNone(self.ext) + self.assertIsInstance(self.ext, ServerKeyShareExtension) + self.assertIsNone(self.ext.server_share) + + def test_create(self): + ext = self.ext.create(bytearray(b'test')) + + self.assertIsInstance(ext, ServerKeyShareExtension) + self.assertEqual(ext.server_share, bytearray(b'test')) + + def test_parse(self): + parser = Parser(bytearray( + b'\x00\x28' # ID of key_share extension + b'\x00\x07' # length of the extension + b'\x00\x0a' # group ID of first entry + b'\x00\x03' # length of share of first entry + b'\x12\x13\x14' # value of share of first entry + )) + + ext = TLSExtension(server=True) + ext = ext.parse(parser) + + self.assertIsInstance(ext, ServerKeyShareExtension) + self.assertIsInstance(ext.server_share, KeyShareEntry) + self.assertEqual(ext.server_share.group, 10) + self.assertEqual(ext.server_share.key_exchange, + bytearray(b'\x12\x13\x14')) + + def test_parse_with_no_data(self): + parser = Parser(bytearray( + b'\x00\x28' # ID of key_share + b'\x00\x00' # empty payload + )) + ext = TLSExtension(server=True) + ext = ext.parse(parser) + + self.assertIsInstance(ext, ServerKeyShareExtension) + self.assertIsNone(ext.server_share) + + def test_parse_with_trailing_data(self): + parser = Parser(bytearray( + b'\x00\x28' # ID of key_share extension + b'\x00\x08' # length of the extension + b'\x00\x0a' # group ID of first entry + b'\x00\x03' # length of share of first entry + b'\x12\x13\x14' # value of share of first entry + b'\x00' # trailing data + )) + + ext = TLSExtension(server=True) + with self.assertRaises(SyntaxError): + ext.parse(parser) + + def test_extData(self): + entry = KeyShareEntry().create(10, bytearray(b'\x12\x13\x14')) + self.ext.create(entry) + + self.assertEqual(self.ext.extData, + bytearray(b'\x00\x0a' + b'\x00\x03' + b'\x12\x13\x14')) + + def test_extData_with_no_entry(self): + self.assertEqual(self.ext.extData, + bytearray(0)) + + if __name__ == '__main__': unittest.main() From cb518de151e35948033f5c2e7ba92ba3b428eb8a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 18 Aug 2017 17:33:51 +0200 Subject: [PATCH 508/574] allow for encrypted extensions type comment out the code as there are no encrypted extensions that need to use EE-specific parser (all use universal ones) --- tlslite/extensions.py | 25 ++++++++++++++++++++----- unit_tests/test_tlslite_extensions.py | 14 +++++++++++++- unit_tests/test_tlslite_messages.py | 5 +++-- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index a4febdfa..ada5d55d 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -57,6 +57,10 @@ class TLSExtension(object): specific parser, otherwise it used universal or ClientHello specific parser + :vartype encExtType: boolean + :ivar encExtType: indicates that the extension should be the type from + Encrypted Extensions + :vartype _universalExtensions: dict :cvar _universalExtensions: dictionary with concrete implementations of specific TLS extensions where key is the numeric value of the extension @@ -72,8 +76,9 @@ class TLSExtension(object): # actual definition at the end of file, after definitions of all classes _universalExtensions = {} _serverExtensions = {} + #_encryptedExtensions = {} - def __init__(self, server=False, extType=None): + def __init__(self, server=False, extType=None, encExt=False): """ Creates a generic TLS extension. @@ -86,10 +91,13 @@ def __init__(self, server=False, extType=None): for parsing :param int extType: type of extension encoded as an integer, to be used by subclasses + :param bool encExt: whether to select the EncryptedExtensions type + for parsing """ self.extType = extType self._extData = bytearray(0) self.serverType = server + self.encExtType = encExt @property def extData(self): @@ -193,12 +201,17 @@ def parse(self, p): extType = p.get(2) extLength = p.get(2) - # first check if we shouldn't use server side parser + # Check if we shouldn't use Encrypted Extensions parser + #if self.encExtType and extType in self._encryptedExtensions: + # return self._parseExt(p, extType, extLength, + # self._encryptedExtensions) + + # then check if we shouldn't use server side parser if self.serverType and extType in self._serverExtensions: return self._parseExt(p, extType, extLength, self._serverExtensions) - # then fallback to universal/ClientHello-specific parsers + # fallback to universal/ClientHello-specific parsers if extType in self._universalExtensions: return self._parseExt(p, extType, extLength, self._universalExtensions) @@ -233,8 +246,10 @@ def __repr__(self): :rtype: str """ return "TLSExtension(extType={0!r}, extData={1!r},"\ - " serverType={2!r})".format(self.extType, self.extData, - self.serverType) + " serverType={2!r}, encExtType={3!r})".format(self.extType, + self.extData, + self.serverType, + self.encExtType) class ListExtension(TLSExtension): diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 5ea2ac87..9eaf85ea 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -332,6 +332,17 @@ def test_parse_of_server_hello_extension(self): self.assertEqual(1, ext.cert_type) + def test_parse_with_encrypted_extensions_type_extension(self): + ext = TLSExtension(encExt=True) + parser = Parser(bytearray(b'\x00\x0a' + b'\x00\x04' + b'\x00\x02' + b'\x00\x13')) + ext = ext.parse(parser) + + self.assertIsInstance(ext, SupportedGroupsExtension) + self.assertEqual(ext.groups, [GroupName.secp192r1]) + def test_parse_with_client_cert_type_extension(self): ext = TLSExtension() @@ -352,7 +363,8 @@ def test___repr__(self): ext = ext.create(0, bytearray(b'\x00\x00')) self.assertEqual("TLSExtension(extType=0, "\ - "extData=bytearray(b'\\x00\\x00'), serverType=False)", + "extData=bytearray(b'\\x00\\x00'), serverType=False, " + "encExtType=False)", repr(ext)) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index b3b2a7d5..9e0c4a2d 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -478,7 +478,7 @@ def test___str___with_extensions(self): "session ID(bytearray(b'')),cipher suites([]),"\ "compression methods([0]),extensions(["\ "TLSExtension(extType=0, extData=bytearray(b'\\x00'), "\ - "serverType=False)])", + "serverType=False, encExtType=False)])", str(client_hello)) def test___repr__(self): @@ -489,7 +489,8 @@ def test___repr__(self): "random=bytearray(b'\\x00'), session_id=bytearray(b''), "\ "cipher_suites=[], compression_methods=[0], "\ "extensions=[TLSExtension(extType=0, "\ - "extData=bytearray(b''), serverType=False)])", + "extData=bytearray(b''), serverType=False, " + "encExtType=False)])", repr(client_hello)) def test_getExtension(self): From 5d6bc8f1e372d1859293e7905649f903f9f62c6e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 27 Jul 2017 17:34:37 +0200 Subject: [PATCH 509/574] parsing and writing TLS 1.3 Server Hello --- tlslite/messages.py | 16 +++++++++---- unit_tests/test_tlslite_messages.py | 35 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 0a205c28..e21e32e8 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -869,9 +869,15 @@ def parse(self, p): p.startLengthCheck(3) self.server_version = (p.get(1), p.get(1)) self.random = p.getFixBytes(32) - self.session_id = p.getVarBytes(1) + if self.server_version <= (3, 3): + self.session_id = p.getVarBytes(1) + else: + self.session_id = None self.cipher_suite = p.get(2) - self.compression_method = p.get(1) + if self.server_version <= (3, 3): + self.compression_method = p.get(1) + else: + self.compression_method = None if not p.atLengthCheck(): self.extensions = [] totalExtLength = p.get(2) @@ -887,9 +893,11 @@ def write(self): w.add(self.server_version[0], 1) w.add(self.server_version[1], 1) w.bytes += self.random - w.addVarSeq(self.session_id, 1, 1) + if self.server_version <= (3, 3): + w.addVarSeq(self.session_id, 1, 1) w.add(self.cipher_suite, 2) - w.add(self.compression_method, 1) + if self.server_version <= (3, 3): + w.add(self.compression_method, 1) if self.extensions is not None: w2 = Writer() diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 9e0c4a2d..c0a2e51e 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -1117,6 +1117,24 @@ def test_parse_with_NPN_extension(self): self.assertEqual([bytearray(b'http/1.1'), bytearray(b'spdy/3')], server_hello.next_protos) + def test_parse_tls1_3(self): + parser = Parser(bytearray( + # b'\x02' + # type - server_hello + b'\x00\x00\x26' + # overall length + b'\x03\x04' + # protocol version + b'\x02' * 32 + # random + b'\x00\x04' + # cipher suite + b'\x00\x00')) # extensions + + server_hello = ServerHello().parse(parser) + + self.assertIsInstance(server_hello, ServerHello) + self.assertEqual(server_hello.server_version, (3, 4)) + self.assertEqual(server_hello.random, bytearray(b'\x02' * 32)) + self.assertEqual(server_hello.cipher_suite, 4) + self.assertEqual(server_hello.extensions, []) + + def test_write(self): server_hello = ServerHello().create( (1,1), # server version @@ -1166,6 +1184,23 @@ def test_write_with_next_protos(self): b'\x68\x74\x74\x70\x2f\x31\x2e\x31' )), list(server_hello.write())) + def test_write_tls1_3(self): + server_hello = ServerHello().create( + (3, 4), # version + bytearray(b'\x02'*32), # random + None, # session id + 4, # cipher suite + extensions=[]) + + self.assertEqual(list(bytearray( + b'\x02' + # type - server_hello + b'\x00\x00\x26' + # overall length + b'\x03\x04' + # protocol version + b'\x02' * 32 + # random + b'\x00\x04' + # cipher suite + b'\x00\x00')), # extensions + list(server_hello.write())) + def test___str__(self): server_hello = ServerHello() server_hello = server_hello.create( From 66943a6ae207dc3873f446bcf02b8556c3de607c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 9 Aug 2017 19:55:24 +0200 Subject: [PATCH 510/574] Encrypted Extensions message --- tlslite/constants.py | 1 + tlslite/messages.py | 44 ++++++++++++++++ unit_tests/test_tlslite_messages.py | 82 ++++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 2 deletions(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 7589dffe..0bdcf001 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -89,6 +89,7 @@ class HandshakeType(TLSEnum): hello_request = 0 client_hello = 1 server_hello = 2 + encrypted_extensions = 8 certificate = 11 server_key_exchange = 12 certificate_request = 13 diff --git a/tlslite/messages.py b/tlslite/messages.py index e21e32e8..a6fc0028 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1680,6 +1680,50 @@ def write(self): return self.postWrite(w) +class EncryptedExtensions(HandshakeMsg): + """Handling of the TLS1.3 Encrypted Extensions message.""" + + def __init__(self): + super(EncryptedExtensions, self).__init__( + HandshakeType.encrypted_extensions) + self.extensions = None + + def create(self, extensions): + """Set the extensions in the message.""" + self.extensions = extensions + + def parse(self, parser): + """Parse the extensions from on the wire data.""" + parser.startLengthCheck(3) + + if not parser.getRemainingLength(): + raise SyntaxError("No list of extensions") + else: + self.extensions = [] + p2 = Parser(parser.getVarBytes(2)) + while p2.getRemainingLength(): + self.extensions.append(TLSExtension(encExt=True).parse(p2)) + + parser.stopLengthCheck() + return self + + def write(self): + """ + Serialise the message to on the wire data. + + :rtype: bytearray + """ + w = Writer() + w2 = Writer() + for ext in self.extensions: + w2.bytes += ext.write() + + w.add(len(w2.bytes), 2) + w.bytes += w2.bytes + + return self.postWrite(w) + + class SSL2Finished(HandshakeMsg): """Handling of the SSL2 FINISHED messages.""" diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index c0a2e51e..474f17a2 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -12,7 +12,7 @@ CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus, \ Certificate, Finished, HelloMessage, ChangeCipherSpec, NextProtocol, \ - ApplicationData + ApplicationData, EncryptedExtensions from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ @@ -20,7 +20,7 @@ SSL2HandshakeType, CertificateStatusType, HandshakeType, \ SignatureScheme from tlslite.extensions import SNIExtension, ClientCertTypeExtension, \ - SRPExtension, TLSExtension, NPNExtension + SRPExtension, TLSExtension, NPNExtension, SupportedGroupsExtension from tlslite.errors import TLSInternalError from tlslite.x509 import X509 from tlslite.x509certchain import X509CertChain @@ -1257,6 +1257,7 @@ def test___repr__(self): "cipher_suite=34500, compression_method=0, _tack_ext=None, "\ "extensions=[])", repr(server_hello)) + class TestServerHello2(unittest.TestCase): def test___init__(self): sh = ServerHello2() @@ -2928,5 +2929,82 @@ def test_splitFirstByte(self): self.assertEqual(app_data.bytes, bytearray(b'est')) +class TestEncryptedExtensions(unittest.TestCase): + def setUp(self): + self.msg = EncryptedExtensions() + + def test___init__(self): + self.assertIsNotNone(self.msg) + self.assertEqual(self.msg.handshakeType, 8) + + def test_create(self): + + ext = SNIExtension() + + self.msg.create([ext]) + + self.assertIsInstance(self.msg.extensions[0], SNIExtension) + + def test_parse(self): + parser = Parser(bytearray( + # b'\x08' # type + b'\x00\x00\x02' # overall length + b'\x00\x00')) # extensions list + + ext = self.msg.parse(parser) + + self.assertEqual(ext.extensions, []) + + def test_parse_with_list_missing(self): + parser = Parser(bytearray( + b'\x00\x00\x00')) + + with self.assertRaises(SyntaxError): + self.msg.parse(parser) + + def test_parse_with_extension(self): + parser = Parser(bytearray( + b'\x00\x00\x0c' # overall length + b'\x00\x0a' # extensions list length + b'\x00\x0a' # supported groups extension + b'\x00\x06' # key share extension length + b'\x00\x04' # length of named_group_list + b'\x00\x17\x00\x1D')) # secp256r1 and x25519 + + ext = self.msg.parse(parser) + + self.assertEqual(len(ext.extensions), 1) + self.assertIsInstance(ext.extensions[0], SupportedGroupsExtension) + self.assertEqual(ext.extensions[0].groups, [GroupName.secp256r1, + GroupName.x25519]) + + def test_parse_with_trailing_data(self): + parser = Parser(bytearray( + b'\x00\x00\x0d' # overall length + b'\x00\x0a' # extensions list length + b'\x00\x0a' # supported groups extension + b'\x00\x06' # key share extension length + b'\x00\x04' # length of named_group_list + b'\x00\x17\x00\x1D' # secp256r1 and x25519 + b'\x00')) # tailing data + + with self.assertRaises(SyntaxError): + self.msg.parse(parser) + + def test_write(self): + ext = SupportedGroupsExtension().create([GroupName.secp256r1, + GroupName.x25519]) + self.msg.create([ext]) + + self.assertEqual(self.msg.write(), + bytearray(b'\x08' # handshake type - encrypted extensions + b'\x00\x00\x0c' # overall length + b'\x00\x0a' # extensions list length + b'\x00\x0a' # supported groups extension + b'\x00\x06' # key share extension length + b'\x00\x04' # length of named_group_list + b'\x00\x17\x00\x1D')) # secp256r1 and x25519 + + if __name__ == '__main__': unittest.main() From 3ebc62b3661a80c255b038c0ba26c529563f11a4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 27 Jul 2017 18:21:44 +0200 Subject: [PATCH 511/574] TLS 1.3 support for Finished --- tlslite/messages.py | 5 ++++- unit_tests/test_tlslite_messages.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index a6fc0028..78d78210 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1654,10 +1654,11 @@ def write(self, trial=False): class Finished(HandshakeMsg): - def __init__(self, version): + def __init__(self, version, hash_length=None): HandshakeMsg.__init__(self, HandshakeType.finished) self.version = version self.verify_data = bytearray(0) + self.hash_length = hash_length def create(self, verify_data): self.verify_data = verify_data @@ -1669,6 +1670,8 @@ def parse(self, p): self.verify_data = p.getFixBytes(36) elif self.version in ((3, 1), (3, 2), (3, 3)): self.verify_data = p.getFixBytes(12) + elif self.version > (3, 3): + self.verify_data = p.getFixBytes(self.hash_length) else: raise AssertionError() p.stopLengthCheck() diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 474f17a2..8c465526 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -2606,6 +2606,15 @@ def test_parse_tls_1_2_with_invalid_number_of_bytes(self): with self.assertRaises(SyntaxError): finished.parse(parser) + def test_parse_tls_1_3(self): + finished = Finished((3, 4), 32) + + parser = Parser(bytearray(b'\x00\x00\x20' + b'\x04' * 32)) + + finished = finished.parse(parser) + + self.assertEqual(finished.verify_data, bytearray(b'\x04' * 32)) + def test_write_tls_1_2(self): finished = Finished((3, 3)) @@ -2614,6 +2623,14 @@ def test_write_tls_1_2(self): self.assertEqual(finished.write(), bytearray(b'\x14' + b'\x00\x00\x0c' + b'\x04' * 12)) + def test_write_tls_1_3(self): + finished = Finished((3, 4), 32) + + finished = finished.create(bytearray(b'\x04' * 32)) + + self.assertEqual(finished.write(), + bytearray(b'\x14' + b'\x00\x00\x20' + b'\x04' * 32)) + class TestClientFinished(unittest.TestCase): def test___init__(self): From 67a2ff23061b8e015274f7952073647d6a90abb5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 9 Aug 2017 18:58:54 +0200 Subject: [PATCH 512/574] TLS 1.3 specific alert descriptions --- tlslite/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 0bdcf001..e3fe2858 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -386,6 +386,7 @@ class AlertDescription(TLSEnum): inappropriate_fallback = 86 user_canceled = 90 no_renegotiation = 100 + missing_extension = 109 unsupported_extension = 110 # RFC 5246 certificate_unobtainable = 111 # RFC 6066 unrecognized_name = 112 # RFC 6066 From f7916e5e604a9256610e2270aca31878f44441d7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 2 Aug 2017 20:32:27 +0200 Subject: [PATCH 513/574] handling of Certificate message-specific extensions --- tlslite/extensions.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index ada5d55d..6d996a19 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -72,13 +72,22 @@ class TLSExtension(object): specific TLS extensions where key is the numeric value of the extension ID. Includes only those extensions that require special handlers for ServerHello versions. + + :vartype _certificateExtensions: dict + :cvat _certificateExtensions: dictionary with concrete implementations of + specific TLS extensions where the key is the numeric value of the + type of the extension and the value is the class. Includes only + those extensions that require special handlers for Certificate + message. """ # actual definition at the end of file, after definitions of all classes _universalExtensions = {} _serverExtensions = {} #_encryptedExtensions = {} + _certificateExtensions = {} - def __init__(self, server=False, extType=None, encExt=False): + def __init__(self, server=False, extType=None, encExt=False, + cert=False): """ Creates a generic TLS extension. @@ -93,11 +102,14 @@ def __init__(self, server=False, extType=None, encExt=False): by subclasses :param bool encExt: whether to select the EncryptedExtensions type for parsing + :param bool cert: whether to select the Certificate type + of extension for parsing """ self.extType = extType self._extData = bytearray(0) self.serverType = server self.encExtType = encExt + self.cert = cert @property def extData(self): @@ -201,6 +213,11 @@ def parse(self, p): extType = p.get(2) extLength = p.get(2) + # check if we shouldn't use Certificate extensions parser + if self.cert and extType in self._certificateExtensions: + return self._parseExt(p, extType, extLength, + self._certificateExtensions) + # Check if we shouldn't use Encrypted Extensions parser #if self.encExtType and extType in self._encryptedExtensions: # return self._parseExt(p, extType, extLength, From 8113f3204d144bbbf9f41b2167503b2db1b12437 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 11 Aug 2017 12:31:24 +0200 Subject: [PATCH 514/574] TLS1.3 Certificate handshake message --- tlslite/messages.py | 129 +++++++++++++++++++++-- unit_tests/test_tlslite_messages.py | 153 +++++++++++++++++++++++++++- 2 files changed, 271 insertions(+), 11 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 78d78210..f8c90dbd 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1001,17 +1001,104 @@ def parse(self, parser): return self -class Certificate(HandshakeMsg): +class CertificateEntry(object): + """ + Object storing a single certificate from TLS 1.3. + + Stores a certificate (or possibly a raw public key) together with + associated extensions + """ + def __init__(self, certificateType): + """Initialise the object for given certificate type.""" + self.certificateType = certificateType + self.certificate = None + self.extensions = None + + def create(self, certificate, extensions): + """Set all values of the certificate entry.""" + self.certificate = certificate + self.extensions = extensions + return self + + def write(self): + """Serialise the object.""" + writer = Writer() + if self.certificateType == CertificateType.x509: + writer.addVarSeq(self.certificate.writeBytes(), 1, 3) + else: + raise ValueError("Set certificate type ({0}) unsupported" + .format(self.certificateType)) + + if self.extensions is not None: + writer2 = Writer() + for ext in self.extensions: + writer2.bytes += ext.write() + writer.addVarSeq(writer2.bytes, 1, 2) + + return writer.bytes + + def parse(self, parser): + """Deserialise the object from on the wire data.""" + if self.certificateType == CertificateType.x509: + certBytes = parser.getVarBytes(3) + x509 = X509() + x509.parseBinary(certBytes) + self.certificate = x509 + else: + raise ValueError("Set certificate type ({0}) unsupported" + .format(self.certificateType)) + + self.extensions = [] + parser.startLengthCheck(2) + while not parser.atLengthCheck(): + ext = TLSExtension(cert=True).parse(parser) + self.extensions.append(ext) + parser.stopLengthCheck() + return self + + +class Certificate(HandshakeMsg): + def __init__(self, certificateType, version=(3, 2)): HandshakeMsg.__init__(self, HandshakeType.certificate) self.certificateType = certificateType - self.certChain = None + self._certChain = None + self.version = version + self.certificate_list = None + self.certificate_request_context = None + + @property + def certChain(self): + if self._certChain: + return self._certChain + elif self.certificate_list is None: + return None + else: + return X509CertChain([i.certificate + for i in self.certificate_list]) - def create(self, certChain): - self.certChain = certChain + def create(self, certChain, context=None): + if isinstance(certChain, X509CertChain): + self._certChain = certChain + else: + self.certificate_list = certChain + self.certificate_request_context = context return self - def parse(self, p): + def _parse_certificate_list(self, parser): + self.certificate_list = [] + while parser.getRemainingLength(): + entry = CertificateEntry(self.certificateType) + self.certificate_list.append(entry.parse(parser)) + + def _parse_tls13(self, parser): + parser.startLengthCheck(3) + self.certificate_request_context = parser.getVarBytes(1) + self._parse_certificate_list(Parser(parser.getVarBytes(3))) + parser.stopLengthCheck() + return self + + def _parse_tls12(self, p): p.startLengthCheck(3) if self.certificateType == CertificateType.x509: chainLength = p.get(3) @@ -1024,19 +1111,34 @@ def parse(self, p): certificate_list.append(x509) index += len(certBytes)+3 if certificate_list: - self.certChain = X509CertChain(certificate_list) + self._certChain = X509CertChain(certificate_list) else: raise AssertionError() p.stopLengthCheck() return self - def write(self): + def parse(self, p): + if self.version <= (3, 3): + return self._parse_tls12(p) + else: + return self._parse_tls13(p) + + def _write_tls13(self): + w = Writer() + w.addVarSeq(self.certificate_request_context, 1, 1) + w2 = Writer() + for entry in self.certificate_list: + w2.bytes += entry.write() + w.addVarSeq(w2.bytes, 1, 3) + return w + + def _write_tls12(self): w = Writer() if self.certificateType == CertificateType.x509: chainLength = 0 - if self.certChain: - certificate_list = self.certChain.x509List + if self._certChain: + certificate_list = self._certChain.x509List else: certificate_list = [] # determine length @@ -1050,7 +1152,14 @@ def write(self): w.addVarSeq(bytes, 1, 3) else: raise AssertionError() - return self.postWrite(w) + return w + + def write(self): + if self.version <= (3, 3): + writer = self._write_tls12() + else: + writer = self._write_tls13() + return self.postWrite(writer) class CertificateRequest(HandshakeMsg): diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 8c465526..91977cb9 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -7,12 +7,20 @@ import unittest2 as unittest except ImportError: import unittest +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call + + from tlslite.messages import ClientHello, ServerHello, RecordHeader3, Alert, \ RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus, \ Certificate, Finished, HelloMessage, ChangeCipherSpec, NextProtocol, \ - ApplicationData, EncryptedExtensions + ApplicationData, EncryptedExtensions, CertificateEntry from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ @@ -2740,6 +2748,98 @@ def test_write(self): b'\xbc\xaa')) +class TestCertificateEntry(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.x509_cert = X509().parse(srv_raw_certificate) + cls.der_cert = cls.x509_cert.writeBytes() + + def test___init__(self): + entry = CertificateEntry(CertificateType.x509) + + self.assertIsNotNone(entry) + self.assertIsInstance(entry, CertificateEntry) + + def test_create(self): + entry = CertificateEntry(CertificateType.x509) + a = mock.Mock() + b = mock.Mock() + entry = entry.create(a, b) + + self.assertIs(entry.certificate, a) + self.assertIs(entry.extensions, b) + + def test_write(self): + entry = CertificateEntry(CertificateType.x509) + ext = TLSExtension(extType=255).create(bytearray(b'\xde\xad')) + entry = entry.create(self.x509_cert, [ext]) + + self.assertEqual(entry.write(), + bytearray(b'\x00\x01\xfa' + #length of certificate + self.der_cert + + b'\x00\x06' + # length of extensions + b'\x00\xff' + # type of first extension + b'\x00\x02' + # length of first extension + b'\xde\xad')) # extension payload + + def test_write_without_extensions(self): + entry = CertificateEntry(CertificateType.x509) + entry = entry.create(self.x509_cert, []) + + self.assertEqual(entry.write(), + bytearray(b'\x00\x01\xfa' + # length of certificate + self.der_cert + + b'\x00\x00')) # length of extensions + + def test_write_with_null_extensions(self): + # that's invalid formatting of the entry, used for debugging/fuzzer + entry = CertificateEntry(CertificateType.x509) + entry = entry.create(self.x509_cert, None) + + self.assertEqual(entry.write(), + bytearray(b'\x00\x01\xfa' + # length of certificate + self.der_cert)) + + def test_write_with_unsupported_type(self): + entry = CertificateEntry(CertificateType.openpgp) + entry = entry.create(self.x509_cert, []) + + with self.assertRaises(ValueError): + entry.write() + + def test_parse(self): + parser = Parser( + bytearray(b'\x00\x01\xfa' + #length of certificate + self.der_cert + + b'\x00\x06' + # length of extensions + b'\x00\xff' + # type of first extension + b'\x00\x02' + # length of first extension + b'\xde\xad')) # extension payload + + entry = CertificateEntry(CertificateType.x509) + entry = entry.parse(parser) + + self.assertIsInstance(entry, CertificateEntry) + self.assertEqual(entry.certificate.writeBytes(), self.der_cert) + self.assertEqual(len(entry.extensions), 1) + self.assertEqual(entry.extensions[0].extData, bytearray(b'\xde\xad')) + + def test_parse_with_unsupported_type(self): + parser = Parser( + bytearray(b'\x00\x01\xfa' + #length of certificate + self.der_cert + + b'\x00\x06' + # length of extensions + b'\x00\xff' + # type of first extension + b'\x00\x02' + # length of first extension + b'\xde\xad')) # extension payload + + entry = CertificateEntry(CertificateType.openpgp) + + with self.assertRaises(ValueError): + entry.parse(parser) + + class TestCertificate(unittest.TestCase): @classmethod @@ -2805,6 +2905,25 @@ def test_write_with_cert(self): b'\x00\x01\xfa' + # length of the first certificate self.der_cert)) + def test_write_tls13_with_cert(self): + cert = Certificate(CertificateType.x509, (3, 4)) + ext = TLSExtension(extType=255).create(bytearray(b'\xde\xad')) + entry = CertificateEntry(CertificateType.x509) + entry = entry.create(self.x509_cert, [ext]) + cert = cert.create([entry], bytearray(b'')) + + self.assertEqual(cert.write(), + bytearray(b'\x0b' + # type of message - certificate + b'\x00\x02\x09' + # length of handshake message + b'\x00' + # length of certificate request context + b'\x00\x02\x05' + # length of certificate list + b'\x00\x01\xfa' + # length of certificate + self.der_cert + + b'\x00\x06' + # length of extensions + b'\x00\xff' + # type of first extension + b'\x00\x02' + # length of first extension + b'\xde\xad')) + def test_parse_with_cert(self): cert = Certificate(CertificateType.x509) parser = Parser( @@ -2821,6 +2940,38 @@ def test_parse_with_cert(self): self.assertEqual(cert.certChain.x509List[0].writeBytes(), self.der_cert) + def test_parse_tls1_3_with_cert(self): + cert = Certificate(CertificateType.x509, (3, 4)) + self.assertEqual(0x0001fa, len(self.der_cert)) + parser = Parser( + bytearray(#b'\x0b' + # type of message - certificate + b'\x00\x02\x09' + # length of handshake message + b'\x00' + # length of certificate request context + b'\x00\x02\x05' + # length of certificate list + b'\x00\x01\xfa' + # length of certificate + self.der_cert + + b'\x00\x06' + # length of extensions + b'\x00\xff' + # type of first extension + b'\x00\x02' + # length of first extension + b'\xde\xad')) + + cert = cert.parse(parser) + + self.assertIsNotNone(cert) + self.assertEqual(cert.certificate_request_context, bytearray(b'')) + self.assertIsNotNone(cert.certificate_list) + self.assertEqual(len(cert.certificate_list), 1) + self.assertIsInstance(cert.certificate_list[0], CertificateEntry) + entry = cert.certificate_list[0] + self.assertEqual(entry.certificate.writeBytes(), self.der_cert) + self.assertEqual(len(entry.extensions), 1) + self.assertEqual(entry.extensions[0].extData, bytearray(b'\xde\xad')) + self.assertIsNotNone(cert.certChain) + self.assertIsInstance(cert.certChain, X509CertChain) + self.assertEqual(len(cert.certChain.x509List), 1) + self.assertEqual(cert.certChain.x509List[0].writeBytes(), + self.der_cert) + def test_parse_with_openpgp_type(self): cert = Certificate(CertificateType.openpgp) From fd7bc2d145a1aa5652a560efec0231f089aa84d1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 10 Aug 2017 18:49:39 +0200 Subject: [PATCH 515/574] __repr__ for Certificate --- tlslite/messages.py | 14 ++++++++++++++ unit_tests/test_tlslite_messages.py | 27 ++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index f8c90dbd..abb3bf75 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1057,6 +1057,10 @@ def parse(self, parser): parser.stopLengthCheck() return self + def __repr__(self): + return "CertificateEntry(certificate={0!r}, extensions={1!r})".format( + self.certificate, self.extensions) + class Certificate(HandshakeMsg): def __init__(self, certificateType, version=(3, 2)): @@ -1161,6 +1165,16 @@ def write(self): writer = self._write_tls13() return self.postWrite(writer) + def __repr__(self): + if self.version <= (3, 3): + return "Certificate(certChain={0!r})".format( + self.certChain.x509List) + else: + return "Certificate(request_context={0!r}, "\ + "certificate_list={1!r})"\ + .format(self.certificate_request_context, + self.certificate_list) + class CertificateRequest(HandshakeMsg): def __init__(self, version): diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 91977cb9..b4b178d9 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -28,7 +28,8 @@ SSL2HandshakeType, CertificateStatusType, HandshakeType, \ SignatureScheme from tlslite.extensions import SNIExtension, ClientCertTypeExtension, \ - SRPExtension, TLSExtension, NPNExtension, SupportedGroupsExtension + SRPExtension, TLSExtension, NPNExtension, SupportedGroupsExtension, \ + ServerCertTypeExtension from tlslite.errors import TLSInternalError from tlslite.x509 import X509 from tlslite.x509certchain import X509CertChain @@ -2853,6 +2854,30 @@ def test___init__(self): self.assertIsNotNone(cert) self.assertIsInstance(cert, Certificate) + def test___repr__(self): + cert = Certificate(CertificateType.x509) + cert = cert.create(X509CertChain([bytearray(b'one'), + bytearray(b'two')])) + self.assertEqual(repr(cert), + "Certificate(certChain=[bytearray(b'one'), " + "bytearray(b'two')])") + + def test___repr___tls_1_3(self): + cert = Certificate(CertificateType.x509, (3, 4)) + ext = ServerCertTypeExtension().create(CertificateType.x509) + entry = CertificateEntry(CertificateType.x509) + entry = entry.create(bytearray(b'this is certificate'), [ext]) + cert = cert.create([entry], bytearray(b'context')) + + self.maxDiff = None + + self.assertEqual(repr(cert), + "Certificate(request_context=bytearray(b'context'), " + "certificate_list=[CertificateEntry(certificate=" + "bytearray(b'this is certificate'), extensions=[" + "ServerCertTypeExtension(cert_type=0)" + "])])") + def test_write_empty(self): cert = Certificate(CertificateType.x509) cert.create(X509CertChain()) From f5626423746a3ffc3c1a96c7fdb3131a787eb7e1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 10 Aug 2017 19:17:10 +0200 Subject: [PATCH 516/574] status_request Certificate extension --- tlslite/extensions.py | 41 +++++++++++++++ unit_tests/test_tlslite_extensions.py | 76 ++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index 6d996a19..d52f5fa6 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -1489,6 +1489,43 @@ def parse(self, parser): return self +class CertificateStatusExtension(TLSExtension): + """Handling of Certificate Status response as redefined in TLS1.3""" + + def __init__(self): + """Create instance of CertificateStatusExtension.""" + super(CertificateStatusExtension, self).__init__( + extType=ExtensionType.status_request) + self.status_type = None + self.response = None + + def create(self, status_type, response): + """Set values of the extension.""" + self.status_type = status_type + self.response = response + return self + + def parse(self, parser): + """Deserialise the data from on the wire representation.""" + self.status_type = parser.get(1) + if self.status_type == 1: + self.response = parser.getVarBytes(3) + else: + raise SyntaxError("Unrecognised type") + if parser.getRemainingLength(): + raise SyntaxError("Trailing data") + return self + + @property + def extData(self): + """Serialise the object.""" + writer = Writer() + writer.add(self.status_type, 1) + writer.addVarSeq(self.response, 1, 3) + + return writer.bytes + + class KeyShareEntry(object): """Handler for of the item of the Key Share extension.""" @@ -1669,3 +1706,7 @@ def parse(self, parser): ExtensionType.cert_type: ServerCertTypeExtension, ExtensionType.tack: TACKExtension, ExtensionType.key_share: ServerKeyShareExtension} + +TLSExtension._certificateExtensions = \ + { + ExtensionType.status_request: CertificateStatusExtension} diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 9eaf85ea..16fb54b4 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -21,7 +21,8 @@ SignatureAlgorithmsExtension, PaddingExtension, VarListExtension, \ RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension, \ SupportedVersionsExtension, VarSeqListExtension, ListExtension, \ - ClientKeyShareExtension, KeyShareEntry, ServerKeyShareExtension + ClientKeyShareExtension, KeyShareEntry, ServerKeyShareExtension, \ + CertificateStatusExtension from tlslite.utils.codec import Parser, Writer from tlslite.constants import NameType, ExtensionType, GroupName,\ ECPointFormat, HashAlgorithm, SignatureAlgorithm, \ @@ -343,6 +344,19 @@ def test_parse_with_encrypted_extensions_type_extension(self): self.assertIsInstance(ext, SupportedGroupsExtension) self.assertEqual(ext.groups, [GroupName.secp192r1]) + def test_parse_of_certificate_extension(self): + ext = TLSExtension(cert=True) + p = Parser(bytearray( + b'\x00\x05' + # status_request + b'\x00\x05' + # length + b'\x01' + # status_type - ocsp + b'\x00\x00\x01' + # ocsp response length + b'\xba')) # ocsp payload + + ext = ext.parse(p) + + self.assertIsInstance(ext, CertificateStatusExtension) + def test_parse_with_client_cert_type_extension(self): ext = TLSExtension() @@ -2052,5 +2066,65 @@ def test_extData_with_no_entry(self): bytearray(0)) +class TestCertificateStatusExtension(unittest.TestCase): + def test___init__(self): + cs = CertificateStatusExtension() + + self.assertIsNone(cs.status_type) + self.assertIsNone(cs.response) + + def test_create(self): + cs = CertificateStatusExtension() + cs = cs.create(CertificateStatusType.ocsp, bytearray(b'resp')) + + self.assertIsInstance(cs, CertificateStatusExtension) + self.assertEqual(cs.status_type, CertificateStatusType.ocsp) + self.assertEqual(cs.response, bytearray(b'resp')) + + def test_extData(self): + cs = CertificateStatusExtension() + cs = cs.create(CertificateStatusType.ocsp, bytearray(b'resp')) + + self.assertEqual(cs.extData, + bytearray(b'\x01' # status type + b'\x00\x00\x04' # length of response + b'resp' # payload + )) + + def test_parse(self): + cs = CertificateStatusExtension() + + parser = Parser(bytearray(b'\x01' # type of ocsp response + b'\x00\x00\x04' # length + b'resp')) # payload + + cs = cs.parse(parser) + + self.assertIsInstance(cs, CertificateStatusExtension) + self.assertEqual(cs.status_type, CertificateStatusType.ocsp) + self.assertEqual(cs.response, bytearray(b'resp')) + + def test_parse_with_unknown_type(self): + cs = CertificateStatusExtension() + + parser = Parser(bytearray(b'\x02' # type of response + b'\x00\x00\x04' # length + b'resp')) + + with self.assertRaises(SyntaxError): + cs.parse(parser) + + def test_parse_with_trailing_data(self): + cs = CertificateStatusExtension() + parser = Parser(bytearray(b'\x01' # type of ocsp response + b'\x00\x00\x04' # length + b'resp' # payload + b'\x01' # trailing data + )) + + with self.assertRaises(SyntaxError): + cs.parse(parser) + + if __name__ == '__main__': unittest.main() From a73bf0693558254cb17619ffe51d7129f1678fd8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 10 Aug 2017 19:58:38 +0200 Subject: [PATCH 517/574] early_data extension codepoint --- tlslite/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index e3fe2858..15f66961 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -134,6 +134,7 @@ class ExtensionType(TLSEnum): encrypt_then_mac = 22 # RFC 7366 extended_master_secret = 23 # RFC 7627 key_share = 40 # TLS 1.3 + early_data = 42 # TLS 1.3 supported_versions = 43 # TLS 1.3 supports_npn = 13172 tack = 0xF300 From d9957a788033f5d8f05be40ce44313ab0991a6f6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 10 Aug 2017 19:58:22 +0200 Subject: [PATCH 518/574] TLS 1.3 session ticket --- tlslite/constants.py | 1 + tlslite/messages.py | 59 +++++++++++++++++ unit_tests/test_tlslite_messages.py | 99 ++++++++++++++++++++++++++++- 3 files changed, 158 insertions(+), 1 deletion(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 15f66961..6f0b2231 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -89,6 +89,7 @@ class HandshakeType(TLSEnum): hello_request = 0 client_hello = 1 server_hello = 2 + new_session_ticket = 4 encrypted_extensions = 8 certificate = 11 server_key_exchange = 12 diff --git a/tlslite/messages.py b/tlslite/messages.py index abb3bf75..60bafe30 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1850,6 +1850,65 @@ def write(self): return self.postWrite(w) +class NewSessionTicket(HandshakeMsg): + """Handling of the TLS1.3 New Session Ticket message.""" + + def __init__(self): + """Create New Session Ticket object.""" + super(NewSessionTicket, self).__init__(HandshakeType + .new_session_ticket) + self.ticket_lifetime = 0 + self.ticket_age_add = 0 + self.ticket_nonce = bytearray(0) + self.ticket = bytearray(0) + self.extensions = [] + + def create(self, ticket_lifetime, ticket_age_add, ticket_nonce, ticket, + extensions): + """Initialise a New Session Ticket.""" + self.ticket_lifetime = ticket_lifetime + self.ticket_age_add = ticket_age_add + self.ticket_nonce = ticket_nonce + self.ticket = ticket + self.extensions = extensions + return self + + def write(self): + """ + Serialise the message to on the wire data. + + :rtype: bytearray + """ + w = Writer() + w.add(self.ticket_lifetime, 4) + w.add(self.ticket_age_add, 4) + w.addVarSeq(self.ticket_nonce, 1, 1) + w.addVarSeq(self.ticket, 1, 2) + w2 = Writer() + for ext in self.extensions: + w2.bytes += ext.write() + w.add(len(w2.bytes), 2) + w.bytes += w2.bytes + + return self.postWrite(w) + + def parse(self, parser): + """Parse the object from on the wire data.""" + parser.startLengthCheck(3) + + self.ticket_lifetime = parser.get(4) + self.ticket_age_add = parser.get(4) + self.ticket_nonce = parser.getVarBytes(1) + self.ticket = parser.getVarBytes(2) + self.extensions = [] + ext_parser = Parser(parser.getVarBytes(2)) + while ext_parser.getRemainingLength(): + self.extensions.append(TLSExtension().parse(ext_parser)) + + parser.stopLengthCheck() + return self + + class SSL2Finished(HandshakeMsg): """Handling of the SSL2 FINISHED messages.""" diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index b4b178d9..1c23124a 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -20,7 +20,8 @@ CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus, \ Certificate, Finished, HelloMessage, ChangeCipherSpec, NextProtocol, \ - ApplicationData, EncryptedExtensions, CertificateEntry + ApplicationData, EncryptedExtensions, CertificateEntry, \ + NewSessionTicket from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ @@ -3199,5 +3200,101 @@ def test_write(self): b'\x00\x17\x00\x1D')) # secp256r1 and x25519 +class TestNewSessionTicket(unittest.TestCase): + def test___init__(self): + ticket = NewSessionTicket() + + self.assertEqual(ticket.ticket_lifetime, 0) + self.assertEqual(ticket.ticket_age_add, 0) + self.assertEqual(ticket.ticket_nonce, bytearray(0)) + self.assertEqual(ticket.ticket, bytearray(0)) + self.assertEqual(ticket.extensions, []) + + def test_create(self): + ticket = NewSessionTicket() + lifetime = mock.Mock() + age_add = mock.Mock() + nonce = mock.Mock() + ticket_proper = mock.Mock() + ext = mock.Mock() + + ticket = ticket.create(lifetime, age_add, nonce, ticket_proper, [ext]) + + self.assertIs(ticket.ticket_lifetime, lifetime) + self.assertIs(ticket.ticket_age_add, age_add) + self.assertIs(ticket.ticket_nonce, nonce) + self.assertIs(ticket.ticket, ticket_proper) + self.assertEqual(len(ticket.extensions), 1) + self.assertIs(ticket.extensions[0], ext) + + def test_write(self): + ticket = NewSessionTicket() + ticket = ticket.create(1, 2, bytearray(b'abc'), bytearray(b'ticket'), + [TLSExtension(extType=ExtensionType.early_data) + .create(bytearray())]) + + + self.assertEqual(ticket.write(), bytearray( + b'\x04' # handshake type - new session ticket + b'\x00\x00\x1a' # overall length + b'\x00\x00\x00\x01' # ticket_lifetime + b'\x00\x00\x00\x02' # ticket_age_add + b'\x03' # ticket nonce length + b'abc' # ticket nonce + b'\x00\x06' # ticket length + b'ticket' # ticket proper + b'\x00\x04' # length of extensions + b'\x00\x2a' # extension type - early_data + b'\x00\x00' # extension length - empty + )) + + def test_parse(self): + ticket = NewSessionTicket() + + parser = Parser(bytearray( + b'\x00\x00\x1a' # overall length + b'\x00\x00\x00\x01' # ticket_lifetime + b'\x00\x00\x00\x02' # ticket_age_add + b'\x03' # ticket nonce length + b'abc' # ticket nonce + b'\x00\x06' # ticket length + b'ticket' # ticket proper + b'\x00\x04' # length of extensions + b'\x00\x2a' # extension type - early_data + b'\x00\x00' # extension length - empty + )) + + ticket = ticket.parse(parser) + + self.assertIsInstance(ticket, NewSessionTicket) + self.assertEqual(ticket.ticket_lifetime, 1) + self.assertEqual(ticket.ticket_age_add, 2) + self.assertEqual(ticket.ticket_nonce, bytearray(b'abc')) + self.assertEqual(ticket.ticket, bytearray(b'ticket')) + self.assertEqual(ticket.extensions, + [TLSExtension(extType=ExtensionType.early_data) + .create(bytearray())]) + + def test_parse_with_trailing_data(self): + ticket = NewSessionTicket() + + parser = Parser(bytearray( + b'\x00\x00\x1b' # overall length + b'\x00\x00\x00\x01' # ticket_lifetime + b'\x00\x00\x00\x02' # ticket_age_add + b'\x03' # ticket nonce length + b'abc' # ticket nonce + b'\x00\x06' # ticket length + b'ticket' # ticket proper + b'\x00\x04' # length of extensions + b'\x00\x2a' # extension type - early_data + b'\x00\x00' # extension length - empty + b'\x00' # trailing byte + )) + + with self.assertRaises(SyntaxError): + ticket.parse(parser) + + if __name__ == '__main__': unittest.main() From fdf26f7ed535018ffed5aecec9b5489debdb96fd Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 23 Aug 2017 16:44:07 +0200 Subject: [PATCH 519/574] HRR Key Share extension --- tlslite/extensions.py | 66 ++++++++++++++++++++++++++- unit_tests/test_tlslite_extensions.py | 47 ++++++++++++++++++- 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/tlslite/extensions.py b/tlslite/extensions.py index d52f5fa6..0f910385 100644 --- a/tlslite/extensions.py +++ b/tlslite/extensions.py @@ -74,20 +74,27 @@ class TLSExtension(object): ServerHello versions. :vartype _certificateExtensions: dict - :cvat _certificateExtensions: dictionary with concrete implementations of + :cvar _certificateExtensions: dictionary with concrete implementations of specific TLS extensions where the key is the numeric value of the type of the extension and the value is the class. Includes only those extensions that require special handlers for Certificate message. + + :vartype _hrrExtensions: dict + :cvar _hrrExtensions: dictionary with concrete implementation of specific + TLS extensions where the key is the numeric type of the extension + and the value is the class. Includes only those extensions that require + special handlers for the Hello Retry Request message. """ # actual definition at the end of file, after definitions of all classes _universalExtensions = {} _serverExtensions = {} #_encryptedExtensions = {} _certificateExtensions = {} + _hrrExtensions = {} def __init__(self, server=False, extType=None, encExt=False, - cert=False): + cert=False, hrr=False): """ Creates a generic TLS extension. @@ -104,12 +111,15 @@ def __init__(self, server=False, extType=None, encExt=False, for parsing :param bool cert: whether to select the Certificate type of extension for parsing + :param bool hrr: whether to select the Hello Retry Request type + of extension for parsing """ self.extType = extType self._extData = bytearray(0) self.serverType = server self.encExtType = encExt self.cert = cert + self.hrr = hrr @property def extData(self): @@ -228,6 +238,10 @@ def parse(self, p): return self._parseExt(p, extType, extLength, self._serverExtensions) + if self.hrr and extType in self._hrrExtensions: + return self._parseExt(p, extType, extLength, + self._hrrExtensions) + # fallback to universal/ClientHello-specific parsers if extType in self._universalExtensions: return self._parseExt(p, extType, extLength, @@ -1685,6 +1699,50 @@ def parse(self, parser): return self +class HRRKeyShareExtension(TLSExtension): + """ + Class for handling the Hello Retry Request variant of the Key Share ext. + + Extension for notifying the client of the server selected group for + key exchange. + """ + def __init__(self): + """Create instance of the object.""" + super(HRRKeyShareExtension, self).__init__(extType=ExtensionType. + key_share, + hrr=True) + self.selected_group = None + + def create(self, selected_group): + """Set the selected group in the extension.""" + self.selected_group = selected_group + return self + + @property + def extData(self): + """Serialise the payload of the extension.""" + if self.selected_group is None: + return bytearray(0) + + w = Writer() + w.add(self.selected_group, 2) + return w.bytes + + def parse(self, parser): + """Parse the extension from on the wire format. + + :param Parser parser: data to be parsed + + :rtype: HRRKeyShareExtension + """ + self.selected_group = parser.get(2) + + if parser.getRemainingLength(): + raise SyntaxError("Trailing data in HRR Key Share extension") + + return self + + TLSExtension._universalExtensions = \ { ExtensionType.server_name: SNIExtension, @@ -1710,3 +1768,7 @@ def parse(self, parser): TLSExtension._certificateExtensions = \ { ExtensionType.status_request: CertificateStatusExtension} + +TLSExtension._hrrExtensions = \ + { + ExtensionType.key_share: HRRKeyShareExtension} diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 16fb54b4..25b0274c 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -22,7 +22,7 @@ RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension, \ SupportedVersionsExtension, VarSeqListExtension, ListExtension, \ ClientKeyShareExtension, KeyShareEntry, ServerKeyShareExtension, \ - CertificateStatusExtension + CertificateStatusExtension, HRRKeyShareExtension from tlslite.utils.codec import Parser, Writer from tlslite.constants import NameType, ExtensionType, GroupName,\ ECPointFormat, HashAlgorithm, SignatureAlgorithm, \ @@ -2126,5 +2126,50 @@ def test_parse_with_trailing_data(self): cs.parse(parser) +class TestHRRKeyShareExtension(unittest.TestCase): + def test___init__(self): + ext = HRRKeyShareExtension() + + self.assertIsNone(ext.selected_group) + + def test_create(self): + val = mock.Mock() + + ext = HRRKeyShareExtension().create(val) + + self.assertIs(ext.selected_group, val) + + def test_extData(self): + ext = HRRKeyShareExtension().create(GroupName.x25519) + + self.assertEqual(bytearray(b'\x00\x28' + b'\x00\x02' + b'\x00\x1d'), + ext.write()) + + def test_extData_with_no_value(self): + ext = HRRKeyShareExtension() + + self.assertEqual(ext.extData, bytearray()) + + def test_parse(self): + parser = Parser(bytearray(b'\x00\x28' + b'\x00\x02' + b'\x00\x1d')) + ext = TLSExtension(hrr=True) + ext = ext.parse(parser) + + self.assertIsInstance(ext, HRRKeyShareExtension) + self.assertEqual(ext.selected_group, GroupName.x25519) + + def test_parse_with_trailing_data(self): + parser = Parser(bytearray(b'\x00\x28' + b'\x00\x03' + b'\x00\x1d\x00')) + ext = TLSExtension(hrr=True) + with self.assertRaises(SyntaxError): + ext.parse(parser) + + if __name__ == '__main__': unittest.main() From 770a65082773ce1dab62850b296d0e81315f55f2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 11 Aug 2017 20:07:07 +0200 Subject: [PATCH 520/574] TLS 1.3 Hello Retry Request message --- tlslite/constants.py | 1 + tlslite/messages.py | 45 ++++++++++++++++++ unit_tests/test_tlslite_messages.py | 72 ++++++++++++++++++++++++++++- 3 files changed, 117 insertions(+), 1 deletion(-) diff --git a/tlslite/constants.py b/tlslite/constants.py index 6f0b2231..8620c0c9 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -90,6 +90,7 @@ class HandshakeType(TLSEnum): client_hello = 1 server_hello = 2 new_session_ticket = 4 + hello_retry_request = 6 encrypted_extensions = 8 certificate = 11 server_key_exchange = 12 diff --git a/tlslite/messages.py b/tlslite/messages.py index 60bafe30..849ab9f5 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1909,6 +1909,51 @@ def parse(self, parser): return self +class HelloRetryRequest(HandshakeMsg): + """Handling of TLS 1.3 Hello Retry Request handshake message.""" + + def __init__(self): + """Create Hello Retry Request object.""" + super(HelloRetryRequest, self).__init__(HandshakeType + .hello_retry_request) + self.server_version = (0, 0) + self.cipher_suite = 0 + self.extensions = [] + + def create(self, server_version, cipher_suite, extensions): + """Initialise a Hello Retry Request message.""" + self.server_version = server_version + self.cipher_suite = cipher_suite + self.extensions = extensions + return self + + def write(self): + """Serialise the object.""" + writer = Writer() + writer.addFixSeq(self.server_version, 1) + writer.add(self.cipher_suite, 2) + w2 = Writer() + for ext in self.extensions: + w2.bytes += ext.write() + writer.add(len(w2.bytes), 2) + writer.bytes += w2.bytes + + return self.postWrite(writer) + + def parse(self, parser): + """Deserialise the object from on the wire data.""" + parser.startLengthCheck(3) + + self.server_version = (parser.get(1), parser.get(1)) + self.cipher_suite = parser.get(2) + self.extensions = [] + ext_parser = Parser(parser.getVarBytes(2)) + while ext_parser.getRemainingLength(): + self.extensions.append(TLSExtension(hrr=True).parse(ext_parser)) + parser.stopLengthCheck() + return self + + class SSL2Finished(HandshakeMsg): """Handling of the SSL2 FINISHED messages.""" diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 1c23124a..af4a70bf 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -21,7 +21,7 @@ ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus, \ Certificate, Finished, HelloMessage, ChangeCipherSpec, NextProtocol, \ ApplicationData, EncryptedExtensions, CertificateEntry, \ - NewSessionTicket + NewSessionTicket, HelloRetryRequest from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ @@ -3296,5 +3296,75 @@ def test_parse_with_trailing_data(self): ticket.parse(parser) +class TestHelloRetryRequest(unittest.TestCase): + def test___init__(self): + hrr = HelloRetryRequest() + + self.assertEqual((0, 0), hrr.server_version) + self.assertEqual(0, hrr.cipher_suite) + self.assertEqual([], hrr.extensions) + + def test_create(self): + version = mock.Mock() + cipher = mock.Mock() + ext = mock.Mock() + + hrr = HelloRetryRequest() + hrr = hrr.create(version, cipher, [ext]) + + self.assertIs(hrr.server_version, version) + self.assertIs(hrr.cipher_suite, cipher) + self.assertEqual(len(hrr.extensions), 1) + self.assertIs(hrr.extensions[0], ext) + + def test_write(self): + hrr = HelloRetryRequest() + hrr = hrr.create((3, 4), CipherSuite.TLS_AES_128_GCM_SHA256, + [TLSExtension(extType=255).create(bytearray(0))]) + + self.assertEqual(hrr.write(), bytearray( + b'\x06' # type - Hello Retry Request + b'\x00\x00\x0a' # overall length + b'\x03\x04' # protocol version + b'\x13\x01' # cipher suite + b'\x00\x04' # extensions length + b'\x00\xff' # extension type + b'\x00\x00' # extension length + )) + + def test_parse(self): + parser = Parser(bytearray( + b'\x00\x00\x0a' # overall length + b'\xfe\xfe' # protocol version + b'\x13\x01' # cipher suite + b'\x00\x04' # extensions length + b'\x00\xff' # extension type + b'\x00\x00' # extension length + )) + hrr = HelloRetryRequest() + + hrr = hrr.parse(parser) + + self.assertEqual((0xfe, 0xfe), hrr.server_version) + self.assertEqual(0x1301, hrr.cipher_suite) + self.assertEqual([TLSExtension(extType=255).create(bytearray(0))], + hrr.extensions) + + def test_parse_with_trailing_data(self): + parser = Parser(bytearray( + b'\x00\x00\x0b' # overall length + b'\xfe\xfe' # protocol version + b'\x13\x01' # cipher suite + b'\x00\x04' # extensions length + b'\x00\xff' # extension type + b'\x00\x00' # extension length + b'\x00' # trailing data + )) + hrr = HelloRetryRequest() + + with self.assertRaises(SyntaxError): + hrr.parse(parser) + + if __name__ == '__main__': unittest.main() From 8f8c4c382a46a642333b015df6dbbfb6d6f8dc34 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 16 Aug 2017 16:42:13 +0200 Subject: [PATCH 521/574] add convenience functions to messages with extensions getExtension() and addExtension() is useful not only for ClientHello and ServerHello, but other messages that have an extensions array --- tlslite/messages.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 849ab9f5..8eb0df65 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1806,13 +1806,12 @@ def write(self): return self.postWrite(w) -class EncryptedExtensions(HandshakeMsg): +class EncryptedExtensions(HelloMessage): """Handling of the TLS1.3 Encrypted Extensions message.""" def __init__(self): super(EncryptedExtensions, self).__init__( HandshakeType.encrypted_extensions) - self.extensions = None def create(self, extensions): """Set the extensions in the message.""" @@ -1850,7 +1849,7 @@ def write(self): return self.postWrite(w) -class NewSessionTicket(HandshakeMsg): +class NewSessionTicket(HelloMessage): """Handling of the TLS1.3 New Session Ticket message.""" def __init__(self): @@ -1909,7 +1908,7 @@ def parse(self, parser): return self -class HelloRetryRequest(HandshakeMsg): +class HelloRetryRequest(HelloMessage): """Handling of TLS 1.3 Hello Retry Request handshake message.""" def __init__(self): From 33821da1480da2db1ba29f056080546515581415 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 23 Aug 2017 19:55:17 +0200 Subject: [PATCH 522/574] try to diagnose spurious ValueError raised by pycrypto Sometimes pycrypto will raise a "ValueError: Message too large" exception. Print debugging info when that happens. --- tlslite/utils/pycrypto_rsakey.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tlslite/utils/pycrypto_rsakey.py b/tlslite/utils/pycrypto_rsakey.py index b8ffaf4e..dfc7794f 100644 --- a/tlslite/utils/pycrypto_rsakey.py +++ b/tlslite/utils/pycrypto_rsakey.py @@ -3,6 +3,9 @@ """PyCrypto RSA implementation.""" +from __future__ import print_function +import sys + from .cryptomath import * from .rsakey import * @@ -29,10 +32,31 @@ def hasPrivateKey(self): return self.rsa.has_private() def _rawPrivateKeyOp(self, m): - return self.rsa.decrypt((compatLong(m),)) + try: + return self.rsa.decrypt((compatLong(m),)) + except ValueError as e: + print("rsa: {0!r}".format(self.rsa), file=sys.stderr) + values = [] + for name in ["n", "e", "d", "p", "q", "dP", "dQ", "qInv"]: + values.append("{0}: {1}".format(name, + getattr(self, name, None))) + print(", ".join(values), file=sys.stderr) + print("m: {0}".format(m), file=sys.stderr) + raise + def _rawPublicKeyOp(self, c): - return self.rsa.encrypt(compatLong(c), None)[0] + try: + return self.rsa.encrypt(compatLong(c), None)[0] + except ValueError as e: + print("rsa: {0!r}".format(self.rsa), file=sys.stderr) + values = [] + for name in ["n", "e", "d", "p", "q", "dP", "dQ", "qInv"]: + values.append("{0}: {1}".format(name, + getattr(self, name, None))) + print(", ".join(values), file=sys.stderr) + print("c: {0}".format(c), file=sys.stderr) + raise def generate(bits): key = PyCrypto_RSAKey() From ddaf97b06e14603617a8fe35e6039f9bf6070381 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 24 Aug 2017 13:03:11 +0200 Subject: [PATCH 523/574] release 0.8.0-alpha1 --- README.md | 6 +++++- docs/conf.py | 4 ++-- setup.py | 2 +- tlslite/__init__.py | 2 +- tlslite/api.py | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cd633694..b3b8ff3f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ``` -tlslite-ng version 0.7.0 2017-07-31 +tlslite-ng version 0.8.0-alpha1 2017-08-24 Hubert Kario https://github.com/tomato42/tlslite-ng/ ``` @@ -586,6 +586,10 @@ encrypt-then-MAC mode for CBC ciphers. 12 History =========== +0.8.0 - wip +* fix minor bugs in message objects, extend test coverage for tlslite.messages +* repr() for SignatureAlgorithmsExtension and Certificate + 0.7.0 - 2017-07-31 * enable and add missing definitions of TLS_ECDHE_RSA_WITH_RC4_128_SHA and diff --git a/docs/conf.py b/docs/conf.py index 22953818..6503ef5e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,9 +59,9 @@ # built documents. # # The short X.Y version. -version = u'0.7' +version = u'0.8' # The full version, including alpha/beta/rc tags. -release = u'0.7.0' +release = u'0.8.0-alpha1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 49a4d52d..355cc09b 100755 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ README = f.read() setup(name="tlslite-ng", - version="0.7.0", + version="0.8.0-alpha1", author="Hubert Kario", author_email="hkario@redhat.com", url="https://github.com/tomato42/tlslite-ng", diff --git a/tlslite/__init__.py b/tlslite/__init__.py index f773d7c9..f6384af1 100644 --- a/tlslite/__init__.py +++ b/tlslite/__init__.py @@ -21,7 +21,7 @@ Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket. (Or, use one of the integration classes in L{tlslite.integration}). -@version: 0.7.0 +@version: 0.8.0-alpha1 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 93694580..1191e64b 100644 --- a/tlslite/api.py +++ b/tlslite/api.py @@ -1,7 +1,7 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -__version__ = "0.7.0" +__version__ = "0.8.0-alpha1" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker From 7d7095be5efe91d613039cc3554f2097904dd534 Mon Sep 17 00:00:00 2001 From: Filip Goldefus Date: Tue, 17 May 2016 21:02:40 +0200 Subject: [PATCH 524/574] Jython2.7 bytearray bug fix. --- tlslite/utils/compat.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tlslite/utils/compat.py b/tlslite/utils/compat.py index 704b67ac..46fa54fa 100644 --- a/tlslite/utils/compat.py +++ b/tlslite/utils/compat.py @@ -5,6 +5,7 @@ import sys import os +import platform import math import binascii import traceback @@ -65,7 +66,9 @@ def formatExceptionTrace(e): # Python 2.6 requires strings instead of bytearrays in a couple places, # so we define this function so it does the conversion if needed. # same thing with very old 2.7 versions - if sys.version_info < (2, 7) or sys.version_info < (2, 7, 4): + # or on Jython + if sys.version_info < (2, 7) or sys.version_info < (2, 7, 4) \ + or platform.system() == 'Java': def compat26Str(x): return str(x) else: def compat26Str(x): return x From 258d5a0c34589fe70acfaa0171dc94bc4b8e503b Mon Sep 17 00:00:00 2001 From: "E.Iosifidis" Date: Sat, 26 Aug 2017 11:54:50 +0300 Subject: [PATCH 525/574] Improvements on precision of Throughput Improvements on precision of Throughput Measuring Mechanism. - Usage of timeit.default_timer() in tlstests.py instead of time.time(). - Measuring of the size for travelling data with the sys.getsizeof(). --- tests/tlstest.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index 11700a95..0e1f16ff 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -8,6 +8,8 @@ # Martin von Loewis - python 3 port # Hubert Kario - several improvements # Google - FALLBACK_SCSV test +# Efthimis Iosifidis - improvemnts of time measurement in Throughput Test +# # # See the LICENSE file for legal information regarding use of this file. from __future__ import print_function @@ -15,7 +17,7 @@ import os import os.path import socket -import time +import timeit import getopt from tempfile import mkstemp try: @@ -476,12 +478,13 @@ def connect(): connection.handshakeClientCert(settings=settings) print("%s %s:" % (connection.getCipherName(), connection.getCipherImplementation()), end=' ') - startTime = time.clock() + startTime = timeit.default_timer() connection.write(b"hello"*10000) h = connection.read(min=50000, max=50000) - stopTime = time.clock() + stopTime = timeit.default_timer() + sizeofdata = sys.getsizeof(h)*2 if stopTime-startTime: - print("100K exchanged at rate of %d bytes/sec" % int(100000/(stopTime-startTime))) + print("100K exchanged at rate of %d bytes/sec" % int(sizeofdata/(stopTime-startTime))) else: print("100K exchanged very fast") From 0b7940284d4b82fb553f0b6e26d296ef8b30aeb1 Mon Sep 17 00:00:00 2001 From: ioef Date: Mon, 28 Aug 2017 20:51:41 +0300 Subject: [PATCH 526/574] Improvements on precision of Throughput Measuring Mechanism Usage of timeit.default_timer() in tlstests.py. Measuring of the size for travelling data with the len() --- tests/tlstest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index 0e1f16ff..7abd2ba6 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -482,7 +482,7 @@ def connect(): connection.write(b"hello"*10000) h = connection.read(min=50000, max=50000) stopTime = timeit.default_timer() - sizeofdata = sys.getsizeof(h)*2 + sizeofdata = len(h)*2 if stopTime-startTime: print("100K exchanged at rate of %d bytes/sec" % int(sizeofdata/(stopTime-startTime))) else: From 425be77db6cb273a0714c7202525e224ad276b0c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 28 Aug 2017 17:38:55 +0200 Subject: [PATCH 527/574] Initialise CRT values when not all provided For initialising the private key, only p and q are necessary. If other values are not provided, calculate them. --- tlslite/utils/python_rsakey.py | 11 +++++++ unit_tests/test_tlslite_utils_rsakey.py | 38 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/tlslite/utils/python_rsakey.py b/tlslite/utils/python_rsakey.py index e01df3e1..f58d8031 100644 --- a/tlslite/utils/python_rsakey.py +++ b/tlslite/utils/python_rsakey.py @@ -14,11 +14,22 @@ def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0): raise AssertionError() self.n = n self.e = e + if p and not q or not p and q: + raise ValueError("p and q must be set or left unset together") + if not d and p and q: + t = lcm(p - 1, q - 1) + d = invMod(e, t) self.d = d self.p = p self.q = q + if not dP and p: + dP = d % (p - 1) self.dP = dP + if not dQ and q: + dQ = d % (q - 1) self.dQ = dQ + if not qInv: + qInv = invMod(q, p) self.qInv = qInv self.blinder = 0 self.unblinder = 0 diff --git a/unit_tests/test_tlslite_utils_rsakey.py b/unit_tests/test_tlslite_utils_rsakey.py index 73331fa7..8d4dd1b8 100644 --- a/unit_tests/test_tlslite_utils_rsakey.py +++ b/unit_tests/test_tlslite_utils_rsakey.py @@ -1227,6 +1227,44 @@ def m(leght): signed = self.rsa.RSASSA_PSS_sign(mHash, 'sha512', 10) self.assertEqual(signed, intendedS) +class TestEncryptDecrypt(unittest.TestCase): + n = int("a8d68acd413c5e195d5ef04e1b4faaf242365cb450196755e92e1215ba59802aa" + "fbadbf2564dd550956abb54f8b1c917844e5f36195d1088c600e07cada5c080ed" + "e679f50b3de32cf4026e514542495c54b1903768791aae9e36f082cd38e941ada" + "89baecada61ab0dd37ad536bcb0a0946271594836e92ab5517301d45176b5", + 16) + e = int("00000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000003", + 16) + p = int("c107a2fe924b76e206cb9bc4af2ab7008547c00846bf6d0680b3eac3ebcbd0c7f" + "d7a54c2b9899b08f80cde1d3691eaaa2816b1eb11822d6be7beaf4e30977c49", + 16) + q = int("dfea984ce4307eafc0d140c2bb82861e5dbac4f8567cbc981d70440dd63949207" + "9031486315e305eb83e591c4a2e96064966f7c894c3ca351925b5ce82d8ef0d", + 16) + d = int("1c23c1cce034ba598f8fd2b7af37f1d30b090f7362aee68e5187adae49b9955c7" + "29f24a863b7a38d6e3c748e2972f6d940b7ba89043a2d6c2100256a1cf0f56a8c" + "d35fc6ee205244876642f6f9c3820a3d9d2c8921df7d82aaadcaf2d7334d39893" + "1ddbba553190b3a416099f3aa07fd5b26214645a828419e122cfb857ad73b", + 16) + + def setUp(self): + self.rsa = Python_RSAKey(self.n, self.e, 0, self.p, self.q) + + def test_init(self): + self.rsa.d == self.d + + def test_encDec(self): + self.assertEqual(bytearray(b'test'), + self.rsa.decrypt(self.rsa.encrypt(bytearray(b'test'))) + ) + + def test_invalid_init(self): + with self.assertRaises(ValueError): + Python_RSAKey(self.n, self.e, self.d, self.p) + class TestRSAPKCS1(unittest.TestCase): n = int("a8d68acd413c5e195d5ef04e1b4faaf242365cb450196755e92e1215ba59802aa" From eb3825a67d5cd171a43b924322e7d4b0e8639c33 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 29 Aug 2017 18:53:22 +0200 Subject: [PATCH 528/574] return self in EncryptedExtensions.create() all create functions return the object after modifications, do the same for EE --- tlslite/messages.py | 1 + unit_tests/test_tlslite_messages.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tlslite/messages.py b/tlslite/messages.py index 8eb0df65..80314aa5 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1816,6 +1816,7 @@ def __init__(self): def create(self, extensions): """Set the extensions in the message.""" self.extensions = extensions + return self def parse(self, parser): """Parse the extensions from on the wire data.""" diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index af4a70bf..f458a1f6 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -3135,8 +3135,9 @@ def test_create(self): ext = SNIExtension() - self.msg.create([ext]) + self.msg = self.msg.create([ext]) + self.assertIsInstance(self.msg, EncryptedExtensions) self.assertIsInstance(self.msg.extensions[0], SNIExtension) def test_parse(self): From e614813b23ed2d2bda945f340d1d210b54a8f2bb Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 29 Aug 2017 18:56:56 +0200 Subject: [PATCH 529/574] init Certificate for TLS1.3 even if create got X509CertChain --- tlslite/messages.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tlslite/messages.py b/tlslite/messages.py index 80314aa5..29f080d0 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -1084,6 +1084,9 @@ def certChain(self): def create(self, certChain, context=None): if isinstance(certChain, X509CertChain): self._certChain = certChain + self.certificate_list = [CertificateEntry(self.certificateType) + .create(i, []) for i + in certChain.x509List] else: self.certificate_list = certChain self.certificate_request_context = context From 945182adc36f313720cf7d8f30efc3a0ebab65c6 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 31 Aug 2017 18:48:22 +0200 Subject: [PATCH 530/574] remove QuantifiedCode badge since QuantifiedCode shut down, there are no associated statistics and thus no badge - remove it --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index b3b8ff3f..00285ae7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ https://github.com/tomato42/tlslite-ng/ [![Coverage Status](https://coveralls.io/repos/tomato42/tlslite-ng/badge.svg?branch=master)](https://coveralls.io/r/tomato42/tlslite-ng?branch=master) [![Code Health](https://landscape.io/github/tomato42/tlslite-ng/master/landscape.svg?style=flat)](https://landscape.io/github/tomato42/tlslite-ng/master) [![Code Climate](https://codeclimate.com/github/tomato42/tlslite-ng/badges/gpa.svg)](https://codeclimate.com/github/tomato42/tlslite-ng) -[![Code Issues](https://www.quantifiedcode.com/api/v1/project/59e6019ef3c84ad7ba30c49ec46d990f/badge.svg)](https://www.quantifiedcode.com/app/project/59e6019ef3c84ad7ba30c49ec46d990f) Table of Contents ================== From 48d7e3be307ef1c646c11b66803b83b1408f5db0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 31 Aug 2017 20:29:14 +0200 Subject: [PATCH 531/574] remove deprecated options from pylint config --- pylintrc | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pylintrc b/pylintrc index cb75db12..59c9602e 100644 --- a/pylintrc +++ b/pylintrc @@ -112,9 +112,6 @@ generated-members=REQUEST,acl_users,aq_parent [BASIC] -# Required attributes for module, separated by a comma -required-attributes= - # List of builtins function names that should not be used, separated by a comma bad-functions=map,filter,apply,input,file @@ -324,10 +321,6 @@ max-public-methods=20 [CLASSES] -# List of interface methods to ignore, separated by a comma. This is used for -# instance to not check methods defines in Zope's Interface base class. -ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by - # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp From cb41be02d3b918973eb1444b7c5eb1f73da9915c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 11 Aug 2017 17:04:57 +0200 Subject: [PATCH 532/574] standalone FFDHE key exchange --- tlslite/keyexchange.py | 129 +++++++++++++++++++------ unit_tests/test_tlslite_keyexchange.py | 48 ++++++++- 2 files changed, 146 insertions(+), 31 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 46cf4aa6..775184ef 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -381,11 +381,12 @@ def makeServerKeyExchange(self): raise TLSInternalError("DHE key exchange attempted despite no " "overlap between supported groups") - # Per RFC 3526, Section 1, the exponent should have double the entropy - # of the strength of the group. - randBytesNeeded = divceil(paramStrength(self.dh_p) * 2, 8) - self.dh_Xs = bytesToNumber(getRandomBytes(randBytesNeeded)) - dh_Ys = powMod(self.dh_g, self.dh_Xs, self.dh_p) + # for TLS < 1.3 we need special algorithm to select params (see above) + # so do not pass in the group, if we selected one + kex = FFDHKeyExchange(None, self.serverHello.server_version, + self.dh_g, self.dh_p) + self.dh_Xs = kex.get_random_private_key() + dh_Ys = kex.calc_public_value(self.dh_Xs) version = self.serverHello.server_version serverKeyExchange = ServerKeyExchange(self.cipherSuite, version) @@ -397,16 +398,9 @@ def processClientKeyExchange(self, clientKeyExchange): """Use client provided parameters to establish premaster secret""" dh_Yc = clientKeyExchange.dh_Yc - # First half of RFC 2631, Section 2.1.5. Validate the client's public - # key. - # use of safe primes also means that the p-1 is invalid - if not 2 <= dh_Yc < self.dh_p - 1: - raise TLSIllegalParameterException("Invalid dh_Yc value") - - S = powMod(dh_Yc, self.dh_Xs, self.dh_p) - if S in (1, self.dh_p - 1): - raise TLSIllegalParameterException("Small subgroup capture") - return numberToByteArray(S) + kex = FFDHKeyExchange(None, self.serverHello.server_version, + self.dh_g, self.dh_p) + return kex.calc_shared_key(self.dh_Xs, dh_Yc) def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): """Process the server key exchange, return premaster secret.""" @@ -415,25 +409,15 @@ def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): # TODO make the minimum changeable if dh_p < 2**1023: raise TLSInsufficientSecurity("DH prime too small") - dh_g = serverKeyExchange.dh_g - if not 2 <= dh_g < dh_p - 1: - raise TLSIllegalParameterException("Invalid DH generator") - - dh_Xc = bytesToNumber(getRandomBytes(32)) dh_Ys = serverKeyExchange.dh_Ys - if not 2 <= dh_Ys < dh_p - 1: - raise TLSIllegalParameterException("Invalid server key share") - self.dh_Yc = powMod(dh_g, dh_Xc, dh_p) - if self.dh_Yc in (1, dh_p - 1): - raise TLSIllegalParameterException("Small subgroup capture") + kex = FFDHKeyExchange(None, self.serverHello.server_version, + dh_g, dh_p) - S = powMod(dh_Ys, dh_Xc, dh_p) - if S in (1, dh_p - 1): - raise TLSIllegalParameterException("Small subgroup capture") - - return numberToByteArray(S) + dh_Xc = kex.get_random_private_key() + self.dh_Yc = kex.calc_public_value(dh_Xc) + return kex.calc_shared_key(dh_Xc, dh_Ys) def makeClientKeyExchange(self): """Create client key share for the key exchange""" @@ -734,3 +718,88 @@ def makeClientKeyExchange(self): cke = super(SRPKeyExchange, self).makeClientKeyExchange() cke.createSRP(self.A) return cke + + +class RawDHKeyExchange(object): + """ + Abstract class for performing Diffe-Hellman key exchange. + + Provides a shared API for X25519, ECDHE and FFDHE key exchange. + """ + + def __init__(self, group, version): + """ + Set the parameters of the key exchange + + Sets group on which the KEX will take part and protocol version used. + """ + self.group = group + self.version = version + + def get_random_private_key(self): + """ + Generate a random value suitable for use as the private value of KEX. + """ + raise NotImplementedError("Abstract class") + + def calc_public_value(self, private): + """Calculate the public value from the provided private value.""" + raise NotImplementedError("Abstract class") + + def calc_shared_key(self, private, peer_share): + """Calcualte the shared key given our private and remote share value""" + raise NotImplementedError("Abstract class") + + +class FFDHKeyExchange(RawDHKeyExchange): + """Implemenation of the Finite Field Diffie-Hellman key exchange.""" + + def __init__(self, group, version, generator=None, prime=None): + super(FFDHKeyExchange, self).__init__(group, version) + if prime and group: + raise ValueError("Can't set the RFC7919 group and custom params" + " at the same time") + if group: + self.generator, self.prime = RFC7919_GROUPS[group-256] + else: + self.prime = prime + self.generator = generator + + if not 1 < self.generator < self.prime: + raise TLSIllegalParameterException("Invalid DH generator") + + def get_random_private_key(self): + """ + Return a random private value for the prime used. + + :rtype: int + """ + # Per RFC 3526, Section 1, the exponent should have double the entropy + # of the strength of the group. + needed_bytes = divceil(paramStrength(self.prime) * 2, 8) + return bytesToNumber(getRandomBytes(needed_bytes)) + + def calc_public_value(self, private): + """ + Calculate the public value for given private value. + + :rtype: int + """ + dh_Y = powMod(self.generator, private, self.prime) + if dh_Y in (1, self.prime - 1): + raise TLSIllegalParameterException("Small subgroup capture") + return dh_Y + + def calc_shared_key(self, private, peer_share): + """Calculate the shared key.""" + # First half of RFC 2631, Section 2.1.5. Validate the client's public + # key. + # use of safe primes also means that the p-1 is invalid + if not 2 <= peer_share < self.prime - 1: + raise TLSIllegalParameterException("Invalid peer key share") + + S = powMod(peer_share, private, self.prime) + + if S in (1, self.prime - 1): + raise TLSIllegalParameterException("Small subgroup capture") + return numberToByteArray(S) diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index 58d7b7f4..80e9afad 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -44,8 +44,10 @@ pass from tlslite.keyexchange import KeyExchange, RSAKeyExchange, \ - DHE_RSAKeyExchange, SRPKeyExchange, ECDHE_RSAKeyExchange + DHE_RSAKeyExchange, SRPKeyExchange, ECDHE_RSAKeyExchange, \ + RawDHKeyExchange, FFDHKeyExchange from tlslite.utils.x25519 import x25519, X25519_G, x448, X448_G +from tlslite.mathtls import RFC7919_GROUPS srv_raw_key = str( "-----BEGIN RSA PRIVATE KEY-----\n"\ @@ -1644,3 +1646,47 @@ def test_client_with_all_zero_ECDHE_key_share(self): srv_key_ex.ecdh_Ys = bytearray(56) with self.assertRaises(TLSIllegalParameterException): client_keyExchange.processServerKeyExchange(None, srv_key_ex) + + +class TestRawDHKeyExchange(unittest.TestCase): + def test___init__(self): + group = mock.Mock() + version = mock.Mock() + kex = RawDHKeyExchange(group, version) + + self.assertIs(kex.group, group) + self.assertIs(kex.version, version) + + def test_get_random_private_key(self): + kex = RawDHKeyExchange(None, None) + + with self.assertRaises(NotImplementedError): + kex.get_random_private_key() + + def test_calc_public_value(self): + kex = RawDHKeyExchange(None, None) + + with self.assertRaises(NotImplementedError): + kex.calc_public_value(None) + + def test_calc_shared_value(self): + kex = RawDHKeyExchange(None, None) + + with self.assertRaises(NotImplementedError): + kex.calc_shared_key(None, None) + + +class TestFFDHKeyExchange(unittest.TestCase): + def test___init___with_conflicting_options(self): + with self.assertRaises(ValueError): + FFDHKeyExchange(GroupName.ffdhe2048, (3, 3), 2, 31) + + def test___init___with_invalid_generator(self): + with self.assertRaises(TLSIllegalParameterException): + FFDHKeyExchange(None, (3, 3), 31, 2) + + def test___init___with_rfc7919_group(self): + kex = FFDHKeyExchange(GroupName.ffdhe2048, (3, 3)) + + self.assertEqual(kex.generator, 2) + self.assertEqual(kex.prime, RFC7919_GROUPS[0][1]) From 3ad32c870a2d24f41993ac20d1c6ee1d7707d484 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 11 Aug 2017 17:23:33 +0200 Subject: [PATCH 533/574] standalone ECDHE key exchange --- tlslite/keyexchange.py | 164 +++++++++++++------------ unit_tests/test_tlslite_keyexchange.py | 17 +++ 2 files changed, 100 insertions(+), 81 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index 775184ef..fd9c6550 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -458,19 +458,6 @@ class AECDHKeyExchange(KeyExchange): ECDHE without signing serverKeyExchange useful for anonymous ECDH """ - @staticmethod - def _non_zero_check(value): - """ - Verify using constant time operation that the bytearray is not zero - - :raises TLSIllegalParameterException: if the value is all zero - """ - summa = 0 - for i in value: - summa |= i - if summa == 0: - raise TLSIllegalParameterException("Invalid key share") - def __init__(self, cipherSuite, clientHello, serverHello, acceptedCurves, defaultCurve=GroupName.secp256r1): super(AECDHKeyExchange, self).__init__(cipherSuite, clientHello, @@ -500,22 +487,10 @@ def makeServerKeyExchange(self, sigHash=None): self.group_id = getFirstMatching(client_curves, self.acceptedCurves) if self.group_id is None: raise TLSInsufficientSecurity("No mutual groups") - if self.group_id in [GroupName.x25519, GroupName.x448]: - if self.group_id == GroupName.x25519: - generator = bytearray(X25519_G) - fun = x25519 - self.ecdhXs = getRandomBytes(X25519_ORDER_SIZE) - else: - generator = bytearray(X448_G) - fun = x448 - self.ecdhXs = getRandomBytes(X448_ORDER_SIZE) - ecdhYs = fun(self.ecdhXs, generator) - else: - curve = getCurveByName(GroupName.toRepr(self.group_id)) - generator = curve.generator - self.ecdhXs = ecdsa.util.randrange(generator.order()) - ecdhYs = encodeX962Point(generator * self.ecdhXs) + kex = ECDHKeyExchange(self.group_id, self.serverHello.server_version) + self.ecdhXs = kex.get_random_private_key() + ecdhYs = kex.calc_public_value(self.ecdhXs) version = self.serverHello.server_version serverKeyExchange = ServerKeyExchange(self.cipherSuite, version) @@ -531,30 +506,9 @@ def processClientKeyExchange(self, clientKeyExchange): if not ecdhYc: raise TLSDecodeError("No key share") - if self.group_id in [GroupName.x25519, GroupName.x448]: - if self.group_id == GroupName.x25519: - if len(ecdhYc) != X25519_ORDER_SIZE: - raise TLSIllegalParameterException("Invalid key share") - sharedSecret = x25519(self.ecdhXs, ecdhYc) - else: - if len(ecdhYc) != X448_ORDER_SIZE: - raise TLSIllegalParameterException("Invalid key share") - sharedSecret = x448(self.ecdhXs, ecdhYc) - self._non_zero_check(sharedSecret) - return sharedSecret - else: - curveName = GroupName.toRepr(self.group_id) - try: - ecdhYc = decodeX962Point(ecdhYc, - getCurveByName(curveName)) - # TODO update python-ecdsa library to raise something more on point - except AssertionError: - raise TLSIllegalParameterException("Invalid ECC point") - sharedSecret = ecdhYc * self.ecdhXs - - return numberToByteArray(sharedSecret.x(), - getPointByteSize(ecdhYc)) + kex = ECDHKeyExchange(self.group_id, self.serverHello.server_version) + return kex.calc_shared_key(self.ecdhXs, ecdhYc) def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): """Process the server key exchange, return premaster secret""" @@ -565,37 +519,15 @@ def processServerKeyExchange(self, srvPublicKey, serverKeyExchange): raise TLSIllegalParameterException("Server picked curve we " "didn't advertise") - if serverKeyExchange.named_curve in [GroupName.x25519, - GroupName.x448]: - if serverKeyExchange.named_curve == GroupName.x25519: - generator = bytearray(X25519_G) - fun = x25519 - ecdhXc = getRandomBytes(X25519_ORDER_SIZE) - if len(serverKeyExchange.ecdh_Ys) != X25519_ORDER_SIZE: - raise TLSIllegalParameterException("Invalid server key " - "share") - else: - generator = bytearray(X448_G) - fun = x448 - ecdhXc = getRandomBytes(X448_ORDER_SIZE) - if len(serverKeyExchange.ecdh_Ys) != X448_ORDER_SIZE: - raise TLSIllegalParameterException("Invalid server key " - "share") - self.ecdhYc = fun(ecdhXc, generator) - S = fun(ecdhXc, serverKeyExchange.ecdh_Ys) - # check if the secret is not all-zero - self._non_zero_check(S) - return S - else: - curveName = GroupName.toStr(serverKeyExchange.named_curve) - curve = getCurveByName(curveName) - generator = curve.generator + ecdh_Ys = serverKeyExchange.ecdh_Ys + if not ecdh_Ys: + raise TLSDecodeError("Empty server key share") - ecdhXc = ecdsa.util.randrange(generator.order()) - ecdhYs = decodeX962Point(serverKeyExchange.ecdh_Ys, curve) - self.ecdhYc = encodeX962Point(generator * ecdhXc) - S = ecdhYs * ecdhXc - return numberToByteArray(S.x(), getPointByteSize(S)) + kex = ECDHKeyExchange(serverKeyExchange.named_curve, + self.serverHello.server_version) + ecdhXc = kex.get_random_private_key() + self.ecdhYc = kex.calc_public_value(ecdhXc) + return kex.calc_shared_key(ecdhXc, ecdh_Ys) def makeClientKeyExchange(self): """Make client key exchange for ECDHE""" @@ -803,3 +735,73 @@ def calc_shared_key(self, private, peer_share): if S in (1, self.prime - 1): raise TLSIllegalParameterException("Small subgroup capture") return numberToByteArray(S) + + +class ECDHKeyExchange(RawDHKeyExchange): + """Implementation of the Elliptic Curve Diffie-Hellman key exchange.""" + + _x_groups = set((GroupName.x25519, GroupName.x448)) + + @staticmethod + def _non_zero_check(value): + """ + Verify using constant time operation that the bytearray is not zero + + :raises TLSIllegalParameterException: if the value is all zero + """ + summa = 0 + for i in value: + summa |= i + if summa == 0: + raise TLSIllegalParameterException("Invalid key share") + + def __init__(self, group, version): + super(ECDHKeyExchange, self).__init__(group, version) + + def get_random_private_key(self): + """Return random private key value for the selected curve.""" + if self.group in self._x_groups: + if self.group == GroupName.x25519: + return getRandomBytes(X25519_ORDER_SIZE) + else: + return getRandomBytes(X448_ORDER_SIZE) + else: + curve = getCurveByName(GroupName.toStr(self.group)) + return ecdsa.util.randrange(curve.generator.order()) + + def _get_fun_gen_size(self): + """Return the function and generator for X25519/X448 KEX.""" + if self.group == GroupName.x25519: + return x25519, bytearray(X25519_G), X25519_ORDER_SIZE + else: + return x448, bytearray(X448_G), X448_ORDER_SIZE + + def calc_public_value(self, private): + """Calculate public value for given private key.""" + if self.group in self._x_groups: + fun, generator, _ = self._get_fun_gen_size() + return fun(private, generator) + else: + curve = getCurveByName(GroupName.toStr(self.group)) + return encodeX962Point(curve.generator * private) + + def calc_shared_key(self, private, peer_share): + """Calculate the shared key,""" + if self.group in self._x_groups: + fun, _, size = self._get_fun_gen_size() + if len(peer_share) != size: + raise TLSIllegalParameterException("Invalid key share") + S = fun(private, peer_share) + self._non_zero_check(S) + return S + else: + curve = getCurveByName(GroupName.toRepr(self.group)) + try: + ecdhYc = decodeX962Point(peer_share, + curve) + except AssertionError: + raise TLSIllegalParameterException("Invalid ECC point") + + S = ecdhYc * private + + return numberToByteArray(S.x(), getPointByteSize(ecdhYc)) diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index 80e9afad..8ef31d83 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -1273,6 +1273,12 @@ def test_ECDHE_key_exchange_with_missing_curves(self): self.assertEqual(ske.curve_type, ECCurveType.named_curve) self.assertEqual(ske.named_curve, GroupName.secp256r1) + def test_ECDHE_key_exchange_with_no_curves_in_ext(self): + self.client_hello.extensions = [SupportedGroupsExtension()] + + with self.assertRaises(TLSInternalError): + ske = self.keyExchange.makeServerKeyExchange('sha1') + def test_ECDHE_key_exchange_with_no_mutual_curves(self): ext = SupportedGroupsExtension().create([GroupName.secp160r1]) self.client_hello.extensions = [ext] @@ -1309,6 +1315,17 @@ def test_client_ECDHE_key_exchange_with_invalid_server_curve(self): with self.assertRaises(TLSIllegalParameterException): client_keyExchange.processServerKeyExchange(None, srv_key_ex) + def test_client_ECDHE_key_exchange_with_no_share(self): + srv_key_ex = self.keyExchange.makeServerKeyExchange('sha1') + srv_key_ex.ecdh_Ys = bytearray() + + client_keyExchange = ECDHE_RSAKeyExchange(self.cipher_suite, + self.client_hello, + self.server_hello, + None, + [GroupName.secp256r1]) + with self.assertRaises(TLSDecodeError): + client_keyExchange.processServerKeyExchange(None, srv_key_ex) class TestRSAKeyExchange_with_PSS_scheme(unittest.TestCase): def setUp(self): From 5c687b48780398e05334defdb45eb59862f35ed0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 4 Sep 2017 19:26:16 +0200 Subject: [PATCH 534/574] TLS 1.3 KDF --- tlslite/utils/cryptomath.py | 43 ++++++++++++++++ unit_tests/test_tlslite_utils_cryptomath.py | 57 ++++++++++++++++++++- 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index 521866f5..c125e64a 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -16,6 +16,7 @@ import sys from .compat import compat26Str, compatHMAC, compatLong, b2a_hex +from .codec import Writer # ************************************************************************** @@ -116,6 +117,48 @@ def HKDF_expand(PRK, info, L, algorithm): Titer = secureHMAC(PRK, Titer + info + bytearray([x]), algorithm) return T[:L] +def HKDF_expand_label(secret, label, hashValue, length, algorithm): + """ + TLS1.3 key derivation function (HKDF-Expand-Label). + + :param bytearray secret: the key from which to derive the keying material + :param bytearray label: label used to differentiate the keying materials + :param bytearray hashValue: bytes used to "salt" the produced keying + material + :param int length: number of bytes to produce + :param str algorithm: name of the secure hash algorithm used as the + basis of the HKDF + :rtype: bytearray + """ + hkdfLabel = Writer() + hkdfLabel.addTwo(length) + hkdfLabel.addVarSeq(bytearray(b"tls13 ") + label, 1, 1) + hkdfLabel.addVarSeq(hashValue, 1, 1) + + return HKDF_expand(secret, hkdfLabel.bytes, length, algorithm) + +def derive_secret(secret, label, handshake_hashes, algorithm): + """ + TLS1.3 key derivation function (Derive-Secret). + + :param bytearray secret: secret key used to derive the keying material + :param bytearray label: label used to differentiate they keying materials + :param HandshakeHashes handshake_hashes: hashes of the handshake messages + or `None` if no handshake transcript is to be used for derivation of + keying material + :param str algorithm: name of the secure hash algorithm used as the + basis of the HKDF algorithm - governs how much keying material will + be generated + :rtype: bytearray + """ + if handshake_hashes is None: + hs_hash = secureHash(bytearray(b''), algorithm) + else: + hs_hash = handshake_hashes.digest(algorithm) + return HKDF_expand_label(secret, label, hs_hash, + getattr(hashlib, algorithm)().digest_size, + algorithm) + # ************************************************************************** # Converter Functions # ************************************************************************** diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index 53e95343..ca358445 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -15,7 +15,9 @@ from tlslite.utils.cryptomath import isPrime, numBits, numBytes, \ numberToByteArray, MD5, SHA1, secureHash, HMAC_MD5, HMAC_SHA1, \ - HMAC_SHA256, HMAC_SHA384, HKDF_expand, bytesToNumber + HMAC_SHA256, HMAC_SHA384, HKDF_expand, bytesToNumber, \ + HKDF_expand_label, derive_secret +from tlslite.handshakehashes import HandshakeHashes class TestIsPrime(unittest.TestCase): def test_with_small_primes(self): @@ -449,3 +451,56 @@ def test_with_empty_string(self): def test_with_empty_string_little_endian(self): self.assertEqual(0, bytesToNumber(b'', "little")) + + +class TestHKDF_expand_label(unittest.TestCase): + def test_with_sha256(self): + secret = bytearray(32) + label = bytearray(b'test') + hash_value = bytearray(b'01' * 32) + length = 32 + + self.assertEqual(HKDF_expand_label(secret, label, hash_value, length, + "sha256"), + bytearray(b"r\x91M\x13~\xd1\xa7\xf0\xa3\xa3\x0f\xce#" + b" \xa9\xe4\xdd\xeb\x05\x07\x80\xee\x10\x93" + b"\x7f\xc4\x18\x02\xb9\x00\'6")) + + def test_with_sha384(self): + secret = bytearray(48) + label = bytearray(b'test') + hash_value = bytearray(b'01' * 48) + length = 48 + + self.assertEqual(HKDF_expand_label(secret, label, hash_value, length, + "sha384"), + bytearray(b'\xb3\rFt\x10\xd96b\xb3\x80pm3g\xc06\xc3' + b'\xa1/8\t\r\x86\xa4\xd4pFaJ\xce\xb9\xf6Nb' + b'\xf76\x12p\x1dQ\xe5\xd9\xfc\n\x16\xc8\x07' + b'\xb8')) + +class TestDerive_secret(unittest.TestCase): + def test_with_no_hashes(self): + secret = bytearray(32) + label = bytearray(b'exporter') + handshake_hashes = None + algorithm = "sha256" + + self.assertEqual(derive_secret(secret, label, handshake_hashes, + algorithm), + bytearray(b'(\xef{\xee\xad\xde\x0b)9\xec\xb6\x89\xf5' + b'\x83\xa2\xc5\xf2_\xf4R\x9e\xe8\xf4N\xef' + b'\xbf\x06g\x95\xd0\x892')) + + def test_with_handshake_hashes(self): + secret = bytearray(32) + label = bytearray(b'exporter') + handshake_hashes = HandshakeHashes() + handshake_hashes.update(bytearray(8)) + algorithm = "sha256" + + self.assertEqual(derive_secret(secret, label, handshake_hashes, + algorithm), + bytearray(b'\t\xec\x01W[Y\xdcP\xac\xebu\x13\xe6\x98' + b'\x19\xccu;\xfa\x90\xc9\xe3\xc1\xe7\xb7' + b'\xcf\x0c\x97;x\xf0F')) From c9676c346c16fdbe9a4631ea5ca9595cf018e53d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 4 Sep 2017 18:56:44 +0200 Subject: [PATCH 535/574] constant for the tls1.3 draft protocol version --- tlslite/constants.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 8620c0c9..b889c682 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -10,6 +10,12 @@ """Constants used in various places.""" + +# protocol version number used for negotiating TLS 1.3 between implementations +# of the draft specification +TLS_1_3_DRAFT = (127, 21) + + class TLSEnum(object): """Base class for different enums of TLS IDs""" From 24e5caee0574936e93b28adb65ddbf9540e0f434 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 4 Sep 2017 19:07:23 +0200 Subject: [PATCH 536/574] TLS 1.3 handshake key derivation vector tests --- unit_tests/test_tls1_3_vectors.py | 295 ++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 unit_tests/test_tls1_3_vectors.py diff --git a/unit_tests/test_tls1_3_vectors.py b/unit_tests/test_tls1_3_vectors.py new file mode 100644 index 00000000..cfd25e6a --- /dev/null +++ b/unit_tests/test_tls1_3_vectors.py @@ -0,0 +1,295 @@ +# Copyright (c) 2017, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +try: + import unittest2 as unittest +except ImportError: + import unittest + +import sys + +from tlslite.recordlayer import RecordLayer +from tlslite.messages import ServerHello, ClientHello, Alert, RecordHeader3 +from tlslite.constants import CipherSuite, AlertDescription, ContentType, \ + ExtensionType, GroupName, ECPointFormat, HashAlgorithm, \ + SignatureAlgorithm, SignatureScheme, HandshakeType, TLS_1_3_DRAFT +from tlslite.tlsconnection import TLSConnection +from tlslite.errors import TLSLocalAlert, TLSRemoteAlert +from tlslite.x509 import X509 +from tlslite.x509certchain import X509CertChain +from tlslite.utils.keyfactory import parsePEMKey +from tlslite.handshakesettings import HandshakeSettings +from tlslite.session import Session +from tlslite.utils.codec import Parser +from tlslite.extensions import TLSExtension, SNIExtension, \ + SupportedGroupsExtension, ECPointFormatsExtension, \ + ClientKeyShareExtension, KeyShareEntry, SupportedVersionsExtension, \ + SignatureAlgorithmsExtension +from tlslite.utils.x25519 import x25519 +from tlslite.utils.cryptomath import secureHMAC, HKDF_expand_label, \ + derive_secret +from tlslite.handshakehashes import HandshakeHashes +from unit_tests.mocksock import MockSocket + + +def str_to_bytearray(value): + if sys.version_info < (2, 7): + return bytearray.fromhex(unicode(value)) + else: + return bytearray.fromhex(value) + + +# values from draft-ietf-tls-tls13-vectors-02 +client_key_private = str_to_bytearray( + "304546ef3c866b23 cc42b5e95282e5df" + "16ab583ffd142c40 743dd4f306e67220") + +client_key_public = str_to_bytearray( + "da6a859ad6d2dbb5 1124fbfe6baff63d" + "8f14365ec990d575 761e4a6164978d31") + +client_hello_plaintext = str_to_bytearray( + "010001fc0303af21 156b04db639e6615" + "4a1fe5adfaeadf9e 413416000d57b8e1 126d4d119a8b0000" + "3e130113031302c0 2bc02fcca9cca8c0 0ac009c013c023c0" + "27c014009eccaa00 3300320067003900 38006b0016001300" + "9c002f003c003500 3d000a0005000401 0001950000000b00" + "0900000673657276 6572ff0100010000 0a00140012001d00" + "1700180019010001 0101020103010400 0b00020100002300" + "0000280026002400 1d0020da6a859ad6 d2dbb51124fbfe6b" + "aff63d8f14365ec9 90d575761e4a6164 978d31002b000706" + "7f1503030302000d 0020001e04030503 0603020308040805" + "0806040105010601 0201040205020602 0202002d00020101" + "001500fc00000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000") + +client_hello_ciphertext = str_to_bytearray( + "1603010200010001 fc0303af21156b04" + "db639e66154a1fe5 adfaeadf9e413416 000d57b8e1126d4d" + "119a8b00003e1301 13031302c02bc02f cca9cca8c00ac009" + "c013c023c027c014 009eccaa00330032 006700390038006b" + "00160013009c002f 003c0035003d000a 0005000401000195" + "0000000b00090000 06736572766572ff 01000100000a0014" + "0012001d00170018 0019010001010102 01030104000b0002" + "0100002300000028 00260024001d0020 da6a859ad6d2dbb5" + "1124fbfe6baff63d 8f14365ec990d575 761e4a6164978d31" + "002b0007067f1503 030302000d002000 1e04030503060302" + "0308040805080604 0105010601020104 0205020602020200" + "2d00020101001500 fc00000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000000000" + "0000000000000000 0000000000000000 0000000000") + +server_hello_ciphertext = str_to_bytearray( + "1603010052020000 4e7f15deac631669" + "eaf28c6b128b2091 d36441e618964dd8 f0ec812e31cda7ae" + "c1d0c11301002800 280024001d00209d 1bfe8053046d2dbd" + "8e0e6221dad11587 584713c8cf497074 d9d26d067c432f") + +server_hello_payload = str_to_bytearray( + "0200004e7f15deac 631669eaf28c6b12" + "8b2091d36441e618 964dd8f0ec812e31 cda7aec1d0c11301" + "002800280024001d 00209d1bfe805304 6d2dbd8e0e6221da" + "d11587584713c8cf 497074d9d26d067c 432f") + +class TestSimple1RTTHandshakeAsClient(unittest.TestCase): + def test(self): + + sock = MockSocket(server_hello_ciphertext) + + record_layer = RecordLayer(sock) + + ext = [SNIExtension().create(bytearray(b'server')), + TLSExtension(extType=ExtensionType.renegotiation_info) + .create(bytearray(b'\x00')), + SupportedGroupsExtension().create([GroupName.x25519, + GroupName.secp256r1, + GroupName.secp384r1, + GroupName.secp521r1, + GroupName.ffdhe2048, + GroupName.ffdhe3072, + GroupName.ffdhe4096, + GroupName.ffdhe6144, + GroupName.ffdhe8192]), + ECPointFormatsExtension().create([ECPointFormat.uncompressed]), + TLSExtension(extType=35), + ClientKeyShareExtension().create([KeyShareEntry().create(GroupName.x25519, + client_key_public, + client_key_private)]), + SupportedVersionsExtension().create([TLS_1_3_DRAFT, + (3, 3), (3, 2)]), + SignatureAlgorithmsExtension().create([(HashAlgorithm.sha256, + SignatureAlgorithm.ecdsa), + (HashAlgorithm.sha384, + SignatureAlgorithm.ecdsa), + (HashAlgorithm.sha512, + SignatureAlgorithm.ecdsa), + (HashAlgorithm.sha1, + SignatureAlgorithm.ecdsa), + SignatureScheme.rsa_pss_sha256, + SignatureScheme.rsa_pss_sha384, + SignatureScheme.rsa_pss_sha512, + SignatureScheme.rsa_pkcs1_sha256, + SignatureScheme.rsa_pkcs1_sha384, + SignatureScheme.rsa_pkcs1_sha512, + SignatureScheme.rsa_pkcs1_sha1, + (HashAlgorithm.sha256, + SignatureAlgorithm.dsa), + (HashAlgorithm.sha384, + SignatureAlgorithm.dsa), + (HashAlgorithm.sha512, + SignatureAlgorithm.dsa), + (HashAlgorithm.sha1, + SignatureAlgorithm.dsa)]), + TLSExtension(extType=45).create(bytearray(b'\x01\x01')), + TLSExtension(extType=ExtensionType.client_hello_padding) + .create(bytearray(252)) + ] + client_hello = ClientHello() + client_hello.create((3, 3), + bytearray(b'\xaf!\x15k\x04\xdbc\x9ef\x15J\x1f\xe5' + b'\xad\xfa\xea\xdf\x9eA4\x16\x00\rW\xb8' + b'\xe1\x12mM\x11\x9a\x8b'), + bytearray(b''), + [CipherSuite.TLS_AES_128_GCM_SHA256, + CipherSuite.TLS_CHACHA20_POLY1305_SHA256, + CipherSuite.TLS_AES_256_GCM_SHA384, + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + 0xCCA9, + CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + 0x0032, + CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + 0x0038, + CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + 0x0013, + CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_RSA_WITH_RC4_128_SHA, + CipherSuite.TLS_RSA_WITH_RC4_128_MD5], + extensions=ext) + + self.assertEqual(client_hello.write(), client_hello_ciphertext[5:]) + + for result in record_layer.recvRecord(): + # check if non-blocking + self.assertNotIn(result, (0, 1)) + header, parser = result + hs_type = parser.get(1) + self.assertEqual(hs_type, HandshakeType.server_hello) + server_hello = ServerHello().parse(parser) + + self.assertEqual(server_hello.server_version, TLS_1_3_DRAFT) + self.assertEqual(server_hello.cipher_suite, CipherSuite.TLS_AES_128_GCM_SHA256) + + server_key_share = server_hello.getExtension(ExtensionType.key_share) + server_key_share = server_key_share.server_share + + self.assertEqual(server_key_share.group, GroupName.x25519) + + # for TLS_AES_128_GCM_SHA256: + prf_name = 'sha256' + prf_size = 256 // 8 + secret = bytearray(prf_size) + psk = bytearray(prf_size) + + # early secret + secret = secureHMAC(secret, psk, prf_name) + + self.assertEqual(secret, + str_to_bytearray( + "33ad0a1c607ec03b 09e6cd9893680ce2" + "10adf300aa1f2660 e1b22e10f170f92a")) + + # derive secret for handshake + secret = derive_secret(secret, b"derived", None, prf_name) + + self.assertEqual(secret, + str_to_bytearray( + "6f2615a108c702c5 678f54fc9dbab697" + "16c076189c48250c ebeac3576c3611ba")) + + # extract secret "handshake" + Z = x25519(client_key_private, server_key_share.key_exchange) + + self.assertEqual(Z, + str_to_bytearray( + "f677c3cdac26a755 455b130efa9b1a3f" + "3cafb153544ca46a ddf670df199d996e")) + + secret = secureHMAC(secret, Z, prf_name) + + self.assertEqual(secret, + str_to_bytearray( + "0cefce00d5d29fd0 9f5de36c86fc8e72" + "99b4ad11ba4211c6 7063c2cc539fc4f9")) + + handshake_hashes = HandshakeHashes() + handshake_hashes.update(client_hello_plaintext) + handshake_hashes.update(server_hello_payload) + + # derive "tls13 c hs traffic" + c_hs_traffic = derive_secret(secret, + bytearray(b'c hs traffic'), + handshake_hashes, + prf_name) + self.assertEqual(c_hs_traffic, + str_to_bytearray( + "5a63db760b817b1b da96e72832333aec" + "6a177deeadb5b407 501ac10c17dac0a4")) + s_hs_traffic = derive_secret(secret, + bytearray(b's hs traffic'), + handshake_hashes, + prf_name) + self.assertEqual(s_hs_traffic, + str_to_bytearray( + "3aa72a3c77b791e8 f4de243f9ccce172" + "941f8392aeb05429 320f4b572ccfe744")) + + # derive master secret + secret = derive_secret(secret, b"derived", None, prf_name) + + self.assertEqual(secret, + str_to_bytearray( + "32cadf38f3089048 5c54bf4f1184eaa5" + "569eeef15a43f3c7 6ab33965a47c9ff6")) + + # extract secret "master + secret = secureHMAC(secret, bytearray(prf_size), prf_name) + + self.assertEqual(secret, + str_to_bytearray( + "6c6d4b3e7c925460 82d7b7a32f6ce219" + "3804f1bb930fed74 5c6b93c71397f424")) From 31d51b4e6b457f15aaaaab87e85865eb5e071664 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 4 Sep 2017 19:01:43 +0200 Subject: [PATCH 537/574] supported versions and TLS 1.3 handling in handshakesettings --- tlslite/handshakesettings.py | 6 ++++-- unit_tests/test_tlslite_handshakesettings.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index dc0ae11b..3a421964 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -7,7 +7,7 @@ """Class for setting handshake parameters.""" -from .constants import CertificateType +from .constants import CertificateType, TLS_1_3_DRAFT from .utils import cryptomath from .utils import cipherfactory from .utils.compat import ecdsaAllCurves, int_types @@ -172,6 +172,7 @@ def __init__(self): self.certificateTypes = list(CERTIFICATE_TYPES) self.minVersion = (3, 1) self.maxVersion = (3, 3) + self.versions = [TLS_1_3_DRAFT, (3, 3), (3, 2), (3, 1)] self.useExperimentalTackExtension = False self.sendFallbackSCSV = False self.useEncryptThenMAC = True @@ -262,7 +263,7 @@ def _sanityCheckProtocolVersions(other): raise ValueError("Versions set incorrectly") if other.minVersion not in ((3, 0), (3, 1), (3, 2), (3, 3)): raise ValueError("minVersion set incorrectly") - if other.maxVersion not in ((3, 0), (3, 1), (3, 2), (3, 3)): + if other.maxVersion not in ((3, 0), (3, 1), (3, 2), (3, 3), (3, 4)): raise ValueError("maxVersion set incorrectly") @staticmethod @@ -314,6 +315,7 @@ def validate(self): other.dhParams = self.dhParams other.dhGroups = self.dhGroups other.defaultCurve = self.defaultCurve + other.versions = self.versions if not cipherfactory.tripleDESPresent: other.cipherNames = [i for i in self.cipherNames if i != "3des"] diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index 9c9f550c..3538ed91 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -126,7 +126,7 @@ def test_minVersion_with_unknown_version(self): def test_maxVersion_with_unknown_version(self): hs = HandshakeSettings() - hs.maxVersion = (3, 4) + hs.maxVersion = (3, 5) with self.assertRaises(ValueError): hs.validate() From ed77633762c149c4d205d33301fe2da2e51ee12e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 9 Aug 2017 18:59:32 +0200 Subject: [PATCH 538/574] exception for handling unexpected messages --- tlslite/errors.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tlslite/errors.py b/tlslite/errors.py index 759fbe8f..a705435d 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -242,6 +242,15 @@ class TLSDecodeError(TLSProtocolException): pass +class TLSUnexpectedMessage(TLSProtocolException): + """ + The received message was unexpected or parsing of Inner Plaintext + failed + """ + + pass + + class TLSRecordOverflow(TLSProtocolException): """The received record size was too big""" From ab1cd8560d722fb1405b4da00662b1dc54f01a32 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 4 Sep 2017 19:30:45 +0200 Subject: [PATCH 539/574] TLS 1.3 record layer --- tlslite/recordlayer.py | 160 +++++++++++++++++--- unit_tests/test_tlslite_recordlayer.py | 200 ++++++++++++++++++++++++- 2 files changed, 336 insertions(+), 24 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 6446a49f..a80078d4 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -6,6 +6,17 @@ import socket import errno +try: + # in python 3 the native zip() returns iterator + from itertools import izip +except ImportError: + izip = zip +try: + # in python 3 the native range() returns an object/iterator + xrange +except NameError: + xrange = range + from .utils import tlshashlib as hashlib from .constants import ContentType, CipherSuite from .messages import RecordHeader3, RecordHeader2, Message @@ -13,10 +24,11 @@ createTripleDES, createCHACHA20 from .utils.codec import Parser, Writer from .utils.compat import compatHMAC -from .utils.cryptomath import getRandomBytes, MD5 +from .utils.cryptomath import getRandomBytes, MD5, HKDF_expand_label from .utils.constanttime import ct_compare_digest, ct_check_cbc_mac_and_pad from .errors import TLSRecordOverflow, TLSIllegalParameterException,\ - TLSAbruptCloseError, TLSDecryptionFailed, TLSBadRecordMAC + TLSAbruptCloseError, TLSDecryptionFailed, TLSBadRecordMAC, \ + TLSUnexpectedMessage from .mathtls import createMAC_SSL, createHMAC, PRF_SSL, PRF, PRF_1_2, \ PRF_1_2_SHA384 @@ -273,7 +285,12 @@ def version(self): def version(self, val): """Set the TLS version used by record layer""" self._version = val - self._recordSocket.version = val + if val <= (3, 3): + self._recordSocket.version = val + else: + # in TLS 1.3 all records need to be sent with the generic version + # which is the same as TLS 1.0 + self._recordSocket.version = (3, 1) def getCipherName(self): """ @@ -386,14 +403,14 @@ def _encryptThenMAC(self, buf, contentType): return buf - @staticmethod - def _getNonce(state, seqnum): + def _getNonce(self, state, seqnum): """Calculate a nonce for a given enc/dec context""" # ChaCha is using the draft-TLS1.3-like nonce derivation - if state.encContext.name == "chacha20-poly1305" and \ - len(state.fixedNonce) == 12: + if (state.encContext.name == "chacha20-poly1305" and + len(state.fixedNonce) == 12) or self.version > (3, 3): # 4 byte nonce is used by the draft cipher - nonce = bytearray(i ^ j for i, j in zip(bytearray(4) + seqnum, + pad = bytearray(len(state.fixedNonce) - len(seqnum)) + nonce = bytearray(i ^ j for i, j in zip(pad + seqnum, state.fixedNonce)) else: nonce = state.fixedNonce + seqnum @@ -404,11 +421,14 @@ def _encryptThenSeal(self, buf, contentType): """Encrypt with AEAD cipher""" #Assemble the authenticated data. seqNumBytes = self._writeState.getSeqNumBytes() - authData = seqNumBytes + bytearray([contentType, - self.version[0], - self.version[1], - len(buf)//256, - len(buf)%256]) + if self.version <= (3, 3): + authData = seqNumBytes + bytearray([contentType, + self.version[0], + self.version[1], + len(buf)//256, + len(buf)%256]) + else: # TLS 1.3 + authData = bytearray(0) nonce = self._getNonce(self._writeState, seqNumBytes) @@ -417,7 +437,7 @@ def _encryptThenSeal(self, buf, contentType): buf = self._writeState.encContext.seal(nonce, buf, authData) #AES-GCM, has an explicit variable nonce. - if "aes" in self._writeState.encContext.name: + if "aes" in self._writeState.encContext.name and self.version < (3, 4): buf = seqNumBytes + buf return buf @@ -460,12 +480,18 @@ def sendRecord(self, msg): data = msg.write() contentType = msg.contentType + # TLS 1.3 hides the content type of messages + if self.version > (3, 3) and self._writeState.encContext: + # TODO - add support for padding + data += bytearray([contentType]) + # in TLS 1.3 contentType is ignored by _encryptThenSeal + contentType = ContentType.application_data + padding = 0 if self.version in ((0, 2), (2, 0)): data, padding = self._ssl2Encrypt(data) - elif self._writeState and \ - self._writeState.encContext and \ - self._writeState.encContext.isAEAD: + elif self._writeState.encContext and \ + self._writeState.encContext.isAEAD: data = self._encryptThenSeal(data, contentType) elif self.encryptThenMAC: data = self._encryptThenMAC(data, contentType) @@ -620,7 +646,7 @@ def _decryptAndUnseal(self, recordType, buf): """Decrypt AEAD encrypted data""" seqnumBytes = self._readState.getSeqNumBytes() #AES-GCM, has an explicit variable nonce. - if "aes" in self._readState.encContext.name: + if "aes" in self._readState.encContext.name and self.version < (3, 4): explicitNonceLength = 8 if explicitNonceLength > len(buf): #Publicly invalid. @@ -634,11 +660,14 @@ def _decryptAndUnseal(self, recordType, buf): #Publicly invalid. raise TLSBadRecordMAC("Truncated tag") - plaintextLen = len(buf) - self._readState.encContext.tagLength - authData = seqnumBytes + bytearray([recordType, self.version[0], - self.version[1], - plaintextLen//256, - plaintextLen%256]) + if self.version in [(3, 0), (3, 1), (3, 2), (3, 3)]: + plaintextLen = len(buf) - self._readState.encContext.tagLength + authData = seqnumBytes + bytearray([recordType, self.version[0], + self.version[1], + plaintextLen//256, + plaintextLen%256]) + else: # TLS 1.3 + authData = bytearray(0) buf = self._readState.encContext.open(nonce, buf, authData) if buf is None: @@ -681,6 +710,30 @@ def _decryptSSL2(self, data, padding): data = data[:-padding] return data + @staticmethod + def _tls13_de_pad(data): + """ + Remove the padding and extract content type from TLSInnerPlaintext. + + :param bytearray data: decrypted plaintext TLS 1.3 record payload + (the serialised TLSInnerPlaintext data structure) + + :rtype: tuple + """ + # the padding is at the end and the first non-zero byte is the + # padding + # could be reversed(enumerate(data)), if that worked at all + # could be reversed(list(enumerate(data))), if that didn't double + # memory usage + for pos, value in izip(reversed(xrange(len(data))), reversed(data)): + if value != 0: + break + else: + raise TLSUnexpectedMessage("Malformed record layer inner plaintext" + " - content type missing") + + return data[:pos], value + def recvRecord(self): """ Read, decrypt and check integrity of a single record @@ -717,6 +770,11 @@ def recvRecord(self): else: data = self._decryptStreamThenMAC(header.type, data) + # TLS 1.3 encrypts the type + if self.version > (3, 3): + data, contentType = self._tls13_de_pad(data) + header = RecordHeader3().create((3, 4), contentType, len(data)) + # RFC 5246, section 6.2.1 if len(data) > 2**14: raise TLSRecordOverflow() @@ -1008,3 +1066,59 @@ def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, #residue to create the IV for each sent block) self.fixedIVBlock = getRandomBytes(ivLength) + def calcTLS1_3PendingState(self, cipherSuite, cl_traffic_secret, + sr_traffic_secret, + implementations): + """ + Create pending state for encryption in TLS 1.3. + + :param int cipherSuite: cipher suite that will be used for encrypting + and decrypting data + :param bytearray cl_traffic_secret: Client Traffic Secret, either + handshake secret or application data secret + :param bytearray sr_traffic_secret: Server Traffic Secret, either + handshake secret or application data secret + :param list implementations: list of names of implementations that + are permitted for the connection + """ + prf_name = 'sha384' if cipherSuite \ + in CipherSuite.sha384PrfSuites \ + else 'sha256' + + key_length, iv_length, cipher_func = \ + self._getCipherSettings(cipherSuite) + iv_length = 12 + + clientPendingState = ConnectionState() + serverPendingState = ConnectionState() + + clientPendingState.macContext = None + clientPendingState.encContext = \ + cipher_func(HKDF_expand_label(cl_traffic_secret, + b"key", b"", + key_length, + prf_name), + implementations) + clientPendingState.fixedNonce = HKDF_expand_label(cl_traffic_secret, + b"iv", b"", + iv_length, + prf_name) + + serverPendingState.macContext = None + serverPendingState.encContext = \ + cipher_func(HKDF_expand_label(sr_traffic_secret, + b"key", b"", + key_length, + prf_name), + implementations) + serverPendingState.fixedNonce = HKDF_expand_label(sr_traffic_secret, + b"iv", b"", + iv_length, + prf_name) + + if self.client: + self._pendingWriteState = clientPendingState + self._pendingReadState = serverPendingState + else: + self._pendingWriteState = serverPendingState + self._pendingReadState = clientPendingState diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index ebcb19c8..77ac311a 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -26,7 +26,8 @@ from tlslite.constants import ContentType, CipherSuite from unit_tests.mocksock import MockSocket from tlslite.errors import TLSRecordOverflow, TLSIllegalParameterException,\ - TLSAbruptCloseError, TLSDecryptionFailed, TLSBadRecordMAC + TLSAbruptCloseError, TLSDecryptionFailed, TLSBadRecordMAC, \ + TLSUnexpectedMessage class TestRecordSocket(unittest.TestCase): def test___init__(self): @@ -514,6 +515,203 @@ def test_sendRecord_with_encrypting_set_up_tls1_2(self): b'\x9f\x73\xec\xa9\xa6\x82\x55\x8e\x3a\x8c\x94\x96\xda\x06\x09\x8d' ), sock.sent[0][5:]) + def test_sendRecord_with_encryption_tls1_3_aes_128_gcm(self): + patcher = mock.patch.object(os, + 'urandom', + lambda x : bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 4) + + recordLayer.calcTLS1_3PendingState(CipherSuite.TLS_AES_128_GCM_SHA256, + bytearray(32), # cl_traffic_sec + bytearray(32), # sr_traffic_sec + None) # implementations + recordLayer.changeWriteState() + + app_data = ApplicationData().create(b'test') + + for result in recordLayer.sendRecord(app_data): + # check if non-blocking socket + self.assertNotIn(result, (0, 1)) + break + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application_data + b'\x03\x01' + # hidden protocol version - TLS 1.x + b'\x00\x15' # length + )) + self.assertEqual(sock.sent[0][5:], bytearray( + b'\xe1\x90\x2d\xd1\xfd\x24\xc8\x47\x70\xd4' + b'\x8c\x36\xf3\x2c\x93\x04\x39\x1f\x6f\x42\xeb' + )) + + def test_recvRecord_with_encryption_tls1_3_aes_128_gcm(self): + patcher = mock.patch.object(os, + 'urandom', + lambda x : bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + sock = MockSocket(bytearray( + b'\x17' # application_data + b'\x03\x01' # hidden protocol version - TLS 1.x + b'\x00\x15' # length + b'\xe1\x90\x2d\xd1\xfd\x24\xc8\x47\x70\xd4' + b'\x8c\x36\xf3\x2c\x93\x04\x39\x1f\x6f\x42\xeb' + )) + + recordLayer = RecordLayer(sock) + recordLayer.client = False + recordLayer.version = (3, 4) + + recordLayer.calcTLS1_3PendingState(CipherSuite.TLS_AES_128_GCM_SHA256, + bytearray(32), # cl_traffic_sec + bytearray(32), # sr_traffic_sec + None) # implementations + recordLayer.changeReadState() + + app_data = ApplicationData().create(b'test') + + for result in recordLayer.recvRecord(): + # check if non-blocking socket + self.assertNotIn(result, (0, 1)) + break + head, parser = result + + self.assertEqual((3, 4), head.version) + self.assertEqual(head.type, ContentType.application_data) + self.assertEqual(bytearray(b'test'), parser.bytes) + + def test_sendRecord_with_encryption_tls1_3_aes_256_gcm(self): + patcher = mock.patch.object(os, + 'urandom', + lambda x : bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 4) + + recordLayer.calcTLS1_3PendingState(CipherSuite.TLS_AES_256_GCM_SHA384, + bytearray(48), # cl_traffic_sec + bytearray(48), # sr_traffic_sec + None) # implementations + recordLayer.changeWriteState() + + app_data = ApplicationData().create(b'test') + + for result in recordLayer.sendRecord(app_data): + # check if non-blocking socket + self.assertNotIn(result, (0, 1)) + break + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application_data + b'\x03\x01' + # hidden protocol version - TLS 1.x + b'\x00\x15' # length + )) + self.assertEqual(sock.sent[0][5:], bytearray( + b'}\x17w_#\xf0\xf2R\xaa*s\xe2\xca\xab\x9d\xea\x9d\xf3\xc1-\xd2' + )) + + def test_sendRecord_with_encryption_tls1_3_chacha20(self): + patcher = mock.patch.object(os, + 'urandom', + lambda x : bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 4) + + ciph = CipherSuite.TLS_CHACHA20_POLY1305_SHA256 + recordLayer.calcTLS1_3PendingState(ciph, + bytearray(48), # cl_traffic_sec + bytearray(48), # sr_traffic_sec + None) # implementations + recordLayer.changeWriteState() + + app_data = ApplicationData().create(b'test') + + for result in recordLayer.sendRecord(app_data): + # check if non-blocking socket + self.assertNotIn(result, (0, 1)) + break + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application_data + b'\x03\x01' + # hidden protocol version - TLS 1.x + b'\x00\x15' # length + )) + self.assertEqual(sock.sent[0][5:], bytearray( + b'o\x9fO\x16\x07\x878]GV\xa5l\x12\xb6\x85\xb5@\x83\x94\x06\xd6' + )) + + def test_sendRecord_with_malformed_inner_plaintext(self): + # setup + patcher = mock.patch.object(os, + 'urandom', + lambda x : bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 4) + self.assertEqual((3, 4), recordLayer.version) + + recordLayer.calcTLS1_3PendingState(CipherSuite.TLS_AES_128_GCM_SHA256, + bytearray(32), # cl_traffic_sec + bytearray(32), # sr_traffic_sec + None) # implementations + recordLayer.changeWriteState() + + app_data = ApplicationData().create(b'\x00\x00\x00\x00') + app_data.contentType = 0 + + for result in recordLayer.sendRecord(app_data): + # check if non-blocking socket + self.assertNotIn(result, (0, 1)) + break + self.assertEqual(len(sock.sent), 1) + + # verification of data + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application_data + b'\x03\x01' + # hidden protocol version - TLS 1.x + b'\x00\x15' # length + )) + self.assertEqual(sock.sent[0][5:], bytearray( + b'\x95\xf5^\xa5\xea\x8cCf\xbb\xbb\xe2\xdb!\x13\xf1\x1b\x93s\x81>M' + )) + + # test proper + sock = MockSocket(sock.sent[0]) + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 4) + recordLayer.client = False + + recordLayer.calcTLS1_3PendingState(CipherSuite.TLS_AES_128_GCM_SHA256, + bytearray(32), # cl_traffic_sec + bytearray(32), # sr_traffic_sec + None) # implementations + recordLayer.changeReadState() + with self.assertRaises(TLSUnexpectedMessage): + for result in recordLayer.recvRecord(): + # verify that it's a non-blocking socket + self.assertNotIn(result, (0, 1)) + break + def test_sendRecord_with_SHA256_tls1_2(self): patcher = mock.patch.object(os, 'urandom', From 4c3be0d327789c0195d06d4aa110b1760b93a5a4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 16 Aug 2017 17:26:10 +0200 Subject: [PATCH 540/574] human-readable list listing --- tlslite/utils/lists.py | 24 ++++++++++++++++++++++++ unit_tests/test_tlslite_utils_lists.py | 20 +++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/tlslite/utils/lists.py b/tlslite/utils/lists.py index 3020470f..d8f2bab7 100644 --- a/tlslite/utils/lists.py +++ b/tlslite/utils/lists.py @@ -5,6 +5,8 @@ """Helper functions for handling lists""" +from itertools import chain + def getFirstMatching(values, matches): """ @@ -23,3 +25,25 @@ def getFirstMatching(values, matches): if not values: return None return next((i for i in values if i in matches), None) + + +def to_str_delimiter(values, delim=", ", last_delim=" or "): + """ + Format the list as a human readable string. + + Will format the list as a human readable enumeration, separated by commas + (changable with `delim`) with last value separated with "or" (changable + with `last_delim`). + + :type values: collections.abc.Iterable + :param values: list of items to concatenate + :type delim: str + :param delim: primary delimiter for objects, comma by default + :type last_delim: str + :param last_delim: delimiter for last object in list + :rtype: str + """ + # we need to slice the iterator, so we need a copy + values = list(values) + return delim.join(chain((str(i) for i in values[:-2]), + [last_delim.join(str(i) for i in values[-2:])])) diff --git a/unit_tests/test_tlslite_utils_lists.py b/unit_tests/test_tlslite_utils_lists.py index 442c059a..8805abd5 100644 --- a/unit_tests/test_tlslite_utils_lists.py +++ b/unit_tests/test_tlslite_utils_lists.py @@ -9,7 +9,7 @@ except ImportError: import unittest -from tlslite.utils.lists import getFirstMatching +from tlslite.utils.lists import getFirstMatching, to_str_delimiter class TestGetFirstMatching(unittest.TestCase): def test_empty_list(self): @@ -33,3 +33,21 @@ def test_empty_matches(self): def test_no_matches(self): with self.assertRaises(AssertionError): getFirstMatching([1, 2, 3], None) + + +class TestToStrDelimiter(unittest.TestCase): + def test_empty_list(self): + self.assertEqual("", to_str_delimiter([])) + + def test_one_element(self): + self.assertEqual("12", to_str_delimiter([12])) + + def test_two_elements(self): + self.assertEqual("12 or 13", to_str_delimiter([12, 13])) + + def test_three_elements(self): + self.assertEqual("12, 13 or 14", to_str_delimiter([12, 13, 14])) + + def test_with_strings(self): + self.assertEqual("abc, def or ghi", + to_str_delimiter(['abc', 'def', 'ghi'])) From befda47c815d3f1e89fd60668d81c8dad32738e8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 16 Aug 2017 17:30:06 +0200 Subject: [PATCH 541/574] Human readable handshake type on unexpected message --- tlslite/tlsrecordlayer.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index b83b8b3b..7cdf506e 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -17,6 +17,7 @@ from .utils.compat import * from .utils.cryptomath import * from .utils.codec import Parser +from .utils.lists import to_str_delimiter from .errors import * from .messages import * from .mathtls import * @@ -763,9 +764,13 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): else: subType = p.get(1) if subType not in secondaryType: - for result in self._sendError(\ - AlertDescription.unexpected_message, - "Expecting %s, got %s" % (str(secondaryType), subType)): + exp = to_str_delimiter(HandshakeType.toStr(i) for i in + secondaryType) + rec = HandshakeType.toStr(subType) + for result in self._sendError(AlertDescription + .unexpected_message, + "Expecting {0}, got {1}" + .format(exp, rec)): yield result #Update handshake hashes From 85c999b2d770212dca9a57eca7330e1bdcf93d43 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 16 Aug 2017 18:25:38 +0200 Subject: [PATCH 542/574] add cookie extension id --- tlslite/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index b889c682..180957dd 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -144,6 +144,7 @@ class ExtensionType(TLSEnum): key_share = 40 # TLS 1.3 early_data = 42 # TLS 1.3 supported_versions = 43 # TLS 1.3 + cookie = 44 # TLS 1.3 supports_npn = 13172 tack = 0xF300 renegotiation_info = 0xff01 # RFC 5746 From 0864d92a02ab4693142876cf7023f1e968719214 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 16 Aug 2017 19:59:44 +0200 Subject: [PATCH 543/574] add message_hash handshake type --- tlslite/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tlslite/constants.py b/tlslite/constants.py index 180957dd..15c18fe3 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -107,6 +107,7 @@ class HandshakeType(TLSEnum): finished = 20 certificate_status = 22 next_protocol = 67 + message_hash = 254 # TLS 1.3 class ContentType(TLSEnum): From 2b9e2a1e1936b55e0b5bd3d383c0bd60e96e27c4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 5 Sep 2017 19:45:56 +0200 Subject: [PATCH 544/574] allow comparing handshake messages For HRR ClientHello we need to be able to compare old hello to new hello, add methods to do that --- tlslite/messages.py | 11 +++++++++++ unit_tests/test_tlslite_messages.py | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/tlslite/messages.py b/tlslite/messages.py index 29f080d0..e023b669 100644 --- a/tlslite/messages.py +++ b/tlslite/messages.py @@ -223,6 +223,17 @@ def __init__(self, handshakeType): self.contentType = ContentType.handshake self.handshakeType = handshakeType + def __eq__(self, other): + """Check if other object represents the same data as this object.""" + if hasattr(self, "write") and hasattr(other, "write"): + return self.write() == other.write() + else: + return False + + def __ne__(self, other): + """Check if other object represents different data as this object.""" + return not self.__eq__(other) + def postWrite(self, w): headerWriter = Writer() headerWriter.add(self.handshakeType, 1) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index f458a1f6..9f536cf1 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -105,6 +105,21 @@ def test_create(self): self.assertEqual([], client_hello.cipher_suites) self.assertEqual([0], client_hello.compression_methods) + def test___eq__(self): + client_hello1 = ClientHello() + client_hello1.session_id = bytearray(b'\x02\x03') + client_hello2 = ClientHello() + client_hello2.session_id = bytearray(b'\x02\x03') + + self.assertEqual(client_hello1, client_hello2) + + def test___ne__(self): + client_hello1 = ClientHello() + client_hello2 = ClientHello() + client_hello2.session_id = bytearray(b'\x02\x03') + + self.assertNotEqual(client_hello1, client_hello2) + def test_create_with_one_ciphersuite(self): client_hello = ClientHello() client_hello.create((3,0), bytearray(32), bytearray(0), \ From 08e59d19c6daff77ccc033a5e9196a8d4b4ecb0b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 6 Sep 2017 15:28:12 +0200 Subject: [PATCH 545/574] TLS1.3 changes to FFDH TLS 1.3 requires that both the public value and the shared value to be left-padded with zeros to the size of the prime used as the group --- tlslite/keyexchange.py | 13 +++++++++---- unit_tests/test_tlslite_keyexchange.py | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index fd9c6550..040fff28 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -16,7 +16,7 @@ getPointByteSize from .utils.rsakey import RSAKey from .utils.cryptomath import bytesToNumber, getRandomBytes, powMod, \ - numBits, numberToByteArray, divceil + numBits, numberToByteArray, divceil, numBytes from .utils.lists import getFirstMatching from .utils import tlshashlib as hashlib from .utils.x25519 import x25519, x448, X25519_G, X448_G, X25519_ORDER_SIZE, \ @@ -720,7 +720,10 @@ def calc_public_value(self, private): dh_Y = powMod(self.generator, private, self.prime) if dh_Y in (1, self.prime - 1): raise TLSIllegalParameterException("Small subgroup capture") - return dh_Y + if self.version < (3, 4): + return dh_Y + else: + return numberToByteArray(dh_Y, numBytes(self.prime)) def calc_shared_key(self, private, peer_share): """Calculate the shared key.""" @@ -731,10 +734,12 @@ def calc_shared_key(self, private, peer_share): raise TLSIllegalParameterException("Invalid peer key share") S = powMod(peer_share, private, self.prime) - if S in (1, self.prime - 1): raise TLSIllegalParameterException("Small subgroup capture") - return numberToByteArray(S) + if self.version < (3, 4): + return numberToByteArray(S) + else: + return numberToByteArray(S, numBytes(self.prime)) class ECDHKeyExchange(RawDHKeyExchange): diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py index 8ef31d83..bbc5a349 100644 --- a/unit_tests/test_tlslite_keyexchange.py +++ b/unit_tests/test_tlslite_keyexchange.py @@ -1707,3 +1707,22 @@ def test___init___with_rfc7919_group(self): self.assertEqual(kex.generator, 2) self.assertEqual(kex.prime, RFC7919_GROUPS[0][1]) + + def test_calc_public_value(self): + kex = FFDHKeyExchange(GroupName.ffdhe2048, (3, 4)) + + private = 2 + public = kex.calc_public_value(private) + # verify that numbers are zero-padded + self.assertEqual(public, + bytearray(b'\x00' * 255 + b'\x04')) + + def test_calc_shared_secret(self): + kex = FFDHKeyExchange(GroupName.ffdhe2048, (3, 4)) + + private = 2 + key_share = 4 + shared = kex.calc_shared_key(private, key_share) + # verify that numbers are zero-padded on MSBs + self.assertEqual(shared, + bytearray(b'\x00' * 255 + b'\x10')) From 5e6364355152a758ef325f59514003c87878621a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 1 Nov 2017 13:59:13 +0100 Subject: [PATCH 546/574] restore API compatibility for RecordLayer Because tools like tlsfuzzer need to be able to send records with arbitrary versions, without hiding content type, we need to restore the API compatibility, in that if just the .version is set to high values (3, 5), (3, 255), etc. the record layer behaves the same as it did in TLS 1.2 and earlier --- tlslite/recordlayer.py | 50 +++++++++++++++++++------- tlslite/tlsrecordlayer.py | 2 ++ unit_tests/test_tlslite_recordlayer.py | 6 ++++ 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index a80078d4..5567e9ed 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -252,12 +252,15 @@ class RecordLayer(object): :ivar encryptThenMAC: use the encrypt-then-MAC mechanism for record integrity :ivar handshake_finished: used in SSL2, True if handshake protocol is over + :ivar tls13record: if True, the record layer will use the TLS 1.3 version + and content type hiding """ def __init__(self, sock): self.sock = sock self._recordSocket = RecordSocket(sock) self._version = (0, 0) + self._tls13record = False self.client = True @@ -276,6 +279,30 @@ def blockSize(self): """Return the size of block used by current symmetric cipher (R/O)""" return self._writeState.encContext.block_size + @property + def tls13record(self): + """Return the value of the tls13record state.""" + return self._tls13record + + @tls13record.setter + def tls13record(self, val): + """Change the record layer to TLS1.3-like operation, if applicable.""" + self._tls13record = val + self._handle_tls13_record() + + def _is_tls13_plus(self): + """Returns True if we're doing real TLS 1.3.""" + return self._version > (3, 3) and self._tls13record + + def _handle_tls13_record(self): + """Make sure that the version and tls13record setting is consistent.""" + if self._is_tls13_plus(): + # in TLS 1.3 all records need to be sent with the generic version + # which is the same as TLS 1.0 + self._recordSocket.version = (3, 1) + else: + self._recordSocket.version = self._version + @property def version(self): """Return the TLS version used by record layer""" @@ -285,12 +312,7 @@ def version(self): def version(self, val): """Set the TLS version used by record layer""" self._version = val - if val <= (3, 3): - self._recordSocket.version = val - else: - # in TLS 1.3 all records need to be sent with the generic version - # which is the same as TLS 1.0 - self._recordSocket.version = (3, 1) + self._handle_tls13_record() def getCipherName(self): """ @@ -407,7 +429,7 @@ def _getNonce(self, state, seqnum): """Calculate a nonce for a given enc/dec context""" # ChaCha is using the draft-TLS1.3-like nonce derivation if (state.encContext.name == "chacha20-poly1305" and - len(state.fixedNonce) == 12) or self.version > (3, 3): + len(state.fixedNonce) == 12) or self._is_tls13_plus(): # 4 byte nonce is used by the draft cipher pad = bytearray(len(state.fixedNonce) - len(seqnum)) nonce = bytearray(i ^ j for i, j in zip(pad + seqnum, @@ -421,7 +443,7 @@ def _encryptThenSeal(self, buf, contentType): """Encrypt with AEAD cipher""" #Assemble the authenticated data. seqNumBytes = self._writeState.getSeqNumBytes() - if self.version <= (3, 3): + if not self._is_tls13_plus(): authData = seqNumBytes + bytearray([contentType, self.version[0], self.version[1], @@ -437,7 +459,8 @@ def _encryptThenSeal(self, buf, contentType): buf = self._writeState.encContext.seal(nonce, buf, authData) #AES-GCM, has an explicit variable nonce. - if "aes" in self._writeState.encContext.name and self.version < (3, 4): + if "aes" in self._writeState.encContext.name and \ + not self._is_tls13_plus(): buf = seqNumBytes + buf return buf @@ -481,7 +504,7 @@ def sendRecord(self, msg): contentType = msg.contentType # TLS 1.3 hides the content type of messages - if self.version > (3, 3) and self._writeState.encContext: + if self._is_tls13_plus() and self._writeState.encContext: # TODO - add support for padding data += bytearray([contentType]) # in TLS 1.3 contentType is ignored by _encryptThenSeal @@ -646,7 +669,8 @@ def _decryptAndUnseal(self, recordType, buf): """Decrypt AEAD encrypted data""" seqnumBytes = self._readState.getSeqNumBytes() #AES-GCM, has an explicit variable nonce. - if "aes" in self._readState.encContext.name and self.version < (3, 4): + if "aes" in self._readState.encContext.name and \ + not self._is_tls13_plus(): explicitNonceLength = 8 if explicitNonceLength > len(buf): #Publicly invalid. @@ -660,7 +684,7 @@ def _decryptAndUnseal(self, recordType, buf): #Publicly invalid. raise TLSBadRecordMAC("Truncated tag") - if self.version in [(3, 0), (3, 1), (3, 2), (3, 3)]: + if not self._is_tls13_plus(): plaintextLen = len(buf) - self._readState.encContext.tagLength authData = seqnumBytes + bytearray([recordType, self.version[0], self.version[1], @@ -771,7 +795,7 @@ def recvRecord(self): data = self._decryptStreamThenMAC(header.type, data) # TLS 1.3 encrypts the type - if self.version > (3, 3): + if self._is_tls13_plus(): data, contentType = self._tls13_de_pad(data) header = RecordHeader3().create((3, 4), contentType, len(data)) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 7cdf506e..f177372e 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -177,6 +177,8 @@ def version(self, value): protocol version. """ self._recordLayer.version = value + if value > (3, 3): + self._recordLayer.tls13record = True @property def encryptThenMAC(self): diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 77ac311a..2e43a05c 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -526,6 +526,7 @@ def test_sendRecord_with_encryption_tls1_3_aes_128_gcm(self): recordLayer = RecordLayer(sock) recordLayer.version = (3, 4) + recordLayer.tls13record = True recordLayer.calcTLS1_3PendingState(CipherSuite.TLS_AES_128_GCM_SHA256, bytearray(32), # cl_traffic_sec @@ -568,6 +569,7 @@ def test_recvRecord_with_encryption_tls1_3_aes_128_gcm(self): recordLayer = RecordLayer(sock) recordLayer.client = False recordLayer.version = (3, 4) + recordLayer.tls13record = True recordLayer.calcTLS1_3PendingState(CipherSuite.TLS_AES_128_GCM_SHA256, bytearray(32), # cl_traffic_sec @@ -598,6 +600,7 @@ def test_sendRecord_with_encryption_tls1_3_aes_256_gcm(self): recordLayer = RecordLayer(sock) recordLayer.version = (3, 4) + recordLayer.tls13record = True recordLayer.calcTLS1_3PendingState(CipherSuite.TLS_AES_256_GCM_SHA384, bytearray(48), # cl_traffic_sec @@ -632,6 +635,7 @@ def test_sendRecord_with_encryption_tls1_3_chacha20(self): recordLayer = RecordLayer(sock) recordLayer.version = (3, 4) + recordLayer.tls13record = True ciph = CipherSuite.TLS_CHACHA20_POLY1305_SHA256 recordLayer.calcTLS1_3PendingState(ciph, @@ -668,6 +672,7 @@ def test_sendRecord_with_malformed_inner_plaintext(self): recordLayer = RecordLayer(sock) recordLayer.version = (3, 4) + recordLayer.tls13record = True self.assertEqual((3, 4), recordLayer.version) recordLayer.calcTLS1_3PendingState(CipherSuite.TLS_AES_128_GCM_SHA256, @@ -699,6 +704,7 @@ def test_sendRecord_with_malformed_inner_plaintext(self): sock = MockSocket(sock.sent[0]) recordLayer = RecordLayer(sock) recordLayer.version = (3, 4) + recordLayer.tls13record = True recordLayer.client = False recordLayer.calcTLS1_3PendingState(CipherSuite.TLS_AES_128_GCM_SHA256, From 92c876cbfac4281dbd3f81dcf1de5111a9d9bd05 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 1 Dec 2017 19:50:11 +0100 Subject: [PATCH 547/574] disable Nagle algorithm in example script since caching and fragmentation is done on record layer anyway, doing it on TCP level just screws with alert delivery --- scripts/tls.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/tls.py b/scripts/tls.py index b9f4b92e..45b5896c 100755 --- a/scripts/tls.py +++ b/scripts/tls.py @@ -278,6 +278,7 @@ def clientCmd(argv): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) sock.connect(address) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) connection = TLSConnection(sock) settings = HandshakeSettings() @@ -368,6 +369,8 @@ def handshake(self, connection): settings = HandshakeSettings() settings.useExperimentalTackExtension=True settings.dhParams = dhparam + connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, + 1) connection.handshakeServer(certChain=certChain, privateKey=privateKey, verifierDB=verifierDB, From f7c12ff37d62c5189a3bb373aa4a4beaf67b3d10 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 18 Jan 2018 13:26:22 +0100 Subject: [PATCH 548/574] make setting the encryptThenMAC apply since next cipher change as the method of encrypting applies to the cipher, not the connection or session, it needs to be kept together with the cipher settings for a specific connection direction fixes issue with EtM negotiation in renegotiation case --- tlslite/recordlayer.py | 33 +++++++++++++++++++++----- unit_tests/test_tlslite_recordlayer.py | 1 + 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index 5567e9ed..c74d79d3 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -233,6 +233,7 @@ def __init__(self): self.encContext = None self.fixedNonce = None self.seqnum = 0 + self.encryptThenMAC = False def getSeqNumBytes(self): """Return encoded sequence number and increment it.""" @@ -249,8 +250,6 @@ class RecordLayer(object): :ivar version: the TLS version to use (tuple encoded as on the wire) :ivar sock: underlying socket :ivar client: whether the connection should use encryption - :ivar encryptThenMAC: use the encrypt-then-MAC mechanism for record - integrity :ivar handshake_finished: used in SSL2, True if handshake protocol is over :ivar tls13record: if True, the record layer will use the TLS 1.3 version and content type hiding @@ -270,10 +269,24 @@ def __init__(self, sock): self._pendingReadState = ConnectionState() self.fixedIVBlock = None - self.encryptThenMAC = False - self.handshake_finished = False + @property + def encryptThenMAC(self): + """ + Set or get the setting of Encrypt Then MAC mechanism. + + set the encrypt-then-MAC mechanism for record + integrity for next parameter change (after CCS), + gets current state + """ + return self._writeState.encryptThenMAC + + @encryptThenMAC.setter + def encryptThenMAC(self, value): + self._pendingWriteState.encryptThenMAC = value + self._pendingReadState.encryptThenMAC = value + @property def blockSize(self): """Return the size of block used by current symmetric cipher (R/O)""" @@ -516,7 +529,7 @@ def sendRecord(self, msg): elif self._writeState.encContext and \ self._writeState.encContext.isAEAD: data = self._encryptThenSeal(data, contentType) - elif self.encryptThenMAC: + elif self._writeState.encryptThenMAC: data = self._encryptThenMAC(data, contentType) else: data = self._macThenEncrypt(data, contentType) @@ -785,7 +798,7 @@ def recvRecord(self): self._readState.encContext and \ self._readState.encContext.isAEAD: data = self._decryptAndUnseal(header.type, data) - elif self.encryptThenMAC: + elif self._readState and self._readState.encryptThenMAC: data = self._macThenDecrypt(header.type, data) elif self._readState and \ self._readState.encContext and \ @@ -1079,10 +1092,18 @@ def calcPendingStates(self, cipherSuite, masterSecret, clientRandom, #Assign new connection states to pending states if self.client: + clientPendingState.encryptThenMAC = \ + self._pendingWriteState.encryptThenMAC self._pendingWriteState = clientPendingState + serverPendingState.encryptThenMAC = \ + self._pendingReadState.encryptThenMAC self._pendingReadState = serverPendingState else: + serverPendingState.encryptThenMAC = \ + self._pendingWriteState.encryptThenMAC self._pendingWriteState = serverPendingState + clientPendingState.encryptThenMAC = \ + self._pendingReadState.encryptThenMAC self._pendingReadState = clientPendingState if self.version >= (3, 2) and ivLength: diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 2e43a05c..4421d464 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -2425,6 +2425,7 @@ def test_recvRecord_with_encryptThenMAC_and_SSLv3(self): bytearray(32), # server random None) sendingRecordLayer.changeWriteState() + self.assertTrue(sendingRecordLayer.encryptThenMAC) msg = ApplicationData().create(bytearray(b'test')) From 6e5eb0f8024493cdc438f3432fcc7a71d097b03a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 5 Feb 2018 16:31:23 +0100 Subject: [PATCH 549/574] depend on isort that works on Py2.6 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5bca9649..b9250a82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -82,7 +82,7 @@ before_install: - git fetch origin master:refs/remotes/origin/master install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then travis_retry pip install unittest2; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then travis_retry pip install unittest2 'isort<4.3'; fi - if [[ $TACKPY == 'true' ]]; then travis_retry pip install tackpy; fi - if [[ $M2CRYPTO == 'true' ]]; then travis_retry pip install --pre m2crypto; fi - if [[ $PYCRYPTO == 'true' ]]; then travis_retry pip install pycrypto; fi From 3a771865d97262074933e1791cd9dfcb5e160e44 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 5 Feb 2018 16:38:43 +0100 Subject: [PATCH 550/574] workaround limitations of distutils on Py3.3 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b9250a82..70548c86 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,7 +87,7 @@ install: - if [[ $M2CRYPTO == 'true' ]]; then travis_retry pip install --pre m2crypto; fi - if [[ $PYCRYPTO == 'true' ]]; then travis_retry pip install pycrypto; fi - if [[ $GMPY == 'true' ]]; then travis_retry pip install gmpy; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then travis_retry pip install 'coverage<4' 'hypothesis<1.10'; elif [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then travis_retry pip install coverage 'hypothesis<3'; else travis_retry pip install coverage hypothesis; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then travis_retry pip install 'coverage<4' 'hypothesis<1.10'; elif [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then travis_retry pip install 'coverage' 'hypothesis<3.44' ; elif [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then travis_retry pip install coverage 'hypothesis<3'; else travis_retry pip install coverage hypothesis; fi - travis_retry pip install -r requirements.txt - travis_retry pip install -r build-requirements.txt From 510e683063bc56152144a836e3a3a5ba41357a96 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 9 Feb 2018 13:08:00 +0100 Subject: [PATCH 551/574] run CI on old stable branches --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 70548c86..f4f63513 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,9 @@ language: python branches: only: - master + - tlslite-ng-0.5 + - tlslite-ng-0.6 + - tlslite-ng-0.7 addons: apt_packages: From e4ca2d32793bb008e46a9f642b8118af52536411 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 9 Feb 2018 12:51:54 +0100 Subject: [PATCH 552/574] add support for m2crypto in python3 --- tlslite/utils/compat.py | 12 +++++++++++- tlslite/utils/cryptomath.py | 13 ++++++++----- tlslite/utils/openssl_aes.py | 2 +- tlslite/utils/openssl_rsakey.py | 3 ++- tlslite/utils/openssl_tripledes.py | 4 ++-- unit_tests/test_tlslite_utils_cryptomath.py | 16 +++++++++++++++- unit_tests/test_tlslite_utils_keyfactory.py | 11 ++++++++++- 7 files changed, 49 insertions(+), 12 deletions(-) diff --git a/tlslite/utils/compat.py b/tlslite/utils/compat.py index 46fa54fa..7f019549 100644 --- a/tlslite/utils/compat.py +++ b/tlslite/utils/compat.py @@ -20,7 +20,13 @@ def compat26Str(x): return x # So, python 2.6 requires strings, python 3 requires 'bytes', # and python 2.7 can handle bytearrays... def compatHMAC(x): return bytes(x) - + + def compatAscii2Bytes(val): + """Convert ASCII string to bytes.""" + if isinstance(val, str): + return bytes(val, 'ascii') + return val + def raw_input(s): return input(s) @@ -73,6 +79,10 @@ def compat26Str(x): return str(x) else: def compat26Str(x): return x + def compatAscii2Bytes(val): + """Convert ASCII string to bytes.""" + return val + # So, python 2.6 requires strings, python 3 requires 'bytes', # and python 2.7 can handle bytearrays... def compatHMAC(x): return compat26Str(x) diff --git a/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index c125e64a..f4576966 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -201,11 +201,14 @@ def numberToByteArray(n, howManyBytes=None, endian="big"): else: raise ValueError("Only 'big' and 'little' endian supported") -def mpiToNumber(mpi): #mpi is an openssl-format bignum string - if (ord(mpi[4]) & 0x80) !=0: #Make sure this is a positive number - raise AssertionError() - b = bytearray(mpi[4:]) - return bytesToNumber(b) + +def mpiToNumber(mpi): + """Convert a MPI (OpenSSL bignum string) to an integer.""" + byte = bytearray(mpi) + if byte[4] & 0x80: + raise ValueError("Input must be a positive integer") + return bytesToNumber(byte[4:]) + def numberToMPI(n): b = numberToByteArray(n) diff --git a/tlslite/utils/openssl_aes.py b/tlslite/utils/openssl_aes.py index 658cf830..685d0480 100644 --- a/tlslite/utils/openssl_aes.py +++ b/tlslite/utils/openssl_aes.py @@ -42,7 +42,7 @@ def decrypt(self, ciphertext): context = self._createContext(0) #I think M2Crypto has a bug - it fails to decrypt and return the last block passed in. #To work around this, we append sixteen zeros to the string, below: - plaintext = m2.cipher_update(context, ciphertext+('\0'*16)) + plaintext = m2.cipher_update(context, ciphertext+(b'\0'*16)) #If this bug is ever fixed, then plaintext will end up having a garbage #plaintext block on the end. That's okay - the below code will discard it. diff --git a/tlslite/utils/openssl_rsakey.py b/tlslite/utils/openssl_rsakey.py index 97ef5efe..71cb1033 100644 --- a/tlslite/utils/openssl_rsakey.py +++ b/tlslite/utils/openssl_rsakey.py @@ -7,6 +7,7 @@ from .rsakey import * from .python_rsakey import Python_RSAKey +from .compat import compatAscii2Bytes #copied from M2Crypto.util.py, so when we load the local copy of m2 #we can still use it @@ -112,7 +113,7 @@ def f(v, prompt1=None, prompt2=None): callback = f bio = m2.bio_new(m2.bio_s_mem()) try: - m2.bio_write(bio, s) + m2.bio_write(bio, compatAscii2Bytes(s)) key = OpenSSL_RSAKey() # parse SSLay format PEM file if s.startswith("-----BEGIN RSA PRIVATE KEY-----"): diff --git a/tlslite/utils/openssl_tripledes.py b/tlslite/utils/openssl_tripledes.py index 15a68bb4..80a94538 100644 --- a/tlslite/utils/openssl_tripledes.py +++ b/tlslite/utils/openssl_tripledes.py @@ -37,11 +37,11 @@ def decrypt(self, ciphertext): context = self._createContext(0) #I think M2Crypto has a bug - it fails to decrypt and return the last block passed in. #To work around this, we append sixteen zeros to the string, below: - plaintext = m2.cipher_update(context, ciphertext+('\0'*16)) + plaintext = m2.cipher_update(context, ciphertext+(b'\0'*16)) #If this bug is ever fixed, then plaintext will end up having a garbage #plaintext block on the end. That's okay - the below code will ignore it. plaintext = plaintext[:len(ciphertext)] m2.cipher_ctx_free(context) self.IV = ciphertext[-self.block_size:] - return bytearray(plaintext) \ No newline at end of file + return bytearray(plaintext) diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index ca358445..5811224a 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -16,7 +16,7 @@ from tlslite.utils.cryptomath import isPrime, numBits, numBytes, \ numberToByteArray, MD5, SHA1, secureHash, HMAC_MD5, HMAC_SHA1, \ HMAC_SHA256, HMAC_SHA384, HKDF_expand, bytesToNumber, \ - HKDF_expand_label, derive_secret + HKDF_expand_label, derive_secret, numberToMPI, mpiToNumber from tlslite.handshakehashes import HandshakeHashes class TestIsPrime(unittest.TestCase): @@ -504,3 +504,17 @@ def test_with_handshake_hashes(self): bytearray(b'\t\xec\x01W[Y\xdcP\xac\xebu\x13\xe6\x98' b'\x19\xccu;\xfa\x90\xc9\xe3\xc1\xe7\xb7' b'\xcf\x0c\x97;x\xf0F')) + + +class TestMPI(unittest.TestCase): + def test_toMPI(self): + r = numberToMPI(200) + self.assertEqual(bytearray(b'\x00\x00\x00\x02\x00\xc8'), r) + + def test_fromMPI(self): + r = mpiToNumber(bytearray(b'\x00\x00\x00\x02\x00\xc8')) + self.assertEqual(r, 200) + + def test_fromMPI_with_negative_number(self): + with self.assertRaises(ValueError): + mpiToNumber(bytearray(b'\x00\x00\x00\x01\xc8')) diff --git a/unit_tests/test_tlslite_utils_keyfactory.py b/unit_tests/test_tlslite_utils_keyfactory.py index cf4b2c67..3c33a193 100644 --- a/unit_tests/test_tlslite_utils_keyfactory.py +++ b/unit_tests/test_tlslite_utils_keyfactory.py @@ -2,6 +2,7 @@ # # See the LICENSE file for legal information regarding use of this file. +import sys # compatibility with Python 2.6, for that we need unittest2 package, # which is not available on 3.3 or 3.4 try: @@ -13,6 +14,9 @@ from tlslite.utils.rsakey import RSAKey from tlslite.utils import cryptomath +if cryptomath.m2cryptoLoaded: + import M2Crypto + class TestParsePEMKey(unittest.TestCase): # generated with: @@ -168,7 +172,12 @@ def test_with_missing_m2crypto(self): def test_key_parse_using_openssl(self): # XXX doesn't handle files without newlines - with self.assertRaises(SyntaxError): + # old version of M2Crypto return a Null, in Python3 it raises exception + if M2Crypto.version_info > (0, 27): + exp = M2Crypto.EVP.EVPError + else: + exp = SyntaxError + with self.assertRaises(exp): key = parsePEMKey(self.privKey_str, private=True, implementations=["openssl"]) From a152c269c14cacd9fa33fb531bdd61c6ff7c1c6a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 9 Feb 2018 12:56:36 +0100 Subject: [PATCH 553/574] also test with m2crypto on py3 --- .travis.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f4f63513..d476e6a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,12 @@ matrix: env: TACKPY=true - python: 2.7 env: M2CRYPTO=true -# no M2crypto on Python 3 + - python: 3.4 + env: M2CRYPTO=true + - python: 3.5 + env: M2CRYPTO=true + - python: 3.6 + env: M2CRYPTO=true - python: 2.7 env: PYCRYPTO=true - python: 3.4 @@ -58,11 +63,11 @@ matrix: - python: 2.7 env: M2CRYPTO=true PYCRYPTO=true GMPY=true - python: 3.4 - env: PYCRYPTO=true GMPY=true + env: M2CRYPTO=true PYCRYPTO=true GMPY=true - python: 3.5 - env: PYCRYPTO=true GMPY=true + env: M2CRYPTO=true PYCRYPTO=true GMPY=true - python: 3.6 - env: PYCRYPTO=true GMPY=true + env: M2CRYPTO=true PYCRYPTO=true GMPY=true before_install: - | From b2e3b89599579ec732b4aaf4dfa3e9d267d2670a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 21 Feb 2018 17:56:03 +0100 Subject: [PATCH 554/574] increase timeouts for iteraction some travis hosts are very slow, so try to workaround it by insreasing timeouts in tests --- Makefile | 10 +++++----- tests/tlstest.py | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 1ba0c144..e7c8d956 100644 --- a/Makefile +++ b/Makefile @@ -39,31 +39,31 @@ dist: docs ./setup.py sdist test: - cd tests/ && python ./tlstest.py server localhost:4433 . & sleep 1 + cd tests/ && python ./tlstest.py server localhost:4433 . & sleep 4 cd tests/ && python ./tlstest.py client localhost:4433 . test-local: - cd tests/ && PYTHONPATH=.. python ./tlstest.py server localhost:4433 . & sleep 1 + cd tests/ && PYTHONPATH=.. python ./tlstest.py server localhost:4433 . & sleep 4 cd tests/ && PYTHONPATH=.. python ./tlstest.py client localhost:4433 . test-dev: ifdef PYTHON2 @echo "Running test suite with Python 2" python2 -m unittest discover -v - cd tests/ && PYTHONPATH=.. python2 ./tlstest.py server localhost:4433 . & sleep 1 + cd tests/ && PYTHONPATH=.. python2 ./tlstest.py server localhost:4433 . & sleep 4 cd tests/ && PYTHONPATH=.. python2 ./tlstest.py client localhost:4433 . endif ifdef PYTHON3 @echo "Running test suite with Python 3" python3 -m unittest discover -v - cd tests/ && PYTHONPATH=.. python3 ./tlstest.py server localhost:4433 . & sleep 1 + cd tests/ && PYTHONPATH=.. python3 ./tlstest.py server localhost:4433 . & sleep 4 cd tests/ && PYTHONPATH=.. python3 ./tlstest.py client localhost:4433 . endif ifndef PYTHON2 ifndef PYTHON3 @echo "Running test suite with default Python" python -m unittest discover -v - cd tests/ && PYTHONPATH=.. python ./tlstest.py server localhost:4433 . & sleep 1 + cd tests/ && PYTHONPATH=.. python ./tlstest.py server localhost:4433 . & sleep 4 cd tests/ && PYTHONPATH=.. python ./tlstest.py client localhost:4433 . endif endif diff --git a/tests/tlstest.py b/tests/tlstest.py index 7abd2ba6..55cb3a6a 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -96,8 +96,7 @@ def clientTestCmd(argv): def connect(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - if hasattr(sock, 'settimeout'): #It's a python 2.3 feature - sock.settimeout(15) + sock.settimeout(15) sock.connect(address) c = TLSConnection(sock) return c @@ -779,7 +778,9 @@ def serverTestCmd(argv): synchro = synchroSocket.accept()[0] def connect(): - return TLSConnection(lsock.accept()[0]) + s = lsock.accept()[0] + s.settimeout(15) + return TLSConnection(s) x509Cert = X509().parse(open(os.path.join(dir, "serverX509Cert.pem")).read()) x509Chain = X509CertChain([x509Cert]) From 5deb12830ff49f671f887ba214884e52efc73c74 Mon Sep 17 00:00:00 2001 From: Stanislav Zidek Date: Thu, 22 Feb 2018 14:45:12 +0100 Subject: [PATCH 555/574] TLS 1.3 padding support --- tlslite/handshakesettings.py | 6 +++ tlslite/recordlayer.py | 9 ++++- tlslite/tlsconnection.py | 2 + unit_tests/test_tlslite_recordlayer.py | 51 ++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 3a421964..7cdf5424 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -161,6 +161,10 @@ class HandshakeSettings(object): did not advertise support for any curves. It does not have to be the first curve for eccCurves and may be distinct from curves from that list. + + :vartype padding_cb: func + :ivar padding_cb: Callback to function computing number of padding bytes + for TLS 1.3. Signature is cb_func(msg_size, content_type, max_size). """ def __init__(self): self.minKeySize = 1023 @@ -185,6 +189,7 @@ def __init__(self): self.dhParams = None self.dhGroups = list(ALL_DH_GROUP_NAMES) self.defaultCurve = "secp256r1" + self.padding_cb = None @staticmethod def _sanityCheckKeySizes(other): @@ -315,6 +320,7 @@ def validate(self): other.dhParams = self.dhParams other.dhGroups = self.dhGroups other.defaultCurve = self.defaultCurve + other.padding_cb = self.padding_cb other.versions = self.versions if not cipherfactory.tripleDESPresent: diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py index c74d79d3..c7a7df8b 100644 --- a/tlslite/recordlayer.py +++ b/tlslite/recordlayer.py @@ -271,6 +271,8 @@ def __init__(self, sock): self.handshake_finished = False + self.padding_cb = None + @property def encryptThenMAC(self): """ @@ -518,8 +520,13 @@ def sendRecord(self, msg): # TLS 1.3 hides the content type of messages if self._is_tls13_plus() and self._writeState.encContext: - # TODO - add support for padding data += bytearray([contentType]) + if self.padding_cb: + max_padding = 2**14 - len(data) - 1 + # add number of zero bytes specified by padding_cb() + data += bytearray(self.padding_cb(len(data), + contentType, + max_padding)) # in TLS 1.3 contentType is ignored by _encryptThenSeal contentType = ContentType.application_data diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 17648586..b7e7c21c 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -436,6 +436,7 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, if not settings: settings = HandshakeSettings() settings = settings.validate() + self.sock.padding_cb = settings.padding_cb if clientCertChain: if not isinstance(clientCertChain, X509CertChain): @@ -1252,6 +1253,7 @@ def _handshakeServerAsyncHelper(self, verifierDB, if not settings: settings = HandshakeSettings() settings = settings.validate() + self.sock.padding_cb = settings.padding_cb # OK Start exchanging messages # ****************************** diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py index 4421d464..89a576af 100644 --- a/unit_tests/test_tlslite_recordlayer.py +++ b/unit_tests/test_tlslite_recordlayer.py @@ -660,6 +660,57 @@ def test_sendRecord_with_encryption_tls1_3_chacha20(self): b'o\x9fO\x16\x07\x878]GV\xa5l\x12\xb6\x85\xb5@\x83\x94\x06\xd6' )) + def test_sendRecord_with_padding_tls1_3(self): + patcher = mock.patch.object(os, + 'urandom', + lambda x : bytearray(x)) + mock_random = patcher.start() + self.addCleanup(patcher.stop) + + sock = MockSocket(bytearray(0)) + + recordLayer = RecordLayer(sock) + recordLayer.version = (3, 4) + recordLayer.tls13record = True + + def padding_cb(length, contenttype, max_padding): + return 100 + recordLayer.padding_cb = padding_cb + + ciph = CipherSuite.TLS_CHACHA20_POLY1305_SHA256 + recordLayer.calcTLS1_3PendingState(ciph, + bytearray(48), # cl_traffic_sec + bytearray(48), # sr_traffic_sec + None) # implementations + recordLayer.changeWriteState() + + app_data = ApplicationData().create(b'test') + + for result in recordLayer.sendRecord(app_data): + # check if non-blocking socket + self.assertNotIn(result, (0, 1)) + break + # we expect length 121 bytes (= 0x79) + # 4 B of application data + # 1 B of content type + # 100 B of padding + # 16 B of authentication tag + self.assertEqual(len(sock.sent), 1) + self.assertEqual(sock.sent[0][:5], bytearray( + b'\x17' + # application_data + b'\x03\x01' + # hidden protocol version - TLS 1.x + b'\x00\x79' # length + )) + self.assertEqual(sock.sent[0][5:], bytearray( + b"o\x9fO\x16\x07\x96\xfdHGf\x8d\xe3\x03\x1a\x93p\xb9\xf6" + + b"\xf1\xafK\xbc\x92\xed\xdc\xa7\x02\xb0\x0e\x1e\x00\xd6\xc2" + + b"\xf6\x10\xe5}\xb1T\x85om\xa4\xfa\x1aS\x1f\xab\xc6b\'\xe6f" + + b"\xb3\xbe\xac\xfd\xed\x06\x93\xadbGMD\xd9\xb9\xca\xf6\x8b" + + b"\xac\x07\x96\xe8\xd13)r\xbcNJ\x9d#YP@\x9b\x8ez\x06\xfb" + + b"\x8f2\x8cz\xb7\xd6IP\xfa\xeezcQ\xf3\xe2n\x82\xd1\x9f\xd1x" + + b"\x01x\xea\xd4ht[)\x06" + )) + def test_sendRecord_with_malformed_inner_plaintext(self): # setup patcher = mock.patch.object(os, From 042c7295a2923402eeaa762fd05bc88629f9987a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 22 Feb 2018 12:52:52 +0100 Subject: [PATCH 556/574] decorator for deprecating method parameter names a lot of methods use parameters names that use camelCase, which is unpythonic, add a decorator that will allow renaming them without breaking backwards compatibility --- tlslite/utils/deprecations.py | 33 +++++++++ unit_tests/test_tlslite_utils_deprecations.py | 71 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 tlslite/utils/deprecations.py create mode 100644 unit_tests/test_tlslite_utils_deprecations.py diff --git a/tlslite/utils/deprecations.py b/tlslite/utils/deprecations.py new file mode 100644 index 00000000..8ad4aced --- /dev/null +++ b/tlslite/utils/deprecations.py @@ -0,0 +1,33 @@ + +import warnings +from functools import wraps + +def deprecated_params(names, warn="Param name '{old_name}' is deprecated, " + "please use '{new_name}'"): + """Decorator to translate obsolete names and warn about their use. + + :param dict names: dictionary with pairs of new_name: old_name pairs + that will be used for translating obsolete param names to new names + + :param str warn: DeprecationWarning format string for informing the user + what is the current parameter name, uses 'old_name' for the + deprecated keyword name and 'new_name' for the current one. + Example: "Old name: {old_name}, use {new_name} instead". + """ + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + for new_name, old_name in names.items(): + if old_name in kwargs: + if new_name in kwargs: + raise TypeError("got multiple values for keyword " + "argument '{0}'".format(new_name)) + warnings.warn(warn.format(old_name=old_name, + new_name=new_name), + DeprecationWarning, + stacklevel=2) + kwargs[new_name] = kwargs.pop(old_name) + return func(*args, **kwargs) + return wrapper + return decorator + diff --git a/unit_tests/test_tlslite_utils_deprecations.py b/unit_tests/test_tlslite_utils_deprecations.py new file mode 100644 index 00000000..b4cf43a7 --- /dev/null +++ b/unit_tests/test_tlslite_utils_deprecations.py @@ -0,0 +1,71 @@ +# Copyright (c) 2018, Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. + +# compatibility with Python 2.6, for that we need unittest2 package, +# which is not available on 3.3 or 3.4 +try: + import unittest2 as unittest +except ImportError: + import unittest + +try: + import mock + from mock import call +except ImportError: + import unittest.mock as mock + from unittest.mock import call + +from tlslite.utils.deprecations import deprecated_params + + +class TestDeprecatedParams(unittest.TestCase): + def test_no_changes(self): + @deprecated_params({}) + def method(param_a, param_b): + """Some doc string.""" + return (param_a, param_b) + + a = mock.Mock() + b = mock.Mock() + + r = method(param_a=a, param_b=b) + + self.assertIsInstance(r, tuple) + self.assertEqual(r, (a, b)) + self.assertIs(r[0], a) + self.assertIs(r[1], b) + + self.assertEqual("Some doc string.", method.__doc__) + + def test_change_param(self): + @deprecated_params({'param_a': 'old_param'}) + def method(param_a, param_b): + return (param_a, param_b) + + old = mock.Mock() + b = mock.Mock() + + with self.assertWarns(DeprecationWarning) as e: + r = method(old_param=old, param_b=b) + + self.assertIsInstance(r, tuple) + self.assertEqual(r, (old, b)) + self.assertIs(r[0], old) + self.assertIs(r[1], b) + + self.assertIn('old_param', str(e.warning)) + + def test_both_params(self): + @deprecated_params({'param_a': 'older_param'}) + def method(param_a, param_b): + return (param_a, param_b) + + a = mock.Mock() + b = mock.Mock() + c = mock.Mock() + + with self.assertRaises(TypeError) as e: + method(param_a=a, param_b=b, older_param=c) + + self.assertIn('multiple values', str(e.exception)) From 0efc3fcdfc5ea8f8e244e72b5af7fc7ad8c777d1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 22 Feb 2018 21:40:06 +0100 Subject: [PATCH 557/574] deprecating class fields and methods --- .travis.yml | 1 + tlslite/utils/deprecations.py | 115 ++++++- unit_tests/test_tlslite_utils_deprecations.py | 291 +++++++++++++++++- 3 files changed, 404 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d476e6a5..fa19cdcd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -91,6 +91,7 @@ before_install: install: - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then travis_retry pip install unittest2 'isort<4.3'; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then travis_retry pip install unittest2; fi - if [[ $TACKPY == 'true' ]]; then travis_retry pip install tackpy; fi - if [[ $M2CRYPTO == 'true' ]]; then travis_retry pip install --pre m2crypto; fi - if [[ $PYCRYPTO == 'true' ]]; then travis_retry pip install pycrypto; fi diff --git a/tlslite/utils/deprecations.py b/tlslite/utils/deprecations.py index 8ad4aced..5a4991f8 100644 --- a/tlslite/utils/deprecations.py +++ b/tlslite/utils/deprecations.py @@ -1,12 +1,16 @@ - +# Copyright (c) 2018 Hubert Kario +# +# See the LICENSE file for legal information regarding use of this file. +"""Methods for deprecating old names for arguments or attributes.""" import warnings from functools import wraps + def deprecated_params(names, warn="Param name '{old_name}' is deprecated, " "please use '{new_name}'"): """Decorator to translate obsolete names and warn about their use. - :param dict names: dictionary with pairs of new_name: old_name pairs + :param dict names: dictionary with pairs of new_name: old_name that will be used for translating obsolete param names to new names :param str warn: DeprecationWarning format string for informing the user @@ -31,3 +35,110 @@ def wrapper(*args, **kwargs): return wrapper return decorator + +def deprecated_instance_attrs(names, + warn="Attribute '{old_name}' is deprecated, " + "please use '{new_name}'"): + """Decorator to deprecate class instance attributes. + + Translates all names in `names` to use new names and emits warnings + if the translation was necessary. Does apply only to instance variables + and attributes (won't modify behaviour of class variables, static methods, + etc. + + :param dict names: dictionary with paris of new_name: old_name that will + be used to translate the calls + :param str warn: DeprecationWarning format string for informing the user + what is the current parameter name, uses 'old_name' for the + deprecated keyword name and 'new_name' for the current one. + Example: "Old name: {old_name}, use {new_name} instead". + """ + # reverse the dict as we're looking for old attributes, not new ones + names = dict((j, i) for i, j in names.items()) + + def decorator(clazz): + def getx(self, name, __old_getx=getattr(clazz, "__getattr__", None)): + if name in names: + warnings.warn(warn.format(old_name=name, + new_name=names[name]), + DeprecationWarning, + stacklevel=2) + return getattr(self, names[name]) + if __old_getx: + if hasattr(__old_getx, "__func__"): + return __old_getx.__func__(self, name) + return __old_getx(self, name) + raise AttributeError("'{0}' object has no attribute '{1}'" + .format(clazz.__name__, name)) + + getx.__name__ = "__getattr__" + clazz.__getattr__ = getx + + def setx(self, name, value, __old_setx=getattr(clazz, "__setattr__")): + if name in names: + warnings.warn(warn.format(old_name=name, + new_name=names[name]), + DeprecationWarning, + stacklevel=2) + setattr(self, names[name], value) + else: + __old_setx(self, name, value) + + setx.__name__ = "__setattr__" + clazz.__setattr__ = setx + + def delx(self, name, __old_delx=getattr(clazz, "__delattr__")): + if name in names: + warnings.warn(warn.format(old_name=name, + new_name=names[name]), + DeprecationWarning, + stacklevel=2) + delattr(self, names[name]) + else: + __old_delx(self, name) + + delx.__name__ = "__delattr__" + clazz.__delattr__ = delx + + return clazz + return decorator + + +def deprecated_attrs(names, warn="Attribute '{old_name}' is deprecated, " + "please use '{new_name}'"): + """Decorator to deprecate all specified attributes in class. + + Translates all names in `names` to use new names and emits warnings + if the translation was necessary. + + Note: uses metaclass magic so is incompatible with other metaclass uses + + :param dict names: dictionary with paris of new_name: old_name that will + be used to translate the calls + :param str warn: DeprecationWarning format string for informing the user + what is the current parameter name, uses 'old_name' for the + deprecated keyword name and 'new_name' for the current one. + Example: "Old name: {old_name}, use {new_name} instead". + """ + # prepare metaclass for handling all the class methods, class variables + # and static methods (as they don't go through instance's __getattr__) + class DeprecatedProps(type): + pass + + metaclass = deprecated_instance_attrs(names, warn)(DeprecatedProps) + + def wrapper(cls): + cls = deprecated_instance_attrs(names, warn)(cls) + + # apply metaclass + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper diff --git a/unit_tests/test_tlslite_utils_deprecations.py b/unit_tests/test_tlslite_utils_deprecations.py index b4cf43a7..c998d588 100644 --- a/unit_tests/test_tlslite_utils_deprecations.py +++ b/unit_tests/test_tlslite_utils_deprecations.py @@ -16,7 +16,8 @@ import unittest.mock as mock from unittest.mock import call -from tlslite.utils.deprecations import deprecated_params +from tlslite.utils.deprecations import deprecated_params, \ + deprecated_attrs class TestDeprecatedParams(unittest.TestCase): @@ -69,3 +70,291 @@ def method(param_a, param_b): method(param_a=a, param_b=b, older_param=c) self.assertIn('multiple values', str(e.exception)) + + def test_in_class(self): + class Clazz(object): + @staticmethod + @deprecated_params({"new_param": "old_param"}) + def method(param, new_param=None): + return "{0} {1}".format(param, new_param) + + instance = Clazz() + + self.assertEqual(instance.method("aa", "BB"), "aa BB") + + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(instance.method("aa", old_param="CC"), "aa CC") + self.assertIn("old_param", str(e.warning)) + self.assertIn("new_param", str(e.warning)) + + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(Clazz.method("g", old_param="D"), "g D") + self.assertIn("old_param", str(e.warning)) + self.assertIn("new_param", str(e.warning)) + + +class TestDeprecatedFields(unittest.TestCase): + def test_no_change(self): + + @deprecated_attrs({}) + class Clazz(object): + """Some nice class.""" + class_field = "I'm class_field" + + def __init__(self): + self.new_field = "I'm new_field" + + def new_method(self): + """Good method.""" + return "in new_method" + + @staticmethod + def new_static_method(): + return "in new_static_method" + + @classmethod + def new_cls_method(cls, param): + return "cls methd: {0}".format(param) + + instance = Clazz() + + self.assertEqual(instance.new_field, "I'm new_field") + self.assertEqual(instance.class_field, "I'm class_field") + self.assertEqual(instance.new_method(), "in new_method") + self.assertEqual(instance.new_static_method(), "in new_static_method") + self.assertEqual(instance.new_cls_method("a"), "cls methd: a") + self.assertEqual(Clazz.new_cls_method("a"), "cls methd: a") + self.assertEqual(Clazz.new_static_method(), "in new_static_method") + self.assertEqual(instance.__doc__, "Some nice class.") + self.assertEqual(instance.new_method.__doc__, "Good method.") + + def test_deprecated_instance_variable(self): + @deprecated_attrs({"new_field": "old_field"}) + class Clazz(object): + def __init__(self): + self.new_field = "I'm new_field" + + instance = Clazz() + + self.assertEqual(instance.new_field, "I'm new_field") + + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(instance.old_field, "I'm new_field") + instance.old_field = "I've been set" + + self.assertEqual(instance.new_field, "I've been set") + + self.assertIn("old_field", str(e.warning)) + + with self.assertWarns(DeprecationWarning): + del instance.old_field + + self.assertFalse(hasattr(instance, "new_field")) + + def test_deprecated_instance_method(self): + @deprecated_attrs({"new_method": "old_method"}) + class Clazz(object): + def new_method(self, param): + return "new_method: {0}".format(param) + + instance = Clazz() + + self.assertEqual(instance.new_method("aa"), "new_method: aa") + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(instance.old_method("aa"), "new_method: aa") + + self.assertIn("old_method", str(e.warning)) + + def test_deprecated_class_method(self): + @deprecated_attrs({"foo": "bar"}) + class Clazz(object): + @classmethod + def foo(cls, arg): + return "foo: {0}".format(arg) + + instance = Clazz() + + self.assertEqual(instance.foo("aa"), "foo: aa") + self.assertEqual(Clazz.foo("aa"), "foo: aa") + + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(instance.bar("aa"), "foo: aa") + self.assertIn("bar", str(e.warning)) + + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(Clazz.bar("aa"), "foo: aa") + self.assertIn("bar", str(e.warning)) + + self.assertFalse(hasattr(Clazz, "non_existing")) + + def test_deprecated_static_method(self): + @deprecated_attrs({"new_stic": "old_stic"}) + class Clazz(object): + @staticmethod + def new_stic(param): + return "new_stic: {0}".format(param) + + instance = Clazz() + + self.assertEqual(instance.new_stic("aaa"), "new_stic: aaa") + self.assertEqual(Clazz.new_stic("aaa"), "new_stic: aaa") + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(instance.old_stic("aaa"), "new_stic: aaa") + self.assertIn("old_stic", str(e.warning)) + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(Clazz.old_stic("aaa"), "new_stic: aaa") + self.assertIn("old_stic", str(e.warning)) + + def test_deprecated_class_variable(self): + @deprecated_attrs({"new_cvar": "old_cvar"}) + class Clazz(object): + new_cvar = "some string" + + def method(self): + return self.new_cvar + + instance = Clazz() + + self.assertEqual(instance.method(), "some string") + Clazz.new_cvar = bytearray(b"new string") + self.assertEqual(instance.new_cvar, b"new string") + + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(instance.old_cvar, b"new string") + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(Clazz.old_cvar, b"new string") + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + # direct assignment to old value won't work, ex: + # Clazz.old_cvar = b'newest string' + with self.assertWarns(DeprecationWarning) as e: + Clazz.old_cvar[:] = b"newest string" + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + self.assertEqual(instance.method(), b"newest string") + + def test_class_with_custom_getattr(self): + @deprecated_attrs({"new_cvar": "old_cvar"}) + class Clazz(object): + new_cvar = "first title" + + def __getattr__(self, name): + if name == "intresting": + return "some value" + raise AttributeError("Clazz does not have {0}".format(name)) + + instance = Clazz() + + self.assertEqual(instance.intresting, "some value") + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(instance.old_cvar, "first title") + + self.assertFalse(hasattr(instance, "non_existing")) + + def test_deprecated_attrs_variable_deletion(self): + @deprecated_attrs({"new_cvar": "old_cvar"}) + class Clazz(object): + new_cvar = "first title" + + def __init__(self): + self.val = "something" + + @classmethod + def method(cls): + return cls.new_cvar + + instance = Clazz() + + self.assertEqual(instance.method(), "first title") + self.assertEqual(instance.new_cvar, "first title") + + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(Clazz.old_cvar, "first title") + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(instance.old_cvar, "first title") + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + Clazz.new_cvar = "second" + + self.assertEqual(instance.method(), "second") + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(instance.old_cvar, "second") + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + with self.assertWarns(DeprecationWarning) as e: + Clazz.old_cvar = "third" + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + self.assertEqual(instance.method(), "third") + self.assertEqual(Clazz.new_cvar, "third") + self.assertEqual(instance.new_cvar, "third") + + with self.assertWarns(DeprecationWarning) as e: + del Clazz.old_cvar + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + self.assertFalse(hasattr(Clazz, "new_cvar")) + with self.assertWarns(DeprecationWarning) as e: + self.assertFalse(hasattr(Clazz, "old_cvar")) + + def test_class_variable_deletion(self): + @deprecated_attrs({"new_cvar": "old_cvar"}) + class Clazz(object): + new_cvar = "first title" + + @classmethod + def method(cls): + return cls.new_cvar + + instance = Clazz() + + self.assertEqual(instance.method(), "first title") + self.assertEqual(instance.new_cvar, "first title") + + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(Clazz.old_cvar, "first title") + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(instance.old_cvar, "first title") + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + Clazz.new_cvar = "second" + + self.assertEqual(instance.method(), "second") + with self.assertWarns(DeprecationWarning) as e: + self.assertEqual(instance.old_cvar, "second") + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + with self.assertWarns(DeprecationWarning) as e: + Clazz.old_cvar = "third" + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + self.assertEqual(instance.method(), "third") + self.assertEqual(Clazz.new_cvar, "third") + self.assertEqual(instance.new_cvar, "third") + + with self.assertWarns(DeprecationWarning) as e: + del Clazz.old_cvar + self.assertIn("old_cvar", str(e.warning)) + self.assertIn("new_cvar", str(e.warning)) + + self.assertFalse(hasattr(Clazz, "new_cvar")) + with self.assertWarns(DeprecationWarning) as e: + self.assertFalse(hasattr(Clazz, "old_cvar")) From 34e74cc6890630257c4828d34e604ef8e4aef9f5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 23 Feb 2018 02:10:34 +0100 Subject: [PATCH 558/574] update pylintrc - forbid camelCase names use the new pylint generated pylintrc as a guide, update rgx --- pylintrc | 94 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/pylintrc b/pylintrc index 59c9602e..aa1b0613 100644 --- a/pylintrc +++ b/pylintrc @@ -1,11 +1,9 @@ [MASTER] -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= # Profiled execution. profile=no @@ -14,26 +12,37 @@ profile=no # paths. ignore=CVS -# Pickle collected data for later comparisons. -persistent=yes +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. +jobs=1 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= -# DEPRECATED -include-ids=no +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= -# DEPRECATED -symbols=no +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no [MESSAGES CONTROL] -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time. See also the "--disable" option for examples. -#enable= +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this @@ -46,22 +55,19 @@ symbols=no # --disable=W" disable=locally-disabled,locally-enabled +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= [REPORTS] -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". files-output=no -# Tells whether to display a full report or only the messages -reports=yes - # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total @@ -77,6 +83,16 @@ comment=no # used to format the message information. See doc for all details #msg-template= +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio).You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages +reports=yes + +# Activate the evaluation score. +score=yes [LOGGING] @@ -116,7 +132,7 @@ generated-members=REQUEST,acl_users,aq_parent bad-functions=map,filter,apply,input,file # Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ +good-names=i,j,k,e,ex,Run,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata @@ -129,17 +145,13 @@ name-group= include-naming-hint=no # Regular expression matching correct function names -#function-rgx=[a-z_][a-z0-9_]{2,30}$ -# mixedCase -function-rgx=_?[a-z][A-Za-z0-9]{1,30}$ +function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Naming hint for function names function-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct variable names -#variable-rgx=[a-z_][a-z0-9_]{2,30}$ -# mixedCase -variable-rgx=_?[a-z][A-Za-z0-9]{1,30}$ +variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Naming hint for variable names variable-name-hint=[a-z_][a-z0-9_]{2,30}$ @@ -151,20 +163,16 @@ const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Regular expression matching correct attribute names -#attr-rgx=[a-z_][a-z0-9_]{2,30}$ -# mixedCase -attr-rgx=_?[a-z][A-Za-z0-9]{1,30}$ +attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{2,30}$ +attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Regular expression matching correct argument names -#argument-rgx=[a-z_][a-z0-9_]{2,30}$ -# mixedCase or _mixedCase -argument-rgx=_?[a-z][A-Za-z0-9]{1,30}$ +argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{2,30}$ +argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ @@ -173,9 +181,7 @@ class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Regular expression matching correct inline iteration names -#inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ -# mixedCase -inlinevar-rgx=_?[a-z][A-Za-z0-9]{1,30}$ +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Naming hint for inline iteration names inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ @@ -193,9 +199,7 @@ module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression matching correct method names -#method-rgx=[a-z_][a-z0-9_]{2,30}$ -# mixedCase -method-rgx=((_?[a-z][A-Za-z0-9]{1,30})|(__.*__))$ +method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ # Naming hint for method names method-name-hint=[a-z_][a-z0-9_]{2,30}$ From 9c48f1237eb0fcd4ef3ae5905a79b94a96ff16bc Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 23 Feb 2018 02:26:00 +0100 Subject: [PATCH 559/574] add deprecations module to docs --- docs/tlslite.utils.deprecations.rst | 8 ++++++++ docs/tlslite.utils.rst | 1 + 2 files changed, 9 insertions(+) create mode 100644 docs/tlslite.utils.deprecations.rst diff --git a/docs/tlslite.utils.deprecations.rst b/docs/tlslite.utils.deprecations.rst new file mode 100644 index 00000000..d5aa48cb --- /dev/null +++ b/docs/tlslite.utils.deprecations.rst @@ -0,0 +1,8 @@ +tlslite.utils.deprecations module +================================= + +.. automodule:: tlslite.utils.deprecations + :members: + :special-members: + :undoc-members: + :show-inheritance: diff --git a/docs/tlslite.utils.rst b/docs/tlslite.utils.rst index fa1005ba..c71c74f2 100644 --- a/docs/tlslite.utils.rst +++ b/docs/tlslite.utils.rst @@ -23,6 +23,7 @@ Submodules tlslite.utils.constanttime tlslite.utils.cryptomath tlslite.utils.datefuncs + tlslite.utils.deprecations tlslite.utils.dns_utils tlslite.utils.ecc tlslite.utils.keyfactory From 1e4c0925f2eb4668f814feb266711a3db7b5d868 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 23 Feb 2018 02:46:33 +0100 Subject: [PATCH 560/574] fix names in tlslite.defragmenter --- tlslite/defragmenter.py | 73 ++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/tlslite/defragmenter.py b/tlslite/defragmenter.py index de6b56a4..559404c2 100644 --- a/tlslite/defragmenter.py +++ b/tlslite/defragmenter.py @@ -2,14 +2,20 @@ # # See the LICENSE file for legal information regarding use of this file. -""" Helper package for handling fragmentation of messages """ +"""Helper package for handling fragmentation of messages.""" from __future__ import generators from .utils.codec import Parser +from .utils.deprecations import deprecated_attrs, deprecated_params -class Defragmenter(object): +@deprecated_attrs({"add_static_size": "addStaticSize", + "add_dynamic_size": "addDynamicSize", + "add_data": "addData", + "get_message": "getMessage", + "clear_buffers": "clearBuffers"}) +class Defragmenter(object): """ Class for demultiplexing TLS messages. @@ -32,17 +38,18 @@ def __init__(self): self.buffers = {} self.decoders = {} - def addStaticSize(self, msgType, size): + @deprecated_params({"msg_type": "msgType"}) + def add_static_size(self, msg_type, size): """Add a message type which all messages are of same length""" - if msgType in self.priorities: + if msg_type in self.priorities: raise ValueError("Message type already defined") if size < 1: raise ValueError("Message size must be positive integer") - self.priorities += [msgType] + self.priorities += [msg_type] - self.buffers[msgType] = bytearray(0) - def sizeHandler(data): + self.buffers[msg_type] = bytearray(0) + def size_handler(data): """ Size of message in parameter @@ -53,64 +60,68 @@ def sizeHandler(data): return None else: return size - self.decoders[msgType] = sizeHandler + self.decoders[msg_type] = size_handler - def addDynamicSize(self, msgType, sizeOffset, sizeOfSize): + @deprecated_params({"msg_type": "msgType", + "size_offset": "sizeOffset", + "size_of_size": "sizeOfSize"}) + def add_dynamic_size(self, msg_type, size_offset, size_of_size): """Add a message type which has a dynamic size set in a header""" - if msgType in self.priorities: + if msg_type in self.priorities: raise ValueError("Message type already defined") - if sizeOfSize < 1: + if size_of_size < 1: raise ValueError("Size of size must be positive integer") - if sizeOffset < 0: + if size_offset < 0: raise ValueError("Offset can't be negative") - self.priorities += [msgType] - self.buffers[msgType] = bytearray(0) + self.priorities += [msg_type] + self.buffers[msg_type] = bytearray(0) - def sizeHandler(data): + def size_handler(data): """ Size of message in parameter If complete message is present in parameter returns its size, None otherwise. """ - if len(data) < sizeOffset+sizeOfSize: + if len(data) < size_offset+size_of_size: return None else: parser = Parser(data) # skip the header - parser.getFixBytes(sizeOffset) + parser.getFixBytes(size_offset) - payloadLength = parser.get(sizeOfSize) - if parser.getRemainingLength() < payloadLength: + payload_length = parser.get(size_of_size) + if parser.getRemainingLength() < payload_length: # not enough bytes in buffer return None - return sizeOffset + sizeOfSize + payloadLength + return size_offset + size_of_size + payload_length - self.decoders[msgType] = sizeHandler + self.decoders[msg_type] = size_handler - def addData(self, msgType, data): + @deprecated_params({"msg_type": "msgType"}) + def add_data(self, msg_type, data): """Adds data to buffers""" - if msgType not in self.priorities: + if msg_type not in self.priorities: raise ValueError("Message type not defined") - self.buffers[msgType] += data + self.buffers[msg_type] += data - def getMessage(self): + def get_message(self): """Extract the highest priority complete message from buffer""" - for msgType in self.priorities: - length = self.decoders[msgType](self.buffers[msgType]) + for msg_type in self.priorities: + length = self.decoders[msg_type](self.buffers[msg_type]) if length is None: continue # extract message - data = self.buffers[msgType][:length] + data = self.buffers[msg_type][:length] # remove it from buffer - self.buffers[msgType] = self.buffers[msgType][length:] - return (msgType, data) + self.buffers[msg_type] = self.buffers[msg_type][length:] + return (msg_type, data) return None - def clearBuffers(self): + def clear_buffers(self): """Remove all data from buffers""" for key in self.buffers.keys(): self.buffers[key] = bytearray(0) From 9d23e4bbcf9c541e37f27630739ac008c27bc7a5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 9 Aug 2017 19:09:30 +0200 Subject: [PATCH 561/574] handling of Encrypted Extensions in record layer --- tlslite/tlsrecordlayer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index f177372e..bf20b348 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -801,6 +801,8 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): yield Finished(self.version).parse(p) elif subType == HandshakeType.next_protocol: yield NextProtocol().parse(p) + elif subType == HandshakeType.encrypted_extensions: + yield EncryptedExtensions().parse(p) else: raise AssertionError() From 85686eb8bfc7a8254f87c767cc3c5ccea5f2208e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 3 Aug 2017 19:49:28 +0200 Subject: [PATCH 562/574] handling of TLS 1.3 Certificate and Finished in record layer --- tlslite/tlsrecordlayer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index bf20b348..75547cc3 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -784,7 +784,7 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): elif subType == HandshakeType.server_hello: yield ServerHello().parse(p) elif subType == HandshakeType.certificate: - yield Certificate(constructorType).parse(p) + yield Certificate(constructorType, self.version).parse(p) elif subType == HandshakeType.certificate_request: yield CertificateRequest(self.version).parse(p) elif subType == HandshakeType.certificate_verify: @@ -798,7 +798,7 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): yield ClientKeyExchange(constructorType, \ self.version).parse(p) elif subType == HandshakeType.finished: - yield Finished(self.version).parse(p) + yield Finished(self.version, constructorType).parse(p) elif subType == HandshakeType.next_protocol: yield NextProtocol().parse(p) elif subType == HandshakeType.encrypted_extensions: From 4cc7620b5956dafe9f5ba4f9ccf92b017ccbf845 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 10 Aug 2017 14:18:23 +0200 Subject: [PATCH 563/574] unittest based integration test - connection --- unit_tests/test_tlslite_tlsconnection.py | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index 39a802eb..4841dc87 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -9,6 +9,9 @@ except ImportError: import unittest +import socket +import threading + from tlslite.recordlayer import RecordLayer from tlslite.messages import ServerHello, ClientHello, Alert, RecordHeader3 from tlslite.constants import CipherSuite, AlertDescription, ContentType @@ -320,5 +323,49 @@ def test_keyingMaterialExporter_tls1_1(self): bytearray(b'\x1f\xf8\x18\x01:\x9f\x15a\xd5x\xaa;Y>' + b'\xafG\x92AH\xa4')) +class TestRealConnection(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.certChain = X509CertChain([X509().parse(srv_raw_certificate)]) + cls.certKey = parsePEMKey(srv_raw_key, private=True) + + def setUp(self): + self.client_socket, self.server_socket = socket.socketpair() + + self.server_socket.settimeout(1) + self.server = TLSConnection(self.server_socket) + + def server_process(server): + server.handshakeServer(certChain=self.certChain, + privateKey=self.certKey) + ret = server.read(min=len("client hello")) + if ret != bytearray(b"client hello"): + raise AssertionError("incorrect query") + server.write(bytearray(b"Conn OK")) + server.close() + + self.thread = threading.Thread(target=server_process, + args=(self.server, )) + self.thread.start() + + def test_connection_no_rsa_pss(self): + settings = HandshakeSettings() + # exclude pss as the keys in this module are too small for + # the needed salt size for sha512 hash + settings.rsaSchemes = ["pkcs1"] + conn = TLSConnection(self.client_socket) + conn.handshakeClientCert(serverName="localhost", + settings=settings) + self.assertIn(conn.session.cipherSuite, CipherSuite.aeadSuites) + conn.write(bytearray(b"client hello")) + ret = conn.read(min=len("Conn OK")) + self.assertEqual(ret, bytearray(b"Conn OK")) + + def tearDown(self): + self.thread.join() + self.client_socket.close() + self.server_socket.close() + + if __name__ == '__main__': unittest.main() From 1ddee5cb4c9d590153ee6368d44fcaff6384cf70 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 10 Aug 2017 19:25:58 +0200 Subject: [PATCH 564/574] handling of New Session Ticket in tls record layer --- tlslite/tlsrecordlayer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 75547cc3..bd6da18e 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -803,6 +803,8 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): yield NextProtocol().parse(p) elif subType == HandshakeType.encrypted_extensions: yield EncryptedExtensions().parse(p) + elif subType == HandshakeType.new_session_ticket: + yield NewSessionTicket().parse(p) else: raise AssertionError() From ee6780264cb46b8427199cb6f51716b62d657379 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 11 Aug 2017 18:02:26 +0200 Subject: [PATCH 565/574] key shares for client hello in handshake settings --- tlslite/handshakesettings.py | 19 ++++++++++++++++ unit_tests/test_tlslite_handshakesettings.py | 24 ++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 7cdf5424..96902056 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -162,6 +162,9 @@ class HandshakeSettings(object): first curve for eccCurves and may be distinct from curves from that list. + :vartype keyShares: list + :ivar keyShares: list of TLS 1.3 key shares to include in Client Hello + :vartype padding_cb: func :ivar padding_cb: Callback to function computing number of padding bytes for TLS 1.3. Signature is cb_func(msg_size, content_type, max_size). @@ -189,6 +192,7 @@ def __init__(self): self.dhParams = None self.dhGroups = list(ALL_DH_GROUP_NAMES) self.defaultCurve = "secp256r1" + self.keyShares = ["secp256r1", "x25519"] self.padding_cb = None @staticmethod @@ -261,6 +265,20 @@ def _sanityCheckPrimitivesNames(other): raise ValueError("Unknown FFDHE group name: '{0}'" .format(unknownDHGroup)) + unknownKeyShare = [val for val in other.keyShares + if val not in ALL_DH_GROUP_NAMES and + val not in ALL_CURVE_NAMES] + if unknownKeyShare: + raise ValueError("Unknown key share: '{0}'" + .format(unknownKeyShare)) + + nonAdvertisedGroup = [val for val in other.keyShares + if val not in other.eccCurves and + val not in other.dhGroups] + if nonAdvertisedGroup: + raise ValueError("Key shares for not enabled groups specified: {0}" + .format(nonAdvertisedGroup)) + @staticmethod def _sanityCheckProtocolVersions(other): """Check if set protocol version are sane""" @@ -322,6 +340,7 @@ def validate(self): other.defaultCurve = self.defaultCurve other.padding_cb = self.padding_cb other.versions = self.versions + other.keyShares = self.keyShares if not cipherfactory.tripleDESPresent: other.cipherNames = [i for i in self.cipherNames if i != "3des"] diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index 3538ed91..ec5125e5 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -284,5 +284,29 @@ def test_invalid_defaultCurve_name(self): with self.assertRaises(ValueError): hs.validate() + def test_invalid_keyShares_name(self): + hs = HandshakeSettings() + hs.keyShares = ["ffdhe1024"] + with self.assertRaises(ValueError): + hs.validate() + + def test_not_matching_keyShares(self): + hs = HandshakeSettings() + hs.keyShares = ["x25519"] + hs.eccCurves = ["x448"] + with self.assertRaises(ValueError) as e: + hs.validate() + + self.assertIn("x25519", str(e.exception)) + + def test_not_matching_ffdhe_keyShares(self): + hs = HandshakeSettings() + hs.keyShares = ["ffdhe2048", "x25519"] + hs.dhGroups = ["ffdhe4096"] + with self.assertRaises(ValueError) as e: + hs.validate() + + self.assertIn("ffdhe2048", str(e.exception)) + if __name__ == '__main__': unittest.main() From 750f15572e8489e4cca15c7633033ed5dad5c5aa Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 11 Aug 2017 19:43:32 +0200 Subject: [PATCH 566/574] save received session tickets after handshake --- tlslite/tlsrecordlayer.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index bd6da18e..268d0f21 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -104,6 +104,9 @@ class TLSRecordLayer(object): throughput after sending few kiB of data. Setting to values greater than 2**14 will cause the connection to be dropped by RFC compliant peers. + + :vartype tickets: list of bytearray + :ivar tickets: list of session tickets received from server, oldest first. """ def __init__(self, sock): @@ -152,6 +155,9 @@ def __init__(self, sock): #Limit the size of outgoing records to following size self.recordSize = 16384 # 2**14 + # NewSessionTickets received from server + self.tickets = [] + @property def _client(self): """Boolean stating if the endpoint acts as a client""" @@ -237,12 +243,23 @@ def readAsync(self, max=None, min=1): :rtype: iterable :returns: A generator; see above for details. """ + if self.version > (3, 3): + allowedTypes = (ContentType.application_data, + ContentType.handshake) + allowedHsTypes = HandshakeType.new_session_ticket + else: + allowedTypes = ContentType.application_data + allowedHsTypes = None try: - while len(self._readBuffer) Date: Wed, 16 Aug 2017 16:52:06 +0200 Subject: [PATCH 567/574] simple TLS 1.3 client basic TLS 1.3 client support - no HRR, no session resumption --- tlslite/handshakesettings.py | 2 +- tlslite/tlsconnection.py | 271 ++++++++++++++++++++++- unit_tests/test_tlslite_tlsconnection.py | 5 +- 3 files changed, 264 insertions(+), 14 deletions(-) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 96902056..484b0704 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -178,7 +178,7 @@ def __init__(self): self.cipherImplementations = list(CIPHER_IMPLEMENTATIONS) self.certificateTypes = list(CERTIFICATE_TYPES) self.minVersion = (3, 1) - self.maxVersion = (3, 3) + self.maxVersion = (3, 4) self.versions = [TLS_1_3_DRAFT, (3, 3), (3, 2), (3, 1)] self.useExperimentalTackExtension = False self.sendFallbackSCSV = False diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index b7e7c21c..a7f1ff48 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -30,7 +30,8 @@ from .handshakesettings import HandshakeSettings from .utils.tackwrapper import * from .keyexchange import KeyExchange, RSAKeyExchange, DHE_RSAKeyExchange, \ - ECDHE_RSAKeyExchange, SRPKeyExchange, ADHKeyExchange, AECDHKeyExchange + ECDHE_RSAKeyExchange, SRPKeyExchange, ADHKeyExchange, \ + AECDHKeyExchange, FFDHKeyExchange, ECDHKeyExchange from .handshakehelpers import HandshakeHelpers class TLSConnection(TLSRecordLayer): @@ -72,6 +73,7 @@ def __init__(self, sock): self.extendedMasterSecret = False self._clientRandom = bytearray(0) self._serverRandom = bytearray(0) + self.next_proto = None def keyingMaterialExporter(self, label, length=20): """Return keying material as described in RFC 5705 @@ -462,11 +464,13 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, if password and self.fault == Fault.badPassword: password += bytearray(b"GARBAGE") - #Tentatively set the version to the client's minimum version. - #We'll use this for the ClientHello, and if an error occurs - #parsing the Server Hello, we'll use this version for the response - self.version = settings.maxVersion - + # Tentatively set the client's record version. + # We'll use this for the ClientHello, and if an error occurs + # parsing the Server Hello, we'll use this version for the response + # in TLS 1.3 it always needs to be set to TLS 1.0 + self.version = \ + (3, 1) if settings.maxVersion > (3, 3) else settings.maxVersion + # OK Start sending messages! # ***************************** @@ -485,7 +489,24 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, else: break serverHello = result cipherSuite = serverHello.cipher_suite - + + # if we're doing tls1.3, use the new code as the negotiation is much + # different + if serverHello.server_version > (3, 3): + for result in self._clientTLS13Handshake(settings, clientHello, + serverHello): + if result in (0, 1): + yield result + else: + break + if result == "finished": + self._handshakeDone(resumed=False) + self._serverRandom = serverHello.random + self._clientRandom = clientHello.random + return + else: + raise Exception("unexpected return") + # Choose a matching Next Protocol from server list against ours # (string or None) nextProto = self._clientSelectNextProto(nextProtos, serverHello) @@ -603,6 +624,7 @@ def _clientSendClientHello(self, settings, session, srpUsername, if srpParams: cipherSuites += CipherSuite.getSrpAllSuites(settings) elif certParams: + cipherSuites += CipherSuite.getTLS13Suites(settings) cipherSuites += CipherSuite.getEcdheCertSuites(settings) cipherSuites += CipherSuite.getDheCertSuites(settings) cipherSuites += CipherSuite.getCertSuites(settings) @@ -655,10 +677,32 @@ def _clientSendClientHello(self, settings, session, srpUsername, # if we know any protocols for ALPN, advertise them if alpn: extensions.append(ALPNExtension().create(alpn)) + + # when TLS 1.3 advertised, add key shares + if next((i for i in settings.versions if i > (3, 3)), None): + extensions.append(SupportedVersionsExtension(). + create(settings.versions)) + + shares = [] + for group_name in settings.keyShares: + group_id = getattr(GroupName, group_name) + if group_id in GroupName.allFF: + kex = FFDHKeyExchange(group_id, (3, 4)) + else: + kex = ECDHKeyExchange(group_id, (3, 4)) + private = kex.get_random_private_key() + share = kex.calc_public_value(private) + + shares.append(KeyShareEntry().create(group_id, share, private)) + if shares: + extensions.append(ClientKeyShareExtension().create(shares)) + # don't send empty list of extensions or extensions in SSLv3 if not extensions or settings.maxVersion == (3, 0): extensions = None + sent_version = min(settings.maxVersion, (3, 3)) + #Either send ClientHello (with a resumable session)... if session and session.sessionID: #If it's resumable, then its @@ -668,7 +712,7 @@ def _clientSendClientHello(self, settings, session, srpUsername, "with parameters") else: clientHello = ClientHello() - clientHello.create(settings.maxVersion, getRandomBytes(32), + clientHello.create(sent_version, getRandomBytes(32), session.sessionID, wireCipherSuites, certificateTypes, session.srpUsername, @@ -679,7 +723,7 @@ def _clientSendClientHello(self, settings, session, srpUsername, #Or send ClientHello (without) else: clientHello = ClientHello() - clientHello.create(settings.maxVersion, getRandomBytes(32), + clientHello.create(sent_version, getRandomBytes(32), bytearray(0), wireCipherSuites, certificateTypes, srpUsername, @@ -698,6 +742,8 @@ def _clientSendClientHello(self, settings, session, srpUsername, def _clientGetServerHello(self, settings, clientHello): + # TODO: add Handshake Retry Request handling + # TODO: reset handshake_hashes if we do get HRR for result in self._getMsg(ContentType.handshake, HandshakeType.server_hello): if result in (0,1): yield result @@ -707,6 +753,10 @@ def _clientGetServerHello(self, settings, clientHello): #Get the server version. Do this before anything else, so any #error alerts will use the server's version self.version = serverHello.server_version + # TODO remove when TLS 1.3 is final (server_version will be set to + # draft version in draft protocol implementations) + if self.version > (3, 4): + self.version = (3, 4) #Check ServerHello if serverHello.server_version < settings.minVersion: @@ -714,7 +764,8 @@ def _clientGetServerHello(self, settings, clientHello): AlertDescription.protocol_version, "Too old version: %s" % str(serverHello.server_version)): yield result - if serverHello.server_version > settings.maxVersion: + if serverHello.server_version > settings.maxVersion and \ + serverHello.server_version not in settings.versions: for result in self._sendError(\ AlertDescription.protocol_version, "Too new version: %s" % str(serverHello.server_version)): @@ -733,7 +784,7 @@ def _clientGetServerHello(self, settings, clientHello): AlertDescription.illegal_parameter, "Server responded with incorrect certificate type"): yield result - if serverHello.compression_method != 0: + if serverVer <= (3, 3) and serverHello.compression_method != 0: for result in self._sendError(\ AlertDescription.illegal_parameter, "Server responded with incorrect compression method"): @@ -782,6 +833,196 @@ def _clientGetServerHello(self, settings, clientHello): yield result yield serverHello + def _clientTLS13Handshake(self, settings, clientHello, serverHello): + """Perform TLS 1.3 handshake as a client.""" + # we have client and server hello in TLS 1.3 so we have the necessary + # key shares to derive the handshake receive key + srKex = serverHello.getExtension(ExtensionType.key_share).server_share + cl_key_share_ex = clientHello.getExtension(ExtensionType.key_share) + cl_kex = next((i for i in cl_key_share_ex.client_shares + if i.group == srKex.group), None) + if cl_kex is None: + raise TLSIllegalParameterException("Server selected not advertised" + " group.") + if srKex.group in GroupName.allFF: + kex = FFDHKeyExchange(srKex.group, self.version) + else: + kex = ECDHKeyExchange(srKex.group, self.version) + + Z = kex.calc_shared_key(cl_kex.private, srKex.key_exchange) + + prfName = 'sha384' if serverHello.cipher_suite \ + in CipherSuite.sha384PrfSuites \ + else 'sha256' + prf_size = getattr(hashlib, prfName)().digest_size + + secret = bytearray(prf_size) + psk = bytearray(prf_size) + # Early Secret + secret = secureHMAC(secret, psk, prfName) + + # Handshake Secret + secret = derive_secret(secret, bytearray(b'derived'), + None, prfName) + secret = secureHMAC(secret, Z, prfName) + + sr_handshake_traffic_secret = derive_secret(secret, + bytearray(b's hs traffic'), + self._handshake_hash, + prfName) + cl_handshake_traffic_secret = derive_secret(secret, + bytearray(b'c hs traffic'), + self._handshake_hash, + prfName) + + # prepare for reading encrypted messages + self._recordLayer.calcTLS1_3PendingState( + serverHello.cipher_suite, + cl_handshake_traffic_secret, + sr_handshake_traffic_secret, + settings.cipherImplementations) + + self._changeReadState() + + for result in self._getMsg(ContentType.handshake, + HandshakeType.encrypted_extensions): + if result in (0, 1): + yield result + else: + break + encrypted_extensions = result + assert isinstance(encrypted_extensions, EncryptedExtensions) + + for result in self._getMsg(ContentType.handshake, + HandshakeType.certificate, + CertificateType.x509): + if result in (0, 1): + yield result + else: + break + + certificate = result + assert isinstance(certificate, Certificate) + + srv_cert_verify_hh = self._handshake_hash.copy() + + for result in self._getMsg(ContentType.handshake, + HandshakeType.certificate_verify): + if result in (0, 1): + yield result + else: + break + certificate_verify = result + assert isinstance(certificate_verify, CertificateVerify) + + signature_scheme = certificate_verify.signatureAlgorithm + + scheme = SignatureScheme.toRepr(signature_scheme) + # keyType = SignatureScheme.getKeyType(scheme) + padType = SignatureScheme.getPadding(scheme) + hashName = SignatureScheme.getHash(scheme) + saltLen = getattr(hashlib, hashName)().digest_size + + signature_context = bytearray(b'\x20' * 64 + + b'TLS 1.3, server CertificateVerify' + + b'\x00') + \ + srv_cert_verify_hh.digest(prfName) + + signature_context = secureHash(signature_context, hashName) + + publicKey = certificate.certChain.getEndEntityPublicKey() + + if not publicKey.verify(certificate_verify.signature, + signature_context, + padType, + hashName, + saltLen): + raise TLSDecryptionFailed("server Certificate Verify signature " + "verification failed") + + transcript_hash = self._handshake_hash.digest(prfName) + + for result in self._getMsg(ContentType.handshake, + HandshakeType.finished, + prf_size): + if result in (0, 1): + yield result + else: + break + finished = result + + server_finish_hs = self._handshake_hash.copy() + + assert isinstance(finished, Finished) + + finished_key = HKDF_expand_label(sr_handshake_traffic_secret, + b"finished", b'', prf_size, prfName) + verify_data = secureHMAC(finished_key, transcript_hash, prfName) + + if finished.verify_data != verify_data: + raise TLSDecryptionFailed("Finished value is not valid") + + # now send client set of messages + self._changeWriteState() + + cl_finished_key = HKDF_expand_label(cl_handshake_traffic_secret, + b"finished", b'', + prf_size, prfName) + cl_verify_data = secureHMAC( + cl_finished_key, + self._handshake_hash.digest(prfName), + prfName) + + cl_finished = Finished(self.version, prf_size) + cl_finished.create(cl_verify_data) + + for result in self._sendMsg(cl_finished): + yield result + + # Master secret + secret = derive_secret(secret, bytearray(b'derived'), None, prfName) + secret = secureHMAC(secret, bytearray(prf_size), prfName) + + cl_app_traffic = derive_secret(secret, bytearray(b'c ap traffic'), + server_finish_hs, prfName) + sr_app_traffic = derive_secret(secret, bytearray(b's ap traffic'), + server_finish_hs, prfName) + + self._recordLayer.calcTLS1_3PendingState( + serverHello.cipher_suite, + cl_app_traffic, + sr_app_traffic, + settings.cipherImplementations) + self._changeReadState() + self._changeWriteState() + + self.session = Session() + self.extendedMasterSecret = True + + serverName = None + if clientHello.server_name: + serverName = clientHello.server_name.decode("utf-8") + + appProto = None + alpnExt = encrypted_extensions.getExtension(ExtensionType.alpn) + if alpnExt: + appProto = alpnExt.protocol_names[0] + + self.session.create(secret, + bytearray(b''), # no session_id in TLS 1.3 + serverHello.cipher_suite, + bytearray(b''), # no SRP + None, # no client cert chain + certificate.certChain, + None, # no TACK + False, # no TACK in hello + serverName, + encryptThenMAC=False, # all ciphers are AEAD + extendedMasterSecret=True, # all TLS1.3 are EMS + appProto=appProto) + + yield "finished" + def _clientSelectNextProto(self, nextProtos, serverHello): # nextProtos is None or non-empty list of strings # serverHello.next_protos is None or possibly-empty list of strings @@ -1254,7 +1495,13 @@ def _handshakeServerAsyncHelper(self, verifierDB, settings = HandshakeSettings() settings = settings.validate() self.sock.padding_cb = settings.padding_cb - + + # Do not support TLS 1.3 on server side + if settings.maxVersion > (3, 3): + settings.maxVersion = (3, 3) + if (0x7f, 21) in settings.versions: + settings.versions.remove((0x7f, 21)) + # OK Start exchanging messages # ****************************** diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index 4841dc87..ef436446 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -218,7 +218,10 @@ def test_padding_extension_with_hello_over_256(self): # create hostname extension with self.assertRaises(TLSRemoteAlert): # use serverName with 252 bytes - conn.handshakeClientCert( + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + settings.keyShares = [] + conn.handshakeClientCert(settings=settings, serverName='aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd.' + 'eeeeeeeeeeffffffffffgggggggggghhhhhhhhhh.' + 'iiiiiiiiiijjjjjjjjjjkkkkkkkkkkllllllllll.' + From 68fb5cd65ad7e61ca9a99ae30d5df9b2ed842c2c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 16 Aug 2017 18:25:17 +0200 Subject: [PATCH 568/574] Parsing of Hello Retry Request in TLS record layer --- tlslite/tlsrecordlayer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 268d0f21..64bcd590 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -822,6 +822,8 @@ def _getMsg(self, expectedType, secondaryType=None, constructorType=None): yield EncryptedExtensions().parse(p) elif subType == HandshakeType.new_session_ticket: yield NewSessionTicket().parse(p) + elif subType == HandshakeType.hello_retry_request: + yield HelloRetryRequest().parse(p) else: raise AssertionError() From a1f9963521c9900d54a6f48a74efa935765914ff Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 16 Aug 2017 20:03:35 +0200 Subject: [PATCH 569/574] TLS 1.3 client handshake with Hello Retry Request --- tlslite/tlsconnection.py | 142 +++++++++++++++++++++++++++++++++------ 1 file changed, 123 insertions(+), 19 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index a7f1ff48..7b515676 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -28,6 +28,7 @@ from .messages import * from .mathtls import * from .handshakesettings import HandshakeSettings +from .handshakehashes import HandshakeHashes from .utils.tackwrapper import * from .keyexchange import KeyExchange, RSAKeyExchange, DHE_RSAKeyExchange, \ ECDHE_RSAKeyExchange, SRPKeyExchange, ADHKeyExchange, \ @@ -686,14 +687,9 @@ def _clientSendClientHello(self, settings, session, srpUsername, shares = [] for group_name in settings.keyShares: group_id = getattr(GroupName, group_name) - if group_id in GroupName.allFF: - kex = FFDHKeyExchange(group_id, (3, 4)) - else: - kex = ECDHKeyExchange(group_id, (3, 4)) - private = kex.get_random_private_key() - share = kex.calc_public_value(private) + key_share = self._genKeyShareEntry(group_id, (3, 4)) - shares.append(KeyShareEntry().create(group_id, share, private)) + shares.append(key_share) if shares: extensions.append(ClientKeyShareExtension().create(shares)) @@ -740,14 +736,99 @@ def _clientSendClientHello(self, settings, session, srpUsername, yield result yield clientHello - def _clientGetServerHello(self, settings, clientHello): - # TODO: add Handshake Retry Request handling - # TODO: reset handshake_hashes if we do get HRR + client_hello_hash = self._handshake_hash.copy() for result in self._getMsg(ContentType.handshake, - HandshakeType.server_hello): + (HandshakeType.server_hello, + HandshakeType.hello_retry_request)): if result in (0,1): yield result else: break + + hello_retry = None + if isinstance(result, HelloRetryRequest): + hello_retry = result + + # create synthetic handshake hash + prf_name, prf_size = self._getPRFParams(hello_retry.cipher_suite) + + self._handshake_hash = HandshakeHashes() + writer = Writer() + writer.add(HandshakeType.message_hash, 1) + writer.addVarSeq(client_hello_hash.digest(prf_name), 1, 3) + self._handshake_hash.update(writer.bytes) + self._handshake_hash.update(hello_retry.write()) + + # check if all extensions in the HRR were present in client hello + ch_ext_types = set(i.extType for i in clientHello.extensions) + ch_ext_types.add(ExtensionType.cookie) + + bad_ext = next((i for i in hello_retry.extensions + if i.extType not in ch_ext_types), None) + if bad_ext: + bad_ext = ExtensionType.toStr(bad_ext) + for result in self._sendError(AlertDescription + .unsupported_extension, + ("Unexpected extension in HRR: " + "{0}").format(bad_ext)): + yield result + + # handle cookie extension + cookie = hello_retry.getExtension(ExtensionType.cookie) + if cookie: + clientHello.addExtension(cookie) + + # handle key share extension + sr_key_share_ext = hello_retry.getExtension(ExtensionType + .key_share) + if sr_key_share_ext: + group_id = sr_key_share_ext.selected_group + # check if group selected by server is valid + groups_ext = clientHello.getExtension(ExtensionType + .supported_groups) + if group_id not in groups_ext.groups: + for result in self._sendError(AlertDescription + .illegal_parameter, + "Server selected group we " + "did not advertise"): + yield result + + cl_key_share_ext = clientHello.getExtension(ExtensionType + .key_share) + # check if the server didn't ask for a group we already sent + if next((entry for entry in cl_key_share_ext.client_shares + if entry.group == group_id), None): + for result in self._sendError(AlertDescription + .illegal_parameter, + "Server selected group we " + "did sent the key share " + "for"): + yield result + + key_share = self._genKeyShareEntry(group_id, (3, 4)) + + # old key shares need to be removed + cl_key_share_ext.client_shares = [key_share] + + if not cookie and not sr_key_share_ext: + # HRR did not result in change to Client Hello + for result in self._sendError(AlertDescription. + illegal_parameter, + "Received HRR did not cause " + "update to Client Hello"): + yield result + + # resend the client hello with performed changes + for result in self._sendMsg(clientHello): + yield result + + # retry getting server hello + for result in self._getMsg(ContentType.handshake, + HandshakeType.server_hello): + if result in (0, 1): + yield result + else: + break + serverHello = result #Get the server version. Do this before anything else, so any @@ -759,6 +840,12 @@ def _clientGetServerHello(self, settings, clientHello): self.version = (3, 4) #Check ServerHello + if hello_retry and \ + hello_retry.cipher_suite != serverHello.cipher_suite: + for result in self._sendError(AlertDescription.illegal_parameter, + "server selected different cipher " + "in HRR and Server Hello"): + yield result if serverHello.server_version < settings.minVersion: for result in self._sendError(\ AlertDescription.protocol_version, @@ -833,6 +920,29 @@ def _clientGetServerHello(self, settings, clientHello): yield result yield serverHello + @staticmethod + def _getKEX(group, version): + """Get object for performing key exchange.""" + if group in GroupName.allFF: + return FFDHKeyExchange(group, version) + return ECDHKeyExchange(group, version) + + @classmethod + def _genKeyShareEntry(cls, group, version): + """Generate KeyShareEntry object from randomly selected private value. + """ + kex = cls._getKEX(group, version) + private = kex.get_random_private_key() + share = kex.calc_public_value(private) + return KeyShareEntry().create(group, share, private) + + @staticmethod + def _getPRFParams(cipher_suite): + """Return name of hash used for PRF and the hash output size.""" + if cipher_suite in CipherSuite.sha384PrfSuites: + return 'sha384', 48 + return 'sha256', 32 + def _clientTLS13Handshake(self, settings, clientHello, serverHello): """Perform TLS 1.3 handshake as a client.""" # we have client and server hello in TLS 1.3 so we have the necessary @@ -844,17 +954,11 @@ def _clientTLS13Handshake(self, settings, clientHello, serverHello): if cl_kex is None: raise TLSIllegalParameterException("Server selected not advertised" " group.") - if srKex.group in GroupName.allFF: - kex = FFDHKeyExchange(srKex.group, self.version) - else: - kex = ECDHKeyExchange(srKex.group, self.version) + kex = self._getKEX(sr_kex.group, self.version) Z = kex.calc_shared_key(cl_kex.private, srKex.key_exchange) - prfName = 'sha384' if serverHello.cipher_suite \ - in CipherSuite.sha384PrfSuites \ - else 'sha256' - prf_size = getattr(hashlib, prfName)().digest_size + prfName, prf_size = self._getPRFParams(serverHello.cipher_suite) secret = bytearray(prf_size) psk = bytearray(prf_size) From a344a508ae89ae0f9d2b8d750d52f705e2f3a33e Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 29 Aug 2017 18:51:09 +0200 Subject: [PATCH 570/574] do not advertise TLS 1.3 version if TLS 1.2 is maxVersion --- tlslite/handshakesettings.py | 8 ++++++-- unit_tests/test_tlslite_handshakesettings.py | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tlslite/handshakesettings.py b/tlslite/handshakesettings.py index 484b0704..c5c9291d 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -36,6 +36,7 @@ ALL_CURVE_NAMES += ["secp224r1", "secp192r1"] ALL_DH_GROUP_NAMES = ["ffdhe2048", "ffdhe3072", "ffdhe4096", "ffdhe6144", "ffdhe8192"] +KNOWN_VERSIONS = ((3, 0), (3, 1), (3, 2), (3, 3), (3, 4)) class HandshakeSettings(object): """ @@ -284,11 +285,14 @@ def _sanityCheckProtocolVersions(other): """Check if set protocol version are sane""" if other.minVersion > other.maxVersion: raise ValueError("Versions set incorrectly") - if other.minVersion not in ((3, 0), (3, 1), (3, 2), (3, 3)): + if other.minVersion not in KNOWN_VERSIONS: raise ValueError("minVersion set incorrectly") - if other.maxVersion not in ((3, 0), (3, 1), (3, 2), (3, 3), (3, 4)): + if other.maxVersion not in KNOWN_VERSIONS: raise ValueError("maxVersion set incorrectly") + if other.maxVersion < (3, 4): + other.versions = [i for i in other.versions if i < (3, 4)] + @staticmethod def _sanityCheckExtensions(other): """Check if set extension settings are sane""" diff --git a/unit_tests/test_tlslite_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index ec5125e5..191920f4 100644 --- a/unit_tests/test_tlslite_handshakesettings.py +++ b/unit_tests/test_tlslite_handshakesettings.py @@ -308,5 +308,14 @@ def test_not_matching_ffdhe_keyShares(self): self.assertIn("ffdhe2048", str(e.exception)) + def test_versions_and_maxVersion_mismatch(self): + hs = HandshakeSettings() + hs.maxVersion = (3, 3) + hs = hs.validate() + + self.assertNotIn((3, 4), hs.versions) + self.assertNotIn((0x7f, 21), hs.versions) + + if __name__ == '__main__': unittest.main() From ea68bea6dbe69fb8891073b5020fda19da5802c1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 31 Aug 2017 20:22:30 +0200 Subject: [PATCH 571/574] Add support for 1-RTT TLS 1.3 handshake on server side --- tests/tlstest.py | 109 +++++++-- tlslite/tlsconnection.py | 284 ++++++++++++++++++++--- tlslite/tlsrecordlayer.py | 20 +- unit_tests/test_tlslite_tlsconnection.py | 3 + 4 files changed, 352 insertions(+), 64 deletions(-) diff --git a/tests/tlstest.py b/tests/tlstest.py index 55cb3a6a..8737d51b 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -108,7 +108,9 @@ def connect(): print("Test {0} - anonymous handshake".format(test_no)) synchro.recv(1) connection = connect() - connection.handshakeClientAnonymous() + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientAnonymous(settings=settings) testConnClient(connection) connection.close() @@ -172,6 +174,7 @@ def connect(): settings = HandshakeSettings() settings.macNames = ["md5"] settings.cipherNames = ["rc4"] + settings.maxVersion = (3, 3) connection.handshakeClientCert(settings=settings) testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) @@ -183,6 +186,7 @@ def connect(): settings = HandshakeSettings() settings.useExperimentalTackExtension = True + settings.maxVersion = (3, 3) test_no += 1 @@ -225,7 +229,9 @@ def connect(): print("Test {0} - good SRP (db)".format(test_no)) synchro.recv(1) connection = connect() - connection.handshakeClientSRP("test", "password") + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientSRP("test", "password", settings=settings) testConnClient(connection) connection.close() @@ -234,7 +240,9 @@ def connect(): print("Test {0} - good SRP".format(test_no)) synchro.recv(1) connection = connect() - connection.handshakeClientSRP("test", "password") + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientSRP("test", "password", settings=settings) testConnClient(connection) connection.close() @@ -245,8 +253,11 @@ def connect(): synchro.recv(1) connection = connect() connection.fault = fault + settings = HandshakeSettings() + settings.maxVersion = (3, 3) try: - connection.handshakeClientSRP("test", "password") + connection.handshakeClientSRP("test", "password", + settings=settings) print(" Good Fault %s" % (Fault.faultNames[fault])) except TLSFaultError as e: print(" BAD FAULT %s: %s" % (Fault.faultNames[fault], str(e))) @@ -272,8 +283,11 @@ def connect(): synchro.recv(1) connection = connect() connection.fault = fault + settings = HandshakeSettings() + settings.maxVersion = (3, 3) try: - connection.handshakeClientSRP("test", "password") + connection.handshakeClientSRP("test", "password", + settings=settings) print(" Good Fault %s" % (Fault.faultNames[fault])) except TLSFaultError as e: print(" BAD FAULT %s: %s" % (Fault.faultNames[fault], str(e))) @@ -303,7 +317,10 @@ def connect(): synchro.recv(1) connection = connect() - connection.handshakeClientCert(x509Chain, x509Key) + # TODO add client certificate support in TLS 1.3 + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientCert(x509Chain, x509Key, settings=settings) testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) connection.close() @@ -354,7 +371,10 @@ def connect(): format(test_no)) synchro.recv(1) connection = connect() - connection.handshakeClientSRP("test", "password", serverName=address[0]) + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientSRP("test", "password", serverName=address[0], + settings=settings) testConnClient(connection) connection.close() session = connection.session @@ -364,8 +384,10 @@ def connect(): print("Test {0} - resumption (plus SNI)".format(test_no)) synchro.recv(1) connection = connect() + settings = HandshakeSettings() + settings.maxVersion = (3, 3) connection.handshakeClientSRP("test", "garbage", serverName=address[0], - session=session) + session=session, settings=settings) testConnClient(connection) #Don't close! -- see below @@ -376,9 +398,12 @@ def connect(): connection.sock.close() #Close the socket without a close_notify! synchro.recv(1) connection = connect() + settings = HandshakeSettings() + settings.maxVersion = (3, 3) try: - connection.handshakeClientSRP("test", "garbage", - serverName=address[0], session=session) + connection.handshakeClientSRP("test", "garbage", + serverName=address[0], + session=session, settings=settings) assert(False) except TLSRemoteAlert as alert: if alert.description != AlertDescription.bad_record_mac: @@ -474,6 +499,8 @@ def connect(): settings = HandshakeSettings() settings.cipherNames = [cipher] settings.cipherImplementations = [implementation, "python"] + if cipher not in ("aes128gcm", "aes256gcm", "chacha20-poly1305"): + settings.maxVersion = (3, 3) connection.handshakeClientCert(settings=settings) print("%s %s:" % (connection.getCipherName(), connection.getCipherImplementation()), end=' ') @@ -495,7 +522,9 @@ def connect(): print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() - connection.handshakeClientCert(nextProtos=[b"http/1.1"]) + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientCert(nextProtos=[b"http/1.1"], settings=settings) #print(" Next-Protocol Negotiated: %s" % connection.next_proto) assert(connection.next_proto == b'http/1.1') connection.close() @@ -505,7 +534,10 @@ def connect(): print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() - connection.handshakeClientCert(nextProtos=[b"spdy/2", b"http/1.1"]) + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientCert(nextProtos=[b"spdy/2", b"http/1.1"], + settings=settings) #print(" Next-Protocol Negotiated: %s" % connection.next_proto) assert(connection.next_proto == b'spdy/2') connection.close() @@ -515,7 +547,10 @@ def connect(): print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() - connection.handshakeClientCert(nextProtos=[b"spdy/2", b"http/1.1"]) + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientCert(nextProtos=[b"spdy/2", b"http/1.1"], + settings=settings) #print(" Next-Protocol Negotiated: %s" % connection.next_proto) assert(connection.next_proto == b'spdy/2') connection.close() @@ -525,7 +560,11 @@ def connect(): print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() - connection.handshakeClientCert(nextProtos=[b"spdy/3", b"spdy/2", b"http/1.1"]) + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientCert(nextProtos=[b"spdy/3", b"spdy/2", + b"http/1.1"], + settings=settings) #print(" Next-Protocol Negotiated: %s" % connection.next_proto) assert(connection.next_proto == b'spdy/2') connection.close() @@ -535,7 +574,11 @@ def connect(): print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() - connection.handshakeClientCert(nextProtos=[b"spdy/3", b"spdy/2", b"http/1.1"]) + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientCert(nextProtos=[b"spdy/3", b"spdy/2", + b"http/1.1"], + settings=settings) #print(" Next-Protocol Negotiated: %s" % connection.next_proto) assert(connection.next_proto == b'spdy/3') connection.close() @@ -545,7 +588,9 @@ def connect(): print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() - connection.handshakeClientCert(nextProtos=[b"http/1.1"]) + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientCert(nextProtos=[b"http/1.1"], settings=settings) #print(" Next-Protocol Negotiated: %s" % connection.next_proto) assert(connection.next_proto == b'http/1.1') connection.close() @@ -555,7 +600,10 @@ def connect(): print("Test {0} - Next-Protocol Client Negotiation".format(test_no)) synchro.recv(1) connection = connect() - connection.handshakeClientCert(nextProtos=[b"spdy/2", b"http/1.1"]) + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientCert(nextProtos=[b"spdy/2", b"http/1.1"], + settings=settings) #print(" Next-Protocol Negotiated: %s" % connection.next_proto) assert(connection.next_proto == b'spdy/2') connection.close() @@ -567,6 +615,8 @@ def connect(): connection = connect() settings = HandshakeSettings() settings.sendFallbackSCSV = True + settings.maxVersion = (3, 3) + # TODO fix FALLBACK_SCSV with TLS 1.3 connection.handshakeClientCert(settings=settings) testConnClient(connection) connection.close() @@ -594,6 +644,7 @@ def connect(): connection = connect() settings = HandshakeSettings() settings.macNames.remove("aead") + settings.maxVersion = (3, 3) assert(settings.useEncryptThenMAC) connection.handshakeClientCert(serverName=address[0], settings=settings) testConnClient(connection) @@ -610,6 +661,7 @@ def connect(): settings = HandshakeSettings() settings.macNames.remove("aead") settings.useEncryptThenMAC = False + settings.maxVersion = (3, 3) connection.handshakeClientCert(serverName=address[0], settings=settings) testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) @@ -624,6 +676,7 @@ def connect(): connection = connect() settings = HandshakeSettings() settings.macNames.remove("aead") + settings.maxVersion = (3, 3) connection.handshakeClientCert(serverName=address[0], settings=settings) testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) @@ -636,7 +689,10 @@ def connect(): # resume synchro.recv(1) connection = connect() - connection.handshakeClientCert(serverName=address[0], session=session) + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientCert(serverName=address[0], session=session, + settings=settings) testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) assert(connection.session.serverName == address[0]) @@ -651,6 +707,7 @@ def connect(): connection = connect() settings = HandshakeSettings() settings.macNames.remove("aead") + settings.maxVersion = (3, 3) connection.handshakeClientCert(serverName=address[0], settings=settings) testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) @@ -665,6 +722,7 @@ def connect(): settings = HandshakeSettings() settings.useEncryptThenMAC = False settings.macNames.remove("aead") + settings.maxVersion = (3, 3) connection = connect() try: connection.handshakeClientCert(serverName=address[0], session=session, @@ -812,7 +870,7 @@ def connect(): test_no += 1 - print("Test {0} - good X.509".format(test_no)) + print("Test {0} - good X.509 (plus SNI)".format(test_no)) synchro.send(b'R') connection = connect() connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) @@ -828,7 +886,6 @@ def connect(): connection = connect() connection.handshakeServer(certChain=x509ChainRSAPSSSig, privateKey=x509KeyRSAPSSSig) - assert(connection.session.serverName == address[0]) assert(connection.extendedMasterSecret) testConnServer(connection) connection.close() @@ -1258,7 +1315,11 @@ def server_bind(self): print("Test {0} - FALLBACK_SCSV".format(test_no)) synchro.send(b'R') connection = connect() - connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + # TODO fix FALLBACK with TLS1.3 + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, + settings=settings) testConnServer(connection) connection.close() @@ -1267,8 +1328,12 @@ def server_bind(self): print("Test {0} - FALLBACK_SCSV".format(test_no)) synchro.send(b'R') connection = connect() + settings = HandshakeSettings() + # TODO fix FALLBACK with TLS1.3 + settings.maxVersion = (3, 3) try: - connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, + settings=settings) assert() except TLSLocalAlert as alert: if alert.description != AlertDescription.inappropriate_fallback: diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 7b515676..64de8fad 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -17,6 +17,7 @@ from __future__ import division import socket +from itertools import chain from .utils.compat import formatExceptionTrace from .tlsrecordlayer import TLSRecordLayer from .session import Session @@ -954,7 +955,7 @@ def _clientTLS13Handshake(self, settings, clientHello, serverHello): if cl_kex is None: raise TLSIllegalParameterException("Server selected not advertised" " group.") - kex = self._getKEX(sr_kex.group, self.version) + kex = self._getKEX(srKex.group, self.version) Z = kex.calc_shared_key(cl_kex.private, srKex.key_exchange) @@ -1600,12 +1601,6 @@ def _handshakeServerAsyncHelper(self, verifierDB, settings = settings.validate() self.sock.padding_cb = settings.padding_cb - # Do not support TLS 1.3 on server side - if settings.maxVersion > (3, 3): - settings.maxVersion = (3, 3) - if (0x7f, 21) in settings.versions: - settings.versions.remove((0x7f, 21)) - # OK Start exchanging messages # ****************************** @@ -1618,7 +1613,23 @@ def _handshakeServerAsyncHelper(self, verifierDB, self._handshakeDone(resumed=True) return # Handshake was resumed, we're done else: break - (clientHello, cipherSuite) = result + (clientHello, cipherSuite, version) = result + + # in TLS 1.3 the handshake is completely different + # (extensions go into different messages, format of messages is + # different, etc.) + if version > (3, 3): + for result in self._serverTLS13Handshake(settings, clientHello, + cipherSuite, + privateKey, certChain, + version): + if result in (0, 1): + yield result + else: + break + if result == "finished": + self._handshakeDone(resumed=False) + return #If not a resumption... @@ -1825,13 +1836,192 @@ def _handshakeServerAsyncHelper(self, verifierDB, self._serverRandom = serverHello.random self._clientRandom = clientHello.random + def _serverTLS13Handshake(self, settings, clientHello, cipherSuite, + privateKey, serverCertChain, version): + """Perform a TLS 1.3 handshake""" + share = clientHello.getExtension(ExtensionType.key_share) + share_ids = [i.group for i in share.client_shares] + for group_name in chain(settings.keyShares, settings.eccCurves, + settings.dhGroups): + selected_group = getattr(GroupName, group_name) + if selected_group in share_ids: + cl_key_share = next(i for i in share.client_shares + if i.group == selected_group) + break + else: + raise ValueError("HRR not supported on server side") + + kex = self._getKEX(selected_group, version) + key_share = self._genKeyShareEntry(selected_group, version) + + sh_extensions = [] + sh_extensions.append(ServerKeyShareExtension().create(key_share)) + + serverHello = ServerHello() + serverHello.create(version, getRandomBytes(32), + None, # session ID + cipherSuite, extensions=sh_extensions) + + for result in self._sendMsg(serverHello): + yield result + + Z = kex.calc_shared_key(key_share.private, cl_key_share.key_exchange) + + prf_name, prf_size = self._getPRFParams(cipherSuite) + + secret = bytearray(prf_size) + psk = bytearray(prf_size) + # Early secret + secret = secureHMAC(secret, psk, prf_name) + + # Handshake Secret + secret = derive_secret(secret, bytearray(b'derived'), None, prf_name) + secret = secureHMAC(secret, Z, prf_name) + + sr_handshake_traffic_secret = derive_secret(secret, + bytearray(b's hs traffic'), + self._handshake_hash, + prf_name) + cl_handshake_traffic_secret = derive_secret(secret, + bytearray(b'c hs traffic'), + self._handshake_hash, + prf_name) + self.version = version + self._recordLayer.calcTLS1_3PendingState( + cipherSuite, + cl_handshake_traffic_secret, + sr_handshake_traffic_secret, + settings.cipherImplementations) + + self._changeWriteState() + + ee_extensions = [] + + encryptedExtensions = EncryptedExtensions().create(ee_extensions) + for result in self._sendMsg(encryptedExtensions): + yield result + + certificate = Certificate(CertificateType.x509, self.version) + certificate.create(serverCertChain, bytearray()) + for result in self._sendMsg(certificate): + yield result + + certificate_verify = CertificateVerify(self.version) + + scheme = self._pickServerKeyExchangeSig(settings, + clientHello, + serverCertChain, + self.version) + signature_scheme = getattr(SignatureScheme, scheme) + keyType = SignatureScheme.getKeyType(scheme) + padType = SignatureScheme.getPadding(scheme) + hashName = SignatureScheme.getHash(scheme) + saltLen = getattr(hashlib, hashName)().digest_size + + signature_context = bytearray(b'\x20' * 64 + + b'TLS 1.3, server CertificateVerify' + + b'\x00') + \ + self._handshake_hash.digest(prf_name) + signature_context = secureHash(signature_context, hashName) + + signature = privateKey.sign(signature_context, + padType, + hashName, + saltLen) + if not privateKey.verify(signature, signature_context, + padType, + hashName, + saltLen): + raise TLSInternalError("Certificate Verify signature failed") + certificate_verify.create(signature, signature_scheme) + + for result in self._sendMsg(certificate_verify): + yield result + + finished_key = HKDF_expand_label(sr_handshake_traffic_secret, + b"finished", b'', prf_size, prf_name) + verify_data = secureHMAC(finished_key, + self._handshake_hash.digest(prf_name), + prf_name) + + finished = Finished(self.version, prf_size).create(verify_data) + + for result in self._sendMsg(finished): + yield result + + self._changeReadState() + + # Master secret + secret = derive_secret(secret, bytearray(b'derived'), None, prf_name) + secret = secureHMAC(secret, bytearray(prf_size), prf_name) + + cl_app_traffic = derive_secret(secret, bytearray(b'c ap traffic'), + self._handshake_hash, prf_name) + sr_app_traffic = derive_secret(secret, bytearray(b's ap traffic'), + self._handshake_hash, prf_name) + self._recordLayer.calcTLS1_3PendingState(serverHello.cipher_suite, + cl_app_traffic, + sr_app_traffic, + settings + .cipherImplementations) + + # verify Finished of client + cl_finished_key = HKDF_expand_label(cl_handshake_traffic_secret, + b"finished", b'', + prf_size, prf_name) + cl_verify_data = secureHMAC(cl_finished_key, + self._handshake_hash.digest(prf_name), + prf_name) + for result in self._getMsg(ContentType.handshake, + HandshakeType.finished, + prf_size): + if result in (0, 1): + yield + else: + break + cl_finished = result + assert isinstance(cl_finished, Finished) + if cl_finished.verify_data != cl_verify_data: + raise TLSDecryptionFailed("Finished value is not valid") + + self.session = Session() + self.extendedMasterSecret = True + server_name = None + if clientHello.server_name: + server_name = clientHello.server_name.decode('utf-8') + + app_proto = None + alpnExt = encryptedExtensions.getExtension(ExtensionType.alpn) + if alpnExt: + app_proto = alpnExt.protocol_names[0] + + self.session.create(secret, + bytearray(b''), # no session_id + serverHello.cipher_suite, + bytearray(b''), # no SRP + None, + serverCertChain, + None, + False, + server_name, + encryptThenMAC=False, + extendedMasterSecret=True, + appProto=app_proto) + + # switch to application_traffic_secret + self._changeWriteState() + self._changeReadState() + + yield "finished" def _serverGetClientHello(self, settings, certChain, verifierDB, sessionCache, anon, alpn, sni): - #Tentatively set version to most-desirable version, so if an error - #occurs parsing the ClientHello, this is what we'll use for the - #error alert - self.version = settings.maxVersion + # Tentatively set version to most-desirable version, so if an error + # occurs parsing the ClientHello, this will be the version we'll use + # for the error alert + # If TLS 1.3 is enabled, use the "universal" TLS 1.x version + self.version = settings.maxVersion if settings.maxVersion < (3, 4) \ + else (3, 1) #Get ClientHello for result in self._getMsg(ContentType.handshake, @@ -1935,13 +2125,29 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, "Master Secret extension"): yield result - #If client's version is too high, propose my highest version + versionsExt = clientHello.getExtension(ExtensionType + .supported_versions) + high_ver = None + if versionsExt: + high_ver = getFirstMatching(settings.versions, + versionsExt.versions) + if high_ver: + # when we selected TLS 1.3, we cannot set the record layer to + # it as well as that also switches it to a mode where the + # content type is encrypted + # use a generic TLS version instead + self.version = (3, 1) + version = high_ver elif clientHello.client_version > settings.maxVersion: - self.version = settings.maxVersion - + # in TLS 1.3 the version is negotiatied with extension, + # but the settings use the (3, 4) as the max version + self.version = settings.maxVersion if settings.maxVersion < (3, 4)\ + else (3, 3) + version = self.version else: #Set the version to the client's version - self.version = clientHello.client_version + self.version = clientHello.client_version + version = self.version #Detect if the client performed an inappropriate fallback. if clientHello.client_version < settings.maxVersion and \ @@ -1979,32 +2185,34 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, else: ffGroupIntersect = True - #Now that the version is known, limit to only the ciphers available to #that version and client capabilities. cipherSuites = [] if verifierDB: if certChain: cipherSuites += \ - CipherSuite.getSrpCertSuites(settings, self.version) - cipherSuites += CipherSuite.getSrpSuites(settings, self.version) + CipherSuite.getSrpCertSuites(settings, version) + cipherSuites += CipherSuite.getSrpSuites(settings, version) elif certChain: + if ecGroupIntersect or ffGroupIntersect: + cipherSuites += CipherSuite.getTLS13Suites(settings, + version) if ecGroupIntersect: cipherSuites += CipherSuite.getEcdheCertSuites(settings, - self.version) + version) if ffGroupIntersect: cipherSuites += CipherSuite.getDheCertSuites(settings, - self.version) - cipherSuites += CipherSuite.getCertSuites(settings, self.version) + version) + cipherSuites += CipherSuite.getCertSuites(settings, version) elif anon: - cipherSuites += CipherSuite.getAnonSuites(settings, self.version) + cipherSuites += CipherSuite.getAnonSuites(settings, version) cipherSuites += CipherSuite.getEcdhAnonSuites(settings, - self.version) + version) else: assert(False) cipherSuites = CipherSuite.filterForVersion(cipherSuites, - minVersion=self.version, - maxVersion=self.version) + minVersion=version, + maxVersion=version) #If resumption was requested and we have a session cache... if clientHello.session_id and sessionCache: session = None @@ -2108,7 +2316,7 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, if not extensions: extensions = None serverHello = ServerHello() - serverHello.create(self.version, getRandomBytes(32), + serverHello.create(version, getRandomBytes(32), session.sessionID, session.cipherSuite, CertificateType.x509, None, None, extensions=extensions) @@ -2181,7 +2389,7 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, # If resumption was not requested, or # we have no session cache, or # the client's session_id was not found in cache: - yield (clientHello, cipherSuite) + yield (clientHello, cipherSuite, version) def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, cipherSuite, privateKey, serverCertChain, @@ -2591,18 +2799,27 @@ def _handshakeWrapperAsync(self, handshaker, checker): raise @staticmethod - def _pickServerKeyExchangeSig(settings, clientHello, certList=None): + def _pickServerKeyExchangeSig(settings, clientHello, certList=None, + version=(3, 3)): """Pick a hash that matches most closely the supported ones""" hashAndAlgsExt = clientHello.getExtension(\ ExtensionType.signature_algorithms) + if version > (3, 3): + if not hashAndAlgsExt: + raise TLSMissingExtension("Signature algorithms extension" + "missing") + if not hashAndAlgsExt.sigalgs: + raise TLSDecodeError("Signature algorithms extension empty") + if hashAndAlgsExt is None or hashAndAlgsExt.sigalgs is None: # RFC 5246 states that if there are no hashes advertised, # sha1 should be picked return "sha1" supported = TLSConnection._sigHashesToList(settings, - certList=certList) + certList=certList, + version=version) for schemeID in supported: if schemeID in hashAndAlgsExt.sigalgs: @@ -2617,7 +2834,8 @@ def _pickServerKeyExchangeSig(settings, clientHello, certList=None): raise TLSHandshakeFailure("No common signature algorithms") @staticmethod - def _sigHashesToList(settings, privateKey=None, certList=None): + def _sigHashesToList(settings, privateKey=None, certList=None, + version=(3, 3)): """Convert list of valid signature hashes to array of tuples""" certType = None if certList: @@ -2625,6 +2843,10 @@ def _sigHashesToList(settings, privateKey=None, certList=None): sigAlgs = [] for schemeName in settings.rsaSchemes: + # pkcs#1 v1.5 signatures are not allowed in TLS 1.3 + if version > (3, 3) and schemeName == "pkcs1": + continue + for hashName in settings.rsaSigHashes: # rsa-pss certificates can't be used to make PKCS#1 v1.5 # signatures diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 64bcd590..14d7b6a7 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -418,18 +418,16 @@ def getVersionName(self): :rtype: str :returns: The name of the TLS version used with this connection. - Either None, 'SSL 3.0', 'TLS 1.0', 'TLS 1.1', or 'TLS 1.2'. + Either None, 'SSL 3.0', 'TLS 1.0', 'TLS 1.1', 'TLS 1.2' or + 'TLS 1.3'. """ - if self.version == (3,0): - return "SSL 3.0" - elif self.version == (3,1): - return "TLS 1.0" - elif self.version == (3,2): - return "TLS 1.1" - elif self.version == (3,3): - return "TLS 1.2" - else: - return None + ver = {(3, 0): "SSL 3.0", + (3, 1): "TLS 1.0", + (3, 2): "TLS 1.1", + (3, 3): "TLS 1.2", + (3, 4): "TLS 1.3", + TLS_1_3_DRAFT: "TLS 1.3"} + return ver.get(self.version) def getCipherName(self): """Get the name of the cipher used with this connection. diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py index ef436446..319d0fb3 100644 --- a/unit_tests/test_tlslite_tlsconnection.py +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -339,6 +339,8 @@ def setUp(self): self.server = TLSConnection(self.server_socket) def server_process(server): + settings = HandshakeSettings() + settings.maxVersion = (3, 3) server.handshakeServer(certChain=self.certChain, privateKey=self.certKey) ret = server.read(min=len("client hello")) @@ -353,6 +355,7 @@ def server_process(server): def test_connection_no_rsa_pss(self): settings = HandshakeSettings() + settings.maxVersion = (3, 3) # exclude pss as the keys in this module are too small for # the needed salt size for sha512 hash settings.rsaSchemes = ["pkcs1"] From 852fa4eed6408a921600d6ba2f106f22cedcffde Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 1 Sep 2017 19:32:47 +0200 Subject: [PATCH 572/574] always send key_share, even if no shares present the protocol requires that key_share extension be always present, even if the list in it would be empty (on penalty of a round-trip-time) --- tlslite/tlsconnection.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 64de8fad..53616f1a 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -691,8 +691,9 @@ def _clientSendClientHello(self, settings, session, srpUsername, key_share = self._genKeyShareEntry(group_id, (3, 4)) shares.append(key_share) - if shares: - extensions.append(ClientKeyShareExtension().create(shares)) + # if TLS 1.3 is enabled, key_share must always be sent + # (unless PSK is used) + extensions.append(ClientKeyShareExtension().create(shares)) # don't send empty list of extensions or extensions in SSLv3 if not extensions or settings.maxVersion == (3, 0): From cba6046c159984edc0c8399d2757da33808ee585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Wed, 21 Feb 2018 22:19:19 +0100 Subject: [PATCH 573/574] Fixes for python 3.7 support In python 3.7, async and await are new reserved keywords which cannot be used as variable names or arguments. This commit renames some parameters called async to comply with that. It also updates metadata identifiers to state python 3.7 support as well as runs with mentioned version on travis. --- .travis.yml | 5 +++ setup.py | 1 + tlslite/integration/asyncstatemachine.py | 2 +- tlslite/tlsconnection.py | 46 ++++++++++++++---------- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index fa19cdcd..f7550143 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,7 @@ python: - 3.4 - 3.5 - 3.6 + - 3.7-dev env: - TACKPY=true @@ -52,6 +53,8 @@ matrix: env: PYCRYPTO=true - python: 3.6 env: PYCRYPTO=true + - python: 3.7-dev + env: PYCRYPTO=true - python: 2.7 env: GMPY=true - python: 3.4 @@ -68,6 +71,8 @@ matrix: env: M2CRYPTO=true PYCRYPTO=true GMPY=true - python: 3.6 env: M2CRYPTO=true PYCRYPTO=true GMPY=true + - python: 3.7-dev + env: M2CRYPTO=true PYCRYPTO=true GMPY=true before_install: - | diff --git a/setup.py b/setup.py index 355cc09b..3d8fa980 100755 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Security :: Cryptography', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: System :: Networking' diff --git a/tlslite/integration/asyncstatemachine.py b/tlslite/integration/asyncstatemachine.py index 249482c5..c6d8694b 100644 --- a/tlslite/integration/asyncstatemachine.py +++ b/tlslite/integration/asyncstatemachine.py @@ -197,7 +197,7 @@ def setHandshakeOp(self, handshaker): :param generator handshaker: A generator created by using one of the asynchronous handshake functions (i.e. :py:meth:`~.TLSConnection.handshakeServerAsync` , or - handshakeClientxxx(..., async=True). + handshakeClientxxx(..., async_=True). """ try: self._checkAssert(0) diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index 53616f1a..61b0f23d 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -31,6 +31,7 @@ from .handshakesettings import HandshakeSettings from .handshakehashes import HandshakeHashes from .utils.tackwrapper import * +from .utils.deprecations import deprecated_params from .keyexchange import KeyExchange, RSAKeyExchange, DHE_RSAKeyExchange, \ ECDHE_RSAKeyExchange, SRPKeyExchange, ADHKeyExchange, \ AECDHKeyExchange, FFDHKeyExchange, ECDHKeyExchange @@ -111,9 +112,12 @@ def keyingMaterialExporter(self, label, length=20): # Client Handshake Functions #********************************************************* + @deprecated_params({"async_": "async"}, + "'{old_name}' is a keyword in Python 3.7, use" + "'{new_name}'") def handshakeClientAnonymous(self, session=None, settings=None, checker=None, serverName=None, - async=False): + async_=False): """Perform an anonymous handshake in the role of client. This function performs an SSL or TLS handshake using an @@ -147,8 +151,8 @@ def handshakeClientAnonymous(self, session=None, settings=None, :type serverName: string :param serverName: The ServerNameIndication TLS Extension. - :type async: bool - :param async: If False, this function will block until the + :type async_: bool + :param async_: If False, this function will block until the handshake is completed. If True, this function will return a generator. Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is @@ -156,7 +160,7 @@ def handshakeClientAnonymous(self, session=None, settings=None, the handshake operation is completed. :rtype: None or an iterable - :returns: If 'async' is True, a generator object will be + :returns: If 'async_' is True, a generator object will be returned. :raises socket.error: If a socket error occurs. @@ -171,15 +175,18 @@ def handshakeClientAnonymous(self, session=None, settings=None, settings=settings, checker=checker, serverName=serverName) - if async: + if async_: return handshaker for result in handshaker: pass + @deprecated_params({"async_": "async"}, + "'{old_name}' is a keyword in Python 3.7, use" + "'{new_name}'") def handshakeClientSRP(self, username, password, session=None, settings=None, checker=None, reqTack=True, serverName=None, - async=False): + async_=False): """Perform an SRP handshake in the role of client. This function performs a TLS/SRP handshake. SRP mutually @@ -224,8 +231,8 @@ def handshakeClientSRP(self, username, password, session=None, :type serverName: string :param serverName: The ServerNameIndication TLS Extension. - :type async: bool - :param async: If False, this function will block until the + :type async_: bool + :param async_: If False, this function will block until the handshake is completed. If True, this function will return a generator. Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is @@ -233,7 +240,7 @@ def handshakeClientSRP(self, username, password, session=None, the handshake operation is completed. :rtype: None or an iterable - :returns: If 'async' is True, a generator object will be + :returns: If 'async_' is True, a generator object will be returned. :raises socket.error: If a socket error occurs. @@ -256,17 +263,20 @@ def handshakeClientSRP(self, username, password, session=None, # fashion, returning 1 when it is waiting to able to write, 0 when # it is waiting to read. # - # If 'async' is True, the generator is returned to the caller, + # If 'async_' is True, the generator is returned to the caller, # otherwise it is executed to completion here. - if async: + if async_: return handshaker for result in handshaker: pass + @deprecated_params({"async_": "async"}, + "'{old_name}' is a keyword in Python 3.7, use" + "'{new_name}'") def handshakeClientCert(self, certChain=None, privateKey=None, session=None, settings=None, checker=None, nextProtos=None, reqTack=True, serverName=None, - async=False, alpn=None): + async_=False, alpn=None): """Perform a certificate-based handshake in the role of client. This function performs an SSL or TLS handshake. The server @@ -323,8 +333,8 @@ def handshakeClientCert(self, certChain=None, privateKey=None, :type serverName: string :param serverName: The ServerNameIndication TLS Extension. - :type async: bool - :param async: If False, this function will block until the + :type async_: bool + :param async_: If False, this function will block until the handshake is completed. If True, this function will return a generator. Successive invocations of the generator will return 0 if it is waiting to read from the socket, 1 if it is @@ -337,7 +347,7 @@ def handshakeClientCert(self, certChain=None, privateKey=None, Example items in the array include b'http/1.1' or b'h2'. :rtype: None or an iterable - :returns: If 'async' is True, a generator object will be + :returns: If 'async_' is True, a generator object will be returned. :raises socket.error: If a socket error occurs. @@ -360,9 +370,9 @@ def handshakeClientCert(self, certChain=None, privateKey=None, # fashion, returning 1 when it is waiting to able to write, 0 when # it is waiting to read. # - # If 'async' is True, the generator is returned to the caller, - # otherwise it is executed to completion here. - if async: + # If 'async_' is True, the generator is returned to the caller, + # otherwise it is executed to completion here. + if async_: return handshaker for result in handshaker: pass From 494743f1877350c2dd2d6bec44fc3edb928250e5 Mon Sep 17 00:00:00 2001 From: Erkki Vahala Date: Thu, 1 Mar 2018 10:45:53 +0200 Subject: [PATCH 574/574] Fix python2 compatibility issue with X509 DER parsing The documentation for X509.parseBinary claims to support python2 str as an input. The input string is correctly converted to bytearray, but the array is not passed to the ASN1Parser - the parser gets the original string and fails with Type error when attempting to logical-or the characters with an integer. --- tlslite/x509.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tlslite/x509.py b/tlslite/x509.py index 1161b888..de4cee57 100644 --- a/tlslite/x509.py +++ b/tlslite/x509.py @@ -58,7 +58,7 @@ def parseBinary(self, bytes): :param bytes: A DER-encoded X.509 certificate. """ self.bytes = bytearray(bytes) - p = ASN1Parser(bytes) + p = ASN1Parser(self.bytes) #Get the tbsCertificate tbsCertificateP = p.getChild(0)