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/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 00000000..e036b6ab --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,39 @@ +engines: + radon: + enabled: true + config: + threshold: "C" + pep8: + enabled: true + duplication: + enabled: true + exclude_fingerprints: + # duplication in tlslite/utils/codec.py Writer.addTwo() .addFour() + - 2e783666ce368f4223c1e7f5b162e2d9 + - 2c398389f33ea2572edefc5370ed49c0 + config: + languages: + python: + python_version: 3 + mass_threshold: 35 + fixme: + enabled: true + config: + strings: + - TODO + - FIXME + - HACK + - BUG + checks: + bug: + enabled: true + markdownlint: + enabled: true +ratings: + paths: + - "tlslite/**" + - "**.py" +exclude_paths: + - "tests/**/*" + - "scripts/**/*" + - "unit_tests/**/*" diff --git a/.gitignore b/.gitignore index d33736e2..cb85c5a6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,9 @@ .pydevproject .settings .coverage +.hypothesis coverage.xml pylint_report.txt build/ -docs/ +docs/_build/ htmlcov/ diff --git a/.landscape.yaml b/.landscape.yaml new file mode 100644 index 00000000..9379218d --- /dev/null +++ b/.landscape.yaml @@ -0,0 +1,16 @@ +doc-warnings: true + +test-warnings: false + +strictness: medium + +max-line-length: 80 + +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 diff --git a/.travis.yml b/.travis.yml index 89d586e2..f7550143 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,13 @@ language: python +# whitelist branches on which tests will be run +branches: + only: + - master + - tlslite-ng-0.5 + - tlslite-ng-0.6 + - tlslite-ng-0.7 + addons: apt_packages: # needed for M2Crypto @@ -13,6 +21,9 @@ python: - 3.2 - 3.3 - 3.4 + - 3.5 + - 3.6 + - 3.7-dev env: - TACKPY=true @@ -28,19 +39,40 @@ 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 env: PYCRYPTO=true + - python: 3.5 + 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 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 + env: M2CRYPTO=true PYCRYPTO=true GMPY=true + - python: 3.5 + 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: - | @@ -57,15 +89,20 @@ 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 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 [[ $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 M2Crypto; 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' '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 script: @@ -84,20 +121,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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..99741642 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,134 @@ +# 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 (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: + +* python (2.7 or 3.4) +* git +* GNU make +* pip +* sphinx + +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 +* enum34 (for Python2, in case of new hypothesis package) + +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-sphinx +``` + +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 python-sphinx +pip2 install diff-cover +pip3 install hypothesis diff-cover pylint +``` + +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 + * 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 + 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) +* 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 + +* [General GitHub documentation](http://help.github.com/) +* [GitHub pull request documentation](http://help.github.com/send-pull-requests/) 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 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 f738f3c8..e7c8d956 100644 --- a/Makefile +++ b/Makefile @@ -18,51 +18,56 @@ 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 tlslite/integration/*.pyc 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 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 - 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 b/README index 46831e76..5c65e7c0 100644 --- a/README +++ b/README @@ -1,670 +1,38 @@ - -tlslite version 0.4.9 Aug 11 2015 -Trevor Perrin -http://trevp.net/tlslite/ -============================================================================ - - -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 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 -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. - -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'. - - -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), Mirko Dziadzka, David Benjamin, and Hubert Kario. - -All 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. - -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. - -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. - -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 . - - If both say "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'. - -'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 -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 - -Try connecting to the server with a web browser, or with: - - 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 - -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 - - tls.py server -v verifierDB localhost:4443 - -Then try connecting to the server with: - - 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. - -TLS Lite 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. - - 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 -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). - -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() - x509.parse(s) - 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. - -If you want more control over the handshake, you can pass in a -HandshakeSettings instance. For example, if you're performing SRP, but you -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"] - settings.minVersion = (3,1) - 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 -support will be enabled by default, but for now it is an experimental feature -which relies on a temporary TLS Extension number, and should not be used for -production software.) This will cause the client to request the server to send -you a TACK (and/or any TACK Break Signatures): - -Finally, every TLSConnection has a session object. You can try to resume a -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) ----------------------------------------------- -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. - -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. - -You can also pass in a HandshakeSettings object, as described in the last -section, for finer control over handshaking details. - -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. - -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: - - 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. - -TACK objects return the (validated) TACK ID via getTACKID(). - -To save yourself the trouble of inspecting certificates after the handshake, -you can pass a Checker object into the handshake function. The checker will be -called if the handshake completes successfully. If the other party isn't -approved by the checker, a subclass of TLSAuthenticationError will be raised. - -If the handshake fails for any reason, including a Checker error, an exception -will be raised and the socket will be closed. If the socket timed out or was -unexpectedly closed, a socket.error or TLSAbruptCloseError will be raised. - -Otherwise, either a TLSLocalAlert or TLSRemoteAlert will be raised, depending -on whether the local or remote implementation signalled the error. The -exception object has a 'description' member which identifies the error based -on the codes in RFC 2246. A TLSLocalAlert also has a 'message' string that may -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 insufficient_security: - - SRP parameters are too small - -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 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 handshake_failure: - - 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, -TLSRemoteAlert, socket.error, or TLSAbruptCloseError, just like the handshake -functions. - -Once the TLS connection is closed by the other side, calls to read() or recv() -will return an empty string. If the socket is closed by the other side without -first closing the TLS connection, calls to read() or recv() will return a -TLSAbruptCloseError, and calls to write() or send() will return a -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. - -If an exception is raised the connection will be automatically closed; you -don't need to call close(). Furthermore, you will probably not be able to -re-use the socket, the connection object, or the session object, and you -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 -closeSocket is set to True.) - - -6 Using TLS Lite with httplib -============================== -TLS Lite 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", "") - r = h.getresponse() - [...] - - #Authenticate server based on its TACK ID - h = HTTPTLSConnection("localhost", 4443, - tackID="B3ARS.EQ61B.F34EL.9KKLN.3WEW5", hardTack=False) - [...] - - #Mutually authenticate with SRP - h = HTTPTLSConnection("localhost", 443, - username="alice", password="abra123cadabra") - [...] - - -7 Using TLS Lite with poplib or imaplib -======================================== -TLS Lite 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. - - #To connect to a POP3 server over SSL and display its fingerprint: - from tlslite.api import * - p = POP3_TLS("---------.net", port=995) - print p.sock.session.serverCertChain.getFingerprint() - [...] - - #To connect to an IMAP server once you know its fingerprint: - from tlslite.api import * - i = IMAP4_TLS("cyrus.andrew.cmu.edu", - x509Fingerprint="00c14371227b3b677ddb9c4901e6f2aee18d3e45") - [...] - - -8 Using TLS Lite with smtplib -============================== -TLS Lite 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. - - #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 TLS Lite 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 -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 -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 -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 -at your own risk. - -TLS Lite does NOT verify certificates by default. - -TLS Lite'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 -are used, or the weak cipher RC4 otherwise. This unhappy situation will remain -until TLS Lite implements authenticated-encryption ciphersuites (like GCM), or -RFC 7366. - - -12 History -=========== -0.4.9 - 08/11/2015 - - Fixed denial of service (runtime exception) on malformed packet (Hubert Kario) - - Fixed session caching bug (Mirko Dziadzka) - - Fixed SRP spec non-compliance - - Added FALLBACK_SCSV (David Benjamin) - - Unit testing (Hubert Kario) - - "make test" and "make test-dev" targets (Hubert Kario) -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 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 -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) -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) - - 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 - -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 ). - -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. - -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 -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 - 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 -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 - - 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 -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 -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. -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 - - 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 - - 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 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 +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: + - 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, 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, 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) + - x25519 and x448 curves for ECDHE_RSA key exchage (RFC 7748. RFC 4492bis) + - anonymous DHE key exchange + - anonymous ECDH key exchange + - NULL encryption ciphersuites + - FALLBACK_SCSV (RFC 7507) + - encrypt-then-MAC mode of operation for CBC ciphersuites (RFC 7366) + - 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) + - 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 +comprehensive set of features and more secure defautls. diff --git a/README.md b/README.md new file mode 100644 index 00000000..00285ae7 --- /dev/null +++ b/README.md @@ -0,0 +1,975 @@ +``` +tlslite-ng version 0.8.0-alpha1 2017-08-24 +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) +[![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 +================== + +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 +=============== + +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/_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 +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, 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 +* Extended master secret +* 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 +============================ + +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, +and Hubert Kario. + +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. + +3 Installation +=============== + +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: + +* 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 +``` + +3.2 Manual +---------- + +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. + +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 +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 +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 +``` + +Try connecting to the server with a web browser, or with: + +``` +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 +``` + +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 + +tls.py server -v verifierDB localhost:4443 +``` + +Then try connecting to the server with: + +``` +tls.py client -u alice -p abra123cadabra localhost:4443 +``` + +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. +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. + +``` + 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 +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 +[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() + x509 = X509() + x509.parse(s) + 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. + +If you want more control over the handshake, you can pass in a +HandshakeSettings instance. For example, if you're performing SRP, but you +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"] + settings.minVersion = (3,1) + 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 +support will be enabled by default, but for now it is an experimental feature +which relies on a temporary TLS Extension number, and should not be used for +production software.) This will cause the client to request the server to send +you a TACK (and/or any TACK Break Signatures): + +Finally, every TLSConnection has a session object. You can try to resume a +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) +---------------------------------------------- + +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. + +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. + +You can also pass in a HandshakeSettings object, as described in the last +section, for finer control over handshaking details. + +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. + +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: + +``` + 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. + +TACK objects return the (validated) TACK ID via getTACKID(). + +To save yourself the trouble of inspecting certificates after the handshake, +you can pass a Checker object into the handshake function. The checker will be +called if the handshake completes successfully. If the other party isn't +approved by the checker, a subclass of TLSAuthenticationError will be raised. + +If the handshake fails for any reason, including a Checker error, an exception +will be raised and the socket will be closed. If the socket timed out or was +unexpectedly closed, a socket.error or TLSAbruptCloseError will be raised. + +Otherwise, either a TLSLocalAlert or TLSRemoteAlert will be raised, depending +on whether the local or remote implementation signalled the error. The +exception object has a 'description' member which identifies the error based +on the codes in RFC 2246. A TLSLocalAlert also has a 'message' string that may +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 `insufficient_security`: + +* SRP parameters are too small + +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 `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 `handshake_failure`: + +* 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, +TLSRemoteAlert, socket.error, or TLSAbruptCloseError, just like the handshake +functions. + +Once the TLS connection is closed by the other side, calls to read() or recv() +will return an empty string. If the socket is closed by the other side without +first closing the TLS connection, calls to read() or recv() will return a +TLSAbruptCloseError, and calls to write() or send() will return a +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. + +If an exception is raised the connection will be automatically closed; you +don't need to call close(). Furthermore, you will probably not be able to +re-use the socket, the connection object, or the session object, and you +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 +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. + +``` + #No authentication whatsoever + h = HTTPTLSConnection("www.amazon.com", 443) + h.request("GET", "") + r = h.getresponse() + [...] + + #Authenticate server based on its TACK ID + h = HTTPTLSConnection("localhost", 4443, + tackID="B3ARS.EQ61B.F34EL.9KKLN.3WEW5", hardTack=False) + [...] + + #Mutually authenticate with SRP + 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 +imaplib to work over SSL/TLS connections. These classes can be constructed +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) + print p.sock.session.serverCertChain.getFingerprint() + [...] + + #To connect to an IMAP server once you know its fingerprint: + from tlslite.api import * + i = IMAP4_TLS("cyrus.andrew.cmu.edu", + x509Fingerprint="00c14371227b3b677ddb9c4901e6f2aee18d3e45") + [...] +``` + +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 +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 +==================================== + +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 +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. + +tlslite-ng **CANNOT** verify certificates - you must use external means to +check if the certificate is the expected one. + +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.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 + 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 +* stricter checks on network messages +* 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 +* 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 +* Fix incorrect handling of Extended Master Secret with client certificates, + 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 the default +* Stricter checks on received SNI (server_name) extension +* Support for x25519 and x448 curve for ECDHE + +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 + 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) +* 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) +* 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 + +* 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 + +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 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 [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) + +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) +* 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 + +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 +[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. + +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 + +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 + 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 + +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 +* 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 + +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 + +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. + +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` +* 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` +* 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 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 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 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..6503ef5e --- /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.8' +# The full version, including alpha/beta/rc tags. +release = u'0.8.0-alpha1' + +# 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.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.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..c71c74f2 --- /dev/null +++ b/docs/tlslite.utils.rst @@ -0,0 +1,54 @@ +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.deprecations + 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: diff --git a/pylintrc b/pylintrc index 5d838ebc..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 @@ -44,24 +53,21 @@ 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 +# 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] @@ -112,14 +128,11 @@ 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 # 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 @@ -132,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}$ @@ -154,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}|(__.*__))$ @@ -176,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_]*$ @@ -196,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}$ @@ -324,10 +325,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 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 48035ce2..45b5896c 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 * @@ -23,12 +24,17 @@ 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!" from tlslite.api import * +from tlslite.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ + GroupName, SignatureScheme 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 @@ -67,12 +73,19 @@ def printUsage(s=None): print("""Commands: server - [-k KEY] [-c CERT] [-t TACK] [-v VERIFIERDB] [-d DIR] - [--reqcert] HOST:PORT + [-k KEY] [-c CERT] [-t TACK] [-v VERIFIERDB] [-d DIR] [-l LABEL] [-L LENGTH] + [--reqcert] [--param DHFILE] HOST:PORT client - [-k KEY] [-c CERT] [-u USER] [-p PASS] + [-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 + DHFILE - file that includes Diffie-Hellman parameters to be used with DHE + key exchange """) sys.exit(-1) @@ -99,13 +112,23 @@ def handleArgs(argv, argString, flagsList=[]): verifierDB = None reqCert = False directory = None - + expLabel = None + expLength = 20 + alpn = [] + dhparam = None + for opt, arg in opts: if opt == "-k": s = open(arg, "rb").read() - privateKey = parsePEMKey(s, private=True) + if sys.version_info[0] >= 3: + s = str(s, 'utf-8') + # OpenSSL/m2crypto does not support RSASSA-PSS certificates + privateKey = parsePEMKey(s, private=True, + implementations=["python"]) 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]) @@ -124,9 +147,23 @@ def handleArgs(argv, argString, flagsList=[]): directory = arg elif opt == "--reqcert": reqCert = True + elif opt == "-l": + expLabel = arg + elif opt == "-L": + 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) - + + # when no names provided, don't return array + if not alpn: + alpn = None if not argv: printError("Missing address") if len(argv)>1: @@ -156,6 +193,14 @@ 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) + if "a" in argString: + retList.append(alpn) + if "param=" in flagsList: + retList.append(dhparam) return retList @@ -164,14 +209,30 @@ 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: 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()) + if connection.version >= (3, 3) and connection.serverSigAlg is not None: + scheme = SignatureScheme.toRepr(connection.serverSigAlg) + if scheme is None: + scheme = "{1}+{0}".format( + HashAlgorithm.toStr(connection.serverSigAlg[0]), + 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))) + 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: @@ -181,12 +242,28 @@ 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( + 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, alpn) = \ + handleArgs(argv, "kcuplLa") if (certChain and not privateKey) or (not certChain and privateKey): raise SyntaxError("Must specify CERT and KEY together") @@ -194,11 +271,14 @@ 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) sock.settimeout(5) sock.connect(address) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) connection = TLSConnection(sock) settings = HandshakeSettings() @@ -211,7 +291,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: @@ -237,12 +317,14 @@ 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, dhparam) = handleArgs(argv, "kctbvdlL", + ["reqcert", "param="]) if (certChain and not privateKey) or (not certChain and privateKey): @@ -262,9 +344,15 @@ def serverCmd(argv): print("Using verifier DB...") if tacks: print("Using Tacks...") + if reqCert: + print("Asking for client certificates...") ############# sessionCache = SessionCache() + username = None + sni = None + if is_valid_hostname(address[0]): + sni = address[0] class MyHTTPServer(ThreadingMixIn, TLSSocketServerMixIn, HTTPServer): def handshake(self, connection): @@ -280,6 +368,9 @@ def handshake(self, connection): start = time.clock() 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, @@ -287,7 +378,10 @@ def handshake(self, connection): activationFlags=activationFlags, sessionCache=sessionCache, settings=settings, - nextProtos=[b"http/1.1"]) + nextProtos=[b"http/1.1"], + alpn=[bytearray(b'http/1.1')], + 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() @@ -318,6 +412,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/setup.py b/setup.py index dc4656df..3d8fa980 100755 --- a/setup.py +++ b/setup.py @@ -4,13 +4,43 @@ # See the LICENSE file for legal information regarding use of this file. from distutils.core import setup +import os -setup(name="tlslite", - version="0.4.9", - author="Trevor Perrin", - author_email="tlslite@trevp.net", - url="http://trevp.net/tlslite/", - description="tlslite implements SSL and TLS.", - license="public domain and BSD", +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.8.0-alpha1", + 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"],) + packages=["tlslite", "tlslite.utils", "tlslite.integration"], + package_data={ + 'package1': ['LICENSE', 'README.md']}, + install_requires=['ecdsa'], + obsoletes=["tlslite"], + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)', + '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', + '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' + ], + keywords="ssl, tls, pure-python" + ) 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/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 029c6e44..8737d51b 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,8 +17,9 @@ import os import os.path import socket -import time +import timeit import getopt +from tempfile import mkstemp try: from BaseHTTPServer import HTTPServer from SimpleHTTPServer import SimpleHTTPRequestHandler @@ -88,38 +91,71 @@ 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 - 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() + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientAnonymous(settings=settings) + testConnClient(connection) + connection.close() + + test_no += 1 + + print("Test {0} - good X509 (plus SNI)".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 cert".format(test_no)) synchro.recv(1) connection = connect() - connection.handshakeClientAnonymous() + 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() - - print("Test 1 - good X509 (plus SNI)") + + 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() - 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() @@ -128,34 +164,45 @@ def connect(): connection.handshakeClientCert(settings=settings) testConnClient(connection) assert(isinstance(connection.session.serverCertChain, X509CertChain)) - connection.close() + connection.close() + + test_no += 1 - print("Test 1.b - good X509, RC4-MD5") + print("Test {0} - good X509, RC4-MD5".format(test_no)) synchro.recv(1) connection = 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)) assert(connection.session.cipherSuite == constants.CipherSuite.TLS_RSA_WITH_RC4_128_MD5) + assert(connection.encryptThenMAC == False) connection.close() if tackpyLoaded: - + settings = HandshakeSettings() settings.useExperimentalTackExtension = True + settings.maxVersion = (3, 3) - 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() + + 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.recv(1) connection = connect() try: @@ -165,27 +212,60 @@ 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)) - print("Test 3 - good SRP") + 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 (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() + + test_no += 1 + + print("Test {0} - good SRP".format(test_no)) + synchro.recv(1) + connection = connect() + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientSRP("test", "password", settings=settings) 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() 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))) 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) @@ -196,19 +276,26 @@ 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() 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))) 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() @@ -220,7 +307,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() @@ -228,12 +317,17 @@ 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() - 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() @@ -244,7 +338,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() @@ -255,7 +351,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() @@ -267,37 +365,54 @@ 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]) + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + connection.handshakeClientSRP("test", "password", serverName=address[0], + settings=settings) testConnClient(connection) 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() + 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 - 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) 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: 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 @@ -334,11 +449,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() @@ -352,100 +471,159 @@ 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 ["aes128", "aes256", "3des", "rc4"]: - if cipher == "3des" and implementation not in ("openssl", "pycrypto"): + for cipher in ["aes128gcm", "aes256gcm", "aes128", "aes256", "3des", + "rc4", "chacha20-poly1305_draft00", + "chacha20-poly1305"]: + # skip tests with implementations that don't support them + if cipher == "3des" and implementation not in ("openssl", + "pycrypto"): + continue + if cipher in ("aes128gcm", "aes256gcm") and \ + implementation not in ("pycrypto", + "python"): + continue + if cipher in ("chacha20-poly1305_draft00", "chacha20-poly1305") \ + and implementation not in ("python", ): continue - print("Test 23:", end=' ') + test_no += 1 + + print("Test {0}:".format(test_no), end=' ') synchro.recv(1) connection = 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=' ') - 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 = len(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") 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"]) + 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() - 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"]) + 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() - - 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"]) + 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() - - 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"]) + 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() - - 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"]) + 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() - 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"]) + 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() - 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"]) + 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() - print("Test 25.a - FALLBACK_SCSV") + test_no += 1 + + print("Test {0} - FALLBACK_SCSV".format(test_no)) synchro.recv(1) 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() - print("Test 25.b - FALLBACK_SCSV") + test_no += 1 + + print("Test {0} - FALLBACK_SCSV".format(test_no)) synchro.recv(1) connection = connect() settings = HandshakeSettings() @@ -459,32 +637,105 @@ def connect(): raise connection.close() - print("Test 26.a - server checks cipher version") + test_no += 1 + + print("Test {0} - no EtM server side".format(test_no)) synchro.recv(1) connection = connect() - # Configure the ClientHello to only advertise SHA-256 ciphers, but not - # support TLS 1.2. - connection.fault = Fault.ignoreVersionForCipher settings = HandshakeSettings() - settings.maxVersion = (3, 2) - settings.macNames = ["sha256"] - connection.handshakeClientCert(settings=settings) + settings.macNames.remove("aead") + settings.maxVersion = (3, 3) + 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 - client checks cipher version") + test_no += 1 + + print("Test {0} - no EtM client side".format(test_no)) synchro.recv(1) connection = 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)) + assert(connection.session.serverName == address[0]) + assert(not connection.encryptThenMAC) + connection.close() + + test_no += 1 + + print("Test {0} - resumption with EtM".format(test_no)) + synchro.recv(1) + 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)) + assert(connection.session.serverName == address[0]) + assert(not connection.resumed) + assert(connection.encryptThenMAC) + connection.close() + session = connection.session + + # resume + synchro.recv(1) + connection = connect() + 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]) + assert(connection.resumed) + assert(connection.encryptThenMAC) + connection.close() + + test_no += 1 + + print("Test {0} - resumption with no EtM in 2nd handshake".format(test_no)) + synchro.recv(1) + 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)) + 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 + settings.macNames.remove("aead") + settings.maxVersion = (3, 3) + connection = connect() try: - connection.handshakeClientCert() - assert() - except TLSLocalAlert as alert: - # The client should reject the ServerHello because it selected an - # invalid cipher for the version. - if alert.description != AlertDescription.illegal_parameter: - raise + 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') + test_no += 1 + + print('Test {0} - good standard XMLRPC https client'.format(test_no)) address = address[0], address[1]+1 synchro.recv(1) try: @@ -501,7 +752,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) @@ -509,29 +762,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 30: IMAP4 good") + + test_no += 1 + + print("Test {0}: IMAP4 good".format(test_no)) p = POP3_TLS("pop.gmail.com") p.quit() - print("Test 31: POP3 good") - except socket.error as e: + + test_no += 1 + + print("Test {0}: POP3 good".format(test_no)) + except (socket.error, socket.timeout) 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") @@ -573,39 +836,88 @@ 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]) s = open(os.path.join(dir, "serverX509Key.pem")).read() x509Key = parsePEMKey(s, private=True) - print("Test 0 - Anonymous server handshake") + 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) + + 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)) 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 (plus SNI)".format(test_no)) synchro.send(b'R') connection = connect() connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) assert(connection.session.serverName == address[0]) + assert(connection.extendedMasterSecret) testConnServer(connection) connection.close() - print("Test 1.a - good X.509, SSL v3") + 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.extendedMasterSecret) + testConnServer(connection) + connection.close() + + 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() settings = HandshakeSettings() settings.minVersion = (3,0) settings.maxVersion = (3,0) connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, settings=settings) + assert(not connection.extendedMasterSecret) 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() @@ -613,24 +925,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() - 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.send(b'R') connection = connect() try: @@ -639,13 +956,51 @@ 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 (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: + 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 + + print("Test {0} - good SRP".format(test_no)) verifierDB = VerifierDB() verifierDB.create() entry = VerifierDB.makeVerifier("test", "password", 1536) - verifierDB["test"] = entry + verifierDB[b"test"] = entry synchro.send(b'R') connection = connect() @@ -653,7 +1008,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() @@ -661,7 +1018,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, \ @@ -669,7 +1028,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() @@ -678,7 +1039,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() @@ -686,15 +1049,19 @@ 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) 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") + test_no += 1 + + print("Test {0} - good mutual X.509, TLSv1.1".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -702,10 +1069,12 @@ 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") + test_no += 1 + + print("Test {0} - good mutual X.509, SSLv3".format(test_no)) synchro.send(b'R') connection = connect() settings = HandshakeSettings() @@ -713,10 +1082,12 @@ 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") + 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() @@ -724,7 +1095,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() @@ -733,7 +1106,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) @@ -741,7 +1116,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) @@ -757,7 +1134,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() @@ -795,11 +1174,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() @@ -813,13 +1196,28 @@ 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 ["aes128", "aes256", "3des", "rc4"]: - if cipher == "3des" and implementation not in ("openssl", "pycrypto"): + for cipher in ["aes128gcm", "aes256gcm", "aes128", "aes256", "3des", + "rc4", "chacha20-poly1305_draft00", + "chacha20-poly1305"]: + # skip tests with implementations that don't support them + if cipher == "3des" and implementation not in ("openssl", + "pycrypto"): + continue + if cipher in ("aes128gcm", "aes256gcm") and \ + implementation not in ("pycrypto", + "python"): continue + if cipher in ("chacha20-poly1305_draft00", "chacha20-poly1305") \ + and implementation not in ("python", ): + continue + + test_no += 1 - print("Test 23:", end=' ') + print("Test {0}:".format(test_no), end=' ') synchro.send(b'R') connection = connect() @@ -835,7 +1233,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() @@ -844,7 +1244,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() @@ -852,8 +1254,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() @@ -862,7 +1266,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() @@ -870,8 +1276,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() @@ -879,8 +1287,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() @@ -888,8 +1298,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() @@ -898,49 +1310,104 @@ 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) + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + # TODO fix FALLBACK with TLS1.3 + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, + settings=settings) 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() + 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: raise connection.close() - print("Test 26.a - server checks cipher version") + test_no += 1 + + print("Test {0} - no EtM server side".format(test_no)) synchro.send(b'R') connection = connect() - try: - connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) - assert() - except TLSLocalAlert as alert: - # The server should reject the connection with a handshake_failure - # because, after taking the version into account, no ciphers match. - if alert.description != AlertDescription.handshake_failure: - raise + settings = HandshakeSettings() + settings.useEncryptThenMAC = False + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, + settings=settings) + testConnServer(connection) connection.close() - print("Test 26.b - client checks cipher version") + test_no += 1 + + print("Test {0} - no EtM client side".format(test_no)) synchro.send(b'R') connection = connect() - # Configure the server to illegally select SHA-256 ciphers at TLS 1.1. - connection.fault = Fault.ignoreVersionForCipher - settings = HandshakeSettings() - settings.maxVersion = (3, 2) - settings.macNames = ["sha256"] - connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, settings=settings) + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key) + testConnServer(connection) + connection.close() + + test_no += 1 + + print("Test {0} - resumption with EtM".format(test_no)) + synchro.send(b'R') + sessionCache = SessionCache() + connection = connect() + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, + sessionCache=sessionCache) + testConnServer(connection) connection.close() - print("Tests 27-29 - XMLRPXC server") + # resume + synchro.send(b'R') + connection = connect() + connection.handshakeServer(certChain=x509Chain, privateKey=x509Key, + sessionCache=sessionCache) + testConnServer(connection) + connection.close() + + test_no += 1 + + print("Test {0} - resumption with no EtM in 2nd handshake".format(test_no)) + 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() + + 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): diff --git a/tlslite/__init__.py b/tlslite/__init__.py index 1a0d38f7..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.4.9 +@version: 0.8.0-alpha1 """ from tlslite.api import * diff --git a/tlslite/api.py b/tlslite/api.py index 4dc75d0a..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.4.9" +__version__ = "0.8.0-alpha1" from .constants import AlertLevel, AlertDescription, Fault from .errors import * from .checker import Checker @@ -29,3 +29,4 @@ from .utils.keyfactory import generateRSAKey, parsePEMKey, \ parseAsPublicKey, parsePrivateKey from .utils.tackwrapper import tackpyLoaded +from .dh import parse as parseDH diff --git a/tlslite/basedb.py b/tlslite/basedb.py index e6b79442..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,22 +91,21 @@ 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") self.lock.acquire() try: - return self.db.has_key(username) + return username in self.db finally: self.lock.release() @@ -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 new file mode 100644 index 00000000..ec579bd7 --- /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. + + :vartype 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/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 6c7e34ec..15c18fe3 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -10,20 +10,94 @@ """Constants used in various places.""" -class CertificateType: + +# 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""" + + @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): + """ + Convert numeric type to string representation + + name if found, None otherwise + """ + fields = cls._recursiveVars(cls) + if blacklist is None: + blacklist = [] + return next((key for key, val in fields.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(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 HandshakeType: + + +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 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""" + hello_request = 0 client_hello = 1 server_hello = 2 + new_session_ticket = 4 + hello_retry_request = 6 + encrypted_extensions = 8 certificate = 11 server_key_exchange = 12 certificate_request = 13 @@ -31,69 +105,271 @@ class HandshakeType: certificate_verify = 15 client_key_exchange = 16 finished = 20 + certificate_status = 22 next_protocol = 67 + message_hash = 254 # TLS 1.3 + + +class ContentType(TLSEnum): + """TLS record layer content types of payloads""" -class ContentType: change_cipher_spec = 20 alert = 21 handshake = 22 application_data = 23 - all = (20,21,22,23) + all = (20, 21, 22, 23) -class ExtensionType: # RFC 6066 / 4366 - server_name = 0 # RFC 6066 / 4366 - srp = 12 # RFC 5054 - cert_type = 9 # RFC 6091 - tack = 0xF300 + @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(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 + 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 + 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 + + +class HashAlgorithm(TLSEnum): + """Hash algorithm IDs used in TLSv1.2""" -class HashAlgorithm: none = 0 md5 = 1 sha1 = 2 sha224 = 3 sha256 = 4 sha384 = 5 + sha512 = 6 + + +class SignatureAlgorithm(TLSEnum): + """Signing algorithms used in TLSv1.2""" -class SignatureAlgorithm: anonymous = 0 rsa = 1 dsa = 2 ecdsa = 3 - -class NameType: + + +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) + + @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""" + + # 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.extend(list(range(26, 29))) + + # draft-ietf-tls-rfc4492bis + x25519 = 29 + x448 = 30 + allEC.extend(list(range(29, 31))) + + # RFC7919 + ffdhe2048 = 256 + ffdhe3072 = 257 + ffdhe4096 = 258 + ffdhe6144 = 259 + ffdhe8192 = 260 + allFF = list(range(256, 261)) + + 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(TLSEnum): + """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] + + @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""" + + explicit_prime = 1 + explicit_char2 = 2 + named_curve = 3 + + +class NameType(TLSEnum): + """Type of entries in Server Name Indication extension.""" + host_name = 0 -class AlertLevel: + +class CertificateStatusType(TLSEnum): + """Type of responses in the status_request and CertificateStatus msgs.""" + + ocsp = 1 + + +class AlertLevel(TLSEnum): + """Enumeration of TLS Alert protocol levels""" + warning = 1 fatal = 2 -class AlertDescription: - """ - @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. +class AlertDescription(TLSEnum): + """ + :cvar bad_record_mac: A TLS record failed to decrypt properly. - 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. + 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. - @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). + :cvar handshake_failure: A problem occurred while handshaking. - @cvar protocol_version: The other party's SSL/TLS version was unacceptable. + 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 indicates that the client and server couldn't agree on which version - of SSL or TLS to use. + :cvar protocol_version: The other party's SSL/TLS version was unacceptable. - @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 @@ -121,64 +397,431 @@ class AlertDescription: 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 + bad_certificate_status_response = 113 # RFC 6066 + bad_certificate_hash_value = 114 # RFC 6066 unknown_psk_identity = 115 + no_application_protocol = 120 # RFC 7301 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 = {} + +# 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] + + # + # 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_RC4_128_MD5 = 0x0004 + ietfNames[0x0004] = 'TLS_RSA_WITH_RC4_128_MD5' + 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_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_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' + 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_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' + + # 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' - # draft-ietf-tls-downgrade-scsv-03 + # RFC 7507 - Fallback Signaling Cipher Suite Value for Preventing Protocol + # Downgrade Attacks TLS_FALLBACK_SCSV = 0x5600 - - TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A - TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D - TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020 + 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' + 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 + 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' + + # 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/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' - TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A - TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F - TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 - TLS_RSA_WITH_RC4_128_SHA = 0x0005 - - TLS_RSA_WITH_RC4_128_MD5 = 0x0004 + # 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' - TLS_DH_ANON_WITH_AES_128_CBC_SHA = 0x0034 - TLS_DH_ANON_WITH_AES_256_CBC_SHA = 0x003A + # 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' - TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C - TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D + # 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 + 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 + # + + #: 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) 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 = [] 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) + 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) + #: 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) 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) + 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) + + #: 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_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) + aes128GcmSuites.append(TLS_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_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) + aes256GcmSuites.append(TLS_AES_256_GCM_SHA384) + #: 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) + chacha20Suites.append(TLS_CHACHA20_POLY1305_SHA256) + + #: 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) - + rc4Suites.append(TLS_ECDH_ANON_WITH_RC4_128_SHA) + + #: no encryption + nullSuites = [] + 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) + + #: 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) @@ -190,38 +833,147 @@ 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) - + 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) + 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) + 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 = [] 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) + 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 + streamSuites = [] + streamSuites.extend(rc4Suites) + streamSuites.extend(nullSuites) + + #: AEAD integrity, any PRF + aeadSuites = [] + aeadSuites.extend(aes128GcmSuites) + aeadSuites.extend(aes256GcmSuites) + aeadSuites.extend(chacha20Suites) + aeadSuites.extend(chacha20draft00Suites) + + #: TLS1.2 with SHA384 PRF + sha384PrfSuites = [] + sha384PrfSuites.extend(sha384Suites) + sha384PrfSuites.extend(aes256GcmSuites) + + #: 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 + ssl3Suites = [] + ssl3Suites.extend(shaSuites) + ssl3Suites.extend(md5Suites) + + #: TLS1.2 specific ciphersuites + tls12Suites = [] + tls12Suites.extend(sha256Suites) + 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): - """ Returns a copy of suites after removing any entries which are not - enabled by any version of TLS between minVersion and maxVersion. """ - excludeSuites = [] - if maxVersion < (3, 3): - excludeSuites += CipherSuite.sha256Suites - return [s for s in suites if s not in excludeSuites] + """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) 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 - 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 "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): + 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): + cipherSuites += CipherSuite.aes256GcmSuites if "aes128" in cipherNames: cipherSuites += CipherSuite.aes128Suites if "aes256" in cipherNames: @@ -230,34 +982,69 @@ def _filterSuites(suites, settings): cipherSuites += CipherSuite.tripleDESSuites if "rc4" in cipherNames: cipherSuites += CipherSuite.rc4Suites + if "null" in cipherNames: + cipherSuites += CipherSuite.nullSuites + + keyExchangeSuites = [] + if version >= (3, 4): + keyExchangeSuites += CipherSuite.tls13Suites + if "rsa" in keyExchangeNames: + 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: + 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] - return [s for s in suites if s in macSuites and s in cipherSuites] + @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) 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) + @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 = [] 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) + @classmethod + 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 - @staticmethod - def getSrpAllSuites(settings): - return CipherSuite._filterSuites(CipherSuite.srpAllSuites, settings) + @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 = [] + 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) certSuites.append(TLS_RSA_WITH_AES_256_CBC_SHA) @@ -265,24 +1052,108 @@ 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) - certAllSuites = srpCertSuites + certSuites - - @staticmethod - def getCertSuites(settings): - return CipherSuite._filterSuites(CipherSuite.certSuites, settings) + certSuites.append(TLS_RSA_WITH_NULL_MD5) + certSuites.append(TLS_RSA_WITH_NULL_SHA) + certSuites.append(TLS_RSA_WITH_NULL_SHA256) + + @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 = [] + 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) + 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) + + @classmethod + def getDheCertSuites(cls, settings, version=None): + """Provide authenticated DHE ciphersuites matching settings""" + return cls._filterSuites(CipherSuite.dheCertSuites, settings, version) + + #: 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) + 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_3DES_EDE_CBC_SHA) + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_RC4_128_SHA) + ecdheCertSuites.append(TLS_ECDHE_RSA_WITH_NULL_SHA) + @classmethod + def getEcdheCertSuites(cls, settings, version=None): + """Provide authenticated ECDHE ciphersuites matching settings""" + return cls._filterSuites(CipherSuite.ecdheCertSuites, settings, version) + + #: 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) + 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) - - @staticmethod - def getAnonSuites(settings): - return CipherSuite._filterSuites(CipherSuite.anonSuites, settings) + anonSuites.append(TLS_DH_ANON_WITH_3DES_EDE_CBC_SHA) + anonSuites.append(TLS_DH_ANON_WITH_RC4_128_MD5) + + @classmethod + def getAnonSuites(cls, settings, version=None): + """Provide anonymous DH ciphersuites matching settings""" + return cls._filterSuites(CipherSuite.anonSuites, settings, version) + + dhAllSuites = dheCertSuites + anonSuites + + #: 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) + + @classmethod + 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 + ecdhAllSuites = ecdheEcdsaSuites + ecdheCertSuites + ecdhAnonSuites @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" @@ -290,13 +1161,23 @@ def canonicalCipherName(ciphersuite): return "rc4" elif ciphersuite in CipherSuite.tripleDESSuites: return "3des" + elif ciphersuite in CipherSuite.nullSuites: + return "null" + elif ciphersuite in CipherSuite.chacha20draft00Suites: + return "chacha20-poly1305_draft00" + elif ciphersuite in CipherSuite.chacha20Suites: + return "chacha20-poly1305" else: return None @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" @@ -328,8 +1209,6 @@ class Fault: badPadding = 302 genericFaults = list(range(300,303)) - ignoreVersionForCipher = 400 - faultAlerts = {\ badUsername: (AlertDescription.unknown_psk_identity, \ AlertDescription.bad_record_mac),\ @@ -340,9 +1219,7 @@ class Fault: badVerifyMessage: (AlertDescription.decrypt_error,),\ badFinished: (AlertDescription.decrypt_error,),\ badMAC: (AlertDescription.bad_record_mac,),\ - badPadding: (AlertDescription.bad_record_mac,),\ - ignoreVersionForCipher: (AlertDescription.illegal_parameter,\ - AlertDescription.handshake_failure) + badPadding: (AlertDescription.bad_record_mac,) } faultNames = {\ @@ -354,6 +1231,5 @@ class Fault: badVerifyMessage: "bad verify message",\ badFinished: "bad finished message",\ badMAC: "bad MAC",\ - badPadding: "bad padding",\ - ignoreVersionForCipher: "ignore version for cipher" + badPadding: "bad padding" } diff --git a/tlslite/defragmenter.py b/tlslite/defragmenter.py new file mode 100644 index 00000000..559404c2 --- /dev/null +++ b/tlslite/defragmenter.py @@ -0,0 +1,127 @@ +# 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 +from .utils.deprecations import deprecated_attrs, deprecated_params + + +@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. + + 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 = {} + + @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 msg_type in self.priorities: + raise ValueError("Message type already defined") + if size < 1: + raise ValueError("Message size must be positive integer") + + self.priorities += [msg_type] + + self.buffers[msg_type] = bytearray(0) + def size_handler(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[msg_type] = size_handler + + @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 msg_type in self.priorities: + raise ValueError("Message type already defined") + if size_of_size < 1: + raise ValueError("Size of size must be positive integer") + if size_offset < 0: + raise ValueError("Offset can't be negative") + + self.priorities += [msg_type] + self.buffers[msg_type] = bytearray(0) + + def size_handler(data): + """ + Size of message in parameter + + If complete message is present in parameter returns its size, + None otherwise. + """ + if len(data) < size_offset+size_of_size: + return None + else: + parser = Parser(data) + # skip the header + parser.getFixBytes(size_offset) + + payload_length = parser.get(size_of_size) + if parser.getRemainingLength() < payload_length: + # not enough bytes in buffer + return None + return size_offset + size_of_size + payload_length + + self.decoders[msg_type] = size_handler + + @deprecated_params({"msg_type": "msgType"}) + def add_data(self, msg_type, data): + """Adds data to buffers""" + if msg_type not in self.priorities: + raise ValueError("Message type not defined") + + self.buffers[msg_type] += data + + def get_message(self): + """Extract the highest priority complete message from buffer""" + 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[msg_type][:length] + # remove it from buffer + self.buffers[msg_type] = self.buffers[msg_type][length:] + return (msg_type, data) + return None + + def clear_buffers(self): + """Remove all data from buffers""" + for key in self.buffers.keys(): + self.buffers[key] = bytearray(0) diff --git a/tlslite/dh.py b/tlslite/dh.py new file mode 100644 index 00000000..922d1ee6 --- /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 bytes 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 bytes data: DH parameters + :rtype: tuple of int + :returns: generator and prime + """ + try: + return parseBinary(data) + except (SyntaxError, TypeError): + pass + + binData = dePem(data, "DH PARAMETERS") + return parseBinary(binData) diff --git a/tlslite/errors.py b/tlslite/errors.py index 2c523176..a705435d 100644 --- a/tlslite/errors.py +++ b/tlslite/errors.py @@ -4,27 +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 TLSError(Exception): +class BaseTLSException(Exception): + """ + Metaclass for TLS Lite exceptions. + + 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.""" + + pass + + +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. @@ -34,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 = {\ @@ -68,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 @@ -96,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 @@ -117,61 +141,177 @@ 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""" + """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 TLSDecodeError(TLSProtocolException): + """The received message encoding does not match specification.""" + + 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""" + + pass + + +class TLSDecryptionFailed(TLSProtocolException): + """Decryption of data was unsuccessful""" + + pass + + +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""" + + pass + + +class TLSHandshakeFailure(TLSProtocolException): + """Could not find acceptable set of handshake parameters""" + + 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 diff --git a/tlslite/extensions.py b/tlslite/extensions.py index fe8185a6..0f910385 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. @@ -9,136 +9,255 @@ 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, \ + SignatureAlgorithm, HashAlgorithm, SignatureScheme from .errors import TLSInternalError 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. + 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. - @type extType: int - @ivar extType: a 2^16-1 limited integer specifying the type of the + 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: + `extData`, `create`, `parse` and `__repr__`. If the parser can be used + for client and optionally server extensions, the extension constructor + should be added to `_universalExtensions`. Otherwise, when the client and + server extensions have completely different forms, you should add client + 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 `_universalExtensions` may be skipped, then + the `TLSExtension` class will be used for implementing it. See + end of the file for type-to-constructor bindings. + + .. note:: Subclassing for the purpose of parsing extensions + is not an officially supported part of API (just as underscores in + their + names would indicate). + + :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 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 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. + + :vartype _certificateExtensions: dict + :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): + def __init__(self, server=False, extType=None, encExt=False, + cert=False, hrr=False): """ - 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 + 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: whatever to select ClientHello or ServerHello version + :param bool server: whether to select ClientHello or ServerHello + version + 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 + :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 = None - self.extData = bytearray(0) + self.extType = extType + self._extData = bytearray(0) self.serverType = server + self.encExtType = encExt + self.cert = cert + self.hrr = hrr - def create(self, extType, data): + @property + def extData(self): """ - Initializes a generic TLS extension that can later be used in - client hello or server hello messages + Return the on the wire encoding of extension - @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} + 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): + """Legacy handling of create method""" self.extType = extType - self.extData = data + 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. + + 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`. + + Child classes need to override this method so that it is possible + to set values for all fields used by the extension. + + :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: + 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 + + 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 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 + 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 + """Parses extension from on the wire format - @type p: L{tlslite.util.codec.Parser} - @param p: data to be parsed + 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 `extData` property. - @raise SyntaxError: when the size of the passed element doesn't match - the internal representation + :param tlslite.util.codec.Parser p: data to be parsed - @rtype: L{TLSExtension} - """ + :raises SyntaxError: when the size of the passed element doesn't match + the internal representation + :rtype: TLSExtension + """ extType = p.get(2) - ext_length = 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, + # self._encryptedExtensions) - # first check if we shouldn't use server side parser + # then 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, extLength, + self._serverExtensions) - # then fallback to universal/ClientHello-specific parsers + 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: - ext = self._universalExtensions[extType]() - ext_parser = Parser(p.getFixBytes(ext_length)) - ext = ext.parse(ext_parser) - return ext + 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): - """ 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. @@ -150,13 +269,203 @@ def __eq__(self, that): return False def __repr__(self): - """ Output human readable representation of object + """Output human readable representation of object - @rtype: str + 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},"\ - " 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): + """ + 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, 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 + + @property + def extData(self): + """ + Return raw data encoding of the extension. + + :rtype: bytearray + """ + raise NotImplementedError("Abstract class") + + def create(self, values): + """ + Set the list to specified values. + + :param list values: list of values to save + """ + self._internalList = values + return self + + def parse(self, parser): + """ + Deserialise extension from on-the-wire data. + + :param tlslite.utils.codec.Parser parser: data + :rtype: Extension + """ + raise NotImplementedError("Abstract class") + + 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}'"\ + .format(self.__class__.__name__, name)) + + def __setattr__(self, name, value): + """Set the special field value.""" + if name == '_fieldName': + super(ListExtension, self).__setattr__(name, value) + return + if hasattr(self, '_fieldName') and name == self._fieldName: + self._internalList = value + return + 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): """ @@ -169,8 +478,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 @@ -185,20 +494,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') @@ -207,15 +516,16 @@ 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 def __repr__(self): """ Return programmer-readable representation of extension - @rtype: str + :rtype: str """ return "SNIExtension(serverNames={0!r})".format(self.serverNames) @@ -224,24 +534,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 @@ -263,19 +571,11 @@ 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. - @rtype: tuple of bytearrays + :rtype: tuple of bytearrays """ # because we can't simulate assignments to array elements we return # an immutable type @@ -288,13 +588,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 = \ @@ -305,17 +605,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) @@ -333,10 +635,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 """ @@ -355,11 +659,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: @@ -374,144 +677,101 @@ def parse(self, p): self.serverNames += [SNIExtension.ServerName(sn_type, sn_name)] p.stopLengthCheck() - return self - -class ClientCertTypeExtension(TLSExtension): - """ - This class handles the Certificate Type extension (variant sent by client) - defined in RFC 6091. + if p.getRemainingLength(): + raise SyntaxError() - @type extType: int - @ivar extType: numeric type of Certificate Type extension, i.e. 9 + return self - @type 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) +class SupportedVersionsExtension(VarSeqListExtension): """ + This class handles the SupportedVersion extensions used in TLS 1.3. - def __init__(self): - """ - Create an instance of ClientCertTypeExtension - - 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 + See draft-ietf-tls-tls13. - @rtype: int - """ + :vartype extType: int + :ivar extType: numeric type of the Supported Versions extension, i.e. 43 - return ExtensionType.cert_type + :vartype extData: bytearray + :ivar extData: raw representation of the extension data - @property - def extData(self): - """ - Return the raw encoding of this extension + :vartype versions: list of tuples + :ivar versions: list of supported protocol versions; each tuple has two + one byte long integers + """ - @rtype: bytearray - """ + def __init__(self): + """Create an instance of SupportedVersionsExtension.""" + super(SupportedVersionsExtension, self).__init__(1, 2, 1, + "versions", + extType= + ExtensionType. + supported_versions) - 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) +class ClientCertTypeExtension(VarListExtension): + """ + This class handles the (client variant of) Certificate Type extension - return w.bytes + See RFC 6091. - def create(self, certTypes=None): - """ - Return instance of this extension with specified certificate types + :vartype extType: int + :ivar extType: numeric type of Certificate Type extension, i.e. 9 - @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 + :vartype extData: bytearray + :ivar extData: raw representation of the extension data - @raises ValueError: when the list includes too big or negative integers - """ - self.certTypes = certTypes - return self + :vartype certTypes: list of int + :ivar certTypes: list of certificate type identifiers (each one byte long) + """ - def parse(self, p): + def __init__(self): """ - 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 + Create an instance of ClientCertTypeExtension - @rtype: L{ClientCertTypeExtension} + See also: :py:meth:`create` and :py:meth:`parse` """ - - self.certTypes = p.getVarList(1, 1) - - return self + super(ClientCertTypeExtension, self).__init__(1, 1, 'certTypes', \ + ExtensionType.cert_type) class ServerCertTypeExtension(TLSExtension): """ This class handles the Certificate Type extension (variant sent by server) defined in RFC 6091. - @type extType: int - @ivar extType: byneruc ttoe if 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) self.cert_type = None def __repr__(self): """ Return programmer-centric description of object - @rtype: str + :rtype: str """ 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): """ Return the raw encoding of the extension data - @rtype: bytearray + :rtype: bytearray """ if self.cert_type is None: return bytearray(0) @@ -524,8 +784,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 @@ -533,8 +792,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: @@ -547,22 +805,23 @@ 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) self.identity = None @@ -570,26 +829,16 @@ def __repr__(self): """ Return programmer-centric description of extension - @rtype: str + :rtype: str """ 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): """ Return raw data encoding of the extension - @rtype: bytearray + :rtype: bytearray """ if self.identity is None: @@ -604,11 +853,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: @@ -624,12 +873,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) @@ -640,22 +888,23 @@ 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) self.protocols = None @@ -663,23 +912,15 @@ def __repr__(self): """ Create programmer-readable version of representation - @rtype: str + :rtype: str """ 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 - @rtype: bytearray + :rtype: bytearray """ if self.protocols is None: return bytearray(0) @@ -694,8 +935,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 @@ -703,13 +943,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 = [] @@ -723,11 +962,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): @@ -749,7 +988,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}, "\ @@ -775,7 +1014,7 @@ def write(self): """ Convert the TACK into on the wire format - @rtype: bytearray + :rtype: bytearray """ w = Writer() if len(self.public_key) != 64: @@ -796,11 +1035,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 """ @@ -840,8 +1078,9 @@ 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) self.tacks = [] self.activation_flags = 0 @@ -850,26 +1089,17 @@ 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) - @property - def extType(self): - """ - Returns the type of TLS extension, in this case - 62208 - - @rtype: int - """ - return ExtensionType.tack - @property def extData(self): """ Return the raw data encoding of the extension - @rtype: bytearray + :rtype: bytearray """ w2 = Writer() for t in self.tacks: @@ -883,9 +1113,9 @@ def extData(self): def create(self, tacks, activation_flags): """ - Initialize the insance of TACKExtension + Initialize the instance of TACKExtension - @rtype: TACKExtension + :rtype: TACKExtension """ self.tacks = tacks @@ -896,10 +1126,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 = [] @@ -912,12 +1141,634 @@ def parse(self, p): return self -TLSExtension._universalExtensions = { - ExtensionType.server_name : SNIExtension, - ExtensionType.cert_type : ClientCertTypeExtension, - ExtensionType.srp : SRPExtension, - ExtensionType.supports_npn : NPNExtension} +class SupportedGroupsExtension(VarListExtension): + """ + Client side list of supported groups of (EC)DHE key exchage. + + See RFC4492, RFC7027 and RFC-ietf-tls-negotiated-ff-dhe-10 + + :vartype groups: int + :ivar groups: list of groups that the client supports + """ + + def __init__(self): + """Create instance of class""" + super(SupportedGroupsExtension, self).__init__(2, 2, 'groups', \ + ExtensionType.supported_groups) + +class ECPointFormatsExtension(VarListExtension): + """ + Client side list of supported ECC point formats. + + See RFC4492. + + :vartype formats: list of int + :ivar formats: list of point formats supported by peer + """ + + def __init__(self): + """Create instance of class""" + super(ECPointFormatsExtension, self).__init__(1, 1, 'formats', \ + ExtensionType.ec_point_formats) + +class SignatureAlgorithmsExtension(VarSeqListExtension): + """ + 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""" + super(SignatureAlgorithmsExtension, self).__init__(1, 2, 2, + 'sigalgs', + extType= + ExtensionType. + signature_algorithms) + + 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()) + + +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.""" + extType = ExtensionType.client_hello_padding + super(PaddingExtension, self).__init__(extType=extType) + self.paddingData = bytearray(0) + + @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. + + :param int size: required padding size in bytes + """ + self.paddingData = bytearray(size) + return self + + def parse(self, p): + """ + Deserialise extension from on the wire data. + + :param Parser p: data to be parsed + + :raises SyntaxError: when the size of the passed element doesn't match + the internal representation + + :rtype: TLSExtension + """ + 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. + + :param bytearray renegotiated_connection: data + """ + self.renegotiated_connection = renegotiated_connection + return self + + def parse(self, parser): + """ + Deserialise extension from on the wire data. + + :param Parser parser: data to be parsed + + :rtype: RenegotiationInfoExtension + """ + if parser.getRemainingLength() == 0: + self.renegotiated_connection = None + else: + self.renegotiated_connection = parser.getVarBytes(1) + + return self + + +class ALPNExtension(TLSExtension): + """ + Handling of Application Layer Protocol Negotiation extension from RFC 7301. + + :vartype protocol_names: list of bytearrays + :ivar protocol_names: list of protocol names acceptable or selected by peer + + :vartype extType: int + :ivar extType: numberic type of ALPNExtension, i.e. 16 + + :vartype extData: bytearray + :ivar extData: raw encoding of the extension data + """ + + def __init__(self): + """ + Create instance of ALPNExtension + + See also: :py:meth:`create` and :py:meth:`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 + + :param list 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 + + :param Parser parser: data to be parsed as extension + + :raises SyntaxError: when the encoding of the extension is self + inconsistent + + :rtype: 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 + + +class StatusRequestExtension(TLSExtension): + """ + Handling of the Certificate Status Request extension from RFC 6066. + + :vartype status_type: int + :ivar status_type: type of the status request + + :vartype responder_id_list: list of bytearray + :ivar responder_id_list: list of DER encoded OCSP responder identifiers + that the client trusts + + :vartype 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. + + :param int status_type: type of status returned + + :param list responder_id_list: list of encoded OCSP responder + identifiers + that the client trusts + + :param bytearray 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. + + :param Parser parser: data to be parsed as extension + + :rtype: 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 + + +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.""" + + 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 + + +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 + + +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._serverExtensions = { - ExtensionType.cert_type : ServerCertTypeExtension, - ExtensionType.tack : TACKExtension} +TLSExtension._universalExtensions = \ + { + ExtensionType.server_name: SNIExtension, + ExtensionType.status_request: StatusRequestExtension, + ExtensionType.cert_type: ClientCertTypeExtension, + ExtensionType.supported_groups: SupportedGroupsExtension, + 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, + ExtensionType.supported_versions: SupportedVersionsExtension, + ExtensionType.key_share: ClientKeyShareExtension} + +TLSExtension._serverExtensions = \ + { + ExtensionType.cert_type: ServerCertTypeExtension, + ExtensionType.tack: TACKExtension, + ExtensionType.key_share: ServerKeyShareExtension} + +TLSExtension._certificateExtensions = \ + { + ExtensionType.status_request: CertificateStatusExtension} + +TLSExtension._hrrExtensions = \ + { + ExtensionType.key_share: HRRKeyShareExtension} diff --git a/tlslite/handshakehashes.py b/tlslite/handshakehashes.py new file mode 100644 index 00000000..55d88fcf --- /dev/null +++ b/tlslite/handshakehashes.py @@ -0,0 +1,110 @@ +# 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 +from .utils import tlshashlib as 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._handshakeSHA224 = hashlib.sha224() + self._handshakeSHA256 = hashlib.sha256() + self._handshakeSHA384 = hashlib.sha384() + self._handshakeSHA512 = hashlib.sha512() + + def update(self, data): + """ + Add `data` to hash input. + + :param bytearray data: serialized TLS handshake message + """ + 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): + """ + Calculate and return digest for the already consumed data. + + Used for Finished and CertificateVerify messages. + + :param str 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 == '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") + + def digestSSL(self, masterSecret, label): + """ + Calculate and return digest for already consumed data (SSLv3 version) + + Used for Finished and CertificateVerify messages. + + :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() + 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._handshakeSHA224 = self._handshakeSHA224.copy() + other._handshakeSHA256 = self._handshakeSHA256.copy() + other._handshakeSHA384 = self._handshakeSHA384.copy() + other._handshakeSHA512 = self._handshakeSHA512.copy() + return other diff --git a/tlslite/handshakehelpers.py b/tlslite/handshakehelpers.py new file mode 100644 index 00000000..a13a9ea8 --- /dev/null +++ b/tlslite/handshakehelpers.py @@ -0,0 +1,38 @@ +# 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. + """ + + @staticmethod + def alignClientHelloPadding(clientHello): + """ + Align ClientHello using the Padding extension to 512 bytes at least. + + :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 + # 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/handshakesettings.py b/tlslite/handshakesettings.py index 921527cd..c5c9291d 100644 --- a/tlslite/handshakesettings.py +++ b/tlslite/handshakesettings.py @@ -7,138 +7,350 @@ """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 - -# 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 +from .utils.compat import ecdsaAllCurves, int_types + +CIPHER_NAMES = ["chacha20-poly1305", + "aes256gcm", "aes128gcm", + "aes256", "aes128", + "3des"] +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", + "ecdh_anon", "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"] +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 = ["x25519", "x448", "secp384r1", "secp256r1", + "secp521r1"] +ALL_CURVE_NAMES = CURVE_NAMES + ["secp256k1"] +if ecdsaAllCurves: + 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): - """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. + + 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. + + If these settings are used with a server handshake, the server will + choose whichever ciphersuite matches the earliest entry in this + list. + + .. 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 'rc4', 'null' and + 'chacha20-poly1305_draft00'. + + :vartype macNames: list + :ivar macNames: The allowed MAC algorithms. - 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 'sha384', 'sha256', 'aead', 'sha' + and 'md5'. - @type maxKeySize: int - @ivar maxKeySize: The maximum bit length for asymmetric keys. + The default value is list that excludes 'md5'. - 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 certificateTypes: list + :ivar certificateTypes: The allowed certificate types. - @type cipherNames: list - @ivar cipherNames: The allowed ciphers, in order of preference. + 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. - 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. - If these settings are used with a server handshake, the server will - choose whichever ciphersuite matches the earliest entry in this - list. + :vartype minVersion: tuple + :ivar minVersion: The minimum allowed SSL/TLS version. - 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. + 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). - The default value is ['rc4', 'aes256', 'aes128', '3des']. + :vartype maxVersion: tuple + :ivar maxVersion: The maximum allowed SSL/TLS version. - @type macNames: list - @ivar macNames: The allowed MAC algorithms. - - The allowed values in this list are 'sha' and 'md5'. - - The default value is ['sha']. + 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 or higher. In this case, try lowering maxVersion to + (3, 1). - @type certificateTypes: list - @ivar certificateTypes: The allowed certificate types, in order of - preference. + :vartype useExperimentalTackExtension: bool + :ivar useExperimentalTackExtension: Whether to enabled TACK support. - 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. + Note that TACK support is not standardized by IETF and uses a temporary + TLS Extension number, so should NOT be used in production software. + :vartype sendFallbackSCSV: bool + :ivar sendFallbackSCSV: Whether to, as a client, send FALLBACK_SCSV. - @type minVersion: tuple - @ivar minVersion: The minimum allowed SSL/TLS version. + :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. - 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). + The list is sorted from most wanted to least wanted algorithm. - @type maxVersion: tuple - @ivar maxVersion: The maximum allowed SSL/TLS version. + The allowed hashes are: "md5", "sha1", "sha224", "sha256", + "sha384" and "sha512". The default list does not include md5. - 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. + :vartype eccCurves: list + :ivar eccCurves: List of named curves that are to be supported - @type sendFallbackSCSV: bool - @ivar sendFallbackSCSV: Whether to, as a client, send FALLBACK_SCSV. + :vartype useEncryptThenMAC: bool + :ivar useEncryptThenMAC: whether to support the encrypt then MAC extension + from RFC 7366. True by default. + + :vartype useExtendedMasterSecret: bool + :ivar useExtendedMasterSecret: whether to support the extended master + secret calculation from RFC 7627. True 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. + + :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. + + :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). """ def __init__(self): self.minKeySize = 1023 self.maxKeySize = 8193 - self.cipherNames = CIPHER_NAMES - self.macNames = MAC_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, 4) + self.versions = [TLS_1_3_DRAFT, (3, 3), (3, 2), (3, 1)] self.useExperimentalTackExtension = False 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 + self.requireExtendedMasterSecret = False + self.dhParams = None + self.dhGroups = list(ALL_DH_GROUP_NAMES) + self.defaultCurve = "secp256r1" + self.keyShares = ["secp256r1", "x25519"] + self.padding_cb = None + + @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.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: + 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: + 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""" + if other.minVersion > other.maxVersion: + raise ValueError("Versions set incorrectly") + if other.minVersion not in KNOWN_VERSIONS: + raise ValueError("minVersion set incorrectly") + 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""" + 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.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") 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 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 other.maxVersion = self.maxVersion other.sendFallbackSCSV = self.sendFallbackSCSV - - if other.maxVersion < (3,3): - other.macNames = [e for e in self.macNames if e != "sha256"] + 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 + other.dhParams = self.dhParams + other.dhGroups = self.dhGroups + other.defaultCurve = self.defaultCurve + other.padding_cb = self.padding_cb + other.versions = self.versions + other.keyShares = self.keyShares 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: @@ -147,37 +359,29 @@ 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 CIPHER_NAMES: - raise ValueError("Unknown cipher 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) + self._sanityCheckKeySizes(other) - if other.minVersion > other.maxVersion: - raise ValueError("Versions set incorrectly") + self._sanityCheckPrimitivesNames(other) - if not other.minVersion in ((3,0), (3,1), (3,2), (3,3)): - raise ValueError("minVersion set incorrectly") + self._sanityCheckProtocolVersions(other) - if not other.maxVersion in ((3,0), (3,1), (3,2), (3,3)): - raise ValueError("maxVersion set incorrectly") + 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 \ + e == "sha" or e == "md5"] + + 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 diff --git a/tlslite/integration/asyncstatemachine.py b/tlslite/integration/asyncstatemachine.py index 50a6f4a3..c6d8694b 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): @@ -71,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 @@ -85,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 @@ -157,7 +162,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 +170,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 +186,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 @@ -189,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) @@ -206,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) @@ -225,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 62e59616..9155ac23 100644 --- a/tlslite/integration/clienthelper.py +++ b/tlslite/integration/clienthelper.py @@ -10,20 +10,23 @@ """ 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 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: + - username, password (SRP) - certChain, privateKey (certificate) @@ -31,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 @@ -41,33 +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. + + :param str password: SRP password for mutual authentication. + Requires the 'username' argument. - @type password: str - @param password: SRP password for mutual authentication. - Requires the 'username' argument. + :param X509CertChain 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. + :param RSAKey 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. + :param Checker checker: Callable object called after handshaking to + evaluate the connection and raise an Exception if necessary. - @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: 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: 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. + :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). """ self.username = None @@ -102,21 +113,51 @@ def __init__(self, self.tlsSession = None + 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 + + @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..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, @@ -104,11 +106,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) 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 new file mode 100644 index 00000000..040fff28 --- /dev/null +++ b/tlslite/keyexchange.py @@ -0,0 +1,812 @@ +# 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, paramStrength, RFC7919_GROUPS +from .errors import TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ + TLSIllegalParameterException, TLSDecryptionFailed, TLSInternalError, \ + TLSDecodeError +from .messages import ServerKeyExchange, ClientKeyExchange, CertificateVerify +from .constants import SignatureAlgorithm, HashAlgorithm, CipherSuite, \ + 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, 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, \ + X448_ORDER_SIZE +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=None): + """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 _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 = serverKeyExchange.hash(self.clientHello.random, + self.serverHello.random) + + serverKeyExchange.signature = \ + self.privateKey.sign(hashBytes, + padding=padType, + hashAlg=hashName, + saltLen=saltLen) + + if not serverKeyExchange.signature: + raise TLSInternalError("Empty signature") + + 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): + """ + 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 + """ + 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: + self._tls12_signSKE(serverKeyExchange, 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' + + hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) + + sigBytes = serverKeyExchange.signature + if not sigBytes: + raise TLSIllegalParameterException("Empty signature") + + if not publicKey.verify(sigBytes, hashBytes, + padding=padType, + hashAlg=hashName, + saltLen=saltLen): + raise TLSDecryptionFailed("Server Key Exchange signature " + "invalid") + + @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): + 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: + KeyExchange._tls12_verify_SKE(serverKeyExchange, publicKey, + clientRandom, serverRandom, + validSigAlgs) + + @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): + 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) + if padding == 'pkcs1': + 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 = getFirstMatching(validSigAlgs, serverSigAlgs) + # if none acceptable, do a last resort: + if signatureAlgorithm is None: + signatureAlgorithm = validSigAlgs[0] + verifyBytes = KeyExchange.calcVerifyBytes(version, handshakeHashes, + signatureAlgorithm, + premasterSecret, + clientRandom, + serverRandom) + 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) + + 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): + """ + 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 + + +class ADHKeyExchange(KeyExchange): + """ + Handling of anonymous Diffie-Hellman Key exchange + + FFDHE without signing serverKeyExchange useful for anonymous DH + """ + + def __init__(self, cipherSuite, clientHello, serverHello, + dhParams=None, dhGroups=None): + super(ADHKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello) +#pylint: enable = invalid-name + self.dh_Xs = None + self.dh_Yc = None + 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] + 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") + + # 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) + serverKeyExchange.createDH(self.dh_p, self.dh_g, dh_Ys) + # No sign for anonymous ServerKeyExchange. + return serverKeyExchange + + def processClientKeyExchange(self, clientKeyExchange): + """Use client provided parameters to establish premaster secret""" + dh_Yc = clientKeyExchange.dh_Yc + + 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.""" + 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_Ys = serverKeyExchange.dh_Ys + + kex = FFDHKeyExchange(None, self.serverHello.server_version, + dh_g, dh_p) + + 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""" + 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(AuthenticatedKeyExchange, ADHKeyExchange): + """ + Handling of authenticated ephemeral Diffe-Hellman Key exchange. + """ + + def __init__(self, cipherSuite, clientHello, serverHello, privateKey, + dhParams=None, dhGroups=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, dhParams, + dhGroups) +#pylint: enable = invalid-name + self.privateKey = privateKey + + +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, + 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( + ExtensionType.supported_groups) + if client_curves is None: + # 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 + + #Pick first client preferred group we support + self.group_id = getFirstMatching(client_curves, self.acceptedCurves) + if self.group_id is None: + raise TLSInsufficientSecurity("No mutual groups") + + 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) + serverKeyExchange.createECDH(ECCurveType.named_curve, + named_curve=self.group_id, + point=ecdhYs) + # No sign for anonymous ServerKeyExchange + return serverKeyExchange + + def processClientKeyExchange(self, clientKeyExchange): + """Calculate premaster secret from previously generated SKE and CKE""" + ecdhYc = clientKeyExchange.ecdh_Yc + + if not ecdhYc: + raise TLSDecodeError("No key share") + + 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""" + 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") + + ecdh_Ys = serverKeyExchange.ecdh_Ys + if not ecdh_Ys: + raise TLSDecodeError("Empty server key share") + + 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""" + 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(AuthenticatedKeyExchange, AECDHKeyExchange): + """Helper class for conducting ECDHE key exchange""" + + def __init__(self, cipherSuite, clientHello, serverHello, privateKey, + acceptedCurves, defaultCurve=GroupName.secp256r1): + super(ECDHE_RSAKeyExchange, self).__init__(cipherSuite, clientHello, + serverHello, + acceptedCurves, + defaultCurve) +#pylint: enable = invalid-name + self.privateKey = privateKey + + +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 + 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 = bytes(self.clientHello.srp_username) + #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, self.srpUsername, self.password) + 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 + + +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") + 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.""" + # 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") + if self.version < (3, 4): + return numberToByteArray(S) + else: + return numberToByteArray(S, numBytes(self.prime)) + + +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/tlslite/mathtls.py b/tlslite/mathtls.py index 60a331ab..6b88d982 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,17 +10,451 @@ from .utils.compat import * from .utils.cryptomath import * +from .constants import CipherSuite +from .utils import tlshashlib as hashlib 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])) + +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): + """ + 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) @@ -51,8 +486,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 +507,32 @@ def PRF_SSL(secret, seed, length): index += 1 return bytes -def calcMasterSecret(version, premasterSecret, clientRandom, serverRandom): +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""" if version == (3,0): masterSecret = PRF_SSL(premasterSecret, clientRandom + serverRandom, 48) @@ -75,12 +540,58 @@ 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 +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) + else: + if isClient: + label = b"client finished" + else: + label = b"server finished" + + 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: @@ -114,7 +625,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() @@ -125,6 +638,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 @@ -143,6 +657,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/messages.py b/tlslite/messages.py index 5ed24a5f..e023b669 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,38 +20,53 @@ 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): + """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 typeName(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: @@ -60,28 +75,98 @@ 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(object): + +class RecordHeader2(RecordHeader): + """ + SSLv2 record header. + + :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): - self.type = 0 - self.version = (0,0) - self.length = 0 - self.ssl2 = True + """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.""" + 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) - def parse(self, p): - if p.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) + 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 return self + def write(self): + """Serialise object to bytearray.""" + writer = Writer() + + shortHeader = not (self.padding or self.securityEscape) + + 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.""" + + 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): @@ -107,95 +192,177 @@ 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, - 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 __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) headerWriter.add(len(w.bytes), 3) return headerWriter.bytes + w.bytes -class ClientHello(HandshakeMsg): + +class HelloMessage(HandshakeMsg): + """ + Class for sharing code between :py:class:`ClientHello` and + :py:class:`ServerHello`. """ - Class for handling the ClientHello TLS message, supports both the SSLv2 - and SSLv3 style messages. - @type certificate_types: list - @ivar certificate_types: list of supported certificate types (deprecated) + 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. + + :rtype: ~tlslite.extensions.TLSExtension + :raises 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) + + 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) + - @type srp_username: bytearray - @ivar srp_username: name of the user in SRP extension (deprecated) +class ClientHello(HelloMessage): + """ + Class for handling the ClientHello SSLv2/SSLv3/TLS message. + + :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): - HandshakeMsg.__init__(self, HandshakeType.client_hello) + super(ClientHello, self).__init__(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 self.compression_methods = [] # a list of 8-bit values - self.extensions = None def __str__(self): """ - Return human readable representation of Client Hello + 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: + 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) @@ -204,54 +371,24 @@ def __str__(self): def __repr__(self): """ - Return machine readable representation of Client Hello + 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}, "\ - "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): - """ - Returns 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): - """ - Adds 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) + "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) @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 + .. deprecated:: 0.5 + use extensions field to get the extension for inspection """ cert_type = self.getExtension(ExtensionType.cert_type) if cert_type is None: @@ -264,12 +401,14 @@ def certificate_types(self): @certificate_types.setter def certificate_types(self, val): """ - Sets the list of supported types to list given in L{val} if the + Set list of supported certificate types. + + 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) @@ -283,9 +422,10 @@ 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 + .. deprecated:: 0.5 + use extensions field to get the extension for inspection """ srp_ext = self.getExtension(ExtensionType.srp) @@ -297,10 +437,10 @@ 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 + :type name: bytearray + :param name: UTF-8 encoded username """ srp_ext = self.getExtension(ExtensionType.srp) @@ -313,88 +453,58 @@ 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 - """ - tack_ext = self.getExtension(ExtensionType.tack) + .. deprecated:: 0.5 + use extensions field to get the extension for inspection - if tack_ext is None: - return False - else: - return True + :rtype: boolean + """ + return self.getExtension(ExtensionType.tack) is not None @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 + :type present: boolean + :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): """ - Returns whatever client supports NPN extension + Return whether client supports NPN extension. - @rtype: boolean - @deprecated: use extensions field to get the extension for inspection - """ - npn_ext = self.getExtension(ExtensionType.supports_npn) + .. deprecated:: 0.5 + use extensions field to get the extension for inspection - if npn_ext is None: - return False - else: - return True + :rtype: boolean + """ + return self.getExtension(ExtensionType.supports_npn) is not None @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 + :type present: boolean + :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): """ - Returns first host_name present in SNI extension + Return first host_name present in SNI extension. + + .. deprecated:: 0.5 + use extensions field to get the extension for inspection - @rtype: bytearray - @deprecated: use extensions field to get the extension for inspection + :rtype: bytearray """ sni_ext = self.getExtension(ExtensionType.server_name) if sni_ext is None: @@ -408,10 +518,10 @@ 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 + :type hostname: bytearray + :param hostname: name of the host_name to set """ sni_ext = self.getExtension(ExtensionType.server_name) if sni_ext is None: @@ -424,80 +534,88 @@ 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. - @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 - (before 1.2) the first 32 bits should include system time + :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 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: - self.srp_username = bytearray(srpUsername, "utf-8") + if srpUsername is not None: + if not isinstance(srpUsername, bytearray): + raise TypeError("srpUsername must be a bytearray object") + self.srp_username = srpUsername self.tack = tack - self.supports_npn = supports_npn - if not serverName is None: + if supports_npn is not None: + self.supports_npn = supports_npn + if serverName is not None: self.server_name = bytearray(serverName, "utf-8") 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)) @@ -508,22 +626,45 @@ 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 - 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) - 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) - if not self.extensions is None: + if self.extensions is not None: w2 = Writer() for ext in self.extensions: w2.bytes += ext.write() @@ -532,48 +673,57 @@ def write(self): w.bytes += w2.bytes return self.postWrite(w) -class ServerHello(HandshakeMsg): - """server_hello message + def write(self): + """Serialise object to on the wire data.""" + if self.ssl2: + return self._writeSSL2() + else: + return self._write() + + +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): - """Initialise ServerHello object""" - HandshakeMsg.__init__(self, HandshakeType.server_hello) - self.server_version = (0,0) + def __init__(self): + """Initialise ServerHello object.""" + 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(...),"\ @@ -591,47 +741,16 @@ 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, - 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 - is not present - - @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) + "_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) @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: @@ -642,19 +761,19 @@ 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 not val is None: + if val is not None: if self.extensions is None: self.extensions = [] @property def certificate_type(self): - """Returns the certificate type selected by server + """ + Return the certificate type selected by server. - @rtype: int + :rtype: int """ cert_type = self.getExtension(ExtensionType.cert_type) if cert_type is None: @@ -665,13 +784,15 @@ 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 + :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: + 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) @@ -683,9 +804,10 @@ 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 + :rtype: list of bytearrays """ npn_ext = self.getExtension(ExtensionType.supports_npn) @@ -696,16 +818,19 @@ 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 + :type val: list + :param val: list of protocols to advertise as UTF-8 encoded names """ 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 ] + # convinience function, make sure the values are properly encoded + val = [bytearray(x) for x in val] npn_ext = self.getExtension(ExtensionType.supports_npn) @@ -717,24 +842,28 @@ 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 + :rtype: list of bytearrays """ return self.next_protos @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 + :type val: list + :param val: list of protocols to advertise as UTF-8 encoded names """ 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 @@ -742,7 +871,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 @@ -750,9 +880,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) @@ -767,12 +903,14 @@ 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.addVarSeq(self.session_id, 1, 1) + w.bytes += self.random + 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 not self.extensions is None: + if self.extensions is not None: w2 = Writer() for ext in self.extensions: w2.bytes += ext.write() @@ -784,20 +922,201 @@ 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 Certificate(HandshakeMsg): + +class ServerHello2(HandshakeMsg): + """ + SERVER-HELLO message from SSLv2. + + :vartype session_id_hit: int + :ivar session_id_hit: non zero if the client provided session ID was + matched in server's session cache + + :vartype certificate_type: int + :ivar certificate_type: type of certificate sent + + :vartype server_version: tuple of ints + :ivar server_version: protocol version selected by server + + :vartype certificate: bytearray + :ivar certificate: certificate sent by server + + :vartype ciphers: array of int + :ivar ciphers: list of ciphers supported by server + + :vartype session_id: bytearray + :ivar session_id: idendifier of negotiated session + """ + + def __init__(self): + super(ServerHello2, self).__init__(SSL2HandshakeType.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 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 + + 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)): 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 - def create(self, certChain): - self.certChain = certChain + @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, 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 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) @@ -810,33 +1129,66 @@ 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 + # 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() 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) + + 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): @@ -855,102 +1207,267 @@ 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): - self.supported_signature_algs = \ - [(b >> 8, b & 0xff) for b in p.getVarList(2, 2)] + 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): - 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 + 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): - def __init__(self, cipherSuite): + """ + Handling TLS Handshake protocol Server Key Exchange messages. + + :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 + """ HandshakeMsg.__init__(self, HandshakeType.server_key_exchange) 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 + self.ecdh_Ys = bytearray(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 __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 + 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 parse(self, p): - p.startLengthCheck(3) + 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 :py:class:`Parser`. + + :type parser: Parser + :param parser: parser to read data from + """ + 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)) - if self.cipherSuite in CipherSuite.srpCertSuites: - self.signature = p.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.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_len = parser.get(2) + self.srp_B = bytesToNumber(parser.getFixBytes(self.srp_B_len)) + elif self.cipherSuite in CipherSuite.dhAllSuites: + 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 + assert self.curve_type == 3 + self.named_curve = parser.get(2) + self.ecdh_Ys = parser.getVarBytes(1) + 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, writeSig=True): - w = Writer() + def writeParams(self): + """ + Serialise the key exchange parameters. + + :rtype: bytearray + """ + 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) - if self.cipherSuite in CipherSuite.srpCertSuites and writeSig: - w.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) - if self.cipherSuite in [] and writeSig: # TODO support for signed_params - w.addVarSeq(self.signature, 1, 2) - return self.postWrite(w) + 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, self.srp_B_len), + 1, 2) + elif self.cipherSuite in CipherSuite.dhAllSuites: + 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 + writer.add(self.named_curve, 2) + writer.addVarSeq(self.ecdh_Ys, 1, 1) + else: + assert(False) + return writer.bytes + + def write(self): + """ + 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 + 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(False)[4:] - return MD5(bytes) + SHA1(bytes) + """ + Calculate hash of parameters to sign. + + :rtype: bytearray + """ + bytesToHash = clientRandom + serverRandom + self.writeParams() + if self.version >= (3, 3): + 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) + class ServerHelloDone(HandshakeMsg): def __init__(self): @@ -968,89 +1485,265 @@ def write(self): w = Writer() return self.postWrite(w) + def __repr__(self): + """Human readable representation of object.""" + return "ServerHelloDone()" + + class ClientKeyExchange(HandshakeMsg): + """ + Handling of TLS Handshake protocol ClientKeyExchange message. + + :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 + """ HandshakeMsg.__init__(self, HandshakeType.client_key_exchange) self.cipherSuite = cipherSuite 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): + """ + Set the SRP client answer. + + returns self + + :type srp_A: int + :param srp_A: client SRP answer + :rtype: ClientKeyExchange + """ self.srp_A = srp_A return self def createRSA(self, encryptedPreMasterSecret): + """ + Set the encrypted PreMaster Secret. + + returns self + + :type encryptedPreMasterSecret: bytearray + :rtype: ClientKeyExchange + """ self.encryptedPreMasterSecret = encryptedPreMasterSecret return self - + def createDH(self, dh_Yc): + """ + Set the client FFDH key share. + + returns self + + :type dh_Yc: int + :rtype: ClientKeyExchange + """ self.dh_Yc = dh_Yc return self - - def parse(self, p): - p.startLengthCheck(3) + + def createECDH(self, ecdh_Yc): + """ + Set the client ECDH key share. + + returns self + + :type ecdh_Yc: bytearray + :rtype: ClientKeyExchange + """ + self.ecdh_Yc = ecdh_Yc + return self + + def parse(self, parser): + """ + Deserialise the message from :py:class:`Parser`, + + returns self + + :type parser: Parser + :rtype: ClientKeyExchange + """ + 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) - elif self.version == (3,0): + if self.version in ((3, 1), (3, 2), (3, 3)): + 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)) + 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() - p.stopLengthCheck() + parser.stopLengthCheck() 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) 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): - w.addFixSeq(self.encryptedPreMasterSecret, 1) + elif self.version == (3, 0): + w.bytes += self.encryptedPreMasterSecret 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) + elif self.cipherSuite in CipherSuite.ecdhAllSuites: + w.addVarSeq(self.ecdh_Yc, 1, 1) else: raise AssertionError() return self.postWrite(w) + +class ClientMasterKey(HandshakeMsg): + """ + Handling of SSLv2 CLIENT-MASTER-KEY message. + + :vartype cipher: int + :ivar cipher: negotiated cipher + + :vartype clear_key: bytearray + :ivar clear_key: the part of master secret key that is sent in clear for + export cipher suites + + :vartype encrypted_key: bytearray + :ivar encrypted_key: (part of) master secret encrypted using server key + + :vartype 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.""" + 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.signatureAlgorithm = None self.signature = bytearray(0) - def create(self, signature_algorithm, signature): - self.signature_algorithm = signature_algorithm + 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) + """ + self.signatureAlgorithm = signatureAlgorithm 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.signatureAlgorithm = (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.signatureAlgorithm[0], 1) + writer.add(self.signatureAlgorithm[1], 1) + writer.addVarSeq(self.signature, 1, 2) + return self.postWrite(writer) + class ChangeCipherSpec(object): def __init__(self): @@ -1069,7 +1762,7 @@ def parse(self, p): def write(self): w = Writer() - w.add(self.type,1) + w.add(self.type, 1) return w.bytes @@ -1096,11 +1789,13 @@ def write(self, trial=False): w.addVarSeq(bytearray(paddingLen), 1, 1) return self.postWrite(w) + 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 @@ -1108,10 +1803,12 @@ 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) + elif self.version > (3, 3): + self.verify_data = p.getFixBytes(self.hash_length) else: raise AssertionError() p.stopLengthCheck() @@ -1119,9 +1816,251 @@ def parse(self, p): def write(self): w = Writer() - w.addFixSeq(self.verify_data, 1) + w.bytes += self.verify_data + return self.postWrite(w) + + +class EncryptedExtensions(HelloMessage): + """Handling of the TLS1.3 Encrypted Extensions message.""" + + def __init__(self): + super(EncryptedExtensions, self).__init__( + HandshakeType.encrypted_extensions) + + 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.""" + 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 NewSessionTicket(HelloMessage): + """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 HelloRetryRequest(HelloMessage): + """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.""" + + 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.bytes += self.verify_data + # does not use postWrite() as it's a SSLv2 message + return writer.bytes + + +class ClientFinished(SSL2Finished): + """ + Handling of SSLv2 CLIENT-FINISHED message. + + :vartype 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. + + :vartype 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 CertificateStatus(HandshakeMsg): + """ + Handling of the CertificateStatus message from RFC 6066. + + Handling of the handshake protocol message that includes the OCSP staple. + + :vartype status_type: int + :ivar status_type: type of response returned + + :vartype 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 @@ -1130,7 +2069,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:] diff --git a/tlslite/messagesocket.py b/tlslite/messagesocket.py new file mode 100644 index 00000000..84ae5171 --- /dev/null +++ b/tlslite/messagesocket.py @@ -0,0 +1,186 @@ +# 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. + + :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: 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) + + 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 + :py:class:`RecordHeader3` and :py:class:`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 :py:meth:`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 :py:meth:`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 :py:meth:`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 :py:meth:`sendMessage`.""" + for _ in self.sendMessage(msg): + pass diff --git a/tlslite/recordlayer.py b/tlslite/recordlayer.py new file mode 100644 index 00000000..c7a7df8b --- /dev/null +++ b/tlslite/recordlayer.py @@ -0,0 +1,1176 @@ +# 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 +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 +from .utils.cipherfactory import createAESGCM, createAES, createRC4, \ + createTripleDES, createCHACHA20 +from .utils.codec import Parser, Writer +from .utils.compat import compatHMAC +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, \ + TLSUnexpectedMessage +from .mathtls import createMAC_SSL, createHMAC, PRF_SSL, PRF, PRF_1_2, \ + PRF_1_2_SHA384 + +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 + :raises 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, 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 + :raises socket.error: when write to socket failed + """ + data = msg.write() + + 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 + + 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 + :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) + + 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 + + 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 + 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 + if buf[0] & 0x80: + readLen = 1 + else: + readLen = 2 + result = None + for result in self._sockRecvAll(readLen): + if result in (0, 1): + yield result + else: break + assert result is not None + buf += result + + + #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)) + + yield record + + def recv(self): + """ + Read a single record from socket, handle SSLv2 and SSLv3 record layer + + :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 + :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(): + 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 + # overhead) + 1024 (maximum encryption overhead) + if record.length > 18432: + raise TLSRecordOverflow() + + #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) + +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.fixedNonce = None + self.seqnum = 0 + self.encryptThenMAC = False + + 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: whether the connection should use encryption + :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 + + self._writeState = ConnectionState() + self._readState = ConnectionState() + self._pendingWriteState = ConnectionState() + self._pendingReadState = ConnectionState() + self.fixedIVBlock = None + + self.handshake_finished = False + + self.padding_cb = None + + @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)""" + 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""" + return self._version + + @version.setter + def version(self, val): + """Set the TLS version used by record layer""" + self._version = val + self._handle_tls13_record() + + def getCipherName(self): + """ + Return the name of the bulk cipher used by this connection + + :rtype: str + :returns: 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 + :returns: 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() + + 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 + # + + def addPadding(self, data): + """Add padding to data so that it is multiple of block size""" + currentLength = len(data) + blockLength = self.blockSize + paddingLength = blockLength - 1 - (currentLength % blockLength) + + paddingBytes = bytearray([paddingLength] * (paddingLength+1)) + 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, pad then encrypt data""" + if self._writeState.macContext: + 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: + #Add padding (for Block Cipher): + if self._writeState.encContext.isBlockCipher: + + #Add TLS 1.1 fixed block + if self.version >= (3, 2): + data = self.fixedIVBlock + data + + data = self.addPadding(data) + + #Encrypt + data = self._writeState.encContext.encrypt(data) + + 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 + + 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._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, + state.fixedNonce)) + else: + nonce = state.fixedNonce + seqnum + return nonce + + + def _encryptThenSeal(self, buf, contentType): + """Encrypt with AEAD cipher""" + #Assemble the authenticated data. + seqNumBytes = self._writeState.getSeqNumBytes() + if not self._is_tls13_plus(): + 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) + + assert len(nonce) == self._writeState.encContext.nonceLength + + buf = self._writeState.encContext.seal(nonce, buf, authData) + + #AES-GCM, has an explicit variable nonce. + if "aes" in self._writeState.encContext.name and \ + not self._is_tls13_plus(): + buf = seqNumBytes + buf + + 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. + + 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. + """ + data = msg.write() + contentType = msg.contentType + + # TLS 1.3 hides the content type of messages + if self._is_tls13_plus() and self._writeState.encContext: + 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 + + padding = 0 + if self.version in ((0, 2), (2, 0)): + data, padding = self._ssl2Encrypt(data) + elif self._writeState.encContext and \ + self._writeState.encContext.isAEAD: + data = self._encryptThenSeal(data, contentType) + elif self._writeState.encryptThenMAC: + data = self._encryptThenMAC(data, contentType) + else: + data = self._macThenEncrypt(data, contentType) + + encryptedMessage = Message(contentType, data) + + for result in self._recordSocket.send(encryptedMessage, padding): + yield result + + # + # 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 not ct_compare_digest(macBytes, checkBytes): + macGood = False + + if not macGood: + raise TLSBadRecordMAC() + + return data + + + 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)) + 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() + 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 and MAC + # + seqnumBytes = self._readState.getSeqNumBytes() + + if not ct_check_cbc_mac_and_pad(data, + self._readState.macContext, + seqnumBytes, + recordType, + self.version): + raise TLSBadRecordMAC() + + # + # strip padding and MAC + # + + endLength = data[-1] + 1 + self._readState.macContext.digest_size + + data = data[:-endLength] + + return data + + def _macThenDecrypt(self, recordType, buf): + """ + Check MAC of data, then decrypt and remove padding + + :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 + 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 not ct_compare_digest(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:] + + if len(buf) == 0: + raise TLSBadRecordMAC("No data left after IV removal") + + # 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 _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 \ + not self._is_tls13_plus(): + explicitNonceLength = 8 + if explicitNonceLength > len(buf): + #Publicly invalid. + raise TLSBadRecordMAC("Truncated nonce") + nonce = self._readState.fixedNonce + buf[:explicitNonceLength] + buf = buf[8:] + else: + nonce = self._getNonce(self._readState, seqnumBytes) + + if self._readState.encContext.tagLength > len(buf): + #Publicly invalid. + raise TLSBadRecordMAC("Truncated tag") + + if not self._is_tls13_plus(): + 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: + 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 + + @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 + + :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(): + if result in (0, 1): + yield result + else: break + assert result is not None + + (header, data) = result + + if isinstance(header, RecordHeader2): + 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) + elif self._readState and self._readState.encryptThenMAC: + data = self._macThenDecrypt(header.type, data) + 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) + + # TLS 1.3 encrypts the type + if self._is_tls13_plus(): + 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() + + yield (header, Parser(data)) + + # + # 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 + :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 + # context + self._pendingWriteState.seqnum = self._writeState.seqnum + 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 + :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 + # context + self._pendingReadState.seqnum = self._readState.seqnum + self._readState = self._pendingReadState + self._pendingReadState = ConnectionState() + + @staticmethod + def _getCipherSettings(cipherSuite): + """Get the settings for cipher suite used""" + if cipherSuite in CipherSuite.aes256GcmSuites: + keyLength = 32 + ivLength = 4 + createCipherFunc = createAESGCM + elif cipherSuite in CipherSuite.aes128GcmSuites: + 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 + createCipherFunc = createCHACHA20 + elif 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 + elif cipherSuite in CipherSuite.nullSuites: + keyLength = 0 + ivLength = 0 + createCipherFunc = None + 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 + elif cipherSuite in CipherSuite.shaSuites: + macLength = 20 + digestmod = hashlib.sha1 + 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 + else: + raise AssertionError() + + 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 version in ((3, 1), (3, 2), (3, 3)): + createMACFunc = createHMAC + + return createMACFunc + + 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, + outputLength) + elif self.version in ((3, 1), (3, 2)): + keyBlock = PRF(masterSecret, + b"key expansion", + serverRandom + clientRandom, + outputLength) + elif self.version == (3, 3): + 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() + + 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.""" + 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() + 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) + + if digestmod: + # Legacy cipher + clientPendingState.macContext = createMACFunc( + compatHMAC(clientMACBlock), digestmod=digestmod) + serverPendingState.macContext = createMACFunc( + compatHMAC(serverMACBlock), digestmod=digestmod) + if createCipherFunc is not None: + 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: + 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: + #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) + + 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/tlslite/session.py b/tlslite/session.py index 6aadf58e..aaacabf9 100644 --- a/tlslite/session.py +++ b/tlslite/session.py @@ -25,23 +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. + + :vartype encryptThenMAC: bool + :ivar encryptThenMAC: True if connection uses CBC cipher in + encrypt-then-MAC mode + + :vartype appProto: bytearray + :ivar appProto: name of the negotiated application level protocol, None + if not negotiated """ def __init__(self): @@ -55,10 +63,15 @@ def __init__(self): self.tackInHelloExt = False self.serverName = "" 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): + srpUsername, clientCertChain, serverCertChain, + tackExt, tackInHelloExt, serverName, resumable=True, + encryptThenMAC=False, extendedMasterSecret=False, + appProto=bytearray(0)): self.masterSecret = masterSecret self.sessionID = sessionID self.cipherSuite = cipherSuite @@ -69,6 +82,9 @@ def create(self, masterSecret, sessionID, cipherSuite, self.tackInHelloExt = tackInHelloExt self.serverName = serverName self.resumable = resumable + self.encryptThenMAC = encryptThenMAC + self.extendedMasterSecret = extendedMasterSecret + self.appProto = appProto def _clone(self): other = Session() @@ -82,13 +98,16 @@ def _clone(self): other.tackInHelloExt = self.tackInHelloExt other.serverName = self.serverName other.resumable = self.resumable + other.encryptThenMAC = self.encryptThenMAC + other.extendedMasterSecret = self.extendedMasterSecret + other.appProto = self.appProto return other 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 @@ -112,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 b19a3aa3..61b0f23d 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 @@ -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. @@ -13,24 +15,31 @@ MAIN CLASS FOR TLS LITE (START HERE!). """ +from __future__ import division import socket +from itertools import chain from .utils.compat import formatExceptionTrace from .tlsrecordlayer import TLSRecordLayer from .session import Session 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 * from .handshakesettings import HandshakeSettings +from .handshakehashes import HandshakeHashes from .utils.tackwrapper import * -from .utils.rsakey import RSAKey - +from .utils.deprecations import deprecated_params +from .keyexchange import KeyExchange, RSAKeyExchange, DHE_RSAKeyExchange, \ + ECDHE_RSAKeyExchange, SRPKeyExchange, ADHKeyExchange, \ + AECDHKeyExchange, FFDHKeyExchange, ECDHKeyExchange +from .handshakehelpers import HandshakeHelpers 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. @@ -48,32 +57,72 @@ 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 + self.ecdhCurve = None + self.dhGroupSize = None + 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 + + :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 #********************************************************* - def handshakeClientAnonymous(self, session=None, settings=None, - checker=None, serverName="", - async=False): + @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): """Perform an anonymous handshake in the role of client. 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. @@ -84,57 +133,60 @@ 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, 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="", - async=False): + settings=None, checker=None, + reqTack=True, serverName=None, + async_=False): """Perform an SRP handshake in the role of client. This function performs a TLS/SRP handshake. SRP mutually @@ -150,54 +202,59 @@ 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 - @param username: The SRP username. - - @type password: str - @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): + 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) @@ -206,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="", - async=False): + nextProtos=None, reqTack=True, serverName=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 @@ -239,79 +299,89 @@ 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. - - @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), 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, + 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 # 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 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, + alpn=None): handshaker = self._handshakeClientAsyncHelper(srpParams=srpParams, certParams=certParams, @@ -320,14 +390,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 @@ -366,12 +438,19 @@ 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") + # 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)) + # Validates the settings and filters out any unsupported ciphers # or crypto libraries that were requested if not settings: settings = HandshakeSettings() settings = settings.validate() + self.sock.padding_cb = settings.padding_cb if clientCertChain: if not isinstance(clientCertChain, X509CertChain): @@ -393,15 +472,17 @@ 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 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 - #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 - # OK Start sending messages! # ***************************** @@ -409,7 +490,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 @@ -420,11 +501,35 @@ 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) + # Check if server selected encrypt-then-MAC + 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, @@ -434,30 +539,37 @@ 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 + # 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 #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 - (premasterSecret, serverCertChain, tackExt) = result + keyExchange = SRPKeyExchange(cipherSuite, clientHello, + serverHello, None, None, + srpUsername=srpUsername, + password=password, + settings=settings) #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, - clientHello.random, serverHello.random): - if result in (0,1): yield result - else: break - (premasterSecret, serverCertChain, tackExt) = result - + elif cipherSuite in CipherSuite.dhAllSuites: + 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 @@ -465,18 +577,28 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, #and also produces a CertificateVerify message that signs the #ClientKeyExchange. else: - for result in self._clientRSAKeyExchange(settings, cipherSuite, - clientCertChain, privateKey, - serverHello.certificate_type, - clientHello.random, serverHello.random, - serverHello.tackExt): - if result in (0,1): yield result - else: break - (premasterSecret, serverCertChain, clientCertChain, - tackExt) = result - + 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, + 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. + # socket buffering is turned off in _clientFinished for result in self._clientFinished(premasterSecret, clientHello.random, serverHello.random, @@ -485,35 +607,44 @@ def _handshakeClientAsyncHelper(self, srpParams, certParams, anonParams, if result in (0,1): yield result 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, - srpUsername, clientCertChain, serverCertChain, - tackExt, serverHello.tackExt!=None, serverName) + srpUsername, clientCertChain, serverCertChain, + tackExt, (serverHello.tackExt is not None), + serverName, + encryptThenMAC=self._recordLayer.encryptThenMAC, + 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: cipherSuites += CipherSuite.getSrpAllSuites(settings) elif certParams: + cipherSuites += CipherSuite.getTLS13Suites(settings) + cipherSuites += CipherSuite.getEcdheCertSuites(settings) + cipherSuites += CipherSuite.getDheCertSuites(settings) cipherSuites += CipherSuite.getCertSuites(settings) elif anonParams: + cipherSuites += CipherSuite.getEcdhAnonSuites(settings) cipherSuites += CipherSuite.getAnonSuites(settings) else: - assert(False) - - #Don't advertise ciphers that aren't enabled in any versions in the - #supported range. - if self.fault != Fault.ignoreVersionForCipher: - cipherSuites = CipherSuite.filterForVersion(cipherSuites, - minVersion=settings.minVersion, - maxVersion=settings.maxVersion) + assert False #Add any SCSVs. These are not real cipher suites, but signaling #values which reuse the cipher suite field in the ClientHello. @@ -523,7 +654,63 @@ def _clientSendClientHello(self, settings, session, srpUsername, #Initialize acceptable certificate types certificateTypes = settings.getCertificateTypes() - + + extensions = [] + + #Initialize TLS extensions + if settings.useEncryptThenMAC: + extensions.append(TLSExtension().\ + create(ExtensionType.encrypt_then_mac, + bytearray(0))) + if settings.useExtendedMasterSecret: + 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: + 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) + 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)) + + # 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) + key_share = self._genKeyShareEntry(group_id, (3, 4)) + + shares.append(key_share) + # 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): + 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 @@ -533,59 +720,159 @@ 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, reqTack, nextProtos is not None, - session.serverName) + session.serverName, + extensions=extensions) #Or send ClientHello (without) else: clientHello = ClientHello() - clientHello.create(settings.maxVersion, getRandomBytes(32), + clientHello.create(sent_version, getRandomBytes(32), bytearray(0), wireCipherSuites, certificateTypes, srpUsername, reqTack, nextProtos is not None, - serverName) + 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 - def _clientGetServerHello(self, settings, clientHello): + 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 #error alerts will use the server's version self.version = serverHello.server_version - - #Future responses from server must use this version - self._versionCheck = True + # 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 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, "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)): yield result - #Re-evaluate supported ciphers against the final protocol version. - if self.fault != Fault.ignoreVersionForCipher: - cipherSuites = CipherSuite.filterForVersion(clientHello.cipher_suites, - minVersion=self.version, - maxVersion=self.version) - else: - cipherSuites = 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, @@ -596,7 +883,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"): @@ -617,8 +904,241 @@ 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 + 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 + @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 + # 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.") + kex = self._getKEX(srKex.group, self.version) + + Z = kex.calc_shared_key(cl_kex.private, srKex.key_exchange) + + prfName, prf_size = self._getPRFParams(serverHello.cipher_suite) + + 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 @@ -657,292 +1177,236 @@ 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): + # 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 - yield "resumed_and_finished" - - def _clientSRPKeyExchange(self, settings, cipherSuite, certificateType, - srpUsername, password, - clientRandom, serverRandom, tackExt): - - #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 + # 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_key_exchange, + cipherSuite): + if result in (0, 1): + yield result + else: break + serverKeyExchange = result + else: + serverKeyExchange = 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 - - #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"): + (HandshakeType.certificate_request, + HandshakeType.server_hello_done)): + if result in (0, 1): yield result + else: break - #Check the server's signature, if server chose an - #SRP+RSA suite - serverCertChain = None - if cipherSuite in CipherSuite.srpCertSuites: - #Hash ServerKeyExchange/ServerSRPParams - hashBytes = serverKeyExchange.hash(clientRandom, serverRandom) + certificateRequest = None + if isinstance(result, CertificateRequest): + certificateRequest = result - #Extract signature bytes from ServerKeyExchange - sigBytes = serverKeyExchange.signature - if len(sigBytes) == 0: + #abort if Certificate Request with inappropriate ciphersuite + if cipherSuite not in CipherSuite.certAllSuites \ + or cipherSuite in CipherSuite.srpAllSuites: for result in self._sendError(\ - AlertDescription.illegal_parameter, - "Server sent an SRP ServerKeyExchange "\ - "message without a signature"): + AlertDescription.unexpected_message, + "Certificate Request with incompatible cipher suite"): 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 - for result in self._clientGetKeyFromChain(serverCertificate, - settings, tackExt): - if result in (0,1): yield 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 - publicKey, serverCertChain, tackExt = result + serverHelloDone = result - #Verify signature - if not publicKey.verify(sigBytes, hashBytes): - for result in self._sendError(\ - AlertDescription.decrypt_error, - "Signature failed to verify"): + serverCertChain = None + 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 - - #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) - - #Send ClientKeyExchange - for result in self._sendMsg(\ - ClientKeyExchange(cipherSuite).createSRP(A)): - yield result - yield (premasterSecret, serverCertChain, tackExt) - - - def _clientRSAKeyExchange(self, settings, cipherSuite, - clientCertChain, privateKey, - certificateType, - clientRandom, serverRandom, - tackExt): - - #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 - 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 + publicKey, serverCertChain, tackExt = result - #Calculate premaster secret - premasterSecret = getRandomBytes(48) - premasterSecret[0] = settings.maxVersion[0] - premasterSecret[1] = settings.maxVersion[1] + #Check the server's signature, if the server chose an authenticated + # PFS-enabled ciphersuite + if serverKeyExchange: + validSigAlgs = self._sigHashesToList(settings, + certList=serverCertChain) + 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 - if self.fault == Fault.badPremasterPadding: - premasterSecret[0] = 5 - if self.fault == Fault.shortPremasterSecret: - premasterSecret = premasterSecret[:-1] + 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) - #Encrypt premaster secret to server's public key - encryptedPreMasterSecret = publicKey.encrypt(premasterSecret) + if cipherSuite in CipherSuite.dhAllSuites: + self.dhGroupSize = numBits(serverKeyExchange.dh_p) + if cipherSuite in CipherSuite.ecdhAllSuites: + self.ecdhCurve = serverKeyExchange.named_curve - #If client authentication was requested, send Certificate - #message, either with certificates or empty + #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 not [sig for sig in \ + certificateRequest.supported_signature_algs\ + if sig[1] == SignatureAlgorithm.rsa]: + 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: + if certificateType == CertificateType.x509 \ + and not isinstance(clientCertChain, X509CertChain): 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: - #The server didn't request client auth, so we - #zeroize these so the clientCertChain won't be - #stored in the session. + #Server didn't ask for cer, zeroise so session doesn't store them privateKey = None clientCertChain = None + try: + ske = serverKeyExchange + premasterSecret = keyExchange.processServerKeyExchange(publicKey, + 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 + + clientKeyExchange = keyExchange.makeClientKeyExchange() + #Send ClientKeyExchange - clientKeyExchange = ClientKeyExchange(cipherSuite, - self.version) - clientKeyExchange.createRSA(encryptedPreMasterSecret) for result in self._sendMsg(clientKeyExchange): yield result - #If client authentication was requested and we have a - #private key, send CertificateVerify + # 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: - 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() - elif 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(signatureAlgorithm, signedBytes) + validSigAlgs = self._sigHashesToList(settings, privateKey, + clientCertChain) + try: + certificateVerify = KeyExchange.makeCertificateVerify( + self.version, + self._certificate_verify_handshake_hash, + validSigAlgs, + privateKey, + certificateRequest, + 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 - yield (premasterSecret, serverCertChain, clientCertChain, tackExt) - def _clientAnonKeyExchange(self, settings, cipherSuite, clientRandom, - serverRandom): - for result in self._getMsg(ContentType.handshake, - HandshakeType.server_key_exchange, cipherSuite): - if result in (0,1): yield result - else: break - serverKeyExchange = result + yield (premasterSecret, serverCertChain, clientCertChain, tackExt) - for result in self._getMsg(ContentType.handshake, - HandshakeType.server_hello_done): - if result in (0,1): yield result - else: break - serverHelloDone = result - - #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) - def _clientFinished(self, premasterSecret, clientRandom, serverRandom, cipherSuite, cipherImplementations, nextProto): - - masterSecret = calcMasterSecret(self.version, premasterSecret, - clientRandom, serverRandom) + 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, + cvhh) + else: + 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): + self.sock.flush() + self.sock.buffer_writes = False + for result in self._getFinished(masterSecret, + cipherSuite, + nextProto=nextProto): yield result yield masterSecret @@ -994,7 +1458,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, sni=None): """Perform a handshake in the role of server. This function performs an SSL or TLS handshake. Depending on @@ -1017,64 +1481,72 @@ 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. - - @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, - nextProtos=nextProtos, anon=anon): + checker, reqCAs, + tacks=tacks, activationFlags=activationFlags, + nextProtos=nextProtos, anon=anon, alpn=alpn, sni=sni): pass @@ -1083,7 +1555,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, sni=None ): """Start a server handshake operation on the TLS connection. @@ -1093,8 +1565,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, @@ -1102,7 +1574,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, sni=sni) for result in self._handshakeWrapperAsync(handshaker, checker): yield result @@ -1111,7 +1583,7 @@ def _handshakeServerAsyncHelper(self, verifierDB, certChain, privateKey, reqCert, sessionCache, settings, reqCAs, tacks, activationFlags, - nextProtos, anon): + nextProtos, anon, alpn, sni): self._handshakeStart(client=False) @@ -1132,25 +1604,44 @@ 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() settings = settings.validate() - + self.sock.padding_cb = settings.padding_cb + # OK Start exchanging messages # ****************************** # 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, sni): if result in (0,1): yield result elif result == None: 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... # Create the ServerHello message @@ -1162,6 +1653,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 @@ -1171,25 +1667,120 @@ def _handshakeServerAsyncHelper(self, verifierDB, tackExt = TackExtension.create(tacks, activationFlags) 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.append(TLSExtension().create(ExtensionType. + encrypt_then_mac, + bytearray(0))) + self._recordLayer.encryptThenMAC = 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 + + 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 + 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))) + + # 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 + 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 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 - # 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 or + cipherSuite in CipherSuite.ecdheCertSuites): + if cipherSuite in CipherSuite.certSuites: + keyExchange = RSAKeyExchange(cipherSuite, + clientHello, + serverHello, + privateKey) + elif cipherSuite in CipherSuite.dheCertSuites: + dhGroups = self._groupNamesToList(settings) + keyExchange = DHE_RSAKeyExchange(cipherSuite, + clientHello, + serverHello, + privateKey, + settings.dhParams, + dhGroups) + elif cipherSuite in CipherSuite.ecdheCertSuites: + acceptedCurves = self._curveNamesToList(settings) + defaultCurve = getattr(GroupName, settings.defaultCurve) + keyExchange = ECDHE_RSAKeyExchange(cipherSuite, + clientHello, + serverHello, + privateKey, + acceptedCurves, + defaultCurve) + 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 @@ -1197,13 +1788,25 @@ def _handshakeServerAsyncHelper(self, verifierDB, (premasterSecret, clientCertChain) = result # Perform anonymous Diffie Hellman key exchange - elif cipherSuite in CipherSuite.anonSuites: - for result in self._serverAnonKeyExchange(clientHello, serverHello, - cipherSuite, settings): + elif (cipherSuite in CipherSuite.anonSuites or + cipherSuite in CipherSuite.ecdhAnonSuites): + if cipherSuite in CipherSuite.anonSuites: + dhGroups = self._groupNamesToList(settings) + keyExchange = ADHKeyExchange(cipherSuite, clientHello, + serverHello, settings.dhParams, + dhGroups) + else: + acceptedCurves = self._curveNamesToList(settings) + defaultCurve = getattr(GroupName, settings.defaultCurve) + keyExchange = AECDHKeyExchange(cipherSuite, clientHello, + serverHello, acceptedCurves, + defaultCurve) + for result in self._serverAnonKeyExchange(serverHello, keyExchange, + cipherSuite): if result in (0,1): yield result else: break premasterSecret = result - + else: assert(False) @@ -1229,36 +1832,207 @@ 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) + srpUsername, clientCertChain, serverCertChain, + tackExt, (serverHello.tackExt is not None), + serverName, + encryptThenMAC=self._recordLayer.encryptThenMAC, + extendedMasterSecret=self.extendedMasterSecret, + appProto=selectedALPN) #Add the session object to the session cache if sessionCache and sessionID: sessionCache[sessionID] = self.session self._handshakeDone(resumed=False) + 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) - 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.getCertSuites(settings) - elif anon: - cipherSuites += CipherSuite.getAnonSuites(settings) - else: - assert(False) + 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 - #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 + 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 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, @@ -1275,13 +2049,116 @@ def _serverGetClientHello(self, settings, certChain, verifierDB, "Too old version: %s" % str(clientHello.client_version)): yield result - #If client's version is too high, propose my highest version - elif clientHello.client_version > settings.maxVersion: - self.version = settings.maxVersion + # 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 + # 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 + + # 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: + 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.decode_error, + "Client sent empty name in ALPN extension"): + yield result + + # 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: + for result in self._sendError( + 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: + 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 + # 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 + + # 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 + + 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: + # 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 \ @@ -1290,13 +2167,63 @@ 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. - if self.fault != Fault.ignoreVersionForCipher: - cipherSuites = CipherSuite.filterForVersion(cipherSuites, - minVersion=self.version, - maxVersion=self.version) + #Check if there's intersection between supported curves by client and + #server + 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 + 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 + 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 ffGroupIntersect: + if clientGroups and \ + any(i for i in clientGroups if i in range(256, 512)): + ffGroupIntersect = False + 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, version) + cipherSuites += CipherSuite.getSrpSuites(settings, version) + elif certChain: + if ecGroupIntersect or ffGroupIntersect: + cipherSuites += CipherSuite.getTLS13Suites(settings, + version) + if ecGroupIntersect: + cipherSuites += CipherSuite.getEcdheCertSuites(settings, + version) + if ffGroupIntersect: + cipherSuites += CipherSuite.getDheCertSuites(settings, + version) + cipherSuites += CipherSuite.getCertSuites(settings, version) + elif anon: + cipherSuites += CipherSuite.getAnonSuites(settings, version) + cipherSuites += CipherSuite.getEcdhAnonSuites(settings, + version) + else: + assert(False) + cipherSuites = CipherSuite.filterForVersion(cipherSuites, + minVersion=version, + maxVersion=version) #If resumption was requested and we have a session cache... if clientHello.session_id and sessionCache: session = None @@ -1328,22 +2255,85 @@ 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 + # 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 #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.append(mte) + if session.extendedMasterSecret: + ems = TLSExtension().create(ExtensionType. + 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))) + 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 serverHello = ServerHello() - serverHello.create(self.version, getRandomBytes(32), + serverHello.create(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 here on, the client's messages must have right version - self._versionCheck = True - #Calculate pending connection states self._calcPendingStates(session.cipherSuite, session.masterSecret, @@ -1352,14 +2342,18 @@ 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 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. @@ -1374,17 +2368,27 @@ 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 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( + 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(\ 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: @@ -1396,34 +2400,34 @@ 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, + settings): + """Perform the server side of SRP key exchange""" + keyExchange = SRPKeyExchange(cipherSuite, + clientHello, + serverHello, + privateKey, + verifierDB) - def _serverSRPKeyExchange(self, clientHello, serverHello, verifierDB, - cipherSuite, privateKey, serverCertChain): + try: + sigHash = self._pickServerKeyExchangeSig(settings, clientHello, + serverCertChain) + except TLSHandshakeFailure as alert: + for result in self._sendError( + AlertDescription.handshake_failure, + str(alert)): + yield result - srpUsername = clientHello.srp_username.decode("utf-8") - self.allegedSrpUsername = srpUsername - #Get parameters from username + #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) - serverKeyExchange.createSRP(N, g, s, B) - if cipherSuite in CipherSuite.srpCertSuites: - hashBytes = serverKeyExchange.hash(clientHello.random, - serverHello.random) - serverKeyExchange.signature = privateKey.sign(hashBytes) #Send ServerHello[, Certificate], ServerKeyExchange, #ServerHelloDone @@ -1438,39 +2442,31 @@ 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, 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 + except TLSDecodeError as alert: + for result in self._sendError(AlertDescription.decode_error, + str(alert)): 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, 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 @@ -1478,23 +2474,30 @@ def _serverCertKeyExchange(self, clientHello, serverHello, msgs.append(serverHello) msgs.append(Certificate(CertificateType.x509).create(serverCertChain)) + try: + sigHashAlg = self._pickServerKeyExchangeSig(settings, clientHello, + serverCertChain) + 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) 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)) + certificateRequest = CertificateRequest(self.version) + if not reqCAs: + reqCAs = [] + validSigAlgs = self._sigHashesToList(settings) + certificateRequest.create([ClientCertificateType.rsa_sign], + reqCAs, + validSigAlgs) + msgs.append(certificateRequest) msgs.append(ServerHelloDone()) 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): @@ -1541,39 +2544,45 @@ 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 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 #Get and check CertificateVerify, if relevant + self._certificate_verify_handshake_hash = self._handshake_hash.copy() if clientCertChain: - if self.version == (3,0): - masterSecret = calcMasterSecret(self.version, premasterSecret, - clientHello.random, serverHello.random) - verifyBytes = self._calcSSLHandshakeHash(masterSecret, b"") - elif self.version in ((3,1), (3,2)): - verifyBytes = self._handshake_md5.digest() + \ - self._handshake_sha.digest() - elif self.version == (3,3): - 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 + 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 + + cvhh = self._certificate_verify_handshake_hash + verifyBytes = KeyExchange.calcVerifyBytes(self.version, + cvhh, + signatureAlgorithm, + premasterSecret, + clientHello.random, + serverHello.random) publicKey = clientCertChain.getEndEntityPublicKey() if len(publicKey) < settings.minKeySize: for result in self._sendError(\ @@ -1587,7 +2596,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"): @@ -1595,70 +2621,75 @@ def _serverCertKeyExchange(self, clientHello, serverHello, yield (premasterSecret, clientCertChain) - 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)) - dh_Ys = powMod(dh_g, dh_Xs, dh_p) + def _serverAnonKeyExchange(self, serverHello, keyExchange, cipherSuite): - #Create ServerKeyExchange - serverKeyExchange = ServerKeyExchange(cipherSuite) - 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 - - #From here on, the client's messages must have the right version - self._versionCheck = True - - #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: + cke = result + try: + premasterSecret = keyExchange.processClientKeyExchange(cke) + except TLSIllegalParameterException as alert: for result in self._sendError(AlertDescription.illegal_parameter, - "Suspicious dh_Yc value"): + str(alert)): + yield result + except TLSDecodeError as alert: + for result in self._sendError(AlertDescription.decode_error, + str(alert)): yield result - assert(False) # Just to ensure we don't fall through somehow - #Calculate premaster secre - S = powMod(dh_Yc,dh_Xs,dh_p) - premasterSecret = numberToByteArray(S) - yield premasterSecret def _serverFinished(self, premasterSecret, clientRandom, serverRandom, cipherSuite, cipherImplementations, nextProtos): - masterSecret = calcMasterSecret(self.version, premasterSecret, - clientRandom, serverRandom) - + 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, + cvhh) + else: + masterSecret = calcMasterSecret(self.version, + cipherSuite, + premasterSecret, + clientRandom, + serverRandom) + #Calculate pending connection states self._calcPendingStates(cipherSuite, masterSecret, 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 @@ -1669,7 +2700,9 @@ def _serverFinished(self, premasterSecret, clientRandom, serverRandom, #********************************************************* - def _sendFinished(self, masterSecret, nextProto=None): + 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 @@ -1683,7 +2716,11 @@ def _sendFinished(self, masterSecret, nextProto=None): yield result #Calculate verification data - verifyData = self._calcFinished(masterSecret, True) + verifyData = calcFinished(self.version, + masterSecret, + cipherSuite, + self._handshake_hash, + self._client) if self.fault == Fault.badFinished: verifyData[0] = (verifyData[0]+1)%256 @@ -1691,8 +2728,11 @@ def _sendFinished(self, masterSecret, 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, 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): @@ -1726,7 +2766,11 @@ def _getFinished(self, masterSecret, expect_next_protocol=False, nextProto=None) self.next_proto = nextProto #Calculate verification data - verifyData = self._calcFinished(masterSecret, 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, @@ -1739,39 +2783,6 @@ def _getFinished(self, masterSecret, expect_next_protocol=False, nextProto=None) "Finished message is incorrect"): yield result - def _calcFinished(self, masterSecret, 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._calcSSLHandshakeHash(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_md5.digest() + \ - self._handshake_sha.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" - - handshakeHashes = self._handshake_sha256.digest() - verifyData = PRF_1_2(masterSecret, label, handshakeHashes, 12) - return verifyData - else: - raise AssertionError() - - def _handshakeWrapperAsync(self, handshaker, checker): try: for result in handshaker: @@ -1797,3 +2808,83 @@ def _handshakeWrapperAsync(self, handshaker, checker): except: self._shutdown(False) raise + + @staticmethod + 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, + version=version) + + 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") + + @staticmethod + def _sigHashesToList(settings, privateKey=None, certList=None, + version=(3, 3)): + """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: + # 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 + if certType == "rsa-pss" and schemeName == "pkcs1": + continue + 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 + 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] diff --git a/tlslite/tlsrecordlayer.py b/tlslite/tlsrecordlayer.py index 06e5597d..14d7b6a7 100644 --- a/tlslite/tlsrecordlayer.py +++ b/tlslite/tlsrecordlayer.py @@ -4,130 +4,133 @@ # 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. """Helper class for TLSConnection.""" from __future__ import generators +import io +import socket + 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 .utils.lists import to_str_delimiter from .errors import * from .messages import * from .mathtls import * from .constants import * -from .utils.cryptomath import getRandomBytes - -import socket -import errno -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 - +from .recordlayer import RecordLayer +from .defragmenter import Defragmenter +from .handshakehashes import HandshakeHashes +from .bufferedsocket import BufferedSocket 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. - - @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. + + :vartype tickets: list of bytearray + :ivar tickets: list of session tickets received from server, oldest first. """ def __init__(self, sock): + sock = BufferedSocket(sock) self.sock = 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._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() #Handshake digests - self._handshake_md5 = hashlib.md5() - self._handshake_sha = hashlib.sha1() - 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() + 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 @@ -149,6 +152,45 @@ def __init__(self, sock): #Fault we will induce, for testing purposes self.fault = None + #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""" + 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._recordLayer.version + + @version.setter + def version(self, value): + """ + 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._recordLayer.version = value + if value > (3, 3): + self._recordLayer.tls13record = True + + @property + def encryptThenMAC(self): + """Whether the connection uses Encrypt Then MAC (RFC 7366)""" + return self._recordLayer.encryptThenMAC + def clearReadBuffer(self): self._readBuffer = b'' @@ -169,21 +211,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 @@ -198,15 +240,26 @@ 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. """ + 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)= 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: @@ -310,10 +350,10 @@ def close(self): Even if an exception is raised, the connection will have been closed. - @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. """ if not self.closed: for result in self._decrefAsync(): @@ -331,8 +371,8 @@ def closeAsync(self): to write to the socket, or will raise StopIteration if the close operation has completed. - @rtype: iterable - @return: A generator; see above for details. + :rtype: iterable + :returns: A generator; see above for details. """ if not self.closed: for result in self._decrefAsync(): @@ -376,51 +416,43 @@ def _decrefAsync(self): def getVersionName(self): """Get the name of this TLS version. - @rtype: str - @return: 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'. + :rtype: str + :returns: The name of the TLS version used with this connection. + 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. - @rtype: str - @return: The name of the cipher used with this connection. - Either 'aes128', 'aes256', 'rc4', or '3des'. + :rtype: str + :returns: The name of the cipher used with this connection. + Either 'aes128', 'aes256', 'rc4', or '3des'. """ - if not self._writeState.encContext: - return None - return self._writeState.encContext.name + return self._recordLayer.getCipherName() def getCipherImplementation(self): """Get the name of the cipher implementation used with this connection. - @rtype: str - @return: The name of the cipher implementation used with - this connection. Either 'python', 'openssl', or 'pycrypto'. + :rtype: str + :returns: The name of the cipher implementation used with + this connection. Either 'python', 'openssl', or 'pycrypto'. """ - if not self._writeState.encContext: - return None - return self._writeState.encContext.implementation - - + return self._recordLayer.getCipherImplementation() #Emulate a socket, somewhat - def send(self, s): """Send data to the TLS connection (socket emulation). - @raise socket.error: If a socket error occurs. + :raises socket.error: If a socket error occurs. """ self.write(s) return len(s) @@ -428,17 +460,17 @@ def send(self, s): def sendall(self, s): """Send data to the TLS connection (socket emulation). - @raise socket.error: If a socket error occurs. + :raises socket.error: If a socket error occurs. """ self.write(s) def recv(self, bufsize): """Get some data from the TLS connection (socket emulation). - @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. """ return self.read(bufsize) @@ -450,10 +482,14 @@ 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). - @rtype: L{socket._fileobject} + :rtype: socket._fileobject """ self._refCount += 1 # So, it is pretty fragile to be using Python internal objects @@ -466,11 +502,20 @@ 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: - # 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).""" @@ -508,10 +553,8 @@ def fileno(self): #********************************************************* def _shutdown(self, resumable): - self._writeState = _ConnectionState() - self._readState = _ConnectionState() + self._recordLayer.shutdown() self.version = (0,0) - self._versionCheck = False self.closed = True if self.closeSocket: self.sock.close() @@ -522,6 +565,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 @@ -529,136 +575,91 @@ 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""" #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). - if not self.closed and randomizeFirstBlock and self.version <= (3,1) \ - and self._writeState.encContext \ - and self._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 - - 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 - - contentType = msg.contentType + for result in self._sendMsgThroughSocket(msgFirstByte): + yield result + if len(msg.write()) == 0: + return + buf = msg.write() + contentType = msg.contentType #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)) - - #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) + self._handshake_hash.update(buf) - #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) + #Fragment big messages + while len(buf) > self.recordSize: + newB = buf[:self.recordSize] + buf = buf[self.recordSize:] + + msgFragment = Message(contentType, newB) + for result in self._sendMsgThroughSocket(msgFragment): + yield result + + msgFragment = Message(contentType, buf) + for result in self._sendMsgThroughSocket(msgFragment): + yield result + + def _sendMsgThroughSocket(self, msg): + """Send message, handle errors""" + + try: + for result in self._recordLayer.sendRecord(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 msg.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 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 + break + # 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: @@ -674,6 +675,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 @@ -731,8 +734,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) @@ -777,15 +781,17 @@ 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 - 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: @@ -793,22 +799,29 @@ 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: 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: 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: + 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() @@ -818,239 +831,102 @@ 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 - - #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: - 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 + # when the message buffer is empty, read next record from socket + for result in self._getNextRecordFromSocket(): + if result in (0, 1): + yield result 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 - - #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 - 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: - 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 - - #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)) - - def _decryptRecord(self, recordType, b): - if self._readState.encContext: + header, parser = result - #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 : ] + # 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) - if len(b) == 0: - for result in self._sendError(\ - AlertDescription.decryption_failed, - "No data left after decryption and IV removal"): - yield result + def _getNextRecordFromSocket(self): + """Read a record, handle errors""" - #Check padding - paddingGood = True - paddingLength = b[-1] - if (paddingLength+1) > len(b): - paddingGood=False - totalPaddingLength = 0 + try: + # otherwise... read the next record + for result in self._recordLayer.recvRecord(): + if result in (0, 1): + yield result 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() + break + 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 - #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()) + header, parser = result - #Compare MACs - if macBytes != checkBytes: - macGood = False + # 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 - if not (paddingGood and macGood): - for result in self._sendError(AlertDescription.bad_record_mac, - "MAC failure (or padding failure)"): - yield result + if header.type not in ContentType.all: + for result in self._sendError(\ + AlertDescription.unexpected_message, \ + "Received record with unknown ContentType"): + yield result - yield b + yield (header, parser) 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._handshakeBuffer = [] + self._handshake_hash = HandshakeHashes() + self._certificate_verify_handshake_hash = None + self._defragmenter.clearBuffers() self.allegedSrpUsername = None self._refCount = 1 @@ -1059,115 +935,13 @@ 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() - - #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 - + self._recordLayer.changeReadState() 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..cbf361dc --- /dev/null +++ b/tlslite/utils/aesgcm.py @@ -0,0 +1,199 @@ +# 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[self._reverseBits(1)] = h + for i in range(2, 16, 2): + 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): + """ + 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 = rawAesEncrypt(counter) + for j in range(i, min(len(out), i + 16)): + out[j] = inp[j] ^ mask[j-i] + self._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 ^= (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 + # 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) + + @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, + ] diff --git a/tlslite/utils/asn1parser.py b/tlslite/utils/asn1parser.py index 618e8559..c619a381 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. + + :vartype length: int + :ivar length: length of the value of the tag + :vartype 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,59 @@ 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 + :returns: decoded child object + """ return ASN1Parser(self.getChildBytes(which)) + def getChildCount(self): + """ + Return number of children, assuming that the object is a SEQUENCE. + + :rtype: int + :returns: 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 + + :type which: int + :param which: ordinal of the child to return + + :rtype: bytearray + :returns: 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 diff --git a/tlslite/utils/chacha.py b/tlslite/utils/chacha.py new file mode 100644 index 00000000..ac6e94c1 --- /dev/null +++ b/tlslite/utils/chacha.py @@ -0,0 +1,157 @@ +# 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 +try: + # in Python 3 the native zip returns iterator + from itertools import izip +except ImportError: + izip = zip + +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""" + 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 + + _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""" + 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): + """Generate a state of a single block""" + state = ChaCha.constants + key + [counter] + nonce + + working_state = state[:] + dbl_round = ChaCha.double_round + for _ in range(0, rounds // 2): + dbl_round(working_state) + + return [(st + wrkSt) & 0xffffffff for st, wrkSt + in izip(state, working_state)] + + @staticmethod + def word_to_bytearray(state): + """Convert state to little endian bytestream""" + return bytearray(struct.pack('>= 8 - newIndex -= 1 - 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 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) + + 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 three-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): - for e in seq: - self.add(e, length) + """ + Add a list of items, encode every item in length bytes - def addVarSeq(self, seq, length, lengthLength): - self.add(len(seq)*length, lengthLength) + 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) + 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 + + :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) + 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): + """ + 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 not seq: + self.add(0, lengthLength) + else: + 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): + """ + 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. + + :vartype bytes: bytearray + :ivar bytes: data to be interpreted (buffer) + + :vartype index: int + :ivar index: current position in the buffer + + :vartype lengthCheck: int + :ivar lengthCheck: size of struct being parsed + + :vartype 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] @@ -51,16 +304,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() @@ -70,19 +354,73 @@ 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. + + :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: + 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): + """ + 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: @@ -91,4 +429,5 @@ def atLengthCheck(self): raise SyntaxError() def getRemainingLength(self): + """Return amount of data remaining in struct being parsed.""" return len(self.bytes) - self.index diff --git a/tlslite/utils/compat.py b/tlslite/utils/compat.py index 95f7498d..7f019549 100644 --- a/tlslite/utils/compat.py +++ b/tlslite/utils/compat.py @@ -5,8 +5,11 @@ import sys import os +import platform import math import binascii +import traceback +import ecdsa if sys.version_info >= (3,0): @@ -17,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) @@ -34,7 +43,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 @@ -51,14 +62,27 @@ def readStdinBinary(): def compatLong(num): return int(num) + int_types = tuple([int]) + + 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. - if sys.version_info < (2,7): + # same thing with very old 2.7 versions + # 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 + 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) @@ -85,9 +109,24 @@ 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 + int_types = (int, long) + + # 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 + getattr(ecdsa, 'NIST192p') +except AttributeError: + ecdsaAllCurves = False +else: + ecdsaAllCurves = True diff --git a/tlslite/utils/constanttime.py b/tlslite/utils/constanttime.py new file mode 100644 index 00000000..60322c14 --- /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 + :returns: 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/tlslite/utils/cryptomath.py b/tlslite/utils/cryptomath.py index f5024135..f4576966 100644 --- a/tlslite/utils/cryptomath.py +++ b/tlslite/utils/cryptomath.py @@ -13,8 +13,10 @@ import math import base64 import binascii +import sys -from .compat import * +from .compat import compat26Str, compatHMAC, compatLong, b2a_hex +from .codec import Writer # ************************************************************************** @@ -26,6 +28,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 @@ -50,9 +60,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)) @@ -66,62 +74,141 @@ def getRandomBytes(howMany): # ************************************************************************** import hmac -import hashlib +from . import tlshashlib as 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 HMAC_MD5(k, b): +def secureHash(data, algorithm): + """Return a digest of `data` using `algorithm`""" + hashInstance = hashlib.new(algorithm) + hashInstance.update(compat26Str(data)) + return bytearray(hashInstance.digest()) + +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): + return secureHMAC(k, b, 'sha384') + +def HKDF_expand(PRK, info, L, algorithm): + N = 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] + +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 # ************************************************************************** -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 +def bytesToNumber(b, endian="big"): + """ + Convert a number stored in bytearray to an integer. -def numberToByteArray(n, howManyBytes=None): - """Convert an integer into a bytearray, zero-pad to howManyBytes. + 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": + return int(b2a_hex(b[::-1]), 16) + else: + raise ValueError("Only 'big' and 'little' endian supported") + +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) - b = bytearray(howManyBytes) - for count in range(howManyBytes-1, -1, -1): - b[count] = int(n % 256) - n >>= 8 - return b + if endian == "big": + return bytearray((n >> i) & 0xff + 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)) + else: + raise ValueError("Only 'big' and 'little' endian supported") + + +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 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 numberToMPI(n): b = numberToByteArray(n) @@ -144,22 +231,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 @@ -219,6 +306,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/deprecations.py b/tlslite/utils/deprecations.py new file mode 100644 index 00000000..5a4991f8 --- /dev/null +++ b/tlslite/utils/deprecations.py @@ -0,0 +1,144 @@ +# 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 + 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 + + +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/tlslite/utils/dns_utils.py b/tlslite/utils/dns_utils.py new file mode 100644 index 00000000..1c4fa298 --- /dev/null +++ b/tlslite/utils/dns_utils.py @@ -0,0 +1,42 @@ +# 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 + :param hostname: string to check + :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}(?>= 8 + return bytearray(ret) + + 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, 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/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/pycrypto_rsakey.py b/tlslite/utils/pycrypto_rsakey.py index 05953b5e..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,12 +32,31 @@ def hasPrivateKey(self): return self.rsa.has_private() def _rawPrivateKeyOp(self, m): - c = self.rsa.decrypt((m,)) - return c + 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): - m = self.rsa.encrypt(c, None)[0] - return m + 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() 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/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") diff --git a/tlslite/utils/python_rsakey.py b/tlslite/utils/python_rsakey.py index 1281f1dc..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 @@ -99,13 +110,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]: - raise SyntaxError("Unrecognized AlgorithmIdentifier") + # 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: {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 algorithm 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/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/rijndael.py b/tlslite/utils/rijndael.py index a1d720a2..b4384ebf 100644 --- a/tlslite/utils/rijndael.py +++ b/tlslite/utils/rijndael.py @@ -33,174 +33,870 @@ # code, in which case it can be made public domain by # 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]], [[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}} - -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 +num_rounds = {16: {16: 10, 24: 12, 32: 14}, + 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, + 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): @@ -277,7 +973,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 @@ -307,20 +1004,21 @@ 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): 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 @@ -350,15 +1048,15 @@ 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): 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): diff --git a/tlslite/utils/rsakey.py b/tlslite/utils/rsakey.py index fb022cc6..5c8c675b 100644 --- a/tlslite/utils/rsakey.py +++ b/tlslite/utils/rsakey.py @@ -4,20 +4,23 @@ """Abstract class for RSA.""" from .cryptomath import * +from . import tlshashlib as hashlib +from ..errors import MaskTooLongError, MessageTooLongError, EncodingError, \ + InvalidSignature, UnknownRSAType 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): @@ -25,80 +28,256 @@ 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() - 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 bytes: str or bytearray + :param bytes: The value which will be hashed and signed. - @rtype: L{bytearray} of unsigned bytes. - @return: A PKCS1-SHA1 signature on the passed-in data. - """ - hashBytes = SHA1(bytearray(bytes)) - prefixedHashBytes = self.addPKCS1SHA1Prefix(hashBytes) - sigBytes = self.sign(prefixedHashBytes) - return sigBytes + :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 - def hashAndVerify(self, sigBytes, bytes): + :rtype: bytearray + :returns: A PKCS1 or PSS signature on the passed-in data. + """ + rsaScheme = rsaScheme.lower() + hAlg = hAlg.lower() + 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): """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: bytearray + :param sigBytes: A PKCS1 or PSS signature. + + :type bytes: str or bytearray + :param bytes: The value which will be hashed and verified. - @type sigBytes: L{bytearray} of unsigned bytes - @param sigBytes: A PKCS1-SHA1 signature. + :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 bytes: str or L{bytearray} of unsigned bytes - @param bytes: The value which will be hashed and verified. + :type hAlg: str + :param hAlg: The hash algorithm that will be used - @rtype: bool - @return: Whether the signature matches the passed-in data. + :type sLen: int + :param sLen: The length of intended salt value, applicable only + for RSASSA-PSS signatures + + :rtype: bool + :returns: 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) - - def sign(self, bytes): - """Sign the passed-in bytes. + rsaScheme = rsaScheme.lower() + hAlg = hAlg.lower() - This requires the key to have a private component. It performs - a PKCS1 signature on the passed-in data. + 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. + + This generates mask based on passed-in seed and output maskLen. + + :type mgfSeed: bytearray + :param mgfSeed: Seed from which mask will be generated. + + :type maskLen: int + :param maskLen: Wished length of the mask, in octets + + :rtype: bytearray + :returns: Mask + """ + hashLen = getattr(hashlib, hAlg)().digest_size + if maskLen > (2 ** 32) * hashLen: + raise MaskTooLongError("Incorrect parameter maskLen") + T = bytearray() + end = 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, mHash, emBits, hAlg, sLen=0): + """Encode the passed in message + + This encodes the message using selected hash algorithm + + :type mHash: bytearray + :param mHash: Hash of 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 + emLen = 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, mHash, hAlg, sLen=0): + """"Sign the passed in message + + This signs the message using selected hash algorithm + + :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(mHash, 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, mHash, EM, emBits, hAlg, sLen=0): + """Verify signature in passed in encoded message + + This verifies the signature in encoded message + + :type mHash: bytearray + :param mHash: Hash of the 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 + emLen = 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]): + 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, mHash, S, hAlg, sLen=0): + """Verify the signature in passed in message + + This verifies the signature in the signed message + + :type mHash: bytearray + :param mHash: Hash of original message - @type bytes: L{bytearray} of unsigned bytes - @param bytes: The value which will be signed. + :type S: bytearray + :param S: Signed message - @rtype: L{bytearray} of unsigned bytes. - @return: A PKCS1 signature on the passed-in data. + :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("Invalid signature") + s = bytesToNumber(S) + m = self._rawPublicKeyOp(s) + EM = numberToByteArray(m, divceil(numBits(self.n) - 1, 8)) + result = self.EMSA_PSS_verify(mHash, EM, numBits(self.n) - 1, + hAlg, sLen) + if result: + return True + else: + raise InvalidSignature("Invalid signature") + + 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) @@ -109,20 +288,44 @@ def sign(self, bytes): sigBytes = numberToByteArray(c, numBytes(self.n)) return sigBytes - def verify(self, sigBytes, bytes): - """Verify the passed-in bytes with the signature. + def sign(self, bytes, padding='pkcs1', hashAlg=None, saltLen=None): + """Sign the passed-in bytes. - This verifies a PKCS1 signature on the passed-in data. + This requires the key to have a private component. It performs + a PKCS1 signature on the passed-in data. + + :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 sigBytes: L{bytearray} of unsigned bytes - @param sigBytes: A PKCS1 signature. + :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 bytes: L{bytearray} of unsigned bytes - @param bytes: The value which will be verified. + :type saltLen: int + :param saltLen: length of salt used for the PSS padding. Default + is the length of the hash output used. - @rtype: bool - @return: Whether the signature matches the passed-in data. + :rtype: bytearray + :returns: A PKCS1 signature on the passed-in data. """ + 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 _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) @@ -133,16 +336,52 @@ def verify(self, sigBytes, bytes): 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. + + :type sigBytes: bytearray + :param sigBytes: A PKCS1 signature. + + :type bytes: bytearray + :param bytes: The value which will be verified. + + :rtype: bool + :returns: Whether the signature matches the passed-in data. + """ + 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) + res = self._raw_pkcs1_verify(sigBytes, bytes) + return res + elif padding == "pss": + try: + res = self.RSASSA_PSS_verify(bytes, sigBytes, hashAlg, saltLen) + except InvalidSignature: + res = False + return res + else: + raise UnknownRSAType("Unknown RSA algorithm type") + def encrypt(self, bytes): """Encrypt the passed-in 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) @@ -158,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() @@ -195,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) @@ -221,8 +460,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 @@ -232,14 +472,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]) - prefixedBytes = prefixBytes + bytes + prefixBytes = cls._pkcs1Prefixes['sha1'] + prefixedBytes = prefixBytes + hashBytes 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])} + + @classmethod + def addPKCS1Prefix(cls, data, hashName): + """Add the PKCS#1 v1.5 algorithm identifier prefix to hash bytes""" + hashName = hashName.lower() + assert hashName in cls._pkcs1Prefixes + prefixBytes = cls._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/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/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" diff --git a/tlslite/utils/x25519.py b/tlslite/utils/x25519.py new file mode 100644 index 00000000..334daad4 --- /dev/null +++ b/tlslite/utils/x25519.py @@ -0,0 +1,136 @@ +# 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 + + +X25519_G = numberToByteArray(9, 32, endian="little") + + +X25519_ORDER_SIZE = 32 + + +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) + + +X448_G = numberToByteArray(5, 56, endian="little") + + +X448_ORDER_SIZE = 56 + + +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/tlslite/verifierdb.py b/tlslite/verifierdb.py index 43845415..0711170c 100644 --- a/tlslite/verifierdb.py +++ b/tlslite/verifierdb.py @@ -20,16 +20,16 @@ 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, "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) @@ -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) @@ -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): @@ -69,27 +69,32 @@ 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. """ - 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/tlslite/x509.py b/tlslite/x509.py index 94dd00d8..de4cee57 100644 --- a/tlslite/x509.py +++ b/tlslite/x509.py @@ -13,45 +13,52 @@ class X509(object): - """This class represents an X.509 certificate. + """ + This class represents an X.509 certificate. + + :vartype bytes: bytearray + :ivar bytes: The DER-encoded ASN.1 certificate - @type bytes: L{bytearray} of unsigned bytes - @ivar bytes: The DER-encoded ASN.1 certificate + :vartype publicKey: ~tlslite.utils.rsakey.RSAKey + :ivar publicKey: The subject public key from the certificate. - @type publicKey: L{tlslite.utils.rsakey.RSAKey} - @ivar publicKey: The subject public key from the certificate. + :vartype subject: bytearray + :ivar subject: The DER-encoded ASN.1 subject distinguished name. - @type subject: L{bytearray} of unsigned bytes - @ivar subject: The DER-encoded ASN.1 subject distinguished name. + :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) + p = ASN1Parser(self.bytes) #Get the tbsCertificate tbsCertificateP = p.getChild(0) @@ -71,12 +78,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) == [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) @@ -97,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) diff --git a/unit_tests/mocksock.py b/unit_tests/mocksock.py new file mode 100644 index 00000000..5e3d282c --- /dev/null +++ b/unit_tests/mocksock.py @@ -0,0 +1,93 @@ +# 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): + # 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): + 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") + + # simulate a socket with full buffers, make it rise "Would block" + # every other call + if self.blockEveryOther: + if self.blockRead: + self.blockRead = False + raise socket.error(errno.EWOULDBLOCK) + 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 + return ret + + 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 + raise socket.error(errno.EWOULDBLOCK) + 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 + + def close(self): + self.closed = True 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")) 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() diff --git a/unit_tests/test_tlslite_constants.py b/unit_tests/test_tlslite_constants.py new file mode 100644 index 00000000..152c69fb --- /dev/null +++ b/unit_tests/test_tlslite_constants.py @@ -0,0 +1,211 @@ +# 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.constants import CipherSuite, HashAlgorithm, SignatureAlgorithm, \ + ContentType, AlertDescription, AlertLevel, HandshakeType, GroupName, \ + TLSEnum, SignatureScheme + +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): + + 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 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 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') + +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): + 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_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_AES_128_GCM_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]) + + 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): + 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_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()) 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) diff --git a/unit_tests/test_tlslite_extensions.py b/unit_tests/test_tlslite_extensions.py index 7a64eb0c..25b0274c 100644 --- a/unit_tests/test_tlslite_extensions.py +++ b/unit_tests/test_tlslite_extensions.py @@ -8,11 +8,25 @@ 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 -from tlslite.utils.codec import Parser -from tlslite.constants import NameType + TACKExtension, SupportedGroupsExtension, ECPointFormatsExtension,\ + SignatureAlgorithmsExtension, PaddingExtension, VarListExtension, \ + RenegotiationInfoExtension, ALPNExtension, StatusRequestExtension, \ + SupportedVersionsExtension, VarSeqListExtension, ListExtension, \ + ClientKeyShareExtension, KeyShareEntry, ServerKeyShareExtension, \ + CertificateStatusExtension, HRRKeyShareExtension +from tlslite.utils.codec import Parser, Writer +from tlslite.constants import NameType, ExtensionType, GroupName,\ + ECPointFormat, HashAlgorithm, SignatureAlgorithm, \ + CertificateStatusType from tlslite.errors import TLSInternalError class TestTLSExtension(unittest.TestCase): @@ -26,10 +40,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() @@ -79,6 +144,8 @@ 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.hostNames[0]) def test_parse_with_SNI_server_side(self): @@ -92,6 +159,143 @@ def test_parse_with_SNI_server_side(self): self.assertIsInstance(ext, SNIExtension) self.assertIsNone(ext.serverNames) + 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_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.assertIsNone(ext.serverNames) + + 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) + + self.assertEqual(ext.extData, bytearray(b'\x00')) + self.assertEqual(ext.extType, 0xff01) + + 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) + + self.assertIsInstance(ext, SupportedGroupsExtension) + + self.assertEqual(ext.groups, [GroupName.secp256r1, + GroupName.secp384r1, + GroupName.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) + + self.assertIsInstance(ext, ECPointFormatsExtension) + + self.assertEqual(ext.formats, [ECPointFormat.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) + + 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)) b = SNIExtension().create() @@ -125,16 +329,161 @@ 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_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_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() + + 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.certTypes) + def test___repr__(self): ext = TLSExtension() 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)) + +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) + + 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 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() @@ -468,6 +817,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( @@ -490,7 +853,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) @@ -560,7 +923,7 @@ def test___init__(self): self.assertEqual(9, cert_type.extType) self.assertEqual(bytearray(0), cert_type.extData) - self.assertEqual(None, cert_type.cert_type) + self.assertIsNone(cert_type.cert_type) def test_create(self): cert_type = ServerCertTypeExtension().create(0) @@ -613,7 +976,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.extType) self.assertEqual(bytearray(0), srp_extension.extData) @@ -621,7 +984,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.extType) self.assertEqual(bytearray(0), srp_extension.extData) @@ -689,7 +1052,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.extType) self.assertEqual(bytearray(0), npn_extension.extData) @@ -697,7 +1060,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.extType) self.assertEqual(bytearray(0), npn_extension.extData) @@ -996,5 +1359,817 @@ 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.extType, 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.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)) + + 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) + + 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() + + self.assertIsNotNone(ext) + self.assertEqual(ext.extData, bytearray(0)) + self.assertEqual(ext.extType, 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) + + 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() + + self.assertIsNotNone(ext) + self.assertIsNone(ext.sigalgs) + self.assertEqual(ext.extType, 13) + self.assertEqual(ext.extData, 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) + + 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() + + 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) + +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')) + + +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')]) + + +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) + + +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) + + +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) + + +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)) + + +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) + + +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() diff --git a/unit_tests/test_tlslite_handshakehashes.py b/unit_tests/test_tlslite_handshakehashes.py new file mode 100644 index 00000000..1f211382 --- /dev/null +++ b/unit_tests/test_tlslite_handshakehashes.py @@ -0,0 +1,116 @@ +# 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_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') + + 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')) 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_handshakesettings.py b/unit_tests/test_tlslite_handshakesettings.py index 31de7157..191920f4 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() @@ -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() @@ -152,3 +152,170 @@ 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() + + 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_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'] + + 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() + + def test_invalid_signature_algorithm(self): + hs = HandshakeSettings() + 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()) + + def test_invalid_curve_name(self): + hs = HandshakeSettings() + 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() + + def test_invalid_dhParams(self): + hs = HandshakeSettings() + hs.dhParams = (2, 'bd') + with self.assertRaises(ValueError): + hs.validate() + + def test_invalid_dhGroups(self): + hs = HandshakeSettings() + hs.dhGroups = ["ffdhe2048", "ffdhe1024"] + 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() + + def test_invalid_defaultCurve_name(self): + hs = HandshakeSettings() + hs.defaultCurve = "ffdhe2048" + 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)) + + 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() diff --git a/unit_tests/test_tlslite_keyexchange.py b/unit_tests/test_tlslite_keyexchange.py new file mode 100644 index 00000000..bbc5a349 --- /dev/null +++ b/unit_tests/test_tlslite_keyexchange.py @@ -0,0 +1,1728 @@ +# 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 +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,\ + CertificateRequest, ClientKeyExchange +from tlslite.constants import CipherSuite, CertificateType, AlertDescription, \ + HashAlgorithm, SignatureAlgorithm, GroupName, ECCurveType, \ + SignatureScheme +from tlslite.errors import TLSLocalAlert, TLSIllegalParameterException, \ + TLSDecryptionFailed, TLSInsufficientSecurity, TLSUnknownPSKIdentity, \ + TLSInternalError, TLSDecodeError +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, isPrime, numBits +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, \ + 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"\ + "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) + + + 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)]) + 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_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) + + 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) + + 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() + 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_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_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)) + 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' + )) + + 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) + 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_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_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)) + clientKeyExchange.createDH(2**16000-1) + + 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, + 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) + + 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): + 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) + 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=bytearray(b'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[b'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_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') + + 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=bytearray(b'user'), + password=bytearray(b'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=bytearray(b'user'), + password=bytearray(b'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=bytearray(b'user'), + password=bytearray(b'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=bytearray(b'user'), + password=bytearray(b'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=bytearray(b'user'), + password=bytearray(b'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_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_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"))] + + 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_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] + 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) + + 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): + 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#+")) + + +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) + + 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): + 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) + + 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) + + +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]) + + 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')) diff --git a/unit_tests/test_tlslite_mathtls.py b/unit_tests/test_tlslite_mathtls.py new file mode 100644 index 00000000..721e0340 --- /dev/null +++ b/unit_tests/test_tlslite_mathtls.py @@ -0,0 +1,249 @@ +# 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, calcFinished, \ + calcExtendedMasterSecret, paramStrength +from tlslite.handshakehashes import HandshakeHashes +from tlslite.constants import CipherSuite + +class TestCalcMasterSecret(unittest.TestCase): + def test_with_empty_values(self): + ret = calcMasterSecret((3, 3), 0, 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 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) + + 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) + +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) + + +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)) diff --git a/unit_tests/test_tlslite_messages.py b/unit_tests/test_tlslite_messages.py index 7e302e2a..9f536cf1 100644 --- a/unit_tests/test_tlslite_messages.py +++ b/unit_tests/test_tlslite_messages.py @@ -7,14 +7,80 @@ 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 + RecordHeader2, Message, ClientKeyExchange, ServerKeyExchange, \ + CertificateRequest, CertificateVerify, ServerHelloDone, ServerHello2, \ + ClientMasterKey, ClientFinished, ServerFinished, CertificateStatus, \ + Certificate, Finished, HelloMessage, ChangeCipherSpec, NextProtocol, \ + ApplicationData, EncryptedExtensions, CertificateEntry, \ + NewSessionTicket, HelloRetryRequest from tlslite.utils.codec import Parser from tlslite.constants import CipherSuite, CertificateType, ContentType, \ - AlertLevel, AlertDescription, ExtensionType + AlertLevel, AlertDescription, ExtensionType, ClientCertificateType, \ + HashAlgorithm, SignatureAlgorithm, ECCurveType, GroupName, \ + SSL2HandshakeType, CertificateStatusType, HandshakeType, \ + SignatureScheme from tlslite.extensions import SNIExtension, ClientCertTypeExtension, \ - SRPExtension, TLSExtension + SRPExtension, TLSExtension, NPNExtension, SupportedGroupsExtension, \ + ServerCertTypeExtension 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): + 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 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): @@ -39,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), \ @@ -64,6 +145,14 @@ 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) + self.assertEqual([NPNExtension()], client_hello.extensions) + def test_parse(self): p = Parser(bytearray( # we don't include the type of message as it is handled by the @@ -300,10 +389,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 @@ -408,7 +503,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): @@ -419,7 +514,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): @@ -554,7 +650,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 @@ -576,7 +672,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) @@ -587,6 +683,159 @@ 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()) + + 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() + 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() + 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() + 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): server_hello = ServerHello() @@ -620,6 +869,81 @@ 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) + + 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 + 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_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 + 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 @@ -764,6 +1088,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 @@ -804,6 +1142,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 @@ -853,6 +1209,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( @@ -869,6 +1242,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( @@ -881,7 +1273,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"\ @@ -890,6 +1282,79 @@ 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() @@ -930,11 +1395,121 @@ 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 + )) - #self.assertEqual(512, rh.length) + rh = RecordHeader2() + rh = rh.parse(parser) + + self.assertEqual(512, rh.length) + self.assertEqual(10, rh.padding) + + 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) + + 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): @@ -1089,5 +1664,1723 @@ 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_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) + + 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_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)) + + 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_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) + + parser = Parser(bytearray( + b'\x00\x00\x00')) + + 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, + (3, 1)) + + 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)) + 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)) + + 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, + (3, 1)) + + 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_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.hashAlg = HashAlgorithm.sha512 + ske.signAlg = SignatureAlgorithm.rsa + 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'\x06\x01' + # SHA512+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, + (3, 3)) + + 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_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)) + + parser = Parser(bytearray( + b'\x00\x00\x03' + + b'\x00\x01' + + 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, + (3, 3)) + + 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_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, + (3, 1)) + + 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_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)) + + 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_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)) + + 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, + (3, 1)) + + 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'\xcb\xe6\xd3=\x8b$\xff\x97e&\xb2\x89\x1dA\xab>' + + 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)) + + with self.assertRaises(AssertionError): + ske.hash(bytearray(32), bytearray(32)) + + 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)) + + ske.createSRP(srp_N=1, + srp_g=2, + srp_s=bytearray(3), + srp_B=4) + ske.hashAlg = HashAlgorithm.sha1 + + hash1 = ske.hash(bytearray(32), bytearray(32)) + + self.assertEqual(hash1, bytearray( + 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): + cr = CertificateRequest((3, 0)) + + self.assertIsNotNone(cr) + self.assertEqual(cr.version, (3, 0)) + self.assertEqual(cr.certificate_types, []) + 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, + [(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: + 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], + [], + [(6, 1), (4, 1), (2, 1)]) + + 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 + )) + +class TestCertificateVerify(unittest.TestCase): + def test___init__(self): + cv = CertificateVerify((3, 1)) + + self.assertIsNotNone(cv) + self.assertEqual(cv.signature, bytearray(0)) + + def test_create(self): + cv = CertificateVerify((3, 1)) + + cv.create(bytearray(b'\xf0\x0f')) + + self.assertEqual(cv.signature, bytearray(b'\xf0\x0f')) + + def test_write(self): + cv = CertificateVerify((3, 1)) + + 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((3, 1)) + + 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')) + + 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.signatureAlgorithm, (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 + )) + +class TestServerHelloDone(unittest.TestCase): + def test___init__(self): + shd = ServerHelloDone() + + 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() + + 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_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)) + + finished = finished.create(bytearray(b'\x04' * 12)) + + 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): + 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) + + +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')) + + +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 + 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___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()) + + 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_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( + 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_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) + + 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() + + +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) + + +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')) + + +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')) + + +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 = self.msg.create([ext]) + + self.assertIsInstance(self.msg, EncryptedExtensions) + 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 + + +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) + + +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() 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]) diff --git a/unit_tests/test_tlslite_recordlayer.py b/unit_tests/test_tlslite_recordlayer.py new file mode 100644 index 00000000..89a576af --- /dev/null +++ b/unit_tests/test_tlslite_recordlayer.py @@ -0,0 +1,2718 @@ +# 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 os +import socket +import errno + +import tlslite.utils.cryptomath as cryptomath +from tlslite.messages import Message, ApplicationData, RecordHeader3, \ + ClientHello, ClientMasterKey, ServerHello2, RecordHeader2 +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, TLSDecryptionFailed, TLSBadRecordMAC, \ + TLSUnexpectedMessage + +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_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) + + 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) + + 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() + + 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) + + self.assertIsNone(recordLayer.getCipherName()) + self.assertIsNone(recordLayer.getCipherImplementation()) + self.assertFalse(recordLayer.isCBCMode()) + + def test_sendRecord(self): + sock = MockSocket(bytearray(0)) + recordLayer = RecordLayer(sock) + + hello = Message(ContentType.handshake, bytearray(10)) + + for result in recordLayer.sendRecord(hello): + if result in (0, 1): + self.assertTrue(False, "Blocking write") + else: + break + + 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()) + 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)) + + 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_sendRecord_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.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' + # 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_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.tls13record = True + + 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.tls13record = True + + 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.tls13record = True + + 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) + recordLayer.tls13record = True + + 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_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, + '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 + 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.tls13record = True + 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', + 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.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' + # 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_sendRecord_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.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\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_sendRecord_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.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\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_sendRecord_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.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\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_sendRecord_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.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\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_sendRecord_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.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\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.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\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_sendRecord_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.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 - 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_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)) + + 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_sendRecord_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_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.sendRecord(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_sendRecord_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.sendRecord(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_sendRecord_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.sendRecord(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_sendRecord_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.sendRecord(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- 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')) 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) + diff --git a/unit_tests/test_tlslite_tlsconnection.py b/unit_tests/test_tlslite_tlsconnection.py new file mode 100644 index 00000000..319d0fb3 --- /dev/null +++ b/unit_tests/test_tlslite_tlsconnection.py @@ -0,0 +1,377 @@ +# 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 + +import socket +import threading + +from tlslite.recordlayer import RecordLayer +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, 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 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 TestTLSConnection(unittest.TestCase): + + 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.sendRecord(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.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) + + 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) + + 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() + 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 252 bytes + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + settings.keyShares = [] + conn.handshakeClientCert(settings=settings, + serverName='aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd.' + + 'eeeeeeeeeeffffffffffgggggggggghhhhhhhhhh.' + + 'iiiiiiiiiijjjjjjjjjjkkkkkkkkkkllllllllll.' + + 'mmmmmmmmmmnnnnnnnnnnoooooooooopppppppppp.' + + 'qqqqqqqqqqrrrrrrrrrrsssssssssstttttttttt.' + + 'uuuuuuuuuuvvvvvvvvvvwwwwwwwwwwxxxxxxxxxx.' + + 'y.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) + + 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')) + +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): + settings = HandshakeSettings() + settings.maxVersion = (3, 3) + 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() + 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"] + 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() diff --git a/unit_tests/test_tlslite_tlsrecordlayer.py b/unit_tests/test_tlslite_tlsrecordlayer.py new file mode 100644 index 00000000..7f57244f --- /dev/null +++ b/unit_tests/test_tlslite_tlsrecordlayer.py @@ -0,0 +1,1010 @@ +# 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.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): + 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) + sock.version = (3,3) + + # 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, 0) + + 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() + + with self.assertRaises(TLSLocalAlert) 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__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) + 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) + + 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) + + for result in gen: + if result in (0, 1): + self.assertTrue(False, "blocking") + else: + break + + # The maximum length that can be sent in single record is 2**14 + # record layer adds 5 byte on top of that + self.assertEqual(len(mock_sock.sent), 5) + 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( + 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) + + message = next(gen) + + 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) + + 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), + 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, + 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_hash.digest('sha256') + 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, + 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, + 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_hash.digest('sha256') + 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_hash.digest('sha256') + 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_hash.digest('sha256') + 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), + 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, + 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_hash.digest('sha256') + 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_hash.digest('sha256') + 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() + 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) diff --git a/unit_tests/test_tlslite_utils_chacha.py b/unit_tests/test_tlslite_utils_chacha.py new file mode 100644 index 00000000..4e9f8560 --- /dev/null +++ b/unit_tests/test_tlslite_utils_chacha.py @@ -0,0 +1,349 @@ +# 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.chacha import ChaCha +from tlslite.utils.cryptomath import bytesToNumber + +class TestChaCha(unittest.TestCase): + def betole32(self, data): + return ((data & 0xff000000) >> 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' + )) diff --git a/unit_tests/test_tlslite_utils_chacha20_poly1305.py b/unit_tests/test_tlslite_utils_chacha20_poly1305.py new file mode 100644 index 00000000..8a9b2ba8 --- /dev/null +++ b/unit_tests/test_tlslite_utils_chacha20_poly1305.py @@ -0,0 +1,131 @@ +# 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.chacha20_poly1305 import CHACHA20_POLY1305 + +class TestPoly1305(unittest.TestCase): + def test___init__(self): + aead = CHACHA20_POLY1305(bytearray(256//8), "python") + + self.assertIsNotNone(aead) + + def test___init___with_invalid_key_size(self): + with self.assertRaises(ValueError): + CHACHA20_POLY1305(bytearray(128//8), "python") + + def test___init___with_unsupported_implementation(self): + with self.assertRaises(ValueError): + CHACHA20_POLY1305(bytearray(256//8), "pycrypto") + + def test_seal(self): + aead = CHACHA20_POLY1305(bytearray( + b'\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f' + b'\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f' + ), "python") + + plaintext = bytearray( + b'Ladies and Gentlemen of the class of \'99: If I could offer you o' + b'nly one tip for the future, sunscreen would be it.') + + aad = bytearray(b'\x50\x51\x52\x53\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7') + + nonce = bytearray(b'\x07\x00\x00\x00\x40\x41\x42\x43\x44\x45\x46\x47') + + ciphertext = aead.seal(nonce, plaintext, aad) + + self.assertEqual(ciphertext, bytearray( + b'\xd3\x1a\x8d\x34\x64\x8e\x60\xdb\x7b\x86\xaf\xbc\x53\xef\x7e\xc2' + b'\xa4\xad\xed\x51\x29\x6e\x08\xfe\xa9\xe2\xb5\xa7\x36\xee\x62\xd6' + b'\x3d\xbe\xa4\x5e\x8c\xa9\x67\x12\x82\xfa\xfb\x69\xda\x92\x72\x8b' + b'\x1a\x71\xde\x0a\x9e\x06\x0b\x29\x05\xd6\xa5\xb6\x7e\xcd\x3b\x36' + b'\x92\xdd\xbd\x7f\x2d\x77\x8b\x8c\x98\x03\xae\xe3\x28\x09\x1b\x58' + b'\xfa\xb3\x24\xe4\xfa\xd6\x75\x94\x55\x85\x80\x8b\x48\x31\xd7\xbc' + b'\x3f\xf4\xde\xf0\x8e\x4b\x7a\x9d\xe5\x76\xd2\x65\x86\xce\xc6\x4b' + b'\x61\x16' + b'\x1a\xe1\x0b\x59\x4f\x09\xe2\x6a\x7e\x90\x2e\xcb\xd0\x60\x06\x91' + )) + + def test_seal_with_invalid_nonce(self): + aead = CHACHA20_POLY1305(bytearray(256//8), "python") + + with self.assertRaises(ValueError): + aead.seal(bytearray(16), bytearray(10), bytearray(10)) + + def test_open(self): + #RFC 7539 Appendix A.5 + + 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' + ) + + ciphertext = bytearray( + b'\x64\xa0\x86\x15\x75\x86\x1a\xf4\x60\xf0\x62\xc7\x9b\xe6\x43\xbd' + b'\x5e\x80\x5c\xfd\x34\x5c\xf3\x89\xf1\x08\x67\x0a\xc7\x6c\x8c\xb2' + b'\x4c\x6c\xfc\x18\x75\x5d\x43\xee\xa0\x9e\xe9\x4e\x38\x2d\x26\xb0' + b'\xbd\xb7\xb7\x3c\x32\x1b\x01\x00\xd4\xf0\x3b\x7f\x35\x58\x94\xcf' + b'\x33\x2f\x83\x0e\x71\x0b\x97\xce\x98\xc8\xa8\x4a\xbd\x0b\x94\x81' + b'\x14\xad\x17\x6e\x00\x8d\x33\xbd\x60\xf9\x82\xb1\xff\x37\xc8\x55' + b'\x97\x97\xa0\x6e\xf4\xf0\xef\x61\xc1\x86\x32\x4e\x2b\x35\x06\x38' + b'\x36\x06\x90\x7b\x6a\x7c\x02\xb0\xf9\xf6\x15\x7b\x53\xc8\x67\xe4' + b'\xb9\x16\x6c\x76\x7b\x80\x4d\x46\xa5\x9b\x52\x16\xcd\xe7\xa4\xe9' + b'\x90\x40\xc5\xa4\x04\x33\x22\x5e\xe2\x82\xa1\xb0\xa0\x6c\x52\x3e' + b'\xaf\x45\x34\xd7\xf8\x3f\xa1\x15\x5b\x00\x47\x71\x8c\xbc\x54\x6a' + b'\x0d\x07\x2b\x04\xb3\x56\x4e\xea\x1b\x42\x22\x73\xf5\x48\x27\x1a' + b'\x0b\xb2\x31\x60\x53\xfa\x76\x99\x19\x55\xeb\xd6\x31\x59\x43\x4e' + b'\xce\xbb\x4e\x46\x6d\xae\x5a\x10\x73\xa6\x72\x76\x27\x09\x7a\x10' + b'\x49\xe6\x17\xd9\x1d\x36\x10\x94\xfa\x68\xf0\xff\x77\x98\x71\x30' + b'\x30\x5b\xea\xba\x2e\xda\x04\xdf\x99\x7b\x71\x4d\x6c\x6f\x2c\x29' + b'\xa6\xad\x5c\xb4\x02\x2b\x02\x70\x9b') + + nonce = bytearray( + b'\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08') + + aad = bytearray( + b'\xf3\x33\x88\x86\x00\x00\x00\x00\x00\x00\x4e\x91') + + tag = bytearray( + b'\xee\xad\x9d\x67\x89\x0c\xbb\x22\x39\x23\x36\xfe\xa1\x85\x1f\x38' + ) + + aead = CHACHA20_POLY1305(key, "python") + + plaintext = aead.open(nonce, ciphertext + tag, aad) + + self.assertEqual(plaintext, bytearray( + b'Internet-Drafts are draft documents valid for a maximum of six m' + b'onths and may be updated, replaced, or obsoleted by other docume' + b'nts at any time. It is inappropriate to use Internet-Drafts as r' + b'eference material or to cite them other than as /' + b'\xe2\x80\x9cwork in progress\x2e\x2f\xe2\x80\x9d')) + + def test_open_with_invalid_size_nonce(self): + aead = CHACHA20_POLY1305(bytearray(256//8), "python") + + with self.assertRaises(ValueError): + aead.open(bytearray(128//8), + bytearray(64), + bytearray(0)) + + def test_open_with_too_short_ciphertext(self): + aead = CHACHA20_POLY1305(bytearray(256//8), "python") + + plaintext = aead.open(bytearray(96//8), bytearray(15), bytearray(0)) + + self.assertIsNone(plaintext) + + def test_open_with_invalid_tag(self): + aead = CHACHA20_POLY1305(bytearray(256//8), "python") + + plaintext = aead.open(bytearray(96//8), bytearray(32), bytearray(0)) + + self.assertIsNone(plaintext) diff --git a/unit_tests/test_tlslite_utils_codec.py b/unit_tests/test_tlslite_utils_codec.py index 999a30f0..2ac7d0b3 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() @@ -193,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) @@ -215,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') @@ -222,5 +364,53 @@ 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) + + def test_addVarTupleSeq_with_overflowing_data(self): + w = Writer() + + 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() diff --git a/unit_tests/test_tlslite_utils_constanttime.py b/unit_tests/test_tlslite_utils_constanttime.py new file mode 100644 index 00000000..0edaf3f4 --- /dev/null +++ b/unit_tests/test_tlslite_utils_constanttime.py @@ -0,0 +1,422 @@ +# 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 hypothesis import given, example +import hypothesis.strategies as st +from tlslite.utils.compat import compatHMAC +from tlslite.recordlayer import RecordLayer +import tlslite.utils.tlshashlib as hashlib +import hmac + +class TestContanttime(unittest.TestCase): + + @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): + + @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))) diff --git a/unit_tests/test_tlslite_utils_cryptomath.py b/unit_tests/test_tlslite_utils_cryptomath.py index 6c96f375..5811224a 100644 --- a/unit_tests/test_tlslite_utils_cryptomath.py +++ b/unit_tests/test_tlslite_utils_cryptomath.py @@ -8,8 +8,16 @@ import unittest2 as unittest except ImportError: import unittest +from hypothesis import given, example +from hypothesis.strategies import integers +import math +import struct -from tlslite.utils.cryptomath import isPrime +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, numberToMPI, mpiToNumber +from tlslite.handshakehashes import HandshakeHashes class TestIsPrime(unittest.TestCase): def test_with_small_primes(self): @@ -30,17 +38,43 @@ 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)) + # 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] @@ -60,3 +94,427 @@ 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')) + + @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)) + + @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("' + 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' + 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): + 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')) + +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'\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("= 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) + + @classmethod + def tearDownClass(cls): + import tlslite.utils.cryptomath + reload(tlslite.utils.cryptomath) diff --git a/unit_tests/test_tlslite_utils_deprecations.py b/unit_tests/test_tlslite_utils_deprecations.py new file mode 100644 index 00000000..c998d588 --- /dev/null +++ b/unit_tests/test_tlslite_utils_deprecations.py @@ -0,0 +1,360 @@ +# 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, \ + deprecated_attrs + + +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)) + + 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")) diff --git a/unit_tests/test_tlslite_utils_dns_utils.py b/unit_tests/test_tlslite_utils_dns_utils.py new file mode 100644 index 00000000..c99f370a --- /dev/null +++ b/unit_tests/test_tlslite_utils_dns_utils.py @@ -0,0 +1,40 @@ +# 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 +from tlslite.utils.dns_utils import is_valid_hostname + +class TestIsValidHostname(unittest.TestCase): + def test_example(self): + self.assertTrue(is_valid_hostname(b'example.com')) + + def test_ip(self): + self.assertFalse(is_valid_hostname(b'192.168.0.1')) + + def test_ip_dot(self): + self.assertFalse(is_valid_hostname(b'192.168.0.1.')) + + def test_ip_lookalike_hostname(self): + self.assertTrue(is_valid_hostname(b'192.168.example.com')) + + def test_with_tld_dot(self): + self.assertTrue(is_valid_hostname(b'example.com.')) + + def test_hostname_alone(self): + self.assertTrue(is_valid_hostname(b'localhost')) + + def test_very_long_hostname(self): + self.assertFalse(is_valid_hostname(b'a' * 250 + b'.example.com')) + + def test_very_long_host(self): + self.assertFalse(is_valid_hostname(b'a' * 70 + b'.example.com')) + + def test_long_hostname(self): + self.assertTrue(is_valid_hostname(b'a' * 60 + b'.example.com')) + 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") diff --git a/unit_tests/test_tlslite_utils_keyfactory.py b/unit_tests/test_tlslite_utils_keyfactory.py index 2c0f334a..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: @@ -119,6 +123,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): @@ -131,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"]) @@ -210,3 +256,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()) diff --git a/unit_tests/test_tlslite_utils_lists.py b/unit_tests/test_tlslite_utils_lists.py new file mode 100644 index 00000000..8805abd5 --- /dev/null +++ b/unit_tests/test_tlslite_utils_lists.py @@ -0,0 +1,53 @@ +# 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, to_str_delimiter + +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) + + +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'])) 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)) diff --git a/unit_tests/test_tlslite_utils_rijndael.py b/unit_tests/test_tlslite_utils_rijndael.py new file mode 100644 index 00000000..67087a7c --- /dev/null +++ b/unit_tests/test_tlslite_utils_rijndael.py @@ -0,0 +1,253 @@ +# 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): + 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 = 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) + + 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): + 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) + diff --git a/unit_tests/test_tlslite_utils_rsakey.py b/unit_tests/test_tlslite_utils_rsakey.py new file mode 100644 index 00000000..8d4dd1b8 --- /dev/null +++ b/unit_tests/test_tlslite_utils_rsakey.py @@ -0,0 +1,1682 @@ +# 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 +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): + # 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, rsaScheme='foo', + hAlg='sha1') + + def test_encodingError(self): + 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' + + 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'), + '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@' + + 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): + mHash = SHA1( + 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')) + self.assertEqual(self.rsa.EMSA_PSS_encode(mHash, 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') + mHash = secureHash(self.message, 'sha1') + self.assertTrue(self.rsa.RSASSA_PSS_verify( + mHash, 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): + mHash = secureHash(self.message, 'sha1') + signed = self.rsa.RSASSA_PSS_sign(mHash, '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): + mHash = secureHash(self.message, 'sha224') + signed = self.rsa.RSASSA_PSS_sign(mHash, '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): + mHash = secureHash(self.message, 'sha256') + signed = self.rsa.RSASSA_PSS_sign(mHash, '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): + mHash = secureHash(self.message, 'sha384') + signed = self.rsa.RSASSA_PSS_sign(mHash, '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): + 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): + 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') + mHash = secureHash(self.message, 'sha1') + self.assertTrue(self.rsa.RSASSA_PSS_verify(mHash, 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') + mHash = secureHash(self.message, 'sha224') + self.assertTrue(self.rsa.RSASSA_PSS_verify(mHash, 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') + mHash = secureHash(self.message, 'sha256') + self.assertTrue(self.rsa.RSASSA_PSS_verify(mHash, 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') + mHash = secureHash(self.message, 'sha384') + self.assertTrue(self.rsa.RSASSA_PSS_verify(mHash, 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') + mHash = secureHash(self.message, 'sha512') + self.assertTrue(self.rsa.RSASSA_PSS_verify(mHash, 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\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 ')) + + 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 ')) 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() diff --git a/unit_tests/test_tlslite_utils_x25519.py b/unit_tests/test_tlslite_utils_x25519.py new file mode 100644 index 00000000..59b431ba --- /dev/null +++ b/unit_tests/test_tlslite_utils_x25519.py @@ -0,0 +1,382 @@ +# 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, X25519_G, X448_G +from tlslite.utils.compat import a2b_hex, b2a_hex +from tlslite.utils.cryptomath import numberToByteArray + +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")) + + +class TestDecodeScalar22519(unittest.TestCase): + def test_x25519_decode_scalar(self): + value = a2b_hex('a546e36bf0527c9d3b16154b82465edd6' + '2144c0ac1fc5a18506a2244ba449ac4') + + scalar = decodeScalar22519(value) + + self.assertEqual(scalar, int("310298424921150409048955604518630896564" + "72772604678260265531221036453811406496")) + + +class TestDecodeScalar448(unittest.TestCase): + def test_x448_decode_scalar(self): + value = a2b_hex('3d262fddf9ec8e88495266fea19a34d2' + '8882acef045104d0d1aae121' + '700a779c984c24f8cdd78fbff44943eb' + 'a368f54b29259a4f1c600ad3') + + scalar = decodeScalar448(value) + + self.assertEqual(int("599189175373896402783756016145213256157230856" + "085026129926891459468622403380588640249457727" + "683869421921443004045221642549886377526240828"), + 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" + "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")) + + + # 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"))