diff --git a/.travis.yml b/.travis.yml index f6faa61a901648c3678b973e435a83cc0c371e4b..32a67c0729f66909541c450affb1fc2916b521d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ branches: - develop install: - - pip install cheetah cryptography + - pip install cheetah pyopenssl==0.13.1 before_script: - chmod +x ./tests/all_tests.py diff --git a/SickBeard.py b/SickBeard.py index 4d6fc1aa5dd470867ac46fc755676bbf87f541ce..fd02d430d7599f1ae8a15611e74f247404581661 100755 --- a/SickBeard.py +++ b/SickBeard.py @@ -64,7 +64,15 @@ else: try: import cryptography except ImportError: - print("SNI is disabled when the cryptography module is missing. You may encounter SSL errors!") + try: + from OpenSSL.version import __version__ as pyOpenSSL_Version + if int(pyOpenSSL_Version.replace('.', '')[:3]) > 13: + raise ImportError + except ImportError: + print('\nSNI is disabled with pyOpenSSL >= 0.14 when the cryptography module is missing,\n' + + 'you will encounter SSL errors with HTTPS! To fix this issue:\n' + + 'pip install pyopenssl==0.13.1 (easy) or pip install cryptography (pita)') + import locale import datetime diff --git a/gui/slick/images/network/the anime network.png b/gui/slick/images/network/the anime network.png new file mode 100644 index 0000000000000000000000000000000000000000..d462d377a762509bc5f37521610cf19617883b50 Binary files /dev/null and b/gui/slick/images/network/the anime network.png differ diff --git a/lib/OpenSSL/COPYING b/lib/OpenSSL/COPYING deleted file mode 100644 index c4792dd27a32d2ce510ec3e6ea008d87a7bdbe9a..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/COPYING +++ /dev/null @@ -1,515 +0,0 @@ - - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[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.] - - Preamble - - 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. -^L - 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. -^L - 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. -^L - 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. -^L - 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. -^L - 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. -^L - 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. -^L - 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 -^L - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms -of the ordinary General Public License). - - To apply these terms, attach the following notices to the library. -It is safest to attach them to the start of each source file to most -effectively convey the exclusion of warranty; and each file should -have at least the "copyright" line and a pointer to where the full -notice is found. - - - <one line to give the library's name and a brief idea of what it -does.> - Copyright (C) <year> <name of author> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -Also add information on how to contact you by electronic and paper -mail. - -You should also get your employer (if you work as a programmer) or -your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James -Random Hacker. - - <signature of Ty Coon>, 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - - diff --git a/lib/OpenSSL/SSL.py b/lib/OpenSSL/SSL.py deleted file mode 100644 index d0cc933082a0de78ee45d691e5e71e442771dbb9..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/SSL.py +++ /dev/null @@ -1,1948 +0,0 @@ -from sys import platform -from functools import wraps, partial -from itertools import count, chain -from weakref import WeakValueDictionary -from errno import errorcode - -from six import text_type as _text_type -from six import binary_type as _binary_type -from six import integer_types as integer_types -from six import int2byte, indexbytes - -from OpenSSL._util import ( - ffi as _ffi, - lib as _lib, - exception_from_error_queue as _exception_from_error_queue, - native as _native, - text_to_bytes_and_warn as _text_to_bytes_and_warn, - path_string as _path_string, - UNSPECIFIED as _UNSPECIFIED, -) - -from OpenSSL.crypto import ( - FILETYPE_PEM, _PassphraseHelper, PKey, X509Name, X509, X509Store) - -try: - _memoryview = memoryview -except NameError: - class _memoryview(object): - pass - -try: - _buffer = buffer -except NameError: - class _buffer(object): - pass - -OPENSSL_VERSION_NUMBER = _lib.OPENSSL_VERSION_NUMBER -SSLEAY_VERSION = _lib.SSLEAY_VERSION -SSLEAY_CFLAGS = _lib.SSLEAY_CFLAGS -SSLEAY_PLATFORM = _lib.SSLEAY_PLATFORM -SSLEAY_DIR = _lib.SSLEAY_DIR -SSLEAY_BUILT_ON = _lib.SSLEAY_BUILT_ON - -SENT_SHUTDOWN = _lib.SSL_SENT_SHUTDOWN -RECEIVED_SHUTDOWN = _lib.SSL_RECEIVED_SHUTDOWN - -SSLv2_METHOD = 1 -SSLv3_METHOD = 2 -SSLv23_METHOD = 3 -TLSv1_METHOD = 4 -TLSv1_1_METHOD = 5 -TLSv1_2_METHOD = 6 - -OP_NO_SSLv2 = _lib.SSL_OP_NO_SSLv2 -OP_NO_SSLv3 = _lib.SSL_OP_NO_SSLv3 -OP_NO_TLSv1 = _lib.SSL_OP_NO_TLSv1 - -OP_NO_TLSv1_1 = getattr(_lib, "SSL_OP_NO_TLSv1_1", 0) -OP_NO_TLSv1_2 = getattr(_lib, "SSL_OP_NO_TLSv1_2", 0) - -try: - MODE_RELEASE_BUFFERS = _lib.SSL_MODE_RELEASE_BUFFERS -except AttributeError: - pass - -OP_SINGLE_DH_USE = _lib.SSL_OP_SINGLE_DH_USE -OP_EPHEMERAL_RSA = _lib.SSL_OP_EPHEMERAL_RSA -OP_MICROSOFT_SESS_ID_BUG = _lib.SSL_OP_MICROSOFT_SESS_ID_BUG -OP_NETSCAPE_CHALLENGE_BUG = _lib.SSL_OP_NETSCAPE_CHALLENGE_BUG -OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG = _lib.SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG -OP_SSLREF2_REUSE_CERT_TYPE_BUG = _lib.SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG -OP_MICROSOFT_BIG_SSLV3_BUFFER = _lib.SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER -try: - OP_MSIE_SSLV2_RSA_PADDING = _lib.SSL_OP_MSIE_SSLV2_RSA_PADDING -except AttributeError: - pass -OP_SSLEAY_080_CLIENT_DH_BUG = _lib.SSL_OP_SSLEAY_080_CLIENT_DH_BUG -OP_TLS_D5_BUG = _lib.SSL_OP_TLS_D5_BUG -OP_TLS_BLOCK_PADDING_BUG = _lib.SSL_OP_TLS_BLOCK_PADDING_BUG -OP_DONT_INSERT_EMPTY_FRAGMENTS = _lib.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS -OP_CIPHER_SERVER_PREFERENCE = _lib.SSL_OP_CIPHER_SERVER_PREFERENCE -OP_TLS_ROLLBACK_BUG = _lib.SSL_OP_TLS_ROLLBACK_BUG -OP_PKCS1_CHECK_1 = _lib.SSL_OP_PKCS1_CHECK_1 -OP_PKCS1_CHECK_2 = _lib.SSL_OP_PKCS1_CHECK_2 -OP_NETSCAPE_CA_DN_BUG = _lib.SSL_OP_NETSCAPE_CA_DN_BUG -OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG= _lib.SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG -try: - OP_NO_COMPRESSION = _lib.SSL_OP_NO_COMPRESSION -except AttributeError: - pass - -OP_NO_QUERY_MTU = _lib.SSL_OP_NO_QUERY_MTU -OP_COOKIE_EXCHANGE = _lib.SSL_OP_COOKIE_EXCHANGE -try: - OP_NO_TICKET = _lib.SSL_OP_NO_TICKET -except AttributeError: - pass - -OP_ALL = _lib.SSL_OP_ALL - -VERIFY_PEER = _lib.SSL_VERIFY_PEER -VERIFY_FAIL_IF_NO_PEER_CERT = _lib.SSL_VERIFY_FAIL_IF_NO_PEER_CERT -VERIFY_CLIENT_ONCE = _lib.SSL_VERIFY_CLIENT_ONCE -VERIFY_NONE = _lib.SSL_VERIFY_NONE - -SESS_CACHE_OFF = _lib.SSL_SESS_CACHE_OFF -SESS_CACHE_CLIENT = _lib.SSL_SESS_CACHE_CLIENT -SESS_CACHE_SERVER = _lib.SSL_SESS_CACHE_SERVER -SESS_CACHE_BOTH = _lib.SSL_SESS_CACHE_BOTH -SESS_CACHE_NO_AUTO_CLEAR = _lib.SSL_SESS_CACHE_NO_AUTO_CLEAR -SESS_CACHE_NO_INTERNAL_LOOKUP = _lib.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP -SESS_CACHE_NO_INTERNAL_STORE = _lib.SSL_SESS_CACHE_NO_INTERNAL_STORE -SESS_CACHE_NO_INTERNAL = _lib.SSL_SESS_CACHE_NO_INTERNAL - -SSL_ST_CONNECT = _lib.SSL_ST_CONNECT -SSL_ST_ACCEPT = _lib.SSL_ST_ACCEPT -SSL_ST_MASK = _lib.SSL_ST_MASK -SSL_ST_INIT = _lib.SSL_ST_INIT -SSL_ST_BEFORE = _lib.SSL_ST_BEFORE -SSL_ST_OK = _lib.SSL_ST_OK -SSL_ST_RENEGOTIATE = _lib.SSL_ST_RENEGOTIATE - -SSL_CB_LOOP = _lib.SSL_CB_LOOP -SSL_CB_EXIT = _lib.SSL_CB_EXIT -SSL_CB_READ = _lib.SSL_CB_READ -SSL_CB_WRITE = _lib.SSL_CB_WRITE -SSL_CB_ALERT = _lib.SSL_CB_ALERT -SSL_CB_READ_ALERT = _lib.SSL_CB_READ_ALERT -SSL_CB_WRITE_ALERT = _lib.SSL_CB_WRITE_ALERT -SSL_CB_ACCEPT_LOOP = _lib.SSL_CB_ACCEPT_LOOP -SSL_CB_ACCEPT_EXIT = _lib.SSL_CB_ACCEPT_EXIT -SSL_CB_CONNECT_LOOP = _lib.SSL_CB_CONNECT_LOOP -SSL_CB_CONNECT_EXIT = _lib.SSL_CB_CONNECT_EXIT -SSL_CB_HANDSHAKE_START = _lib.SSL_CB_HANDSHAKE_START -SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE - -class Error(Exception): - """ - An error occurred in an `OpenSSL.SSL` API. - """ - - - -_raise_current_error = partial(_exception_from_error_queue, Error) - - -class WantReadError(Error): - pass - - - -class WantWriteError(Error): - pass - - - -class WantX509LookupError(Error): - pass - - - -class ZeroReturnError(Error): - pass - - - -class SysCallError(Error): - pass - - -class _CallbackExceptionHelper(object): - """ - A base class for wrapper classes that allow for intelligent exception - handling in OpenSSL callbacks. - - :ivar list _problems: Any exceptions that occurred while executing in a - context where they could not be raised in the normal way. Typically - this is because OpenSSL has called into some Python code and requires a - return value. The exceptions are saved to be raised later when it is - possible to do so. - """ - def __init__(self): - self._problems = [] - - - def raise_if_problem(self): - """ - Raise an exception from the OpenSSL error queue or that was previously - captured whe running a callback. - """ - if self._problems: - try: - _raise_current_error() - except Error: - pass - raise self._problems.pop(0) - - -class _VerifyHelper(_CallbackExceptionHelper): - """ - Wrap a callback such that it can be used as a certificate verification - callback. - """ - def __init__(self, callback): - _CallbackExceptionHelper.__init__(self) - - @wraps(callback) - def wrapper(ok, store_ctx): - cert = X509.__new__(X509) - cert._x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx) - error_number = _lib.X509_STORE_CTX_get_error(store_ctx) - error_depth = _lib.X509_STORE_CTX_get_error_depth(store_ctx) - - index = _lib.SSL_get_ex_data_X509_STORE_CTX_idx() - ssl = _lib.X509_STORE_CTX_get_ex_data(store_ctx, index) - connection = Connection._reverse_mapping[ssl] - - try: - result = callback(connection, cert, error_number, error_depth, ok) - except Exception as e: - self._problems.append(e) - return 0 - else: - if result: - _lib.X509_STORE_CTX_set_error(store_ctx, _lib.X509_V_OK) - return 1 - else: - return 0 - - self.callback = _ffi.callback( - "int (*)(int, X509_STORE_CTX *)", wrapper) - - -class _NpnAdvertiseHelper(_CallbackExceptionHelper): - """ - Wrap a callback such that it can be used as an NPN advertisement callback. - """ - def __init__(self, callback): - _CallbackExceptionHelper.__init__(self) - - @wraps(callback) - def wrapper(ssl, out, outlen, arg): - try: - conn = Connection._reverse_mapping[ssl] - protos = callback(conn) - - # Join the protocols into a Python bytestring, length-prefixing - # each element. - protostr = b''.join( - chain.from_iterable((int2byte(len(p)), p) for p in protos) - ) - - # Save our callback arguments on the connection object. This is - # done to make sure that they don't get freed before OpenSSL - # uses them. Then, return them appropriately in the output - # parameters. - conn._npn_advertise_callback_args = [ - _ffi.new("unsigned int *", len(protostr)), - _ffi.new("unsigned char[]", protostr), - ] - outlen[0] = conn._npn_advertise_callback_args[0][0] - out[0] = conn._npn_advertise_callback_args[1] - return 0 - except Exception as e: - self._problems.append(e) - return 2 # SSL_TLSEXT_ERR_ALERT_FATAL - - self.callback = _ffi.callback( - "int (*)(SSL *, const unsigned char **, unsigned int *, void *)", - wrapper - ) - - -class _NpnSelectHelper(_CallbackExceptionHelper): - """ - Wrap a callback such that it can be used as an NPN selection callback. - """ - def __init__(self, callback): - _CallbackExceptionHelper.__init__(self) - - @wraps(callback) - def wrapper(ssl, out, outlen, in_, inlen, arg): - try: - conn = Connection._reverse_mapping[ssl] - - # The string passed to us is actually made up of multiple - # length-prefixed bytestrings. We need to split that into a - # list. - instr = _ffi.buffer(in_, inlen)[:] - protolist = [] - while instr: - l = indexbytes(instr, 0) - proto = instr[1:l+1] - protolist.append(proto) - instr = instr[l+1:] - - # Call the callback - outstr = callback(conn, protolist) - - # Save our callback arguments on the connection object. This is - # done to make sure that they don't get freed before OpenSSL - # uses them. Then, return them appropriately in the output - # parameters. - conn._npn_select_callback_args = [ - _ffi.new("unsigned char *", len(outstr)), - _ffi.new("unsigned char[]", outstr), - ] - outlen[0] = conn._npn_select_callback_args[0][0] - out[0] = conn._npn_select_callback_args[1] - return 0 - except Exception as e: - self._problems.append(e) - return 2 # SSL_TLSEXT_ERR_ALERT_FATAL - - self.callback = _ffi.callback( - "int (*)(SSL *, unsigned char **, unsigned char *, " - "const unsigned char *, unsigned int, void *)", - wrapper - ) - - -class _ALPNSelectHelper(_CallbackExceptionHelper): - """ - Wrap a callback such that it can be used as an ALPN selection callback. - """ - def __init__(self, callback): - _CallbackExceptionHelper.__init__(self) - - @wraps(callback) - def wrapper(ssl, out, outlen, in_, inlen, arg): - try: - conn = Connection._reverse_mapping[ssl] - - # The string passed to us is made up of multiple - # length-prefixed bytestrings. We need to split that into a - # list. - instr = _ffi.buffer(in_, inlen)[:] - protolist = [] - while instr: - encoded_len = indexbytes(instr, 0) - proto = instr[1:encoded_len + 1] - protolist.append(proto) - instr = instr[encoded_len + 1:] - - # Call the callback - outstr = callback(conn, protolist) - - if not isinstance(outstr, _binary_type): - raise TypeError("ALPN callback must return a bytestring.") - - # Save our callback arguments on the connection object to make - # sure that they don't get freed before OpenSSL can use them. - # Then, return them in the appropriate output parameters. - conn._alpn_select_callback_args = [ - _ffi.new("unsigned char *", len(outstr)), - _ffi.new("unsigned char[]", outstr), - ] - outlen[0] = conn._alpn_select_callback_args[0][0] - out[0] = conn._alpn_select_callback_args[1] - return 0 - except Exception as e: - self._problems.append(e) - return 2 # SSL_TLSEXT_ERR_ALERT_FATAL - - self.callback = _ffi.callback( - "int (*)(SSL *, unsigned char **, unsigned char *, " - "const unsigned char *, unsigned int, void *)", - wrapper - ) - - -def _asFileDescriptor(obj): - fd = None - if not isinstance(obj, integer_types): - meth = getattr(obj, "fileno", None) - if meth is not None: - obj = meth() - - if isinstance(obj, integer_types): - fd = obj - - if not isinstance(fd, integer_types): - raise TypeError("argument must be an int, or have a fileno() method.") - elif fd < 0: - raise ValueError( - "file descriptor cannot be a negative integer (%i)" % (fd,)) - - return fd - - - -def SSLeay_version(type): - """ - Return a string describing the version of OpenSSL in use. - - :param type: One of the SSLEAY_ constants defined in this module. - """ - return _ffi.string(_lib.SSLeay_version(type)) - - -def _requires_npn(func): - """ - Wraps any function that requires NPN support in OpenSSL, ensuring that - NotImplementedError is raised if NPN is not present. - """ - @wraps(func) - def wrapper(*args, **kwargs): - if not _lib.Cryptography_HAS_NEXTPROTONEG: - raise NotImplementedError("NPN not available.") - - return func(*args, **kwargs) - - return wrapper - - - -def _requires_alpn(func): - """ - Wraps any function that requires ALPN support in OpenSSL, ensuring that - NotImplementedError is raised if ALPN support is not present. - """ - @wraps(func) - def wrapper(*args, **kwargs): - if not _lib.Cryptography_HAS_ALPN: - raise NotImplementedError("ALPN not available.") - - return func(*args, **kwargs) - - return wrapper - - - -class Session(object): - pass - - - -class Context(object): - """ - :py:obj:`OpenSSL.SSL.Context` instances define the parameters for setting up - new SSL connections. - """ - _methods = { - SSLv2_METHOD: "SSLv2_method", - SSLv3_METHOD: "SSLv3_method", - SSLv23_METHOD: "SSLv23_method", - TLSv1_METHOD: "TLSv1_method", - TLSv1_1_METHOD: "TLSv1_1_method", - TLSv1_2_METHOD: "TLSv1_2_method", - } - _methods = dict( - (identifier, getattr(_lib, name)) - for (identifier, name) in _methods.items() - if getattr(_lib, name, None) is not None) - - - def __init__(self, method): - """ - :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, or - TLSv1_METHOD. - """ - if not isinstance(method, integer_types): - raise TypeError("method must be an integer") - - try: - method_func = self._methods[method] - except KeyError: - raise ValueError("No such protocol") - - method_obj = method_func() - if method_obj == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - context = _lib.SSL_CTX_new(method_obj) - if context == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - context = _ffi.gc(context, _lib.SSL_CTX_free) - - self._context = context - self._passphrase_helper = None - self._passphrase_callback = None - self._passphrase_userdata = None - self._verify_helper = None - self._verify_callback = None - self._info_callback = None - self._tlsext_servername_callback = None - self._app_data = None - self._npn_advertise_helper = None - self._npn_advertise_callback = None - self._npn_select_helper = None - self._npn_select_callback = None - self._alpn_select_helper = None - self._alpn_select_callback = None - - # SSL_CTX_set_app_data(self->ctx, self); - # SSL_CTX_set_mode(self->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | - # SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | - # SSL_MODE_AUTO_RETRY); - self.set_mode(_lib.SSL_MODE_ENABLE_PARTIAL_WRITE) - - - def load_verify_locations(self, cafile, capath=None): - """ - Let SSL know where we can find trusted certificates for the certificate - chain - - :param cafile: In which file we can find the certificates (``bytes`` or - ``unicode``). - :param capath: In which directory we can find the certificates - (``bytes`` or ``unicode``). - - :return: None - """ - if cafile is None: - cafile = _ffi.NULL - else: - cafile = _path_string(cafile) - - if capath is None: - capath = _ffi.NULL - else: - capath = _path_string(capath) - - load_result = _lib.SSL_CTX_load_verify_locations(self._context, cafile, capath) - if not load_result: - _raise_current_error() - - - def _wrap_callback(self, callback): - @wraps(callback) - def wrapper(size, verify, userdata): - return callback(size, verify, self._passphrase_userdata) - return _PassphraseHelper( - FILETYPE_PEM, wrapper, more_args=True, truncate=True) - - - def set_passwd_cb(self, callback, userdata=None): - """ - Set the passphrase callback - - :param callback: The Python callback to use - :param userdata: (optional) A Python object which will be given as - argument to the callback - :return: None - """ - if not callable(callback): - raise TypeError("callback must be callable") - - self._passphrase_helper = self._wrap_callback(callback) - self._passphrase_callback = self._passphrase_helper.callback - _lib.SSL_CTX_set_default_passwd_cb( - self._context, self._passphrase_callback) - self._passphrase_userdata = userdata - - - def set_default_verify_paths(self): - """ - Use the platform-specific CA certificate locations - - :return: None - """ - set_result = _lib.SSL_CTX_set_default_verify_paths(self._context) - if not set_result: - # TODO: This is untested. - _raise_current_error() - - - def use_certificate_chain_file(self, certfile): - """ - Load a certificate chain from a file - - :param certfile: The name of the certificate chain file (``bytes`` or - ``unicode``). - - :return: None - """ - certfile = _path_string(certfile) - - result = _lib.SSL_CTX_use_certificate_chain_file(self._context, certfile) - if not result: - _raise_current_error() - - - def use_certificate_file(self, certfile, filetype=FILETYPE_PEM): - """ - Load a certificate from a file - - :param certfile: The name of the certificate file (``bytes`` or - ``unicode``). - :param filetype: (optional) The encoding of the file, default is PEM - - :return: None - """ - certfile = _path_string(certfile) - if not isinstance(filetype, integer_types): - raise TypeError("filetype must be an integer") - - use_result = _lib.SSL_CTX_use_certificate_file(self._context, certfile, filetype) - if not use_result: - _raise_current_error() - - - def use_certificate(self, cert): - """ - Load a certificate from a X509 object - - :param cert: The X509 object - :return: None - """ - if not isinstance(cert, X509): - raise TypeError("cert must be an X509 instance") - - use_result = _lib.SSL_CTX_use_certificate(self._context, cert._x509) - if not use_result: - _raise_current_error() - - - def add_extra_chain_cert(self, certobj): - """ - Add certificate to chain - - :param certobj: The X509 certificate object to add to the chain - :return: None - """ - if not isinstance(certobj, X509): - raise TypeError("certobj must be an X509 instance") - - copy = _lib.X509_dup(certobj._x509) - add_result = _lib.SSL_CTX_add_extra_chain_cert(self._context, copy) - if not add_result: - # TODO: This is untested. - _lib.X509_free(copy) - _raise_current_error() - - - def _raise_passphrase_exception(self): - if self._passphrase_helper is None: - _raise_current_error() - exception = self._passphrase_helper.raise_if_problem(Error) - if exception is not None: - raise exception - - - def use_privatekey_file(self, keyfile, filetype=_UNSPECIFIED): - """ - Load a private key from a file - - :param keyfile: The name of the key file (``bytes`` or ``unicode``) - :param filetype: (optional) The encoding of the file, default is PEM - - :return: None - """ - keyfile = _path_string(keyfile) - - if filetype is _UNSPECIFIED: - filetype = FILETYPE_PEM - elif not isinstance(filetype, integer_types): - raise TypeError("filetype must be an integer") - - use_result = _lib.SSL_CTX_use_PrivateKey_file( - self._context, keyfile, filetype) - if not use_result: - self._raise_passphrase_exception() - - - def use_privatekey(self, pkey): - """ - Load a private key from a PKey object - - :param pkey: The PKey object - :return: None - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - - use_result = _lib.SSL_CTX_use_PrivateKey(self._context, pkey._pkey) - if not use_result: - self._raise_passphrase_exception() - - - def check_privatekey(self): - """ - Check that the private key and certificate match up - - :return: None (raises an exception if something's wrong) - """ - if not _lib.SSL_CTX_check_private_key(self._context): - _raise_current_error() - - - def load_client_ca(self, cafile): - """ - Load the trusted certificates that will be sent to the client (basically - telling the client "These are the guys I trust"). Does not actually - imply any of the certificates are trusted; that must be configured - separately. - - :param cafile: The name of the certificates file - :return: None - """ - - def set_session_id(self, buf): - """ - Set the session identifier. This is needed if you want to do session - resumption. - - :param buf: A Python object that can be safely converted to a string - :returns: None - """ - - def set_session_cache_mode(self, mode): - """ - Enable/disable session caching and specify the mode used. - - :param mode: One or more of the SESS_CACHE_* flags (combine using - bitwise or) - :returns: The previously set caching mode. - """ - if not isinstance(mode, integer_types): - raise TypeError("mode must be an integer") - - return _lib.SSL_CTX_set_session_cache_mode(self._context, mode) - - - def get_session_cache_mode(self): - """ - :returns: The currently used cache mode. - """ - return _lib.SSL_CTX_get_session_cache_mode(self._context) - - - def set_verify(self, mode, callback): - """ - Set the verify mode and verify callback - - :param mode: The verify mode, this is either VERIFY_NONE or - VERIFY_PEER combined with possible other flags - :param callback: The Python callback to use - :return: None - - See SSL_CTX_set_verify(3SSL) for further details. - """ - if not isinstance(mode, integer_types): - raise TypeError("mode must be an integer") - - if not callable(callback): - raise TypeError("callback must be callable") - - self._verify_helper = _VerifyHelper(callback) - self._verify_callback = self._verify_helper.callback - _lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback) - - - def set_verify_depth(self, depth): - """ - Set the verify depth - - :param depth: An integer specifying the verify depth - :return: None - """ - if not isinstance(depth, integer_types): - raise TypeError("depth must be an integer") - - _lib.SSL_CTX_set_verify_depth(self._context, depth) - - - def get_verify_mode(self): - """ - Get the verify mode - - :return: The verify mode - """ - return _lib.SSL_CTX_get_verify_mode(self._context) - - - def get_verify_depth(self): - """ - Get the verify depth - - :return: The verify depth - """ - return _lib.SSL_CTX_get_verify_depth(self._context) - - - def load_tmp_dh(self, dhfile): - """ - Load parameters for Ephemeral Diffie-Hellman - - :param dhfile: The file to load EDH parameters from (``bytes`` or - ``unicode``). - - :return: None - """ - dhfile = _path_string(dhfile) - - bio = _lib.BIO_new_file(dhfile, b"r") - if bio == _ffi.NULL: - _raise_current_error() - bio = _ffi.gc(bio, _lib.BIO_free) - - dh = _lib.PEM_read_bio_DHparams(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - dh = _ffi.gc(dh, _lib.DH_free) - _lib.SSL_CTX_set_tmp_dh(self._context, dh) - - - def set_tmp_ecdh(self, curve): - """ - Select a curve to use for ECDHE key exchange. - - :param curve: A curve object to use as returned by either - :py:meth:`OpenSSL.crypto.get_elliptic_curve` or - :py:meth:`OpenSSL.crypto.get_elliptic_curves`. - - :return: None - """ - _lib.SSL_CTX_set_tmp_ecdh(self._context, curve._to_EC_KEY()) - - - def set_cipher_list(self, cipher_list): - """ - Change the cipher list - - :param cipher_list: A cipher list, see ciphers(1) - :return: None - """ - if isinstance(cipher_list, _text_type): - cipher_list = cipher_list.encode("ascii") - - if not isinstance(cipher_list, bytes): - raise TypeError("cipher_list must be bytes or unicode") - - result = _lib.SSL_CTX_set_cipher_list(self._context, cipher_list) - if not result: - _raise_current_error() - - - def set_client_ca_list(self, certificate_authorities): - """ - Set the list of preferred client certificate signers for this server context. - - This list of certificate authorities will be sent to the client when the - server requests a client certificate. - - :param certificate_authorities: a sequence of X509Names. - :return: None - """ - name_stack = _lib.sk_X509_NAME_new_null() - if name_stack == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - try: - for ca_name in certificate_authorities: - if not isinstance(ca_name, X509Name): - raise TypeError( - "client CAs must be X509Name objects, not %s objects" % ( - type(ca_name).__name__,)) - copy = _lib.X509_NAME_dup(ca_name._name) - if copy == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - push_result = _lib.sk_X509_NAME_push(name_stack, copy) - if not push_result: - _lib.X509_NAME_free(copy) - _raise_current_error() - except: - _lib.sk_X509_NAME_free(name_stack) - raise - - _lib.SSL_CTX_set_client_CA_list(self._context, name_stack) - - - def add_client_ca(self, certificate_authority): - """ - Add the CA certificate to the list of preferred signers for this context. - - The list of certificate authorities will be sent to the client when the - server requests a client certificate. - - :param certificate_authority: certificate authority's X509 certificate. - :return: None - """ - if not isinstance(certificate_authority, X509): - raise TypeError("certificate_authority must be an X509 instance") - - add_result = _lib.SSL_CTX_add_client_CA( - self._context, certificate_authority._x509) - if not add_result: - # TODO: This is untested. - _raise_current_error() - - - def set_timeout(self, timeout): - """ - Set session timeout - - :param timeout: The timeout in seconds - :return: The previous session timeout - """ - if not isinstance(timeout, integer_types): - raise TypeError("timeout must be an integer") - - return _lib.SSL_CTX_set_timeout(self._context, timeout) - - - def get_timeout(self): - """ - Get the session timeout - - :return: The session timeout - """ - return _lib.SSL_CTX_get_timeout(self._context) - - - def set_info_callback(self, callback): - """ - Set the info callback - - :param callback: The Python callback to use - :return: None - """ - @wraps(callback) - def wrapper(ssl, where, return_code): - callback(Connection._reverse_mapping[ssl], where, return_code) - self._info_callback = _ffi.callback( - "void (*)(const SSL *, int, int)", wrapper) - _lib.SSL_CTX_set_info_callback(self._context, self._info_callback) - - - def get_app_data(self): - """ - Get the application data (supplied via set_app_data()) - - :return: The application data - """ - return self._app_data - - - def set_app_data(self, data): - """ - Set the application data (will be returned from get_app_data()) - - :param data: Any Python object - :return: None - """ - self._app_data = data - - - def get_cert_store(self): - """ - Get the certificate store for the context. - - :return: A X509Store object or None if it does not have one. - """ - store = _lib.SSL_CTX_get_cert_store(self._context) - if store == _ffi.NULL: - # TODO: This is untested. - return None - - pystore = X509Store.__new__(X509Store) - pystore._store = store - return pystore - - - def set_options(self, options): - """ - Add options. Options set before are not cleared! - - :param options: The options to add. - :return: The new option bitmask. - """ - if not isinstance(options, integer_types): - raise TypeError("options must be an integer") - - return _lib.SSL_CTX_set_options(self._context, options) - - - def set_mode(self, mode): - """ - Add modes via bitmask. Modes set before are not cleared! - - :param mode: The mode to add. - :return: The new mode bitmask. - """ - if not isinstance(mode, integer_types): - raise TypeError("mode must be an integer") - - return _lib.SSL_CTX_set_mode(self._context, mode) - - - def set_tlsext_servername_callback(self, callback): - """ - Specify a callback function to be called when clients specify a server name. - - :param callback: The callback function. It will be invoked with one - argument, the Connection instance. - """ - @wraps(callback) - def wrapper(ssl, alert, arg): - callback(Connection._reverse_mapping[ssl]) - return 0 - - self._tlsext_servername_callback = _ffi.callback( - "int (*)(const SSL *, int *, void *)", wrapper) - _lib.SSL_CTX_set_tlsext_servername_callback( - self._context, self._tlsext_servername_callback) - - - @_requires_npn - def set_npn_advertise_callback(self, callback): - """ - Specify a callback function that will be called when offering `Next - Protocol Negotiation - <https://technotes.googlecode.com/git/nextprotoneg.html>`_ as a server. - - :param callback: The callback function. It will be invoked with one - argument, the Connection instance. It should return a list of - bytestrings representing the advertised protocols, like - ``[b'http/1.1', b'spdy/2']``. - """ - self._npn_advertise_helper = _NpnAdvertiseHelper(callback) - self._npn_advertise_callback = self._npn_advertise_helper.callback - _lib.SSL_CTX_set_next_protos_advertised_cb( - self._context, self._npn_advertise_callback, _ffi.NULL) - - - @_requires_npn - def set_npn_select_callback(self, callback): - """ - Specify a callback function that will be called when a server offers - Next Protocol Negotiation options. - - :param callback: The callback function. It will be invoked with two - arguments: the Connection, and a list of offered protocols as - bytestrings, e.g. ``[b'http/1.1', b'spdy/2']``. It should return - one of those bytestrings, the chosen protocol. - """ - self._npn_select_helper = _NpnSelectHelper(callback) - self._npn_select_callback = self._npn_select_helper.callback - _lib.SSL_CTX_set_next_proto_select_cb( - self._context, self._npn_select_callback, _ffi.NULL) - - @_requires_alpn - def set_alpn_protos(self, protos): - """ - Specify the clients ALPN protocol list. - - These protocols are offered to the server during protocol negotiation. - - :param protos: A list of the protocols to be offered to the server. - This list should be a Python list of bytestrings representing the - protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``. - """ - # Take the list of protocols and join them together, prefixing them - # with their lengths. - protostr = b''.join( - chain.from_iterable((int2byte(len(p)), p) for p in protos) - ) - - # Build a C string from the list. We don't need to save this off - # because OpenSSL immediately copies the data out. - input_str = _ffi.new("unsigned char[]", protostr) - input_str_len = _ffi.cast("unsigned", len(protostr)) - _lib.SSL_CTX_set_alpn_protos(self._context, input_str, input_str_len) - - @_requires_alpn - def set_alpn_select_callback(self, callback): - """ - Set the callback to handle ALPN protocol choice. - - :param callback: The callback function. It will be invoked with two - arguments: the Connection, and a list of offered protocols as - bytestrings, e.g ``[b'http/1.1', b'spdy/2']``. It should return - one of those bytestrings, the chosen protocol. - """ - self._alpn_select_helper = _ALPNSelectHelper(callback) - self._alpn_select_callback = self._alpn_select_helper.callback - _lib.SSL_CTX_set_alpn_select_cb( - self._context, self._alpn_select_callback, _ffi.NULL) - -ContextType = Context - - - -class Connection(object): - """ - """ - _reverse_mapping = WeakValueDictionary() - - def __init__(self, context, socket=None): - """ - Create a new Connection object, using the given OpenSSL.SSL.Context - instance and socket. - - :param context: An SSL Context to use for this connection - :param socket: The socket to use for transport layer - """ - if not isinstance(context, Context): - raise TypeError("context must be a Context instance") - - ssl = _lib.SSL_new(context._context) - self._ssl = _ffi.gc(ssl, _lib.SSL_free) - self._context = context - - # References to strings used for Next Protocol Negotiation. OpenSSL's - # header files suggest that these might get copied at some point, but - # doesn't specify when, so we store them here to make sure they don't - # get freed before OpenSSL uses them. - self._npn_advertise_callback_args = None - self._npn_select_callback_args = None - - # References to strings used for Application Layer Protocol - # Negotiation. These strings get copied at some point but it's well - # after the callback returns, so we have to hang them somewhere to - # avoid them getting freed. - self._alpn_select_callback_args = None - - self._reverse_mapping[self._ssl] = self - - if socket is None: - self._socket = None - # Don't set up any gc for these, SSL_free will take care of them. - self._into_ssl = _lib.BIO_new(_lib.BIO_s_mem()) - self._from_ssl = _lib.BIO_new(_lib.BIO_s_mem()) - - if self._into_ssl == _ffi.NULL or self._from_ssl == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - _lib.SSL_set_bio(self._ssl, self._into_ssl, self._from_ssl) - else: - self._into_ssl = None - self._from_ssl = None - self._socket = socket - set_result = _lib.SSL_set_fd(self._ssl, _asFileDescriptor(self._socket)) - if not set_result: - # TODO: This is untested. - _raise_current_error() - - - def __getattr__(self, name): - """ - Look up attributes on the wrapped socket object if they are not found on - the Connection object. - """ - return getattr(self._socket, name) - - - def _raise_ssl_error(self, ssl, result): - if self._context._verify_helper is not None: - self._context._verify_helper.raise_if_problem() - if self._context._npn_advertise_helper is not None: - self._context._npn_advertise_helper.raise_if_problem() - if self._context._npn_select_helper is not None: - self._context._npn_select_helper.raise_if_problem() - if self._context._alpn_select_helper is not None: - self._context._alpn_select_helper.raise_if_problem() - - error = _lib.SSL_get_error(ssl, result) - if error == _lib.SSL_ERROR_WANT_READ: - raise WantReadError() - elif error == _lib.SSL_ERROR_WANT_WRITE: - raise WantWriteError() - elif error == _lib.SSL_ERROR_ZERO_RETURN: - raise ZeroReturnError() - elif error == _lib.SSL_ERROR_WANT_X509_LOOKUP: - # TODO: This is untested. - raise WantX509LookupError() - elif error == _lib.SSL_ERROR_SYSCALL: - if _lib.ERR_peek_error() == 0: - if result < 0: - if platform == "win32": - errno = _ffi.getwinerror()[0] - else: - errno = _ffi.errno - raise SysCallError(errno, errorcode.get(errno)) - else: - raise SysCallError(-1, "Unexpected EOF") - else: - # TODO: This is untested. - _raise_current_error() - elif error == _lib.SSL_ERROR_NONE: - pass - else: - _raise_current_error() - - - def get_context(self): - """ - Get session context - """ - return self._context - - - def set_context(self, context): - """ - Switch this connection to a new session context - - :param context: A :py:class:`Context` instance giving the new session - context to use. - """ - if not isinstance(context, Context): - raise TypeError("context must be a Context instance") - - _lib.SSL_set_SSL_CTX(self._ssl, context._context) - self._context = context - - - def get_servername(self): - """ - Retrieve the servername extension value if provided in the client hello - message, or None if there wasn't one. - - :return: A byte string giving the server name or :py:data:`None`. - """ - name = _lib.SSL_get_servername(self._ssl, _lib.TLSEXT_NAMETYPE_host_name) - if name == _ffi.NULL: - return None - - return _ffi.string(name) - - - def set_tlsext_host_name(self, name): - """ - Set the value of the servername extension to send in the client hello. - - :param name: A byte string giving the name. - """ - if not isinstance(name, bytes): - raise TypeError("name must be a byte string") - elif b"\0" in name: - raise TypeError("name must not contain NUL byte") - - # XXX I guess this can fail sometimes? - _lib.SSL_set_tlsext_host_name(self._ssl, name) - - - def pending(self): - """ - Get the number of bytes that can be safely read from the connection - - :return: The number of bytes available in the receive buffer. - """ - return _lib.SSL_pending(self._ssl) - - - def send(self, buf, flags=0): - """ - Send data on the connection. NOTE: If you get one of the WantRead, - WantWrite or WantX509Lookup exceptions on this, you have to call the - method again with the SAME buffer. - - :param buf: The string, buffer or memoryview to send - :param flags: (optional) Included for compatibility with the socket - API, the value is ignored - :return: The number of bytes written - """ - # Backward compatibility - buf = _text_to_bytes_and_warn("buf", buf) - - if isinstance(buf, _memoryview): - buf = buf.tobytes() - if isinstance(buf, _buffer): - buf = str(buf) - if not isinstance(buf, bytes): - raise TypeError("data must be a memoryview, buffer or byte string") - - result = _lib.SSL_write(self._ssl, buf, len(buf)) - self._raise_ssl_error(self._ssl, result) - return result - write = send - - - def sendall(self, buf, flags=0): - """ - Send "all" data on the connection. This calls send() repeatedly until - all data is sent. If an error occurs, it's impossible to tell how much - data has been sent. - - :param buf: The string, buffer or memoryview to send - :param flags: (optional) Included for compatibility with the socket - API, the value is ignored - :return: The number of bytes written - """ - buf = _text_to_bytes_and_warn("buf", buf) - - if isinstance(buf, _memoryview): - buf = buf.tobytes() - if isinstance(buf, _buffer): - buf = str(buf) - if not isinstance(buf, bytes): - raise TypeError("buf must be a memoryview, buffer or byte string") - - left_to_send = len(buf) - total_sent = 0 - data = _ffi.new("char[]", buf) - - while left_to_send: - result = _lib.SSL_write(self._ssl, data + total_sent, left_to_send) - self._raise_ssl_error(self._ssl, result) - total_sent += result - left_to_send -= result - - - def recv(self, bufsiz, flags=None): - """ - Receive data on the connection. NOTE: If you get one of the WantRead, - WantWrite or WantX509Lookup exceptions on this, you have to call the - method again with the SAME buffer. - - :param bufsiz: The maximum number of bytes to read - :param flags: (optional) Included for compatibility with the socket - API, the value is ignored - :return: The string read from the Connection - """ - buf = _ffi.new("char[]", bufsiz) - result = _lib.SSL_read(self._ssl, buf, bufsiz) - self._raise_ssl_error(self._ssl, result) - return _ffi.buffer(buf, result)[:] - read = recv - - - def recv_into(self, buffer, nbytes=None, flags=None): - """ - Receive data on the connection and store the data into a buffer rather - than creating a new string. - - :param buffer: The buffer to copy into. - :param nbytes: (optional) The maximum number of bytes to read into the - buffer. If not present, defaults to the size of the buffer. If - larger than the size of the buffer, is reduced to the size of the - buffer. - :param flags: (optional) Included for compatibility with the socket - API, the value is ignored. - :return: The number of bytes read into the buffer. - """ - if nbytes is None: - nbytes = len(buffer) - else: - nbytes = min(nbytes, len(buffer)) - - # We need to create a temporary buffer. This is annoying, it would be - # better if we could pass memoryviews straight into the SSL_read call, - # but right now we can't. Revisit this if CFFI gets that ability. - buf = _ffi.new("char[]", nbytes) - result = _lib.SSL_read(self._ssl, buf, nbytes) - self._raise_ssl_error(self._ssl, result) - - # This strange line is all to avoid a memory copy. The buffer protocol - # should allow us to assign a CFFI buffer to the LHS of this line, but - # on CPython 3.3+ that segfaults. As a workaround, we can temporarily - # wrap it in a memoryview, except on Python 2.6 which doesn't have a - # memoryview type. - try: - buffer[:result] = memoryview(_ffi.buffer(buf, result)) - except NameError: - buffer[:result] = _ffi.buffer(buf, result) - - return result - - - def _handle_bio_errors(self, bio, result): - if _lib.BIO_should_retry(bio): - if _lib.BIO_should_read(bio): - raise WantReadError() - elif _lib.BIO_should_write(bio): - # TODO: This is untested. - raise WantWriteError() - elif _lib.BIO_should_io_special(bio): - # TODO: This is untested. I think io_special means the socket - # BIO has a not-yet connected socket. - raise ValueError("BIO_should_io_special") - else: - # TODO: This is untested. - raise ValueError("unknown bio failure") - else: - # TODO: This is untested. - _raise_current_error() - - - def bio_read(self, bufsiz): - """ - When using non-socket connections this function reads the "dirty" data - that would have traveled away on the network. - - :param bufsiz: The maximum number of bytes to read - :return: The string read. - """ - if self._from_ssl is None: - raise TypeError("Connection sock was not None") - - if not isinstance(bufsiz, integer_types): - raise TypeError("bufsiz must be an integer") - - buf = _ffi.new("char[]", bufsiz) - result = _lib.BIO_read(self._from_ssl, buf, bufsiz) - if result <= 0: - self._handle_bio_errors(self._from_ssl, result) - - return _ffi.buffer(buf, result)[:] - - - def bio_write(self, buf): - """ - When using non-socket connections this function sends "dirty" data that - would have traveled in on the network. - - :param buf: The string to put into the memory BIO. - :return: The number of bytes written - """ - buf = _text_to_bytes_and_warn("buf", buf) - - if self._into_ssl is None: - raise TypeError("Connection sock was not None") - - if not isinstance(buf, bytes): - raise TypeError("buf must be a byte string") - - result = _lib.BIO_write(self._into_ssl, buf, len(buf)) - if result <= 0: - self._handle_bio_errors(self._into_ssl, result) - return result - - - def renegotiate(self): - """ - Renegotiate the session - - :return: True if the renegotiation can be started, false otherwise - """ - - def do_handshake(self): - """ - Perform an SSL handshake (usually called after renegotiate() or one of - set_*_state()). This can raise the same exceptions as send and recv. - - :return: None. - """ - result = _lib.SSL_do_handshake(self._ssl) - self._raise_ssl_error(self._ssl, result) - - - def renegotiate_pending(self): - """ - Check if there's a renegotiation in progress, it will return false once - a renegotiation is finished. - - :return: Whether there's a renegotiation in progress - """ - - def total_renegotiations(self): - """ - Find out the total number of renegotiations. - - :return: The number of renegotiations. - """ - return _lib.SSL_total_renegotiations(self._ssl) - - - def connect(self, addr): - """ - Connect to remote host and set up client-side SSL - - :param addr: A remote address - :return: What the socket's connect method returns - """ - _lib.SSL_set_connect_state(self._ssl) - return self._socket.connect(addr) - - - def connect_ex(self, addr): - """ - Connect to remote host and set up client-side SSL. Note that if the socket's - connect_ex method doesn't return 0, SSL won't be initialized. - - :param addr: A remove address - :return: What the socket's connect_ex method returns - """ - connect_ex = self._socket.connect_ex - self.set_connect_state() - return connect_ex(addr) - - - def accept(self): - """ - Accept incoming connection and set up SSL on it - - :return: A (conn,addr) pair where conn is a Connection and addr is an - address - """ - client, addr = self._socket.accept() - conn = Connection(self._context, client) - conn.set_accept_state() - return (conn, addr) - - - def bio_shutdown(self): - """ - When using non-socket connections this function signals end of - data on the input for this connection. - - :return: None - """ - if self._from_ssl is None: - raise TypeError("Connection sock was not None") - - _lib.BIO_set_mem_eof_return(self._into_ssl, 0) - - - def shutdown(self): - """ - Send closure alert - - :return: True if the shutdown completed successfully (i.e. both sides - have sent closure alerts), false otherwise (i.e. you have to - wait for a ZeroReturnError on a recv() method call - """ - result = _lib.SSL_shutdown(self._ssl) - if result < 0: - self._raise_ssl_error(self._ssl, result) - elif result > 0: - return True - else: - return False - - - def get_cipher_list(self): - """ - Get the session cipher list - - :return: A list of cipher strings - """ - ciphers = [] - for i in count(): - result = _lib.SSL_get_cipher_list(self._ssl, i) - if result == _ffi.NULL: - break - ciphers.append(_native(_ffi.string(result))) - return ciphers - - - def get_client_ca_list(self): - """ - Get CAs whose certificates are suggested for client authentication. - - :return: If this is a server connection, a list of X509Names representing - the acceptable CAs as set by :py:meth:`OpenSSL.SSL.Context.set_client_ca_list` or - :py:meth:`OpenSSL.SSL.Context.add_client_ca`. If this is a client connection, - the list of such X509Names sent by the server, or an empty list if that - has not yet happened. - """ - ca_names = _lib.SSL_get_client_CA_list(self._ssl) - if ca_names == _ffi.NULL: - # TODO: This is untested. - return [] - - result = [] - for i in range(_lib.sk_X509_NAME_num(ca_names)): - name = _lib.sk_X509_NAME_value(ca_names, i) - copy = _lib.X509_NAME_dup(name) - if copy == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - pyname = X509Name.__new__(X509Name) - pyname._name = _ffi.gc(copy, _lib.X509_NAME_free) - result.append(pyname) - return result - - - def makefile(self): - """ - The makefile() method is not implemented, since there is no dup semantics - for SSL connections - - :raise: NotImplementedError - """ - raise NotImplementedError("Cannot make file object of OpenSSL.SSL.Connection") - - - def get_app_data(self): - """ - Get application data - - :return: The application data - """ - return self._app_data - - - def set_app_data(self, data): - """ - Set application data - - :param data - The application data - :return: None - """ - self._app_data = data - - - def get_shutdown(self): - """ - Get shutdown state - - :return: The shutdown state, a bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. - """ - return _lib.SSL_get_shutdown(self._ssl) - - - def set_shutdown(self, state): - """ - Set shutdown state - - :param state - bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. - :return: None - """ - if not isinstance(state, integer_types): - raise TypeError("state must be an integer") - - _lib.SSL_set_shutdown(self._ssl, state) - - - def state_string(self): - """ - Get a verbose state description - - :return: A string representing the state - """ - - def server_random(self): - """ - Get a copy of the server hello nonce. - - :return: A string representing the state - """ - if self._ssl.session == _ffi.NULL: - return None - return _ffi.buffer( - self._ssl.s3.server_random, - _lib.SSL3_RANDOM_SIZE)[:] - - - def client_random(self): - """ - Get a copy of the client hello nonce. - - :return: A string representing the state - """ - if self._ssl.session == _ffi.NULL: - return None - return _ffi.buffer( - self._ssl.s3.client_random, - _lib.SSL3_RANDOM_SIZE)[:] - - - def master_key(self): - """ - Get a copy of the master key. - - :return: A string representing the state - """ - if self._ssl.session == _ffi.NULL: - return None - return _ffi.buffer( - self._ssl.session.master_key, - self._ssl.session.master_key_length)[:] - - - def sock_shutdown(self, *args, **kwargs): - """ - See shutdown(2) - - :return: What the socket's shutdown() method returns - """ - return self._socket.shutdown(*args, **kwargs) - - - def get_peer_certificate(self): - """ - Retrieve the other side's certificate (if any) - - :return: The peer's certificate - """ - cert = _lib.SSL_get_peer_certificate(self._ssl) - if cert != _ffi.NULL: - pycert = X509.__new__(X509) - pycert._x509 = _ffi.gc(cert, _lib.X509_free) - return pycert - return None - - - def get_peer_cert_chain(self): - """ - Retrieve the other side's certificate (if any) - - :return: A list of X509 instances giving the peer's certificate chain, - or None if it does not have one. - """ - cert_stack = _lib.SSL_get_peer_cert_chain(self._ssl) - if cert_stack == _ffi.NULL: - return None - - result = [] - for i in range(_lib.sk_X509_num(cert_stack)): - # TODO could incref instead of dup here - cert = _lib.X509_dup(_lib.sk_X509_value(cert_stack, i)) - pycert = X509.__new__(X509) - pycert._x509 = _ffi.gc(cert, _lib.X509_free) - result.append(pycert) - return result - - - def want_read(self): - """ - Checks if more data has to be read from the transport layer to complete an - operation. - - :return: True iff more data has to be read - """ - return _lib.SSL_want_read(self._ssl) - - - def want_write(self): - """ - Checks if there is data to write to the transport layer to complete an - operation. - - :return: True iff there is data to write - """ - return _lib.SSL_want_write(self._ssl) - - - def set_accept_state(self): - """ - Set the connection to work in server mode. The handshake will be handled - automatically by read/write. - - :return: None - """ - _lib.SSL_set_accept_state(self._ssl) - - - def set_connect_state(self): - """ - Set the connection to work in client mode. The handshake will be handled - automatically by read/write. - - :return: None - """ - _lib.SSL_set_connect_state(self._ssl) - - - def get_session(self): - """ - Returns the Session currently used. - - @return: An instance of :py:class:`OpenSSL.SSL.Session` or :py:obj:`None` if - no session exists. - """ - session = _lib.SSL_get1_session(self._ssl) - if session == _ffi.NULL: - return None - - pysession = Session.__new__(Session) - pysession._session = _ffi.gc(session, _lib.SSL_SESSION_free) - return pysession - - - def set_session(self, session): - """ - Set the session to be used when the TLS/SSL connection is established. - - :param session: A Session instance representing the session to use. - :returns: None - """ - if not isinstance(session, Session): - raise TypeError("session must be a Session instance") - - result = _lib.SSL_set_session(self._ssl, session._session) - if not result: - _raise_current_error() - - - def _get_finished_message(self, function): - """ - Helper to implement :py:meth:`get_finished` and - :py:meth:`get_peer_finished`. - - :param function: Either :py:data:`SSL_get_finished`: or - :py:data:`SSL_get_peer_finished`. - - :return: :py:data:`None` if the desired message has not yet been - received, otherwise the contents of the message. - :rtype: :py:class:`bytes` or :py:class:`NoneType` - """ - # The OpenSSL documentation says nothing about what might happen if the - # count argument given is zero. Specifically, it doesn't say whether - # the output buffer may be NULL in that case or not. Inspection of the - # implementation reveals that it calls memcpy() unconditionally. - # Section 7.1.4, paragraph 1 of the C standard suggests that - # memcpy(NULL, source, 0) is not guaranteed to produce defined (let - # alone desirable) behavior (though it probably does on just about - # every implementation...) - # - # Allocate a tiny buffer to pass in (instead of just passing NULL as - # one might expect) for the initial call so as to be safe against this - # potentially undefined behavior. - empty = _ffi.new("char[]", 0) - size = function(self._ssl, empty, 0) - if size == 0: - # No Finished message so far. - return None - - buf = _ffi.new("char[]", size) - function(self._ssl, buf, size) - return _ffi.buffer(buf, size)[:] - - - def get_finished(self): - """ - Obtain the latest `handshake finished` message sent to the peer. - - :return: The contents of the message or :py:obj:`None` if the TLS - handshake has not yet completed. - :rtype: :py:class:`bytes` or :py:class:`NoneType` - """ - return self._get_finished_message(_lib.SSL_get_finished) - - - def get_peer_finished(self): - """ - Obtain the latest `handshake finished` message received from the peer. - - :return: The contents of the message or :py:obj:`None` if the TLS - handshake has not yet completed. - :rtype: :py:class:`bytes` or :py:class:`NoneType` - """ - return self._get_finished_message(_lib.SSL_get_peer_finished) - - - def get_cipher_name(self): - """ - Obtain the name of the currently used cipher. - - :returns: The name of the currently used cipher or :py:obj:`None` - if no connection has been established. - :rtype: :py:class:`unicode` or :py:class:`NoneType` - """ - cipher = _lib.SSL_get_current_cipher(self._ssl) - if cipher == _ffi.NULL: - return None - else: - name = _ffi.string(_lib.SSL_CIPHER_get_name(cipher)) - return name.decode("utf-8") - - - def get_cipher_bits(self): - """ - Obtain the number of secret bits of the currently used cipher. - - :returns: The number of secret bits of the currently used cipher - or :py:obj:`None` if no connection has been established. - :rtype: :py:class:`int` or :py:class:`NoneType` - """ - cipher = _lib.SSL_get_current_cipher(self._ssl) - if cipher == _ffi.NULL: - return None - else: - return _lib.SSL_CIPHER_get_bits(cipher, _ffi.NULL) - - - def get_cipher_version(self): - """ - Obtain the protocol version of the currently used cipher. - - :returns: The protocol name of the currently used cipher - or :py:obj:`None` if no connection has been established. - :rtype: :py:class:`unicode` or :py:class:`NoneType` - """ - cipher = _lib.SSL_get_current_cipher(self._ssl) - if cipher == _ffi.NULL: - return None - else: - version =_ffi.string(_lib.SSL_CIPHER_get_version(cipher)) - return version.decode("utf-8") - - - @_requires_npn - def get_next_proto_negotiated(self): - """ - Get the protocol that was negotiated by NPN. - """ - data = _ffi.new("unsigned char **") - data_len = _ffi.new("unsigned int *") - - _lib.SSL_get0_next_proto_negotiated(self._ssl, data, data_len) - - return _ffi.buffer(data[0], data_len[0])[:] - - @_requires_alpn - def set_alpn_protos(self, protos): - """ - Specify the client's ALPN protocol list. - - These protocols are offered to the server during protocol negotiation. - - :param protos: A list of the protocols to be offered to the server. - This list should be a Python list of bytestrings representing the - protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``. - """ - # Take the list of protocols and join them together, prefixing them - # with their lengths. - protostr = b''.join( - chain.from_iterable((int2byte(len(p)), p) for p in protos) - ) - - # Build a C string from the list. We don't need to save this off - # because OpenSSL immediately copies the data out. - input_str = _ffi.new("unsigned char[]", protostr) - input_str_len = _ffi.cast("unsigned", len(protostr)) - _lib.SSL_set_alpn_protos(self._ssl, input_str, input_str_len) - - - def get_alpn_proto_negotiated(self): - """ - Get the protocol that was negotiated by ALPN. - """ - if not _lib.Cryptography_HAS_ALPN: - raise NotImplementedError("ALPN not available") - - data = _ffi.new("unsigned char **") - data_len = _ffi.new("unsigned int *") - - _lib.SSL_get0_alpn_selected(self._ssl, data, data_len) - - if not data_len: - return b'' - - return _ffi.buffer(data[0], data_len[0])[:] - - - -ConnectionType = Connection - -# This is similar to the initialization calls at the end of OpenSSL/crypto.py -# but is exercised mostly by the Context initializer. -_lib.SSL_library_init() diff --git a/lib/OpenSSL/__init__.py b/lib/OpenSSL/__init__.py deleted file mode 100644 index db96e1fbde99865b5309cf599d8b9b316da859ed..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (C) AB Strakt -# See LICENSE for details. - -""" -pyOpenSSL - A simple wrapper around the OpenSSL library -""" - -from OpenSSL import rand, crypto, SSL -from OpenSSL.version import __version__ - -__all__ = [ - 'rand', 'crypto', 'SSL', 'tsafe', '__version__'] diff --git a/lib/OpenSSL/_util.py b/lib/OpenSSL/_util.py deleted file mode 100644 index 0cc34d80cdaf161dbf0a8da74f2a9bd5968d362e..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/_util.py +++ /dev/null @@ -1,127 +0,0 @@ -from warnings import warn -import sys - -from six import PY3, binary_type, text_type - -from cryptography.hazmat.bindings.openssl.binding import Binding -binding = Binding() -ffi = binding.ffi -lib = binding.lib - - - -def text(charp): - """ - Get a native string type representing of the given CFFI ``char*`` object. - - :param charp: A C-style string represented using CFFI. - - :return: :class:`str` - """ - if not charp: - return "" - return native(ffi.string(charp)) - - - -def exception_from_error_queue(exception_type): - """ - Convert an OpenSSL library failure into a Python exception. - - When a call to the native OpenSSL library fails, this is usually signalled - by the return value, and an error code is stored in an error queue - associated with the current thread. The err library provides functions to - obtain these error codes and textual error messages. - """ - - errors = [] - - while True: - error = lib.ERR_get_error() - if error == 0: - break - errors.append(( - text(lib.ERR_lib_error_string(error)), - text(lib.ERR_func_error_string(error)), - text(lib.ERR_reason_error_string(error)))) - - raise exception_type(errors) - - - -def native(s): - """ - Convert :py:class:`bytes` or :py:class:`unicode` to the native - :py:class:`str` type, using UTF-8 encoding if conversion is necessary. - - :raise UnicodeError: The input string is not UTF-8 decodeable. - - :raise TypeError: The input is neither :py:class:`bytes` nor - :py:class:`unicode`. - """ - if not isinstance(s, (binary_type, text_type)): - raise TypeError("%r is neither bytes nor unicode" % s) - if PY3: - if isinstance(s, binary_type): - return s.decode("utf-8") - else: - if isinstance(s, text_type): - return s.encode("utf-8") - return s - - - -def path_string(s): - """ - Convert a Python string to a :py:class:`bytes` string identifying the same - path and which can be passed into an OpenSSL API accepting a filename. - - :param s: An instance of :py:class:`bytes` or :py:class:`unicode`. - - :return: An instance of :py:class:`bytes`. - """ - if isinstance(s, binary_type): - return s - elif isinstance(s, text_type): - return s.encode(sys.getfilesystemencoding()) - else: - raise TypeError("Path must be represented as bytes or unicode string") - - -if PY3: - def byte_string(s): - return s.encode("charmap") -else: - def byte_string(s): - return s - - -# A marker object to observe whether some optional arguments are passed any -# value or not. -UNSPECIFIED = object() - -_TEXT_WARNING = ( - text_type.__name__ + " for {0} is no longer accepted, use bytes" -) - -def text_to_bytes_and_warn(label, obj): - """ - If ``obj`` is text, emit a warning that it should be bytes instead and try - to convert it to bytes automatically. - - :param str label: The name of the parameter from which ``obj`` was taken - (so a developer can easily find the source of the problem and correct - it). - - :return: If ``obj`` is the text string type, a ``bytes`` object giving the - UTF-8 encoding of that text is returned. Otherwise, ``obj`` itself is - returned. - """ - if isinstance(obj, text_type): - warn( - _TEXT_WARNING.format(label), - category=DeprecationWarning, - stacklevel=3 - ) - return obj.encode('utf-8') - return obj diff --git a/lib/OpenSSL/crypto.py b/lib/OpenSSL/crypto.py deleted file mode 100644 index 50ff74fadcad21d824f987396c637369eea5c79e..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/crypto.py +++ /dev/null @@ -1,2639 +0,0 @@ -from time import time -from base64 import b16encode -from functools import partial -from operator import __eq__, __ne__, __lt__, __le__, __gt__, __ge__ -from warnings import warn as _warn - -from six import ( - integer_types as _integer_types, - text_type as _text_type, - PY3 as _PY3) - -from OpenSSL._util import ( - ffi as _ffi, - lib as _lib, - exception_from_error_queue as _exception_from_error_queue, - byte_string as _byte_string, - native as _native, - UNSPECIFIED as _UNSPECIFIED, - text_to_bytes_and_warn as _text_to_bytes_and_warn, -) - -FILETYPE_PEM = _lib.SSL_FILETYPE_PEM -FILETYPE_ASN1 = _lib.SSL_FILETYPE_ASN1 - -# TODO This was an API mistake. OpenSSL has no such constant. -FILETYPE_TEXT = 2 ** 16 - 1 - -TYPE_RSA = _lib.EVP_PKEY_RSA -TYPE_DSA = _lib.EVP_PKEY_DSA - - - -class Error(Exception): - """ - An error occurred in an `OpenSSL.crypto` API. - """ - - -_raise_current_error = partial(_exception_from_error_queue, Error) - - - -def _untested_error(where): - """ - An OpenSSL API failed somehow. Additionally, the failure which was - encountered isn't one that's exercised by the test suite so future behavior - of pyOpenSSL is now somewhat less predictable. - """ - raise RuntimeError("Unknown %s failure" % (where,)) - - - -def _new_mem_buf(buffer=None): - """ - Allocate a new OpenSSL memory BIO. - - Arrange for the garbage collector to clean it up automatically. - - :param buffer: None or some bytes to use to put into the BIO so that they - can be read out. - """ - if buffer is None: - bio = _lib.BIO_new(_lib.BIO_s_mem()) - free = _lib.BIO_free - else: - data = _ffi.new("char[]", buffer) - bio = _lib.BIO_new_mem_buf(data, len(buffer)) - # Keep the memory alive as long as the bio is alive! - def free(bio, ref=data): - return _lib.BIO_free(bio) - - if bio == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - bio = _ffi.gc(bio, free) - return bio - - - -def _bio_to_string(bio): - """ - Copy the contents of an OpenSSL BIO object into a Python byte string. - """ - result_buffer = _ffi.new('char**') - buffer_length = _lib.BIO_get_mem_data(bio, result_buffer) - return _ffi.buffer(result_buffer[0], buffer_length)[:] - - - -def _set_asn1_time(boundary, when): - """ - The the time value of an ASN1 time object. - - @param boundary: An ASN1_GENERALIZEDTIME pointer (or an object safely - castable to that type) which will have its value set. - @param when: A string representation of the desired time value. - - @raise TypeError: If C{when} is not a L{bytes} string. - @raise ValueError: If C{when} does not represent a time in the required - format. - @raise RuntimeError: If the time value cannot be set for some other - (unspecified) reason. - """ - if not isinstance(when, bytes): - raise TypeError("when must be a byte string") - - set_result = _lib.ASN1_GENERALIZEDTIME_set_string( - _ffi.cast('ASN1_GENERALIZEDTIME*', boundary), when) - if set_result == 0: - dummy = _ffi.gc(_lib.ASN1_STRING_new(), _lib.ASN1_STRING_free) - _lib.ASN1_STRING_set(dummy, when, len(when)) - check_result = _lib.ASN1_GENERALIZEDTIME_check( - _ffi.cast('ASN1_GENERALIZEDTIME*', dummy)) - if not check_result: - raise ValueError("Invalid string") - else: - _untested_error() - - - -def _get_asn1_time(timestamp): - """ - Retrieve the time value of an ASN1 time object. - - @param timestamp: An ASN1_GENERALIZEDTIME* (or an object safely castable to - that type) from which the time value will be retrieved. - - @return: The time value from C{timestamp} as a L{bytes} string in a certain - format. Or C{None} if the object contains no time value. - """ - string_timestamp = _ffi.cast('ASN1_STRING*', timestamp) - if _lib.ASN1_STRING_length(string_timestamp) == 0: - return None - elif _lib.ASN1_STRING_type(string_timestamp) == _lib.V_ASN1_GENERALIZEDTIME: - return _ffi.string(_lib.ASN1_STRING_data(string_timestamp)) - else: - generalized_timestamp = _ffi.new("ASN1_GENERALIZEDTIME**") - _lib.ASN1_TIME_to_generalizedtime(timestamp, generalized_timestamp) - if generalized_timestamp[0] == _ffi.NULL: - # This may happen: - # - if timestamp was not an ASN1_TIME - # - if allocating memory for the ASN1_GENERALIZEDTIME failed - # - if a copy of the time data from timestamp cannot be made for - # the newly allocated ASN1_GENERALIZEDTIME - # - # These are difficult to test. cffi enforces the ASN1_TIME type. - # Memory allocation failures are a pain to trigger - # deterministically. - _untested_error("ASN1_TIME_to_generalizedtime") - else: - string_timestamp = _ffi.cast( - "ASN1_STRING*", generalized_timestamp[0]) - string_data = _lib.ASN1_STRING_data(string_timestamp) - string_result = _ffi.string(string_data) - _lib.ASN1_GENERALIZEDTIME_free(generalized_timestamp[0]) - return string_result - - - -class PKey(object): - _only_public = False - _initialized = True - - def __init__(self): - pkey = _lib.EVP_PKEY_new() - self._pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free) - self._initialized = False - - - def generate_key(self, type, bits): - """ - Generate a key of a given type, with a given number of a bits - - :param type: The key type (TYPE_RSA or TYPE_DSA) - :param bits: The number of bits - - :return: None - """ - if not isinstance(type, int): - raise TypeError("type must be an integer") - - if not isinstance(bits, int): - raise TypeError("bits must be an integer") - - # TODO Check error return - exponent = _lib.BN_new() - exponent = _ffi.gc(exponent, _lib.BN_free) - _lib.BN_set_word(exponent, _lib.RSA_F4) - - if type == TYPE_RSA: - if bits <= 0: - raise ValueError("Invalid number of bits") - - rsa = _lib.RSA_new() - - result = _lib.RSA_generate_key_ex(rsa, bits, exponent, _ffi.NULL) - if result == 0: - # TODO: The test for this case is commented out. Different - # builds of OpenSSL appear to have different failure modes that - # make it hard to test. Visual inspection of the OpenSSL - # source reveals that a return value of 0 signals an error. - # Manual testing on a particular build of OpenSSL suggests that - # this is probably the appropriate way to handle those errors. - _raise_current_error() - - result = _lib.EVP_PKEY_assign_RSA(self._pkey, rsa) - if not result: - # TODO: It appears as though this can fail if an engine is in - # use which does not support RSA. - _raise_current_error() - - elif type == TYPE_DSA: - dsa = _lib.DSA_generate_parameters( - bits, _ffi.NULL, 0, _ffi.NULL, _ffi.NULL, _ffi.NULL, _ffi.NULL) - if dsa == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - if not _lib.DSA_generate_key(dsa): - # TODO: This is untested. - _raise_current_error() - if not _lib.EVP_PKEY_assign_DSA(self._pkey, dsa): - # TODO: This is untested. - _raise_current_error() - else: - raise Error("No such key type") - - self._initialized = True - - - def check(self): - """ - Check the consistency of an RSA private key. - - :return: True if key is consistent. - :raise Error: if the key is inconsistent. - :raise TypeError: if the key is of a type which cannot be checked. - Only RSA keys can currently be checked. - """ - if self._only_public: - raise TypeError("public key only") - - if _lib.EVP_PKEY_type(self._pkey.type) != _lib.EVP_PKEY_RSA: - raise TypeError("key type unsupported") - - rsa = _lib.EVP_PKEY_get1_RSA(self._pkey) - rsa = _ffi.gc(rsa, _lib.RSA_free) - result = _lib.RSA_check_key(rsa) - if result: - return True - _raise_current_error() - - - def type(self): - """ - Returns the type of the key - - :return: The type of the key. - """ - return self._pkey.type - - - def bits(self): - """ - Returns the number of bits of the key - - :return: The number of bits of the key. - """ - return _lib.EVP_PKEY_bits(self._pkey) -PKeyType = PKey - - - -class _EllipticCurve(object): - """ - A representation of a supported elliptic curve. - - @cvar _curves: :py:obj:`None` until an attempt is made to load the curves. - Thereafter, a :py:type:`set` containing :py:type:`_EllipticCurve` - instances each of which represents one curve supported by the system. - @type _curves: :py:type:`NoneType` or :py:type:`set` - """ - _curves = None - - if _PY3: - # This only necessary on Python 3. Morever, it is broken on Python 2. - def __ne__(self, other): - """ - Implement cooperation with the right-hand side argument of ``!=``. - - Python 3 seems to have dropped this cooperation in this very narrow - circumstance. - """ - if isinstance(other, _EllipticCurve): - return super(_EllipticCurve, self).__ne__(other) - return NotImplemented - - - @classmethod - def _load_elliptic_curves(cls, lib): - """ - Get the curves supported by OpenSSL. - - :param lib: The OpenSSL library binding object. - - :return: A :py:type:`set` of ``cls`` instances giving the names of the - elliptic curves the underlying library supports. - """ - if lib.Cryptography_HAS_EC: - num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0) - builtin_curves = _ffi.new('EC_builtin_curve[]', num_curves) - # The return value on this call should be num_curves again. We could - # check it to make sure but if it *isn't* then.. what could we do? - # Abort the whole process, I suppose...? -exarkun - lib.EC_get_builtin_curves(builtin_curves, num_curves) - return set( - cls.from_nid(lib, c.nid) - for c in builtin_curves) - return set() - - - @classmethod - def _get_elliptic_curves(cls, lib): - """ - Get, cache, and return the curves supported by OpenSSL. - - :param lib: The OpenSSL library binding object. - - :return: A :py:type:`set` of ``cls`` instances giving the names of the - elliptic curves the underlying library supports. - """ - if cls._curves is None: - cls._curves = cls._load_elliptic_curves(lib) - return cls._curves - - - @classmethod - def from_nid(cls, lib, nid): - """ - Instantiate a new :py:class:`_EllipticCurve` associated with the given - OpenSSL NID. - - :param lib: The OpenSSL library binding object. - - :param nid: The OpenSSL NID the resulting curve object will represent. - This must be a curve NID (and not, for example, a hash NID) or - subsequent operations will fail in unpredictable ways. - :type nid: :py:class:`int` - - :return: The curve object. - """ - return cls(lib, nid, _ffi.string(lib.OBJ_nid2sn(nid)).decode("ascii")) - - - def __init__(self, lib, nid, name): - """ - :param _lib: The :py:mod:`cryptography` binding instance used to - interface with OpenSSL. - - :param _nid: The OpenSSL NID identifying the curve this object - represents. - :type _nid: :py:class:`int` - - :param name: The OpenSSL short name identifying the curve this object - represents. - :type name: :py:class:`unicode` - """ - self._lib = lib - self._nid = nid - self.name = name - - - def __repr__(self): - return "<Curve %r>" % (self.name,) - - - def _to_EC_KEY(self): - """ - Create a new OpenSSL EC_KEY structure initialized to use this curve. - - The structure is automatically garbage collected when the Python object - is garbage collected. - """ - key = self._lib.EC_KEY_new_by_curve_name(self._nid) - return _ffi.gc(key, _lib.EC_KEY_free) - - - -def get_elliptic_curves(): - """ - Return a set of objects representing the elliptic curves supported in the - OpenSSL build in use. - - The curve objects have a :py:class:`unicode` ``name`` attribute by which - they identify themselves. - - The curve objects are useful as values for the argument accepted by - :py:meth:`Context.set_tmp_ecdh` to specify which elliptical curve should be - used for ECDHE key exchange. - """ - return _EllipticCurve._get_elliptic_curves(_lib) - - - -def get_elliptic_curve(name): - """ - Return a single curve object selected by name. - - See :py:func:`get_elliptic_curves` for information about curve objects. - - :param name: The OpenSSL short name identifying the curve object to - retrieve. - :type name: :py:class:`unicode` - - If the named curve is not supported then :py:class:`ValueError` is raised. - """ - for curve in get_elliptic_curves(): - if curve.name == name: - return curve - raise ValueError("unknown curve name", name) - - - -class X509Name(object): - def __init__(self, name): - """ - Create a new X509Name, copying the given X509Name instance. - - :param name: An X509Name object to copy - """ - name = _lib.X509_NAME_dup(name._name) - self._name = _ffi.gc(name, _lib.X509_NAME_free) - - - def __setattr__(self, name, value): - if name.startswith('_'): - return super(X509Name, self).__setattr__(name, value) - - # Note: we really do not want str subclasses here, so we do not use - # isinstance. - if type(name) is not str: - raise TypeError("attribute name must be string, not '%.200s'" % ( - type(value).__name__,)) - - nid = _lib.OBJ_txt2nid(_byte_string(name)) - if nid == _lib.NID_undef: - try: - _raise_current_error() - except Error: - pass - raise AttributeError("No such attribute") - - # If there's an old entry for this NID, remove it - for i in range(_lib.X509_NAME_entry_count(self._name)): - ent = _lib.X509_NAME_get_entry(self._name, i) - ent_obj = _lib.X509_NAME_ENTRY_get_object(ent) - ent_nid = _lib.OBJ_obj2nid(ent_obj) - if nid == ent_nid: - ent = _lib.X509_NAME_delete_entry(self._name, i) - _lib.X509_NAME_ENTRY_free(ent) - break - - if isinstance(value, _text_type): - value = value.encode('utf-8') - - add_result = _lib.X509_NAME_add_entry_by_NID( - self._name, nid, _lib.MBSTRING_UTF8, value, -1, -1, 0) - if not add_result: - _raise_current_error() - - - def __getattr__(self, name): - """ - Find attribute. An X509Name object has the following attributes: - countryName (alias C), stateOrProvince (alias ST), locality (alias L), - organization (alias O), organizationalUnit (alias OU), commonName (alias - CN) and more... - """ - nid = _lib.OBJ_txt2nid(_byte_string(name)) - if nid == _lib.NID_undef: - # This is a bit weird. OBJ_txt2nid indicated failure, but it seems - # a lower level function, a2d_ASN1_OBJECT, also feels the need to - # push something onto the error queue. If we don't clean that up - # now, someone else will bump into it later and be quite confused. - # See lp#314814. - try: - _raise_current_error() - except Error: - pass - return super(X509Name, self).__getattr__(name) - - entry_index = _lib.X509_NAME_get_index_by_NID(self._name, nid, -1) - if entry_index == -1: - return None - - entry = _lib.X509_NAME_get_entry(self._name, entry_index) - data = _lib.X509_NAME_ENTRY_get_data(entry) - - result_buffer = _ffi.new("unsigned char**") - data_length = _lib.ASN1_STRING_to_UTF8(result_buffer, data) - if data_length < 0: - # TODO: This is untested. - _raise_current_error() - - try: - result = _ffi.buffer(result_buffer[0], data_length)[:].decode('utf-8') - finally: - # XXX untested - _lib.OPENSSL_free(result_buffer[0]) - return result - - - def _cmp(op): - def f(self, other): - if not isinstance(other, X509Name): - return NotImplemented - result = _lib.X509_NAME_cmp(self._name, other._name) - return op(result, 0) - return f - - __eq__ = _cmp(__eq__) - __ne__ = _cmp(__ne__) - - __lt__ = _cmp(__lt__) - __le__ = _cmp(__le__) - - __gt__ = _cmp(__gt__) - __ge__ = _cmp(__ge__) - - def __repr__(self): - """ - String representation of an X509Name - """ - result_buffer = _ffi.new("char[]", 512); - format_result = _lib.X509_NAME_oneline( - self._name, result_buffer, len(result_buffer)) - - if format_result == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - return "<X509Name object '%s'>" % ( - _native(_ffi.string(result_buffer)),) - - - def hash(self): - """ - Return the hash value of this name - - :return: None - """ - return _lib.X509_NAME_hash(self._name) - - - def der(self): - """ - Return the DER encoding of this name - - :return: A :py:class:`bytes` instance giving the DER encoded form of - this name. - """ - result_buffer = _ffi.new('unsigned char**') - encode_result = _lib.i2d_X509_NAME(self._name, result_buffer) - if encode_result < 0: - # TODO: This is untested. - _raise_current_error() - - string_result = _ffi.buffer(result_buffer[0], encode_result)[:] - _lib.OPENSSL_free(result_buffer[0]) - return string_result - - - def get_components(self): - """ - Returns the split-up components of this name. - - :return: List of tuples (name, value). - """ - result = [] - for i in range(_lib.X509_NAME_entry_count(self._name)): - ent = _lib.X509_NAME_get_entry(self._name, i) - - fname = _lib.X509_NAME_ENTRY_get_object(ent) - fval = _lib.X509_NAME_ENTRY_get_data(ent) - - nid = _lib.OBJ_obj2nid(fname) - name = _lib.OBJ_nid2sn(nid) - - result.append(( - _ffi.string(name), - _ffi.string( - _lib.ASN1_STRING_data(fval), - _lib.ASN1_STRING_length(fval)))) - - return result -X509NameType = X509Name - - -class X509Extension(object): - def __init__(self, type_name, critical, value, subject=None, issuer=None): - """ - :param typename: The name of the extension to create. - :type typename: :py:data:`str` - - :param critical: A flag indicating whether this is a critical extension. - - :param value: The value of the extension. - :type value: :py:data:`str` - - :param subject: Optional X509 cert to use as subject. - :type subject: :py:class:`X509` - - :param issuer: Optional X509 cert to use as issuer. - :type issuer: :py:class:`X509` - - :return: The X509Extension object - """ - ctx = _ffi.new("X509V3_CTX*") - - # A context is necessary for any extension which uses the r2i conversion - # method. That is, X509V3_EXT_nconf may segfault if passed a NULL ctx. - # Start off by initializing most of the fields to NULL. - _lib.X509V3_set_ctx(ctx, _ffi.NULL, _ffi.NULL, _ffi.NULL, _ffi.NULL, 0) - - # We have no configuration database - but perhaps we should (some - # extensions may require it). - _lib.X509V3_set_ctx_nodb(ctx) - - # Initialize the subject and issuer, if appropriate. ctx is a local, - # and as far as I can tell none of the X509V3_* APIs invoked here steal - # any references, so no need to mess with reference counts or duplicates. - if issuer is not None: - if not isinstance(issuer, X509): - raise TypeError("issuer must be an X509 instance") - ctx.issuer_cert = issuer._x509 - if subject is not None: - if not isinstance(subject, X509): - raise TypeError("subject must be an X509 instance") - ctx.subject_cert = subject._x509 - - if critical: - # There are other OpenSSL APIs which would let us pass in critical - # separately, but they're harder to use, and since value is already - # a pile of crappy junk smuggling a ton of utterly important - # structured data, what's the point of trying to avoid nasty stuff - # with strings? (However, X509V3_EXT_i2d in particular seems like it - # would be a better API to invoke. I do not know where to get the - # ext_struc it desires for its last parameter, though.) - value = b"critical," + value - - extension = _lib.X509V3_EXT_nconf(_ffi.NULL, ctx, type_name, value) - if extension == _ffi.NULL: - _raise_current_error() - self._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) - - - @property - def _nid(self): - return _lib.OBJ_obj2nid(self._extension.object) - - _prefixes = { - _lib.GEN_EMAIL: "email", - _lib.GEN_DNS: "DNS", - _lib.GEN_URI: "URI", - } - - def _subjectAltNameString(self): - method = _lib.X509V3_EXT_get(self._extension) - if method == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - payload = self._extension.value.data - length = self._extension.value.length - - payloadptr = _ffi.new("unsigned char**") - payloadptr[0] = payload - - if method.it != _ffi.NULL: - ptr = _lib.ASN1_ITEM_ptr(method.it) - data = _lib.ASN1_item_d2i(_ffi.NULL, payloadptr, length, ptr) - names = _ffi.cast("GENERAL_NAMES*", data) - else: - names = _ffi.cast( - "GENERAL_NAMES*", - method.d2i(_ffi.NULL, payloadptr, length)) - - parts = [] - for i in range(_lib.sk_GENERAL_NAME_num(names)): - name = _lib.sk_GENERAL_NAME_value(names, i) - try: - label = self._prefixes[name.type] - except KeyError: - bio = _new_mem_buf() - _lib.GENERAL_NAME_print(bio, name) - parts.append(_native(_bio_to_string(bio))) - else: - value = _native( - _ffi.buffer(name.d.ia5.data, name.d.ia5.length)[:]) - parts.append(label + ":" + value) - return ", ".join(parts) - - - def __str__(self): - """ - :return: a nice text representation of the extension - """ - if _lib.NID_subject_alt_name == self._nid: - return self._subjectAltNameString() - - bio = _new_mem_buf() - print_result = _lib.X509V3_EXT_print(bio, self._extension, 0, 0) - if not print_result: - # TODO: This is untested. - _raise_current_error() - - return _native(_bio_to_string(bio)) - - - def get_critical(self): - """ - Returns the critical field of the X509Extension - - :return: The critical field. - """ - return _lib.X509_EXTENSION_get_critical(self._extension) - - - def get_short_name(self): - """ - Returns the short version of the type name of the X509Extension - - :return: The short type name. - """ - obj = _lib.X509_EXTENSION_get_object(self._extension) - nid = _lib.OBJ_obj2nid(obj) - return _ffi.string(_lib.OBJ_nid2sn(nid)) - - - def get_data(self): - """ - Returns the data of the X509Extension - - :return: A :py:data:`str` giving the X509Extension's ASN.1 encoded data. - """ - octet_result = _lib.X509_EXTENSION_get_data(self._extension) - string_result = _ffi.cast('ASN1_STRING*', octet_result) - char_result = _lib.ASN1_STRING_data(string_result) - result_length = _lib.ASN1_STRING_length(string_result) - return _ffi.buffer(char_result, result_length)[:] - -X509ExtensionType = X509Extension - - -class X509Req(object): - def __init__(self): - req = _lib.X509_REQ_new() - self._req = _ffi.gc(req, _lib.X509_REQ_free) - - - def set_pubkey(self, pkey): - """ - Set the public key of the certificate request - - :param pkey: The public key to use - :return: None - """ - set_result = _lib.X509_REQ_set_pubkey(self._req, pkey._pkey) - if not set_result: - # TODO: This is untested. - _raise_current_error() - - - def get_pubkey(self): - """ - Get the public key from the certificate request - - :return: The public key - """ - pkey = PKey.__new__(PKey) - pkey._pkey = _lib.X509_REQ_get_pubkey(self._req) - if pkey._pkey == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) - pkey._only_public = True - return pkey - - - def set_version(self, version): - """ - Set the version subfield (RFC 2459, section 4.1.2.1) of the certificate - request. - - :param version: The version number - :return: None - """ - set_result = _lib.X509_REQ_set_version(self._req, version) - if not set_result: - _raise_current_error() - - - def get_version(self): - """ - Get the version subfield (RFC 2459, section 4.1.2.1) of the certificate - request. - - :return: an integer giving the value of the version subfield - """ - return _lib.X509_REQ_get_version(self._req) - - - def get_subject(self): - """ - Create an X509Name object for the subject of the certificate request - - :return: An X509Name object - """ - name = X509Name.__new__(X509Name) - name._name = _lib.X509_REQ_get_subject_name(self._req) - if name._name == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - # The name is owned by the X509Req structure. As long as the X509Name - # Python object is alive, keep the X509Req Python object alive. - name._owner = self - - return name - - - def add_extensions(self, extensions): - """ - Add extensions to the request. - - :param extensions: a sequence of X509Extension objects - :return: None - """ - stack = _lib.sk_X509_EXTENSION_new_null() - if stack == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - stack = _ffi.gc(stack, _lib.sk_X509_EXTENSION_free) - - for ext in extensions: - if not isinstance(ext, X509Extension): - raise ValueError("One of the elements is not an X509Extension") - - # TODO push can fail (here and elsewhere) - _lib.sk_X509_EXTENSION_push(stack, ext._extension) - - add_result = _lib.X509_REQ_add_extensions(self._req, stack) - if not add_result: - # TODO: This is untested. - _raise_current_error() - - - def get_extensions(self): - """ - Get extensions to the request. - - :return: A :py:class:`list` of :py:class:`X509Extension` objects. - """ - exts = [] - native_exts_obj = _lib.X509_REQ_get_extensions(self._req) - for i in range(_lib.sk_X509_EXTENSION_num(native_exts_obj)): - ext = X509Extension.__new__(X509Extension) - ext._extension = _lib.sk_X509_EXTENSION_value(native_exts_obj, i) - exts.append(ext) - return exts - - - def sign(self, pkey, digest): - """ - Sign the certificate request using the supplied key and digest - - :param pkey: The key to sign with - :param digest: The message digest to use - :return: None - """ - if pkey._only_public: - raise ValueError("Key has only public part") - - if not pkey._initialized: - raise ValueError("Key is uninitialized") - - digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - sign_result = _lib.X509_REQ_sign(self._req, pkey._pkey, digest_obj) - if not sign_result: - # TODO: This is untested. - _raise_current_error() - - - def verify(self, pkey): - """ - Verifies a certificate request using the supplied public key - - :param key: a public key - :return: True if the signature is correct. - - :raise OpenSSL.crypto.Error: If the signature is invalid or there is a - problem verifying the signature. - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - - result = _lib.X509_REQ_verify(self._req, pkey._pkey) - if result <= 0: - _raise_current_error() - - return result - - -X509ReqType = X509Req - - - -class X509(object): - def __init__(self): - # TODO Allocation failure? And why not __new__ instead of __init__? - x509 = _lib.X509_new() - self._x509 = _ffi.gc(x509, _lib.X509_free) - - - def set_version(self, version): - """ - Set version number of the certificate - - :param version: The version number - :type version: :py:class:`int` - - :return: None - """ - if not isinstance(version, int): - raise TypeError("version must be an integer") - - _lib.X509_set_version(self._x509, version) - - - def get_version(self): - """ - Return version number of the certificate - - :return: Version number as a Python integer - """ - return _lib.X509_get_version(self._x509) - - - def get_pubkey(self): - """ - Get the public key of the certificate - - :return: The public key - """ - pkey = PKey.__new__(PKey) - pkey._pkey = _lib.X509_get_pubkey(self._x509) - if pkey._pkey == _ffi.NULL: - _raise_current_error() - pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) - pkey._only_public = True - return pkey - - - def set_pubkey(self, pkey): - """ - Set the public key of the certificate - - :param pkey: The public key - - :return: None - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - - set_result = _lib.X509_set_pubkey(self._x509, pkey._pkey) - if not set_result: - _raise_current_error() - - - def sign(self, pkey, digest): - """ - Sign the certificate using the supplied key and digest - - :param pkey: The key to sign with - :param digest: The message digest to use - :return: None - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - - if pkey._only_public: - raise ValueError("Key only has public part") - - if not pkey._initialized: - raise ValueError("Key is uninitialized") - - evp_md = _lib.EVP_get_digestbyname(_byte_string(digest)) - if evp_md == _ffi.NULL: - raise ValueError("No such digest method") - - sign_result = _lib.X509_sign(self._x509, pkey._pkey, evp_md) - if not sign_result: - _raise_current_error() - - - def get_signature_algorithm(self): - """ - Retrieve the signature algorithm used in the certificate - - :return: A byte string giving the name of the signature algorithm used in - the certificate. - :raise ValueError: If the signature algorithm is undefined. - """ - alg = self._x509.cert_info.signature.algorithm - nid = _lib.OBJ_obj2nid(alg) - if nid == _lib.NID_undef: - raise ValueError("Undefined signature algorithm") - return _ffi.string(_lib.OBJ_nid2ln(nid)) - - - def digest(self, digest_name): - """ - Return the digest of the X509 object. - - :param digest_name: The name of the digest algorithm to use. - :type digest_name: :py:class:`bytes` - - :return: The digest of the object - """ - digest = _lib.EVP_get_digestbyname(_byte_string(digest_name)) - if digest == _ffi.NULL: - raise ValueError("No such digest method") - - result_buffer = _ffi.new("char[]", _lib.EVP_MAX_MD_SIZE) - result_length = _ffi.new("unsigned int[]", 1) - result_length[0] = len(result_buffer) - - digest_result = _lib.X509_digest( - self._x509, digest, result_buffer, result_length) - - if not digest_result: - # TODO: This is untested. - _raise_current_error() - - return b":".join([ - b16encode(ch).upper() for ch - in _ffi.buffer(result_buffer, result_length[0])]) - - - def subject_name_hash(self): - """ - Return the hash of the X509 subject. - - :return: The hash of the subject. - """ - return _lib.X509_subject_name_hash(self._x509) - - - def set_serial_number(self, serial): - """ - Set serial number of the certificate - - :param serial: The serial number - :type serial: :py:class:`int` - - :return: None - """ - if not isinstance(serial, _integer_types): - raise TypeError("serial must be an integer") - - hex_serial = hex(serial)[2:] - if not isinstance(hex_serial, bytes): - hex_serial = hex_serial.encode('ascii') - - bignum_serial = _ffi.new("BIGNUM**") - - # BN_hex2bn stores the result in &bignum. Unless it doesn't feel like - # it. If bignum is still NULL after this call, then the return value is - # actually the result. I hope. -exarkun - small_serial = _lib.BN_hex2bn(bignum_serial, hex_serial) - - if bignum_serial[0] == _ffi.NULL: - set_result = _lib.ASN1_INTEGER_set( - _lib.X509_get_serialNumber(self._x509), small_serial) - if set_result: - # TODO Not tested - _raise_current_error() - else: - asn1_serial = _lib.BN_to_ASN1_INTEGER(bignum_serial[0], _ffi.NULL) - _lib.BN_free(bignum_serial[0]) - if asn1_serial == _ffi.NULL: - # TODO Not tested - _raise_current_error() - asn1_serial = _ffi.gc(asn1_serial, _lib.ASN1_INTEGER_free) - set_result = _lib.X509_set_serialNumber(self._x509, asn1_serial) - if not set_result: - # TODO Not tested - _raise_current_error() - - - def get_serial_number(self): - """ - Return serial number of the certificate - - :return: Serial number as a Python integer - """ - asn1_serial = _lib.X509_get_serialNumber(self._x509) - bignum_serial = _lib.ASN1_INTEGER_to_BN(asn1_serial, _ffi.NULL) - try: - hex_serial = _lib.BN_bn2hex(bignum_serial) - try: - hexstring_serial = _ffi.string(hex_serial) - serial = int(hexstring_serial, 16) - return serial - finally: - _lib.OPENSSL_free(hex_serial) - finally: - _lib.BN_free(bignum_serial) - - - def gmtime_adj_notAfter(self, amount): - """ - Adjust the time stamp for when the certificate stops being valid - - :param amount: The number of seconds by which to adjust the ending - validity time. - :type amount: :py:class:`int` - - :return: None - """ - if not isinstance(amount, int): - raise TypeError("amount must be an integer") - - notAfter = _lib.X509_get_notAfter(self._x509) - _lib.X509_gmtime_adj(notAfter, amount) - - - def gmtime_adj_notBefore(self, amount): - """ - Change the timestamp for when the certificate starts being valid to the current - time plus an offset. - - :param amount: The number of seconds by which to adjust the starting validity - time. - :return: None - """ - if not isinstance(amount, int): - raise TypeError("amount must be an integer") - - notBefore = _lib.X509_get_notBefore(self._x509) - _lib.X509_gmtime_adj(notBefore, amount) - - - def has_expired(self): - """ - Check whether the certificate has expired. - - :return: True if the certificate has expired, false otherwise - """ - now = int(time()) - notAfter = _lib.X509_get_notAfter(self._x509) - return _lib.ASN1_UTCTIME_cmp_time_t( - _ffi.cast('ASN1_UTCTIME*', notAfter), now) < 0 - - - def _get_boundary_time(self, which): - return _get_asn1_time(which(self._x509)) - - - def get_notBefore(self): - """ - Retrieve the time stamp for when the certificate starts being valid - - :return: A string giving the timestamp, in the format:: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - - or None if there is no value set. - """ - return self._get_boundary_time(_lib.X509_get_notBefore) - - - def _set_boundary_time(self, which, when): - return _set_asn1_time(which(self._x509), when) - - - def set_notBefore(self, when): - """ - Set the time stamp for when the certificate starts being valid - - :param when: A string giving the timestamp, in the format: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - :type when: :py:class:`bytes` - - :return: None - """ - return self._set_boundary_time(_lib.X509_get_notBefore, when) - - - def get_notAfter(self): - """ - Retrieve the time stamp for when the certificate stops being valid - - :return: A string giving the timestamp, in the format:: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - - or None if there is no value set. - """ - return self._get_boundary_time(_lib.X509_get_notAfter) - - - def set_notAfter(self, when): - """ - Set the time stamp for when the certificate stops being valid - - :param when: A string giving the timestamp, in the format: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - :type when: :py:class:`bytes` - - :return: None - """ - return self._set_boundary_time(_lib.X509_get_notAfter, when) - - - def _get_name(self, which): - name = X509Name.__new__(X509Name) - name._name = which(self._x509) - if name._name == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - # The name is owned by the X509 structure. As long as the X509Name - # Python object is alive, keep the X509 Python object alive. - name._owner = self - - return name - - - def _set_name(self, which, name): - if not isinstance(name, X509Name): - raise TypeError("name must be an X509Name") - set_result = which(self._x509, name._name) - if not set_result: - # TODO: This is untested. - _raise_current_error() - - - def get_issuer(self): - """ - Create an X509Name object for the issuer of the certificate - - :return: An X509Name object - """ - return self._get_name(_lib.X509_get_issuer_name) - - - def set_issuer(self, issuer): - """ - Set the issuer of the certificate - - :param issuer: The issuer name - :type issuer: :py:class:`X509Name` - - :return: None - """ - return self._set_name(_lib.X509_set_issuer_name, issuer) - - - def get_subject(self): - """ - Create an X509Name object for the subject of the certificate - - :return: An X509Name object - """ - return self._get_name(_lib.X509_get_subject_name) - - - def set_subject(self, subject): - """ - Set the subject of the certificate - - :param subject: The subject name - :type subject: :py:class:`X509Name` - :return: None - """ - return self._set_name(_lib.X509_set_subject_name, subject) - - - def get_extension_count(self): - """ - Get the number of extensions on the certificate. - - :return: The number of extensions as an integer. - """ - return _lib.X509_get_ext_count(self._x509) - - - def add_extensions(self, extensions): - """ - Add extensions to the certificate. - - :param extensions: a sequence of X509Extension objects - :return: None - """ - for ext in extensions: - if not isinstance(ext, X509Extension): - raise ValueError("One of the elements is not an X509Extension") - - add_result = _lib.X509_add_ext(self._x509, ext._extension, -1) - if not add_result: - _raise_current_error() - - - def get_extension(self, index): - """ - Get a specific extension of the certificate by index. - - :param index: The index of the extension to retrieve. - :return: The X509Extension object at the specified index. - """ - ext = X509Extension.__new__(X509Extension) - ext._extension = _lib.X509_get_ext(self._x509, index) - if ext._extension == _ffi.NULL: - raise IndexError("extension index out of bounds") - - extension = _lib.X509_EXTENSION_dup(ext._extension) - ext._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) - return ext - -X509Type = X509 - - - -class X509Store(object): - def __init__(self): - store = _lib.X509_STORE_new() - self._store = _ffi.gc(store, _lib.X509_STORE_free) - - - def add_cert(self, cert): - if not isinstance(cert, X509): - raise TypeError() - - result = _lib.X509_STORE_add_cert(self._store, cert._x509) - if not result: - _raise_current_error() - - -X509StoreType = X509Store - - -class X509StoreContextError(Exception): - """ - An error occurred while verifying a certificate using - `OpenSSL.X509StoreContext.verify_certificate`. - - :ivar certificate: The certificate which caused verificate failure. - :type cert: :class:`X509` - - """ - def __init__(self, message, certificate): - super(X509StoreContextError, self).__init__(message) - self.certificate = certificate - - -class X509StoreContext(object): - """ - An X.509 store context. - - An :py:class:`X509StoreContext` is used to define some of the criteria for - certificate verification. The information encapsulated in this object - includes, but is not limited to, a set of trusted certificates, - verification parameters, and revoked certificates. - - Of these, only the set of trusted certificates is currently exposed. - - :ivar _store_ctx: The underlying X509_STORE_CTX structure used by this - instance. It is dynamically allocated and automatically garbage - collected. - - :ivar _store: See the ``store`` ``__init__`` parameter. - - :ivar _cert: See the ``certificate`` ``__init__`` parameter. - """ - - def __init__(self, store, certificate): - """ - :param X509Store store: The certificates which will be trusted for the - purposes of any verifications. - - :param X509 certificate: The certificate to be verified. - """ - store_ctx = _lib.X509_STORE_CTX_new() - self._store_ctx = _ffi.gc(store_ctx, _lib.X509_STORE_CTX_free) - self._store = store - self._cert = certificate - # Make the store context available for use after instantiating this - # class by initializing it now. Per testing, subsequent calls to - # :py:meth:`_init` have no adverse affect. - self._init() - - - def _init(self): - """ - Set up the store context for a subsequent verification operation. - """ - ret = _lib.X509_STORE_CTX_init(self._store_ctx, self._store._store, self._cert._x509, _ffi.NULL) - if ret <= 0: - _raise_current_error() - - - def _cleanup(self): - """ - Internally cleans up the store context. - - The store context can then be reused with a new call to - :py:meth:`_init`. - """ - _lib.X509_STORE_CTX_cleanup(self._store_ctx) - - - def _exception_from_context(self): - """ - Convert an OpenSSL native context error failure into a Python - exception. - - When a call to native OpenSSL X509_verify_cert fails, additonal information - about the failure can be obtained from the store context. - """ - errors = [ - _lib.X509_STORE_CTX_get_error(self._store_ctx), - _lib.X509_STORE_CTX_get_error_depth(self._store_ctx), - _native(_ffi.string(_lib.X509_verify_cert_error_string( - _lib.X509_STORE_CTX_get_error(self._store_ctx)))), - ] - # A context error should always be associated with a certificate, so we - # expect this call to never return :class:`None`. - _x509 = _lib.X509_STORE_CTX_get_current_cert(self._store_ctx) - _cert = _lib.X509_dup(_x509) - pycert = X509.__new__(X509) - pycert._x509 = _ffi.gc(_cert, _lib.X509_free) - return X509StoreContextError(errors, pycert) - - - def set_store(self, store): - """ - Set the context's trust store. - - :param X509Store store: The certificates which will be trusted for the - purposes of any *future* verifications. - """ - self._store = store - - - def verify_certificate(self): - """ - Verify a certificate in a context. - - :param store_ctx: The :py:class:`X509StoreContext` to verify. - :raises: Error - """ - # Always re-initialize the store context in case - # :py:meth:`verify_certificate` is called multiple times. - self._init() - ret = _lib.X509_verify_cert(self._store_ctx) - self._cleanup() - if ret <= 0: - raise self._exception_from_context() - - - -def load_certificate(type, buffer): - """ - Load a certificate from a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - - :param buffer: The buffer the certificate is stored in - :type buffer: :py:class:`bytes` - - :return: The X509 object - """ - if isinstance(buffer, _text_type): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - if type == FILETYPE_PEM: - x509 = _lib.PEM_read_bio_X509(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - elif type == FILETYPE_ASN1: - x509 = _lib.d2i_X509_bio(bio, _ffi.NULL); - else: - raise ValueError( - "type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if x509 == _ffi.NULL: - _raise_current_error() - - cert = X509.__new__(X509) - cert._x509 = _ffi.gc(x509, _lib.X509_free) - return cert - - -def dump_certificate(type, cert): - """ - Dump a certificate to a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1, or - FILETYPE_TEXT) - :param cert: The certificate to dump - :return: The buffer with the dumped certificate in - """ - bio = _new_mem_buf() - - if type == FILETYPE_PEM: - result_code = _lib.PEM_write_bio_X509(bio, cert._x509) - elif type == FILETYPE_ASN1: - result_code = _lib.i2d_X509_bio(bio, cert._x509) - elif type == FILETYPE_TEXT: - result_code = _lib.X509_print_ex(bio, cert._x509, 0, 0) - else: - raise ValueError( - "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " - "FILETYPE_TEXT") - - return _bio_to_string(bio) - - - -def dump_privatekey(type, pkey, cipher=None, passphrase=None): - """ - Dump a private key to a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1, or - FILETYPE_TEXT) - :param pkey: The PKey to dump - :param cipher: (optional) if encrypted PEM format, the cipher to - use - :param passphrase: (optional) if encrypted PEM format, this can be either - the passphrase to use, or a callback for providing the - passphrase. - :return: The buffer with the dumped key in - :rtype: :py:data:`str` - """ - bio = _new_mem_buf() - - if cipher is not None: - if passphrase is None: - raise TypeError( - "if a value is given for cipher " - "one must also be given for passphrase") - cipher_obj = _lib.EVP_get_cipherbyname(_byte_string(cipher)) - if cipher_obj == _ffi.NULL: - raise ValueError("Invalid cipher name") - else: - cipher_obj = _ffi.NULL - - helper = _PassphraseHelper(type, passphrase) - if type == FILETYPE_PEM: - result_code = _lib.PEM_write_bio_PrivateKey( - bio, pkey._pkey, cipher_obj, _ffi.NULL, 0, - helper.callback, helper.callback_args) - helper.raise_if_problem() - elif type == FILETYPE_ASN1: - result_code = _lib.i2d_PrivateKey_bio(bio, pkey._pkey) - elif type == FILETYPE_TEXT: - rsa = _lib.EVP_PKEY_get1_RSA(pkey._pkey) - result_code = _lib.RSA_print(bio, rsa, 0) - # TODO RSA_free(rsa)? - else: - raise ValueError( - "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " - "FILETYPE_TEXT") - - if result_code == 0: - _raise_current_error() - - return _bio_to_string(bio) - - - -def _X509_REVOKED_dup(original): - copy = _lib.X509_REVOKED_new() - if copy == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - if original.serialNumber != _ffi.NULL: - _lib.ASN1_INTEGER_free(copy.serialNumber) - copy.serialNumber = _lib.ASN1_INTEGER_dup(original.serialNumber) - - if original.revocationDate != _ffi.NULL: - _lib.ASN1_TIME_free(copy.revocationDate) - copy.revocationDate = _lib.M_ASN1_TIME_dup(original.revocationDate) - - if original.extensions != _ffi.NULL: - extension_stack = _lib.sk_X509_EXTENSION_new_null() - for i in range(_lib.sk_X509_EXTENSION_num(original.extensions)): - original_ext = _lib.sk_X509_EXTENSION_value(original.extensions, i) - copy_ext = _lib.X509_EXTENSION_dup(original_ext) - _lib.sk_X509_EXTENSION_push(extension_stack, copy_ext) - copy.extensions = extension_stack - - copy.sequence = original.sequence - return copy - - - -class Revoked(object): - # http://www.openssl.org/docs/apps/x509v3_config.html#CRL_distribution_points_ - # which differs from crl_reasons of crypto/x509v3/v3_enum.c that matches - # OCSP_crl_reason_str. We use the latter, just like the command line - # program. - _crl_reasons = [ - b"unspecified", - b"keyCompromise", - b"CACompromise", - b"affiliationChanged", - b"superseded", - b"cessationOfOperation", - b"certificateHold", - # b"removeFromCRL", - ] - - def __init__(self): - revoked = _lib.X509_REVOKED_new() - self._revoked = _ffi.gc(revoked, _lib.X509_REVOKED_free) - - - def set_serial(self, hex_str): - """ - Set the serial number of a revoked Revoked structure - - :param hex_str: The new serial number. - :type hex_str: :py:data:`str` - :return: None - """ - bignum_serial = _ffi.gc(_lib.BN_new(), _lib.BN_free) - bignum_ptr = _ffi.new("BIGNUM**") - bignum_ptr[0] = bignum_serial - bn_result = _lib.BN_hex2bn(bignum_ptr, hex_str) - if not bn_result: - raise ValueError("bad hex string") - - asn1_serial = _ffi.gc( - _lib.BN_to_ASN1_INTEGER(bignum_serial, _ffi.NULL), - _lib.ASN1_INTEGER_free) - _lib.X509_REVOKED_set_serialNumber(self._revoked, asn1_serial) - - - def get_serial(self): - """ - Return the serial number of a Revoked structure - - :return: The serial number as a string - """ - bio = _new_mem_buf() - - result = _lib.i2a_ASN1_INTEGER(bio, self._revoked.serialNumber) - if result < 0: - # TODO: This is untested. - _raise_current_error() - - return _bio_to_string(bio) - - - def _delete_reason(self): - stack = self._revoked.extensions - for i in range(_lib.sk_X509_EXTENSION_num(stack)): - ext = _lib.sk_X509_EXTENSION_value(stack, i) - if _lib.OBJ_obj2nid(ext.object) == _lib.NID_crl_reason: - _lib.X509_EXTENSION_free(ext) - _lib.sk_X509_EXTENSION_delete(stack, i) - break - - - def set_reason(self, reason): - """ - Set the reason of a Revoked object. - - If :py:data:`reason` is :py:data:`None`, delete the reason instead. - - :param reason: The reason string. - :type reason: :py:class:`str` or :py:class:`NoneType` - :return: None - """ - if reason is None: - self._delete_reason() - elif not isinstance(reason, bytes): - raise TypeError("reason must be None or a byte string") - else: - reason = reason.lower().replace(b' ', b'') - reason_code = [r.lower() for r in self._crl_reasons].index(reason) - - new_reason_ext = _lib.ASN1_ENUMERATED_new() - if new_reason_ext == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - new_reason_ext = _ffi.gc(new_reason_ext, _lib.ASN1_ENUMERATED_free) - - set_result = _lib.ASN1_ENUMERATED_set(new_reason_ext, reason_code) - if set_result == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - self._delete_reason() - add_result = _lib.X509_REVOKED_add1_ext_i2d( - self._revoked, _lib.NID_crl_reason, new_reason_ext, 0, 0) - - if not add_result: - # TODO: This is untested. - _raise_current_error() - - - def get_reason(self): - """ - Return the reason of a Revoked object. - - :return: The reason as a string - """ - extensions = self._revoked.extensions - for i in range(_lib.sk_X509_EXTENSION_num(extensions)): - ext = _lib.sk_X509_EXTENSION_value(extensions, i) - if _lib.OBJ_obj2nid(ext.object) == _lib.NID_crl_reason: - bio = _new_mem_buf() - - print_result = _lib.X509V3_EXT_print(bio, ext, 0, 0) - if not print_result: - print_result = _lib.M_ASN1_OCTET_STRING_print(bio, ext.value) - if print_result == 0: - # TODO: This is untested. - _raise_current_error() - - return _bio_to_string(bio) - - - def all_reasons(self): - """ - Return a list of all the supported reason strings. - - :return: A list of reason strings. - """ - return self._crl_reasons[:] - - - def set_rev_date(self, when): - """ - Set the revocation timestamp - - :param when: A string giving the timestamp, in the format: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - - :return: None - """ - return _set_asn1_time(self._revoked.revocationDate, when) - - - def get_rev_date(self): - """ - Retrieve the revocation date - - :return: A string giving the timestamp, in the format: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - """ - return _get_asn1_time(self._revoked.revocationDate) - - - -class CRL(object): - def __init__(self): - """ - Create a new empty CRL object. - """ - crl = _lib.X509_CRL_new() - self._crl = _ffi.gc(crl, _lib.X509_CRL_free) - - - def get_revoked(self): - """ - Return revoked portion of the CRL structure (by value not reference). - - :return: A tuple of Revoked objects. - """ - results = [] - revoked_stack = self._crl.crl.revoked - for i in range(_lib.sk_X509_REVOKED_num(revoked_stack)): - revoked = _lib.sk_X509_REVOKED_value(revoked_stack, i) - revoked_copy = _X509_REVOKED_dup(revoked) - pyrev = Revoked.__new__(Revoked) - pyrev._revoked = _ffi.gc(revoked_copy, _lib.X509_REVOKED_free) - results.append(pyrev) - if results: - return tuple(results) - - - def add_revoked(self, revoked): - """ - Add a revoked (by value not reference) to the CRL structure - - :param revoked: The new revoked. - :type revoked: :class:`X509` - - :return: None - """ - copy = _X509_REVOKED_dup(revoked._revoked) - if copy == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - add_result = _lib.X509_CRL_add0_revoked(self._crl, copy) - if add_result == 0: - # TODO: This is untested. - _raise_current_error() - - - def export(self, cert, key, type=FILETYPE_PEM, days=100, - digest=_UNSPECIFIED): - """ - export a CRL as a string - - :param cert: Used to sign CRL. - :type cert: :class:`X509` - - :param key: Used to sign CRL. - :type key: :class:`PKey` - - :param type: The export format, either :py:data:`FILETYPE_PEM`, - :py:data:`FILETYPE_ASN1`, or :py:data:`FILETYPE_TEXT`. - - :param int days: The number of days until the next update of this CRL. - - :param bytes digest: The name of the message digest to use (eg - ``b"sha1"``). - - :return: :py:data:`bytes` - """ - if not isinstance(cert, X509): - raise TypeError("cert must be an X509 instance") - if not isinstance(key, PKey): - raise TypeError("key must be a PKey instance") - if not isinstance(type, int): - raise TypeError("type must be an integer") - - if digest is _UNSPECIFIED: - _warn( - "The default message digest (md5) is deprecated. " - "Pass the name of a message digest explicitly.", - category=DeprecationWarning, - stacklevel=2, - ) - digest = b"md5" - - digest_obj = _lib.EVP_get_digestbyname(digest) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - bio = _lib.BIO_new(_lib.BIO_s_mem()) - if bio == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - # A scratch time object to give different values to different CRL fields - sometime = _lib.ASN1_TIME_new() - if sometime == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - _lib.X509_gmtime_adj(sometime, 0) - _lib.X509_CRL_set_lastUpdate(self._crl, sometime) - - _lib.X509_gmtime_adj(sometime, days * 24 * 60 * 60) - _lib.X509_CRL_set_nextUpdate(self._crl, sometime) - - _lib.X509_CRL_set_issuer_name(self._crl, _lib.X509_get_subject_name(cert._x509)) - - sign_result = _lib.X509_CRL_sign(self._crl, key._pkey, digest_obj) - if not sign_result: - _raise_current_error() - - if type == FILETYPE_PEM: - ret = _lib.PEM_write_bio_X509_CRL(bio, self._crl) - elif type == FILETYPE_ASN1: - ret = _lib.i2d_X509_CRL_bio(bio, self._crl) - elif type == FILETYPE_TEXT: - ret = _lib.X509_CRL_print(bio, self._crl) - else: - raise ValueError( - "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT") - - if not ret: - # TODO: This is untested. - _raise_current_error() - - return _bio_to_string(bio) -CRLType = CRL - - - -class PKCS7(object): - def type_is_signed(self): - """ - Check if this NID_pkcs7_signed object - - :return: True if the PKCS7 is of type signed - """ - if _lib.PKCS7_type_is_signed(self._pkcs7): - return True - return False - - - def type_is_enveloped(self): - """ - Check if this NID_pkcs7_enveloped object - - :returns: True if the PKCS7 is of type enveloped - """ - if _lib.PKCS7_type_is_enveloped(self._pkcs7): - return True - return False - - - def type_is_signedAndEnveloped(self): - """ - Check if this NID_pkcs7_signedAndEnveloped object - - :returns: True if the PKCS7 is of type signedAndEnveloped - """ - if _lib.PKCS7_type_is_signedAndEnveloped(self._pkcs7): - return True - return False - - - def type_is_data(self): - """ - Check if this NID_pkcs7_data object - - :return: True if the PKCS7 is of type data - """ - if _lib.PKCS7_type_is_data(self._pkcs7): - return True - return False - - - def get_type_name(self): - """ - Returns the type name of the PKCS7 structure - - :return: A string with the typename - """ - nid = _lib.OBJ_obj2nid(self._pkcs7.type) - string_type = _lib.OBJ_nid2sn(nid) - return _ffi.string(string_type) - -PKCS7Type = PKCS7 - - - -class PKCS12(object): - def __init__(self): - self._pkey = None - self._cert = None - self._cacerts = None - self._friendlyname = None - - - def get_certificate(self): - """ - Return certificate portion of the PKCS12 structure - - :return: X509 object containing the certificate - """ - return self._cert - - - def set_certificate(self, cert): - """ - Replace the certificate portion of the PKCS12 structure - - :param cert: The new certificate. - :type cert: :py:class:`X509` or :py:data:`None` - :return: None - """ - if not isinstance(cert, X509): - raise TypeError("cert must be an X509 instance") - self._cert = cert - - - def get_privatekey(self): - """ - Return private key portion of the PKCS12 structure - - :returns: PKey object containing the private key - """ - return self._pkey - - - def set_privatekey(self, pkey): - """ - Replace or set the certificate portion of the PKCS12 structure - - :param pkey: The new private key. - :type pkey: :py:class:`PKey` - :return: None - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - self._pkey = pkey - - - def get_ca_certificates(self): - """ - Return CA certificates within of the PKCS12 object - - :return: A newly created tuple containing the CA certificates in the chain, - if any are present, or None if no CA certificates are present. - """ - if self._cacerts is not None: - return tuple(self._cacerts) - - - def set_ca_certificates(self, cacerts): - """ - Replace or set the CA certificates within the PKCS12 object. - - :param cacerts: The new CA certificates. - :type cacerts: :py:data:`None` or an iterable of :py:class:`X509` - :return: None - """ - if cacerts is None: - self._cacerts = None - else: - cacerts = list(cacerts) - for cert in cacerts: - if not isinstance(cert, X509): - raise TypeError("iterable must only contain X509 instances") - self._cacerts = cacerts - - - def set_friendlyname(self, name): - """ - Replace or set the certificate portion of the PKCS12 structure - - :param name: The new friendly name. - :type name: :py:class:`bytes` - :return: None - """ - if name is None: - self._friendlyname = None - elif not isinstance(name, bytes): - raise TypeError("name must be a byte string or None (not %r)" % (name,)) - self._friendlyname = name - - - def get_friendlyname(self): - """ - Return friendly name portion of the PKCS12 structure - - :returns: String containing the friendlyname - """ - return self._friendlyname - - - def export(self, passphrase=None, iter=2048, maciter=1): - """ - Dump a PKCS12 object as a string. See also "man PKCS12_create". - - :param passphrase: used to encrypt the PKCS12 - :type passphrase: :py:data:`bytes` - - :param iter: How many times to repeat the encryption - :type iter: :py:data:`int` - - :param maciter: How many times to repeat the MAC - :type maciter: :py:data:`int` - - :return: The string containing the PKCS12 - """ - passphrase = _text_to_bytes_and_warn("passphrase", passphrase) - - if self._cacerts is None: - cacerts = _ffi.NULL - else: - cacerts = _lib.sk_X509_new_null() - cacerts = _ffi.gc(cacerts, _lib.sk_X509_free) - for cert in self._cacerts: - _lib.sk_X509_push(cacerts, cert._x509) - - if passphrase is None: - passphrase = _ffi.NULL - - friendlyname = self._friendlyname - if friendlyname is None: - friendlyname = _ffi.NULL - - if self._pkey is None: - pkey = _ffi.NULL - else: - pkey = self._pkey._pkey - - if self._cert is None: - cert = _ffi.NULL - else: - cert = self._cert._x509 - - pkcs12 = _lib.PKCS12_create( - passphrase, friendlyname, pkey, cert, cacerts, - _lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC, - _lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC, - iter, maciter, 0) - if pkcs12 == _ffi.NULL: - _raise_current_error() - pkcs12 = _ffi.gc(pkcs12, _lib.PKCS12_free) - - bio = _new_mem_buf() - _lib.i2d_PKCS12_bio(bio, pkcs12) - return _bio_to_string(bio) - -PKCS12Type = PKCS12 - - - -class NetscapeSPKI(object): - def __init__(self): - spki = _lib.NETSCAPE_SPKI_new() - self._spki = _ffi.gc(spki, _lib.NETSCAPE_SPKI_free) - - - def sign(self, pkey, digest): - """ - Sign the certificate request using the supplied key and digest - - :param pkey: The key to sign with - :param digest: The message digest to use - :return: None - """ - if pkey._only_public: - raise ValueError("Key has only public part") - - if not pkey._initialized: - raise ValueError("Key is uninitialized") - - digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - sign_result = _lib.NETSCAPE_SPKI_sign(self._spki, pkey._pkey, digest_obj) - if not sign_result: - # TODO: This is untested. - _raise_current_error() - - - def verify(self, key): - """ - Verifies a certificate request using the supplied public key - - :param key: a public key - :return: True if the signature is correct. - :raise OpenSSL.crypto.Error: If the signature is invalid or there is a - problem verifying the signature. - """ - answer = _lib.NETSCAPE_SPKI_verify(self._spki, key._pkey) - if answer <= 0: - _raise_current_error() - return True - - - def b64_encode(self): - """ - Generate a base64 encoded string from an SPKI - - :return: The base64 encoded string - """ - encoded = _lib.NETSCAPE_SPKI_b64_encode(self._spki) - result = _ffi.string(encoded) - _lib.CRYPTO_free(encoded) - return result - - - def get_pubkey(self): - """ - Get the public key of the certificate - - :return: The public key - """ - pkey = PKey.__new__(PKey) - pkey._pkey = _lib.NETSCAPE_SPKI_get_pubkey(self._spki) - if pkey._pkey == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) - pkey._only_public = True - return pkey - - - def set_pubkey(self, pkey): - """ - Set the public key of the certificate - - :param pkey: The public key - :return: None - """ - set_result = _lib.NETSCAPE_SPKI_set_pubkey(self._spki, pkey._pkey) - if not set_result: - # TODO: This is untested. - _raise_current_error() -NetscapeSPKIType = NetscapeSPKI - - -class _PassphraseHelper(object): - def __init__(self, type, passphrase, more_args=False, truncate=False): - if type != FILETYPE_PEM and passphrase is not None: - raise ValueError("only FILETYPE_PEM key format supports encryption") - self._passphrase = passphrase - self._more_args = more_args - self._truncate = truncate - self._problems = [] - - - @property - def callback(self): - if self._passphrase is None: - return _ffi.NULL - elif isinstance(self._passphrase, bytes): - return _ffi.NULL - elif callable(self._passphrase): - return _ffi.callback("pem_password_cb", self._read_passphrase) - else: - raise TypeError("Last argument must be string or callable") - - - @property - def callback_args(self): - if self._passphrase is None: - return _ffi.NULL - elif isinstance(self._passphrase, bytes): - return self._passphrase - elif callable(self._passphrase): - return _ffi.NULL - else: - raise TypeError("Last argument must be string or callable") - - - def raise_if_problem(self, exceptionType=Error): - try: - _exception_from_error_queue(exceptionType) - except exceptionType as e: - from_queue = e - if self._problems: - raise self._problems[0] - return from_queue - - - def _read_passphrase(self, buf, size, rwflag, userdata): - try: - if self._more_args: - result = self._passphrase(size, rwflag, userdata) - else: - result = self._passphrase(rwflag) - if not isinstance(result, bytes): - raise ValueError("String expected") - if len(result) > size: - if self._truncate: - result = result[:size] - else: - raise ValueError("passphrase returned by callback is too long") - for i in range(len(result)): - buf[i] = result[i:i + 1] - return len(result) - except Exception as e: - self._problems.append(e) - return 0 - - - -def load_privatekey(type, buffer, passphrase=None): - """ - Load a private key from a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param buffer: The buffer the key is stored in - :param passphrase: (optional) if encrypted PEM format, this can be - either the passphrase to use, or a callback for - providing the passphrase. - - :return: The PKey object - """ - if isinstance(buffer, _text_type): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - helper = _PassphraseHelper(type, passphrase) - if type == FILETYPE_PEM: - evp_pkey = _lib.PEM_read_bio_PrivateKey( - bio, _ffi.NULL, helper.callback, helper.callback_args) - helper.raise_if_problem() - elif type == FILETYPE_ASN1: - evp_pkey = _lib.d2i_PrivateKey_bio(bio, _ffi.NULL) - else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if evp_pkey == _ffi.NULL: - _raise_current_error() - - pkey = PKey.__new__(PKey) - pkey._pkey = _ffi.gc(evp_pkey, _lib.EVP_PKEY_free) - return pkey - - - -def dump_certificate_request(type, req): - """ - Dump a certificate request to a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param req: The certificate request to dump - :return: The buffer with the dumped certificate request in - """ - bio = _new_mem_buf() - - if type == FILETYPE_PEM: - result_code = _lib.PEM_write_bio_X509_REQ(bio, req._req) - elif type == FILETYPE_ASN1: - result_code = _lib.i2d_X509_REQ_bio(bio, req._req) - elif type == FILETYPE_TEXT: - result_code = _lib.X509_REQ_print_ex(bio, req._req, 0, 0) - else: - raise ValueError("type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT") - - if result_code == 0: - # TODO: This is untested. - _raise_current_error() - - return _bio_to_string(bio) - - - -def load_certificate_request(type, buffer): - """ - Load a certificate request from a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param buffer: The buffer the certificate request is stored in - :return: The X509Req object - """ - if isinstance(buffer, _text_type): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - if type == FILETYPE_PEM: - req = _lib.PEM_read_bio_X509_REQ(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - elif type == FILETYPE_ASN1: - req = _lib.d2i_X509_REQ_bio(bio, _ffi.NULL) - else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if req == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - x509req = X509Req.__new__(X509Req) - x509req._req = _ffi.gc(req, _lib.X509_REQ_free) - return x509req - - - -def sign(pkey, data, digest): - """ - Sign data with a digest - - :param pkey: Pkey to sign with - :param data: data to be signed - :param digest: message digest to use - :return: signature - """ - data = _text_to_bytes_and_warn("data", data) - - digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - md_ctx = _ffi.new("EVP_MD_CTX*") - md_ctx = _ffi.gc(md_ctx, _lib.EVP_MD_CTX_cleanup) - - _lib.EVP_SignInit(md_ctx, digest_obj) - _lib.EVP_SignUpdate(md_ctx, data, len(data)) - - signature_buffer = _ffi.new("unsigned char[]", 512) - signature_length = _ffi.new("unsigned int*") - signature_length[0] = len(signature_buffer) - final_result = _lib.EVP_SignFinal( - md_ctx, signature_buffer, signature_length, pkey._pkey) - - if final_result != 1: - # TODO: This is untested. - _raise_current_error() - - return _ffi.buffer(signature_buffer, signature_length[0])[:] - - - -def verify(cert, signature, data, digest): - """ - Verify a signature - - :param cert: signing certificate (X509 object) - :param signature: signature returned by sign function - :param data: data to be verified - :param digest: message digest to use - :return: None if the signature is correct, raise exception otherwise - """ - data = _text_to_bytes_and_warn("data", data) - - digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - pkey = _lib.X509_get_pubkey(cert._x509) - if pkey == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free) - - md_ctx = _ffi.new("EVP_MD_CTX*") - md_ctx = _ffi.gc(md_ctx, _lib.EVP_MD_CTX_cleanup) - - _lib.EVP_VerifyInit(md_ctx, digest_obj) - _lib.EVP_VerifyUpdate(md_ctx, data, len(data)) - verify_result = _lib.EVP_VerifyFinal(md_ctx, signature, len(signature), pkey) - - if verify_result != 1: - _raise_current_error() - - -def load_crl(type, buffer): - """ - Load a certificate revocation list from a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param buffer: The buffer the CRL is stored in - - :return: The PKey object - """ - if isinstance(buffer, _text_type): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - if type == FILETYPE_PEM: - crl = _lib.PEM_read_bio_X509_CRL(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - elif type == FILETYPE_ASN1: - crl = _lib.d2i_X509_CRL_bio(bio, _ffi.NULL) - else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if crl == _ffi.NULL: - _raise_current_error() - - result = CRL.__new__(CRL) - result._crl = crl - return result - - - -def load_pkcs7_data(type, buffer): - """ - Load pkcs7 data from a buffer - - :param type: The file type (one of FILETYPE_PEM or FILETYPE_ASN1) - :param buffer: The buffer with the pkcs7 data. - :return: The PKCS7 object - """ - if isinstance(buffer, _text_type): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - if type == FILETYPE_PEM: - pkcs7 = _lib.PEM_read_bio_PKCS7(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - elif type == FILETYPE_ASN1: - pkcs7 = _lib.d2i_PKCS7_bio(bio, _ffi.NULL) - else: - # TODO: This is untested. - _raise_current_error() - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if pkcs7 == _ffi.NULL: - _raise_current_error() - - pypkcs7 = PKCS7.__new__(PKCS7) - pypkcs7._pkcs7 = _ffi.gc(pkcs7, _lib.PKCS7_free) - return pypkcs7 - - - -def load_pkcs12(buffer, passphrase=None): - """ - Load a PKCS12 object from a buffer - - :param buffer: The buffer the certificate is stored in - :param passphrase: (Optional) The password to decrypt the PKCS12 lump - :returns: The PKCS12 object - """ - passphrase = _text_to_bytes_and_warn("passphrase", passphrase) - - if isinstance(buffer, _text_type): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - # Use null passphrase if passphrase is None or empty string. With PKCS#12 - # password based encryption no password and a zero length password are two - # different things, but OpenSSL implementation will try both to figure out - # which one works. - if not passphrase: - passphrase = _ffi.NULL - - p12 = _lib.d2i_PKCS12_bio(bio, _ffi.NULL) - if p12 == _ffi.NULL: - _raise_current_error() - p12 = _ffi.gc(p12, _lib.PKCS12_free) - - pkey = _ffi.new("EVP_PKEY**") - cert = _ffi.new("X509**") - cacerts = _ffi.new("Cryptography_STACK_OF_X509**") - - parse_result = _lib.PKCS12_parse(p12, passphrase, pkey, cert, cacerts) - if not parse_result: - _raise_current_error() - - cacerts = _ffi.gc(cacerts[0], _lib.sk_X509_free) - - # openssl 1.0.0 sometimes leaves an X509_check_private_key error in the - # queue for no particular reason. This error isn't interesting to anyone - # outside this function. It's not even interesting to us. Get rid of it. - try: - _raise_current_error() - except Error: - pass - - if pkey[0] == _ffi.NULL: - pykey = None - else: - pykey = PKey.__new__(PKey) - pykey._pkey = _ffi.gc(pkey[0], _lib.EVP_PKEY_free) - - if cert[0] == _ffi.NULL: - pycert = None - friendlyname = None - else: - pycert = X509.__new__(X509) - pycert._x509 = _ffi.gc(cert[0], _lib.X509_free) - - friendlyname_length = _ffi.new("int*") - friendlyname_buffer = _lib.X509_alias_get0(cert[0], friendlyname_length) - friendlyname = _ffi.buffer(friendlyname_buffer, friendlyname_length[0])[:] - if friendlyname_buffer == _ffi.NULL: - friendlyname = None - - pycacerts = [] - for i in range(_lib.sk_X509_num(cacerts)): - pycacert = X509.__new__(X509) - pycacert._x509 = _lib.sk_X509_value(cacerts, i) - pycacerts.append(pycacert) - if not pycacerts: - pycacerts = None - - pkcs12 = PKCS12.__new__(PKCS12) - pkcs12._pkey = pykey - pkcs12._cert = pycert - pkcs12._cacerts = pycacerts - pkcs12._friendlyname = friendlyname - return pkcs12 - - -def _initialize_openssl_threads(get_ident, Lock): - import _ssl - return - - locks = list(Lock() for n in range(_lib.CRYPTO_num_locks())) - - def locking_function(mode, index, filename, line): - if mode & _lib.CRYPTO_LOCK: - locks[index].acquire() - else: - locks[index].release() - - _lib.CRYPTO_set_id_callback( - _ffi.callback("unsigned long (*)(void)", get_ident)) - - _lib.CRYPTO_set_locking_callback( - _ffi.callback( - "void (*)(int, int, const char*, int)", locking_function)) - - -try: - from thread import get_ident - from threading import Lock -except ImportError: - pass -else: - _initialize_openssl_threads(get_ident, Lock) - del get_ident, Lock - -# There are no direct unit tests for this initialization. It is tested -# indirectly since it is necessary for functions like dump_privatekey when -# using encryption. -# -# Thus OpenSSL.test.test_crypto.FunctionTests.test_dump_privatekey_passphrase -# and some other similar tests may fail without this (though they may not if -# the Python runtime has already done some initialization of the underlying -# OpenSSL library (and is linked against the same one that cryptography is -# using)). -_lib.OpenSSL_add_all_algorithms() - -# This is similar but exercised mainly by exception_from_error_queue. It calls -# both ERR_load_crypto_strings() and ERR_load_SSL_strings(). -_lib.SSL_load_error_strings() diff --git a/lib/OpenSSL/rand.py b/lib/OpenSSL/rand.py deleted file mode 100644 index 3adf69369a4a58b01926fe58b7e3bf322ddaedb9..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/rand.py +++ /dev/null @@ -1,180 +0,0 @@ -""" -PRNG management routines, thin wrappers. - -See the file RATIONALE for a short explanation of why this module was written. -""" - -from functools import partial - -from six import integer_types as _integer_types - -from OpenSSL._util import ( - ffi as _ffi, - lib as _lib, - exception_from_error_queue as _exception_from_error_queue, - path_string as _path_string) - - -class Error(Exception): - """ - An error occurred in an `OpenSSL.rand` API. - """ - -_raise_current_error = partial(_exception_from_error_queue, Error) - -_unspecified = object() - -_builtin_bytes = bytes - -def bytes(num_bytes): - """ - Get some random bytes as a string. - - :param num_bytes: The number of bytes to fetch - :return: A string of random bytes - """ - if not isinstance(num_bytes, _integer_types): - raise TypeError("num_bytes must be an integer") - - if num_bytes < 0: - raise ValueError("num_bytes must not be negative") - - result_buffer = _ffi.new("char[]", num_bytes) - result_code = _lib.RAND_bytes(result_buffer, num_bytes) - if result_code == -1: - # TODO: No tests for this code path. Triggering a RAND_bytes failure - # might involve supplying a custom ENGINE? That's hard. - _raise_current_error() - - return _ffi.buffer(result_buffer)[:] - - - -def add(buffer, entropy): - """ - Add data with a given entropy to the PRNG - - :param buffer: Buffer with random data - :param entropy: The entropy (in bytes) measurement of the buffer - :return: None - """ - if not isinstance(buffer, _builtin_bytes): - raise TypeError("buffer must be a byte string") - - if not isinstance(entropy, int): - raise TypeError("entropy must be an integer") - - # TODO Nothing tests this call actually being made, or made properly. - _lib.RAND_add(buffer, len(buffer), entropy) - - - -def seed(buffer): - """ - Alias for rand_add, with entropy equal to length - - :param buffer: Buffer with random data - :return: None - """ - if not isinstance(buffer, _builtin_bytes): - raise TypeError("buffer must be a byte string") - - # TODO Nothing tests this call actually being made, or made properly. - _lib.RAND_seed(buffer, len(buffer)) - - - -def status(): - """ - Retrieve the status of the PRNG - - :return: True if the PRNG is seeded enough, false otherwise - """ - return _lib.RAND_status() - - - -def egd(path, bytes=_unspecified): - """ - Query an entropy gathering daemon (EGD) for random data and add it to the - PRNG. I haven't found any problems when the socket is missing, the function - just returns 0. - - :param path: The path to the EGD socket - :param bytes: (optional) The number of bytes to read, default is 255 - :returns: The number of bytes read (NB: a value of 0 isn't necessarily an - error, check rand.status()) - """ - if not isinstance(path, _builtin_bytes): - raise TypeError("path must be a byte string") - - if bytes is _unspecified: - bytes = 255 - elif not isinstance(bytes, int): - raise TypeError("bytes must be an integer") - - return _lib.RAND_egd_bytes(path, bytes) - - - -def cleanup(): - """ - Erase the memory used by the PRNG. - - :return: None - """ - # TODO Nothing tests this call actually being made, or made properly. - _lib.RAND_cleanup() - - - -def load_file(filename, maxbytes=_unspecified): - """ - Seed the PRNG with data from a file - - :param filename: The file to read data from (``bytes`` or ``unicode``). - :param maxbytes: (optional) The number of bytes to read, default is to read - the entire file - - :return: The number of bytes read - """ - filename = _path_string(filename) - - if maxbytes is _unspecified: - maxbytes = -1 - elif not isinstance(maxbytes, int): - raise TypeError("maxbytes must be an integer") - - return _lib.RAND_load_file(filename, maxbytes) - - - -def write_file(filename): - """ - Save PRNG state to a file - - :param filename: The file to write data to (``bytes`` or ``unicode``). - - :return: The number of bytes written - """ - filename = _path_string(filename) - return _lib.RAND_write_file(filename) - - -# TODO There are no tests for screen at all -def screen(): - """ - Add the current contents of the screen to the PRNG state. Availability: - Windows. - - :return: None - """ - _lib.RAND_screen() - -if getattr(_lib, 'RAND_screen', None) is None: - del screen - - -# TODO There are no tests for the RAND strings being loaded, whatever that -# means. -_lib.ERR_load_RAND_strings() diff --git a/lib/OpenSSL/test/__init__.py b/lib/OpenSSL/test/__init__.py deleted file mode 100644 index 9b08060ca535909b13904140a27378352c4ac8a2..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/test/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (C) Jean-Paul Calderone -# See LICENSE for details. - -""" -Package containing unit tests for :py:mod:`OpenSSL`. -""" diff --git a/lib/OpenSSL/test/test_crypto.py b/lib/OpenSSL/test/test_crypto.py deleted file mode 100644 index f6f075154d2bc6b8b45f58d3a743aa90277101a2..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/test/test_crypto.py +++ /dev/null @@ -1,3671 +0,0 @@ -# Copyright (c) Jean-Paul Calderone -# See LICENSE file for details. - -""" -Unit tests for :py:mod:`OpenSSL.crypto`. -""" - -from unittest import main -from warnings import catch_warnings, simplefilter - -import base64 -import os -import re -from subprocess import PIPE, Popen -from datetime import datetime, timedelta - -from six import u, b, binary_type, PY3 -from warnings import simplefilter -from warnings import catch_warnings - -from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType -from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType -from OpenSSL.crypto import X509Store, X509StoreType, X509StoreContext, X509StoreContextError -from OpenSSL.crypto import X509Req, X509ReqType -from OpenSSL.crypto import X509Extension, X509ExtensionType -from OpenSSL.crypto import load_certificate, load_privatekey -from OpenSSL.crypto import FILETYPE_PEM, FILETYPE_ASN1, FILETYPE_TEXT -from OpenSSL.crypto import dump_certificate, load_certificate_request -from OpenSSL.crypto import dump_certificate_request, dump_privatekey -from OpenSSL.crypto import PKCS7Type, load_pkcs7_data -from OpenSSL.crypto import PKCS12, PKCS12Type, load_pkcs12 -from OpenSSL.crypto import CRL, Revoked, load_crl -from OpenSSL.crypto import NetscapeSPKI, NetscapeSPKIType -from OpenSSL.crypto import ( - sign, verify, get_elliptic_curve, get_elliptic_curves) -from OpenSSL.test.util import ( - EqualityTestsMixin, TestCase, WARNING_TYPE_EXPECTED -) -from OpenSSL._util import native, lib - -def normalize_certificate_pem(pem): - return dump_certificate(FILETYPE_PEM, load_certificate(FILETYPE_PEM, pem)) - - -def normalize_privatekey_pem(pem): - return dump_privatekey(FILETYPE_PEM, load_privatekey(FILETYPE_PEM, pem)) - - -GOOD_CIPHER = "blowfish" -BAD_CIPHER = "zippers" - -GOOD_DIGEST = "MD5" -BAD_DIGEST = "monkeys" - -root_cert_pem = b("""-----BEGIN CERTIFICATE----- -MIIC7TCCAlagAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE -BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU -ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwIhgPMjAwOTAzMjUxMjM2 -NThaGA8yMDE3MDYxMTEyMzY1OFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklM -MRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9U -ZXN0aW5nIFJvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPmaQumL -urpE527uSEHdL1pqcDRmWzu+98Y6YHzT/J7KWEamyMCNZ6fRW1JCR782UQ8a07fy -2xXsKy4WdKaxyG8CcatwmXvpvRQ44dSANMihHELpANTdyVp6DCysED6wkQFurHlF -1dshEaJw8b/ypDhmbVIo6Ci1xvCJqivbLFnbAgMBAAGjgbswgbgwHQYDVR0OBBYE -FINVdy1eIfFJDAkk51QJEo3IfgSuMIGIBgNVHSMEgYAwfoAUg1V3LV4h8UkMCSTn -VAkSjch+BK6hXKRaMFgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UE -BxMHQ2hpY2FnbzEQMA4GA1UEChMHVGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBS -b290IENBggg9DMTgxt659DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GB -AGGCDazMJGoWNBpc03u6+smc95dEead2KlZXBATOdFT1VesY3+nUOqZhEhTGlDMi -hkgaZnzoIq/Uamidegk4hirsCT/R+6vsKAAxNTcBjUeZjlykCJWy5ojShGftXIKY -w/njVbKMXrvc83qmTdGl3TAM0fxQIpqgcglFLveEBgzn ------END CERTIFICATE----- -""") - -root_key_pem = b("""-----BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQD5mkLpi7q6ROdu7khB3S9aanA0Zls7vvfGOmB80/yeylhGpsjA -jWen0VtSQke/NlEPGtO38tsV7CsuFnSmschvAnGrcJl76b0UOOHUgDTIoRxC6QDU -3claegwsrBA+sJEBbqx5RdXbIRGicPG/8qQ4Zm1SKOgotcbwiaor2yxZ2wIDAQAB -AoGBAPCgMpmLxzwDaUmcFbTJUvlLW1hoxNNYSu2jIZm1k/hRAcE60JYwvBkgz3UB -yMEh0AtLxYe0bFk6EHah11tMUPgscbCq73snJ++8koUw+csk22G65hOs51bVb7Aa -6JBe67oLzdtvgCUFAA2qfrKzWRZzAdhUirQUZgySZk+Xq1pBAkEA/kZG0A6roTSM -BVnx7LnPfsycKUsTumorpXiylZJjTi9XtmzxhrYN6wgZlDOOwOLgSQhszGpxVoMD -u3gByT1b2QJBAPtL3mSKdvwRu/+40zaZLwvSJRxaj0mcE4BJOS6Oqs/hS1xRlrNk -PpQ7WJ4yM6ZOLnXzm2mKyxm50Mv64109FtMCQQDOqS2KkjHaLowTGVxwC0DijMfr -I9Lf8sSQk32J5VWCySWf5gGTfEnpmUa41gKTMJIbqZZLucNuDcOtzUaeWZlZAkA8 -ttXigLnCqR486JDPTi9ZscoZkZ+w7y6e/hH8t6d5Vjt48JVyfjPIaJY+km58LcN3 -6AWSeGAdtRFHVzR7oHjVAkB4hutvxiOeiIVQNBhM6RSI9aBPMI21DoX2JRoxvNW2 -cbvAhow217X9V0dVerEOKxnNYspXRrh36h7k4mQA+sDq ------END RSA PRIVATE KEY----- -""") - -intermediate_cert_pem = b("""-----BEGIN CERTIFICATE----- -MIICVzCCAcCgAwIBAgIRAMPzhm6//0Y/g2pmnHR2C4cwDQYJKoZIhvcNAQENBQAw -WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAw -DgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMTQw -ODI4MDIwNDA4WhcNMjQwODI1MDIwNDA4WjBmMRUwEwYDVQQDEwxpbnRlcm1lZGlh -dGUxDDAKBgNVBAoTA29yZzERMA8GA1UECxMIb3JnLXVuaXQxCzAJBgNVBAYTAlVT -MQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU2FuIERpZWdvMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQDYcEQw5lfbEQRjr5Yy4yxAHGV0b9Al+Lmu7wLHMkZ/ZMmK -FGIbljbviiD1Nz97Oh2cpB91YwOXOTN2vXHq26S+A5xe8z/QJbBsyghMur88CjdT -21H2qwMa+r5dCQwEhuGIiZ3KbzB/n4DTMYI5zy4IYPv0pjxShZn4aZTCCK2IUwID -AQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAPIWSkLX -QRMApOjjyC+tMxumT5e2pMqChHmxobQK4NMdrf2VCx+cRT6EmY8sK3/Xl/X8UBQ+ -9n5zXb1ZwhW/sTWgUvmOceJ4/XVs9FkdWOOn1J0XBch9ZIiFe/s5ASIgG7fUdcUF -9mAWS6FK2ca3xIh5kIupCXOFa0dPvlw/YUFT ------END CERTIFICATE----- -""") - -intermediate_key_pem = b("""-----BEGIN RSA PRIVATE KEY----- -MIICWwIBAAKBgQDYcEQw5lfbEQRjr5Yy4yxAHGV0b9Al+Lmu7wLHMkZ/ZMmKFGIb -ljbviiD1Nz97Oh2cpB91YwOXOTN2vXHq26S+A5xe8z/QJbBsyghMur88CjdT21H2 -qwMa+r5dCQwEhuGIiZ3KbzB/n4DTMYI5zy4IYPv0pjxShZn4aZTCCK2IUwIDAQAB -AoGAfSZVV80pSeOKHTYfbGdNY/jHdU9eFUa/33YWriXU+77EhpIItJjkRRgivIfo -rhFJpBSGmDLblaqepm8emsXMeH4+2QzOYIf0QGGP6E6scjTt1PLqdqKfVJ1a2REN -147cujNcmFJb/5VQHHMpaPTgttEjlzuww4+BCDPsVRABWrkCQQD3loH36nLoQTtf -+kQq0T6Bs9/UWkTAGo0ND81ALj0F8Ie1oeZg6RNT96RxZ3aVuFTESTv6/TbjWywO -wdzlmV1vAkEA38rTJ6PTwaJlw5OttdDzAXGPB9tDmzh9oSi7cHwQQXizYd8MBYx4 -sjHUKD3dCQnb1dxJFhd3BT5HsnkRMbVZXQJAbXduH17ZTzcIOXc9jHDXYiFVZV5D -52vV0WCbLzVCZc3jMrtSUKa8lPN5EWrdU3UchWybyG0MR5mX8S5lrF4SoQJAIyUD -DBKaSqpqONCUUx1BTFS9FYrFjzbL4+c1qHCTTPTblt8kUCrDOZjBrKAqeiTmNSum -/qUot9YUBF8m6BuGsQJATHHmdFy/fG1VLkyBp49CAa8tN3Z5r/CgTznI4DfMTf4C -NbRHn2UmYlwQBa+L5lg9phewNe8aEwpPyPLoV85U8Q== ------END RSA PRIVATE KEY----- -""") - -server_cert_pem = b("""-----BEGIN CERTIFICATE----- -MIICKDCCAZGgAwIBAgIJAJn/HpR21r/8MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV -BAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UEBxMHQ2hpY2FnbzEQMA4GA1UEChMH -VGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBSb290IENBMCIYDzIwMDkwMzI1MTIz -NzUzWhgPMjAxNzA2MTExMjM3NTNaMBgxFjAUBgNVBAMTDWxvdmVseSBzZXJ2ZXIw -gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAL6m+G653V0tpBC/OKl22VxOi2Cv -lK4TYu9LHSDP9uDVTe7V5D5Tl6qzFoRRx5pfmnkqT5B+W9byp2NU3FC5hLm5zSAr -b45meUhjEJ/ifkZgbNUjHdBIGP9MAQUHZa5WKdkGIJvGAvs8UzUqlr4TBWQIB24+ -lJ+Ukk/CRgasrYwdAgMBAAGjNjA0MB0GA1UdDgQWBBS4kC7Ij0W1TZXZqXQFAM2e -gKEG2DATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQUFAAOBgQBh30Li -dJ+NlxIOx5343WqIBka3UbsOb2kxWrbkVCrvRapCMLCASO4FqiKWM+L0VDBprqIp -2mgpFQ6FHpoIENGvJhdEKpptQ5i7KaGhnDNTfdy3x1+h852G99f1iyj0RmbuFcM8 -uzujnS8YXWvM7DM1Ilozk4MzPug8jzFp5uhKCQ== ------END CERTIFICATE----- -""") - -server_key_pem = normalize_privatekey_pem(b("""-----BEGIN RSA PRIVATE KEY----- -MIICWwIBAAKBgQC+pvhuud1dLaQQvzipdtlcTotgr5SuE2LvSx0gz/bg1U3u1eQ+ -U5eqsxaEUceaX5p5Kk+QflvW8qdjVNxQuYS5uc0gK2+OZnlIYxCf4n5GYGzVIx3Q -SBj/TAEFB2WuVinZBiCbxgL7PFM1Kpa+EwVkCAduPpSflJJPwkYGrK2MHQIDAQAB -AoGAbwuZ0AR6JveahBaczjfnSpiFHf+mve2UxoQdpyr6ROJ4zg/PLW5K/KXrC48G -j6f3tXMrfKHcpEoZrQWUfYBRCUsGD5DCazEhD8zlxEHahIsqpwA0WWssJA2VOLEN -j6DuV2pCFbw67rfTBkTSo32ahfXxEKev5KswZk0JIzH3ooECQQDgzS9AI89h0gs8 -Dt+1m11Rzqo3vZML7ZIyGApUzVan+a7hbc33nbGRkAXjHaUBJO31it/H6dTO+uwX -msWwNG5ZAkEA2RyFKs5xR5USTFaKLWCgpH/ydV96KPOpBND7TKQx62snDenFNNbn -FwwOhpahld+vqhYk+pfuWWUpQciE+Bu7ZQJASjfT4sQv4qbbKK/scePicnDdx9th -4e1EeB9xwb+tXXXUo/6Bor/AcUNwfiQ6Zt9PZOK9sR3lMZSsP7rMi7kzuQJABie6 -1sXXjFH7nNJvRG4S39cIxq8YRYTy68II/dlB2QzGpKxV/POCxbJ/zu0CU79tuYK7 -NaeNCFfH3aeTrX0LyQJAMBWjWmeKM2G2sCExheeQK0ROnaBC8itCECD4Jsve4nqf -r50+LF74iLXFwqysVCebPKMOpDWp/qQ1BbJQIPs7/A== ------END RSA PRIVATE KEY----- -""")) - -intermediate_server_cert_pem = b("""-----BEGIN CERTIFICATE----- -MIICWDCCAcGgAwIBAgIRAPQFY9jfskSihdiNSNdt6GswDQYJKoZIhvcNAQENBQAw -ZjEVMBMGA1UEAxMMaW50ZXJtZWRpYXRlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT -CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh -biBEaWVnbzAeFw0xNDA4MjgwMjEwNDhaFw0yNDA4MjUwMjEwNDhaMG4xHTAbBgNV -BAMTFGludGVybWVkaWF0ZS1zZXJ2aWNlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT -CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh -biBEaWVnbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqpJZygd+w1faLOr1 -iOAmbBhx5SZWcTCZ/ZjHQTJM7GuPT624QkqsixFghRKdDROwpwnAP7gMRukLqiy4 -+kRuGT5OfyGggL95i2xqA+zehjj08lSTlvGHpePJgCyTavIy5+Ljsj4DKnKyuhxm -biXTRrH83NDgixVkObTEmh/OVK0CAwEAATANBgkqhkiG9w0BAQ0FAAOBgQBa0Npw -UkzjaYEo1OUE1sTI6Mm4riTIHMak4/nswKh9hYup//WVOlr/RBSBtZ7Q/BwbjobN -3bfAtV7eSAqBsfxYXyof7G1ALANQERkq3+oyLP1iVt08W1WOUlIMPhdCF/QuCwy6 -x9MJLhUCGLJPM+O2rAPWVD9wCmvq10ALsiH3yA== ------END CERTIFICATE----- -""") - -intermediate_server_key_pem = b("""-----BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQCqklnKB37DV9os6vWI4CZsGHHlJlZxMJn9mMdBMkzsa49PrbhC -SqyLEWCFEp0NE7CnCcA/uAxG6QuqLLj6RG4ZPk5/IaCAv3mLbGoD7N6GOPTyVJOW -8Yel48mALJNq8jLn4uOyPgMqcrK6HGZuJdNGsfzc0OCLFWQ5tMSaH85UrQIDAQAB -AoGAIQ594j5zna3/9WaPsTgnmhlesVctt4AAx/n827DA4ayyuHFlXUuVhtoWR5Pk -5ezj9mtYW8DyeCegABnsu2vZni/CdvU6uiS1Hv6qM1GyYDm9KWgovIP9rQCDSGaz -d57IWVGxx7ODFkm3gN5nxnSBOFVHytuW1J7FBRnEsehRroECQQDXHFOv82JuXDcz -z3+4c74IEURdOHcbycxlppmK9kFqm5lsUdydnnGW+mvwDk0APOB7Wg7vyFyr393e -dpmBDCzNAkEAyv6tVbTKUYhSjW+QhabJo896/EqQEYUmtMXxk4cQnKeR/Ao84Rkf -EqD5IykMUfUI0jJU4DGX+gWZ10a7kNbHYQJAVFCuHNFxS4Cpwo0aqtnzKoZaHY/8 -X9ABZfafSHCtw3Op92M+7ikkrOELXdS9KdKyyqbKJAKNEHF3LbOfB44WIQJAA2N4 -9UNNVUsXRbElEnYUS529CdUczo4QdVgQjkvk5RiPAUwSdBd9Q0xYnFOlFwEmIowg -ipWJWe0aAlP18ZcEQQJBAL+5lekZ/GUdQoZ4HAsN5a9syrzavJ9VvU1KOOPorPZK -nMRZbbQgP+aSB7yl6K0gaLaZ8XaK0pjxNBh6ASqg9f4= ------END RSA PRIVATE KEY----- -""") - -client_cert_pem = b("""-----BEGIN CERTIFICATE----- -MIICJjCCAY+gAwIBAgIJAKxpFI5lODkjMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV -BAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UEBxMHQ2hpY2FnbzEQMA4GA1UEChMH -VGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBSb290IENBMCIYDzIwMDkwMzI1MTIz -ODA1WhgPMjAxNzA2MTExMjM4MDVaMBYxFDASBgNVBAMTC3VnbHkgY2xpZW50MIGf -MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAZh/SRtNm5ntMT4qb6YzEpTroMlq2 -rn+GrRHRiZ+xkCw/CGNhbtPir7/QxaUj26BSmQrHw1bGKEbPsWiW7bdXSespl+xK -iku4G/KvnnmWdeJHqsiXeUZtqurMELcPQAw9xPHEuhqqUJvvEoMTsnCEqGM+7Dtb -oCRajYyHfluARQIDAQABozYwNDAdBgNVHQ4EFgQUNQB+qkaOaEVecf1J3TTUtAff -0fAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADgYEAyv/Jh7gM -Q3OHvmsFEEvRI+hsW8y66zK4K5de239Y44iZrFYkt7Q5nBPMEWDj4F2hLYWL/qtI -9Zdr0U4UDCU9SmmGYh4o7R4TZ5pGFvBYvjhHbkSFYFQXZxKUi+WUxplP6I0wr2KJ -PSTJCjJOn3xo2NTKRgV1gaoTf2EhL+RG8TQ= ------END CERTIFICATE----- -""") - -client_key_pem = normalize_privatekey_pem(b("""-----BEGIN RSA PRIVATE KEY----- -MIICXgIBAAKBgQDAZh/SRtNm5ntMT4qb6YzEpTroMlq2rn+GrRHRiZ+xkCw/CGNh -btPir7/QxaUj26BSmQrHw1bGKEbPsWiW7bdXSespl+xKiku4G/KvnnmWdeJHqsiX -eUZtqurMELcPQAw9xPHEuhqqUJvvEoMTsnCEqGM+7DtboCRajYyHfluARQIDAQAB -AoGATkZ+NceY5Glqyl4mD06SdcKfV65814vg2EL7V9t8+/mi9rYL8KztSXGlQWPX -zuHgtRoMl78yQ4ZJYOBVo+nsx8KZNRCEBlE19bamSbQLCeQMenWnpeYyQUZ908gF -h6L9qsFVJepgA9RDgAjyDoS5CaWCdCCPCH2lDkdcqC54SVUCQQDseuduc4wi8h4t -V8AahUn9fn9gYfhoNuM0gdguTA0nPLVWz4hy1yJiWYQe0H7NLNNTmCKiLQaJpAbb -TC6vE8C7AkEA0Ee8CMJUc20BnGEmxwgWcVuqFWaKCo8jTH1X38FlATUsyR3krjW2 -dL3yDD9NwHxsYP7nTKp/U8MV7U9IBn4y/wJBAJl7H0/BcLeRmuJk7IqJ7b635iYB -D/9beFUw3MUXmQXZUfyYz39xf6CDZsu1GEdEC5haykeln3Of4M9d/4Kj+FcCQQCY -si6xwT7GzMDkk/ko684AV3KPc/h6G0yGtFIrMg7J3uExpR/VdH2KgwMkZXisSMvw -JJEQjOMCVsEJlRk54WWjAkEAzoZNH6UhDdBK5F38rVt/y4SEHgbSfJHIAmPS32Kq -f6GGcfNpip0Uk7q7udTKuX7Q/buZi/C4YW7u3VKAquv9NA== ------END RSA PRIVATE KEY----- -""")) - -cleartextCertificatePEM = b("""-----BEGIN CERTIFICATE----- -MIIC7TCCAlagAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE -BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU -ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwIhgPMjAwOTAzMjUxMjM2 -NThaGA8yMDE3MDYxMTEyMzY1OFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklM -MRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9U -ZXN0aW5nIFJvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPmaQumL -urpE527uSEHdL1pqcDRmWzu+98Y6YHzT/J7KWEamyMCNZ6fRW1JCR782UQ8a07fy -2xXsKy4WdKaxyG8CcatwmXvpvRQ44dSANMihHELpANTdyVp6DCysED6wkQFurHlF -1dshEaJw8b/ypDhmbVIo6Ci1xvCJqivbLFnbAgMBAAGjgbswgbgwHQYDVR0OBBYE -FINVdy1eIfFJDAkk51QJEo3IfgSuMIGIBgNVHSMEgYAwfoAUg1V3LV4h8UkMCSTn -VAkSjch+BK6hXKRaMFgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UE -BxMHQ2hpY2FnbzEQMA4GA1UEChMHVGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBS -b290IENBggg9DMTgxt659DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GB -AGGCDazMJGoWNBpc03u6+smc95dEead2KlZXBATOdFT1VesY3+nUOqZhEhTGlDMi -hkgaZnzoIq/Uamidegk4hirsCT/R+6vsKAAxNTcBjUeZjlykCJWy5ojShGftXIKY -w/njVbKMXrvc83qmTdGl3TAM0fxQIpqgcglFLveEBgzn ------END CERTIFICATE----- -""") - -cleartextPrivateKeyPEM = normalize_privatekey_pem(b("""\ ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQD5mkLpi7q6ROdu7khB3S9aanA0Zls7vvfGOmB80/yeylhGpsjA -jWen0VtSQke/NlEPGtO38tsV7CsuFnSmschvAnGrcJl76b0UOOHUgDTIoRxC6QDU -3claegwsrBA+sJEBbqx5RdXbIRGicPG/8qQ4Zm1SKOgotcbwiaor2yxZ2wIDAQAB -AoGBAPCgMpmLxzwDaUmcFbTJUvlLW1hoxNNYSu2jIZm1k/hRAcE60JYwvBkgz3UB -yMEh0AtLxYe0bFk6EHah11tMUPgscbCq73snJ++8koUw+csk22G65hOs51bVb7Aa -6JBe67oLzdtvgCUFAA2qfrKzWRZzAdhUirQUZgySZk+Xq1pBAkEA/kZG0A6roTSM -BVnx7LnPfsycKUsTumorpXiylZJjTi9XtmzxhrYN6wgZlDOOwOLgSQhszGpxVoMD -u3gByT1b2QJBAPtL3mSKdvwRu/+40zaZLwvSJRxaj0mcE4BJOS6Oqs/hS1xRlrNk -PpQ7WJ4yM6ZOLnXzm2mKyxm50Mv64109FtMCQQDOqS2KkjHaLowTGVxwC0DijMfr -I9Lf8sSQk32J5VWCySWf5gGTfEnpmUa41gKTMJIbqZZLucNuDcOtzUaeWZlZAkA8 -ttXigLnCqR486JDPTi9ZscoZkZ+w7y6e/hH8t6d5Vjt48JVyfjPIaJY+km58LcN3 -6AWSeGAdtRFHVzR7oHjVAkB4hutvxiOeiIVQNBhM6RSI9aBPMI21DoX2JRoxvNW2 -cbvAhow217X9V0dVerEOKxnNYspXRrh36h7k4mQA+sDq ------END RSA PRIVATE KEY----- -""")) - -cleartextCertificateRequestPEM = b("""-----BEGIN CERTIFICATE REQUEST----- -MIIBnjCCAQcCAQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQH -EwdDaGljYWdvMRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEXMBUGA1UEAxMORnJl -ZGVyaWNrIERlYW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANp6Y17WzKSw -BsUWkXdqg6tnXy8H8hA1msCMWpc+/2KJ4mbv5NyD6UD+/SqagQqulPbF/DFea9nA -E0zhmHJELcM8gUTIlXv/cgDWnmK4xj8YkjVUiCdqKRAKeuzLG1pGmwwF5lGeJpXN -xQn5ecR0UYSOWj6TTGXB9VyUMQzCClcBAgMBAAGgADANBgkqhkiG9w0BAQUFAAOB -gQAAJGuF/R/GGbeC7FbFW+aJgr9ee0Xbl6nlhu7pTe67k+iiKT2dsl2ti68MVTnu -Vrb3HUNqOkiwsJf6kCtq5oPn3QVYzTa76Dt2y3Rtzv6boRSlmlfrgS92GNma8JfR -oICQk3nAudi6zl1Dix3BCv1pUp5KMtGn3MeDEi6QFGy2rA== ------END CERTIFICATE REQUEST----- -""") - -encryptedPrivateKeyPEM = b("""-----BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: DES-EDE3-CBC,9573604A18579E9E - -SHOho56WxDkT0ht10UTeKc0F5u8cqIa01kzFAmETw0MAs8ezYtK15NPdCXUm3X/2 -a17G7LSF5bkxOgZ7vpXyMzun/owrj7CzvLxyncyEFZWvtvzaAhPhvTJtTIB3kf8B -8+qRcpTGK7NgXEgYBW5bj1y4qZkD4zCL9o9NQzsKI3Ie8i0239jsDOWR38AxjXBH -mGwAQ4Z6ZN5dnmM4fhMIWsmFf19sNyAML4gHenQCHhmXbjXeVq47aC2ProInJbrm -+00TcisbAQ40V9aehVbcDKtS4ZbMVDwncAjpXpcncC54G76N6j7F7wL7L/FuXa3A -fvSVy9n2VfF/pJ3kYSflLHH2G/DFxjF7dl0GxhKPxJjp3IJi9VtuvmN9R2jZWLQF -tfC8dXgy/P9CfFQhlinqBTEwgH0oZ/d4k4NVFDSdEMaSdmBAjlHpc+Vfdty3HVnV -rKXj//wslsFNm9kIwJGIgKUa/n2jsOiydrsk1mgH7SmNCb3YHgZhbbnq0qLat/HC -gHDt3FHpNQ31QzzL3yrenFB2L9osIsnRsDTPFNi4RX4SpDgNroxOQmyzCCV6H+d4 -o1mcnNiZSdxLZxVKccq0AfRpHqpPAFnJcQHP6xyT9MZp6fBa0XkxDnt9kNU8H3Qw -7SJWZ69VXjBUzMlQViLuaWMgTnL+ZVyFZf9hTF7U/ef4HMLMAVNdiaGG+G+AjCV/ -MbzjS007Oe4qqBnCWaFPSnJX6uLApeTbqAxAeyCql56ULW5x6vDMNC3dwjvS/CEh -11n8RkgFIQA0AhuKSIg3CbuartRsJnWOLwgLTzsrKYL4yRog1RJrtw== ------END RSA PRIVATE KEY----- -""") - -encryptedPrivateKeyPEMPassphrase = b("foobar") - -# Some PKCS#7 stuff. Generated with the openssl command line: -# -# openssl crl2pkcs7 -inform pem -outform pem -certfile s.pem -nocrl -# -# with a certificate and key (but the key should be irrelevant) in s.pem -pkcs7Data = b("""\ ------BEGIN PKCS7----- -MIIDNwYJKoZIhvcNAQcCoIIDKDCCAyQCAQExADALBgkqhkiG9w0BBwGgggMKMIID -BjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzERMA8G -A1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtN -MkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNA -cG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzELMAkG -A1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhvc3Qx -HTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEBBQAD -SwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh5kwI -zOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQCMAAw -LAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G -A1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7hyNp -65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0y -Q3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8g -Q2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNv -bYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6BoJu -VwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++7QGG -/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JEWUQ9 -Ho4EzbYCOaEAMQA= ------END PKCS7----- -""") - -pkcs7DataASN1 = base64.b64decode(b""" -MIIDNwYJKoZIhvcNAQcCoIIDKDCCAyQCAQExADALBgkqhkiG9w0BBwGgggMKMIID -BjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzERMA8G -A1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtN -MkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNA -cG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzELMAkG -A1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhvc3Qx -HTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEBBQAD -SwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh5kwI -zOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQCMAAw -LAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G -A1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7hyNp -65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0y -Q3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8g -Q2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNv -bYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6BoJu -VwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++7QGG -/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JEWUQ9 -Ho4EzbYCOaEAMQA= -""") - -crlData = b("""\ ------BEGIN X509 CRL----- -MIIBWzCBxTANBgkqhkiG9w0BAQQFADBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMC -SUwxEDAOBgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAWBgNVBAMT -D1Rlc3RpbmcgUm9vdCBDQRcNMDkwNzI2MDQzNDU2WhcNMTIwOTI3MDI0MTUyWjA8 -MBUCAgOrGA8yMDA5MDcyNTIzMzQ1NlowIwICAQAYDzIwMDkwNzI1MjMzNDU2WjAM -MAoGA1UdFQQDCgEEMA0GCSqGSIb3DQEBBAUAA4GBAEBt7xTs2htdD3d4ErrcGAw1 -4dKcVnIWTutoI7xxen26Wwvh8VCsT7i/UeP+rBl9rC/kfjWjzQk3/zleaarGTpBT -0yp4HXRFFoRhhSE/hP+eteaPXRgrsNRLHe9ZDd69wmh7J1wMDb0m81RG7kqcbsid -vrzEeLDRiiPl92dyyWmu ------END X509 CRL----- -""") - - -# A broken RSA private key which can be used to test the error path through -# PKey.check. -inconsistentPrivateKeyPEM = b("""-----BEGIN RSA PRIVATE KEY----- -MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh -5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEaAQJBAIqm/bz4NA1H++Vx5Ewx -OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT -zIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4 -nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2 -HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNeH+vRWsAYU/gbx+OQB+7VOcBAiEA -oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w= ------END RSA PRIVATE KEY----- -""") - -# certificate with NULL bytes in subjectAltName and common name - -nulbyteSubjectAltNamePEM = b("""-----BEGIN CERTIFICATE----- -MIIE2DCCA8CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBxTELMAkGA1UEBhMCVVMx -DzANBgNVBAgMBk9yZWdvbjESMBAGA1UEBwwJQmVhdmVydG9uMSMwIQYDVQQKDBpQ -eXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEgMB4GA1UECwwXUHl0aG9uIENvcmUg -RGV2ZWxvcG1lbnQxJDAiBgNVBAMMG251bGwucHl0aG9uLm9yZwBleGFtcGxlLm9y -ZzEkMCIGCSqGSIb3DQEJARYVcHl0aG9uLWRldkBweXRob24ub3JnMB4XDTEzMDgw -NzEzMTE1MloXDTEzMDgwNzEzMTI1MlowgcUxCzAJBgNVBAYTAlVTMQ8wDQYDVQQI -DAZPcmVnb24xEjAQBgNVBAcMCUJlYXZlcnRvbjEjMCEGA1UECgwaUHl0aG9uIFNv -ZnR3YXJlIEZvdW5kYXRpb24xIDAeBgNVBAsMF1B5dGhvbiBDb3JlIERldmVsb3Bt -ZW50MSQwIgYDVQQDDBtudWxsLnB5dGhvbi5vcmcAZXhhbXBsZS5vcmcxJDAiBgkq -hkiG9w0BCQEWFXB5dGhvbi1kZXZAcHl0aG9uLm9yZzCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBALXq7cn7Rn1vO3aA3TrzA5QLp6bb7B3f/yN0CJ2XFj+j -pHs+Gw6WWSUDpybiiKnPec33BFawq3kyblnBMjBU61ioy5HwQqVkJ8vUVjGIUq3P -vX/wBmQfzCe4o4uM89gpHyUL9UYGG8oCRa17dgqcv7u5rg0Wq2B1rgY+nHwx3JIv -KRrgSwyRkGzpN8WQ1yrXlxWjgI9de0mPVDDUlywcWze1q2kwaEPTM3hLAmD1PESA -oY/n8A/RXoeeRs9i/Pm/DGUS8ZPINXk/yOzsR/XvvkTVroIeLZqfmFpnZeF0cHzL -08LODkVJJ9zjLdT7SA4vnne4FEbAxDbKAq5qkYzaL4UCAwEAAaOB0DCBzTAMBgNV -HRMBAf8EAjAAMB0GA1UdDgQWBBSIWlXAUv9hzVKjNQ/qWpwkOCL3XDALBgNVHQ8E -BAMCBeAwgZAGA1UdEQSBiDCBhYIeYWx0bnVsbC5weXRob24ub3JnAGV4YW1wbGUu -Y29tgSBudWxsQHB5dGhvbi5vcmcAdXNlckBleGFtcGxlLm9yZ4YpaHR0cDovL251 -bGwucHl0aG9uLm9yZwBodHRwOi8vZXhhbXBsZS5vcmeHBMAAAgGHECABDbgAAAAA -AAAAAAAAAAEwDQYJKoZIhvcNAQEFBQADggEBAKxPRe99SaghcI6IWT7UNkJw9aO9 -i9eo0Fj2MUqxpKbdb9noRDy2CnHWf7EIYZ1gznXPdwzSN4YCjV5d+Q9xtBaowT0j -HPERs1ZuytCNNJTmhyqZ8q6uzMLoht4IqH/FBfpvgaeC5tBTnTT0rD5A/olXeimk -kX4LxlEx5RAvpGB2zZVRGr6LobD9rVK91xuHYNIxxxfEGE8tCCWjp0+3ksri9SXx -VHWBnbM9YaL32u3hxm8sYB/Yb8WSBavJCWJJqRStVRHM1koZlJmXNx2BX4vPo6iW -RFEIPQsFZRLrtnCAiEhyT8bC2s/Njlu6ly9gtJZWSV46Q3ZjBL4q9sHKqZQ= ------END CERTIFICATE-----""") - - -class X509ExtTests(TestCase): - """ - Tests for :py:class:`OpenSSL.crypto.X509Extension`. - """ - - def setUp(self): - """ - Create a new private key and start a certificate request (for a test - method to finish in one way or another). - """ - super(X509ExtTests, self).setUp() - # Basic setup stuff to generate a certificate - self.pkey = PKey() - self.pkey.generate_key(TYPE_RSA, 384) - self.req = X509Req() - self.req.set_pubkey(self.pkey) - # Authority good you have. - self.req.get_subject().commonName = "Yoda root CA" - self.x509 = X509() - self.subject = self.x509.get_subject() - self.subject.commonName = self.req.get_subject().commonName - self.x509.set_issuer(self.subject) - self.x509.set_pubkey(self.pkey) - now = b(datetime.now().strftime("%Y%m%d%H%M%SZ")) - expire = b((datetime.now() + timedelta(days=100)).strftime("%Y%m%d%H%M%SZ")) - self.x509.set_notBefore(now) - self.x509.set_notAfter(expire) - - - def tearDown(self): - """ - Forget all of the pyOpenSSL objects so they can be garbage collected, - their memory released, and not interfere with the leak detection code. - """ - self.pkey = self.req = self.x509 = self.subject = None - super(X509ExtTests, self).tearDown() - - - def test_str(self): - """ - The string representation of :py:class:`X509Extension` instances as returned by - :py:data:`str` includes stuff. - """ - # This isn't necessarily the best string representation. Perhaps it - # will be changed/improved in the future. - self.assertEquals( - str(X509Extension(b('basicConstraints'), True, b('CA:false'))), - 'CA:FALSE') - - - def test_type(self): - """ - :py:class:`X509Extension` and :py:class:`X509ExtensionType` refer to the same type object - and can be used to create instances of that type. - """ - self.assertIdentical(X509Extension, X509ExtensionType) - self.assertConsistentType( - X509Extension, - 'X509Extension', b('basicConstraints'), True, b('CA:true')) - - - def test_construction(self): - """ - :py:class:`X509Extension` accepts an extension type name, a critical flag, - and an extension value and returns an :py:class:`X509ExtensionType` instance. - """ - basic = X509Extension(b('basicConstraints'), True, b('CA:true')) - self.assertTrue( - isinstance(basic, X509ExtensionType), - "%r is of type %r, should be %r" % ( - basic, type(basic), X509ExtensionType)) - - comment = X509Extension( - b('nsComment'), False, b('pyOpenSSL unit test')) - self.assertTrue( - isinstance(comment, X509ExtensionType), - "%r is of type %r, should be %r" % ( - comment, type(comment), X509ExtensionType)) - - - def test_invalid_extension(self): - """ - :py:class:`X509Extension` raises something if it is passed a bad extension - name or value. - """ - self.assertRaises( - Error, X509Extension, b('thisIsMadeUp'), False, b('hi')) - self.assertRaises( - Error, X509Extension, b('basicConstraints'), False, b('blah blah')) - - # Exercise a weird one (an extension which uses the r2i method). This - # exercises the codepath that requires a non-NULL ctx to be passed to - # X509V3_EXT_nconf. It can't work now because we provide no - # configuration database. It might be made to work in the future. - self.assertRaises( - Error, X509Extension, b('proxyCertInfo'), True, - b('language:id-ppl-anyLanguage,pathlen:1,policy:text:AB')) - - - def test_get_critical(self): - """ - :py:meth:`X509ExtensionType.get_critical` returns the value of the - extension's critical flag. - """ - ext = X509Extension(b('basicConstraints'), True, b('CA:true')) - self.assertTrue(ext.get_critical()) - ext = X509Extension(b('basicConstraints'), False, b('CA:true')) - self.assertFalse(ext.get_critical()) - - - def test_get_short_name(self): - """ - :py:meth:`X509ExtensionType.get_short_name` returns a string giving the short - type name of the extension. - """ - ext = X509Extension(b('basicConstraints'), True, b('CA:true')) - self.assertEqual(ext.get_short_name(), b('basicConstraints')) - ext = X509Extension(b('nsComment'), True, b('foo bar')) - self.assertEqual(ext.get_short_name(), b('nsComment')) - - - def test_get_data(self): - """ - :py:meth:`X509Extension.get_data` returns a string giving the data of the - extension. - """ - ext = X509Extension(b('basicConstraints'), True, b('CA:true')) - # Expect to get back the DER encoded form of CA:true. - self.assertEqual(ext.get_data(), b('0\x03\x01\x01\xff')) - - - def test_get_data_wrong_args(self): - """ - :py:meth:`X509Extension.get_data` raises :py:exc:`TypeError` if passed any arguments. - """ - ext = X509Extension(b('basicConstraints'), True, b('CA:true')) - self.assertRaises(TypeError, ext.get_data, None) - self.assertRaises(TypeError, ext.get_data, "foo") - self.assertRaises(TypeError, ext.get_data, 7) - - - def test_unused_subject(self): - """ - The :py:data:`subject` parameter to :py:class:`X509Extension` may be provided for an - extension which does not use it and is ignored in this case. - """ - ext1 = X509Extension( - b('basicConstraints'), False, b('CA:TRUE'), subject=self.x509) - self.x509.add_extensions([ext1]) - self.x509.sign(self.pkey, 'sha1') - # This is a little lame. Can we think of a better way? - text = dump_certificate(FILETYPE_TEXT, self.x509) - self.assertTrue(b('X509v3 Basic Constraints:') in text) - self.assertTrue(b('CA:TRUE') in text) - - - def test_subject(self): - """ - If an extension requires a subject, the :py:data:`subject` parameter to - :py:class:`X509Extension` provides its value. - """ - ext3 = X509Extension( - b('subjectKeyIdentifier'), False, b('hash'), subject=self.x509) - self.x509.add_extensions([ext3]) - self.x509.sign(self.pkey, 'sha1') - text = dump_certificate(FILETYPE_TEXT, self.x509) - self.assertTrue(b('X509v3 Subject Key Identifier:') in text) - - - def test_missing_subject(self): - """ - If an extension requires a subject and the :py:data:`subject` parameter is - given no value, something happens. - """ - self.assertRaises( - Error, X509Extension, b('subjectKeyIdentifier'), False, b('hash')) - - - def test_invalid_subject(self): - """ - If the :py:data:`subject` parameter is given a value which is not an - :py:class:`X509` instance, :py:exc:`TypeError` is raised. - """ - for badObj in [True, object(), "hello", [], self]: - self.assertRaises( - TypeError, - X509Extension, - 'basicConstraints', False, 'CA:TRUE', subject=badObj) - - - def test_unused_issuer(self): - """ - The :py:data:`issuer` parameter to :py:class:`X509Extension` may be provided for an - extension which does not use it and is ignored in this case. - """ - ext1 = X509Extension( - b('basicConstraints'), False, b('CA:TRUE'), issuer=self.x509) - self.x509.add_extensions([ext1]) - self.x509.sign(self.pkey, 'sha1') - text = dump_certificate(FILETYPE_TEXT, self.x509) - self.assertTrue(b('X509v3 Basic Constraints:') in text) - self.assertTrue(b('CA:TRUE') in text) - - - def test_issuer(self): - """ - If an extension requires an issuer, the :py:data:`issuer` parameter to - :py:class:`X509Extension` provides its value. - """ - ext2 = X509Extension( - b('authorityKeyIdentifier'), False, b('issuer:always'), - issuer=self.x509) - self.x509.add_extensions([ext2]) - self.x509.sign(self.pkey, 'sha1') - text = dump_certificate(FILETYPE_TEXT, self.x509) - self.assertTrue(b('X509v3 Authority Key Identifier:') in text) - self.assertTrue(b('DirName:/CN=Yoda root CA') in text) - - - def test_missing_issuer(self): - """ - If an extension requires an issue and the :py:data:`issuer` parameter is given - no value, something happens. - """ - self.assertRaises( - Error, - X509Extension, - b('authorityKeyIdentifier'), False, - b('keyid:always,issuer:always')) - - - def test_invalid_issuer(self): - """ - If the :py:data:`issuer` parameter is given a value which is not an - :py:class:`X509` instance, :py:exc:`TypeError` is raised. - """ - for badObj in [True, object(), "hello", [], self]: - self.assertRaises( - TypeError, - X509Extension, - 'authorityKeyIdentifier', False, 'keyid:always,issuer:always', - issuer=badObj) - - - -class PKeyTests(TestCase): - """ - Unit tests for :py:class:`OpenSSL.crypto.PKey`. - """ - def test_type(self): - """ - :py:class:`PKey` and :py:class:`PKeyType` refer to the same type object - and can be used to create instances of that type. - """ - self.assertIdentical(PKey, PKeyType) - self.assertConsistentType(PKey, 'PKey') - - - def test_construction(self): - """ - :py:class:`PKey` takes no arguments and returns a new :py:class:`PKey` instance. - """ - self.assertRaises(TypeError, PKey, None) - key = PKey() - self.assertTrue( - isinstance(key, PKeyType), - "%r is of type %r, should be %r" % (key, type(key), PKeyType)) - - - def test_pregeneration(self): - """ - :py:attr:`PKeyType.bits` and :py:attr:`PKeyType.type` return :py:data:`0` before the key is - generated. :py:attr:`PKeyType.check` raises :py:exc:`TypeError` before the key is - generated. - """ - key = PKey() - self.assertEqual(key.type(), 0) - self.assertEqual(key.bits(), 0) - self.assertRaises(TypeError, key.check) - - - def test_failedGeneration(self): - """ - :py:meth:`PKeyType.generate_key` takes two arguments, the first giving the key - type as one of :py:data:`TYPE_RSA` or :py:data:`TYPE_DSA` and the second giving the - number of bits to generate. If an invalid type is specified or - generation fails, :py:exc:`Error` is raised. If an invalid number of bits is - specified, :py:exc:`ValueError` or :py:exc:`Error` is raised. - """ - key = PKey() - self.assertRaises(TypeError, key.generate_key) - self.assertRaises(TypeError, key.generate_key, 1, 2, 3) - self.assertRaises(TypeError, key.generate_key, "foo", "bar") - self.assertRaises(Error, key.generate_key, -1, 0) - - self.assertRaises(ValueError, key.generate_key, TYPE_RSA, -1) - self.assertRaises(ValueError, key.generate_key, TYPE_RSA, 0) - - # XXX RSA generation for small values of bits is fairly buggy in a wide - # range of OpenSSL versions. I need to figure out what the safe lower - # bound for a reasonable number of OpenSSL versions is and explicitly - # check for that in the wrapper. The failure behavior is typically an - # infinite loop inside OpenSSL. - - # self.assertRaises(Error, key.generate_key, TYPE_RSA, 2) - - # XXX DSA generation seems happy with any number of bits. The DSS - # says bits must be between 512 and 1024 inclusive. OpenSSL's DSA - # generator doesn't seem to care about the upper limit at all. For - # the lower limit, it uses 512 if anything smaller is specified. - # So, it doesn't seem possible to make generate_key fail for - # TYPE_DSA with a bits argument which is at least an int. - - # self.assertRaises(Error, key.generate_key, TYPE_DSA, -7) - - - def test_rsaGeneration(self): - """ - :py:meth:`PKeyType.generate_key` generates an RSA key when passed - :py:data:`TYPE_RSA` as a type and a reasonable number of bits. - """ - bits = 128 - key = PKey() - key.generate_key(TYPE_RSA, bits) - self.assertEqual(key.type(), TYPE_RSA) - self.assertEqual(key.bits(), bits) - self.assertTrue(key.check()) - - - def test_dsaGeneration(self): - """ - :py:meth:`PKeyType.generate_key` generates a DSA key when passed - :py:data:`TYPE_DSA` as a type and a reasonable number of bits. - """ - # 512 is a magic number. The DSS (Digital Signature Standard) - # allows a minimum of 512 bits for DSA. DSA_generate_parameters - # will silently promote any value below 512 to 512. - bits = 512 - key = PKey() - key.generate_key(TYPE_DSA, bits) - # self.assertEqual(key.type(), TYPE_DSA) - # self.assertEqual(key.bits(), bits) - # self.assertRaises(TypeError, key.check) - - - def test_regeneration(self): - """ - :py:meth:`PKeyType.generate_key` can be called multiple times on the same - key to generate new keys. - """ - key = PKey() - for type, bits in [(TYPE_RSA, 512), (TYPE_DSA, 576)]: - key.generate_key(type, bits) - self.assertEqual(key.type(), type) - self.assertEqual(key.bits(), bits) - - - def test_inconsistentKey(self): - """ - :py:`PKeyType.check` returns :py:exc:`Error` if the key is not consistent. - """ - key = load_privatekey(FILETYPE_PEM, inconsistentPrivateKeyPEM) - self.assertRaises(Error, key.check) - - - def test_check_wrong_args(self): - """ - :py:meth:`PKeyType.check` raises :py:exc:`TypeError` if called with any arguments. - """ - self.assertRaises(TypeError, PKey().check, None) - self.assertRaises(TypeError, PKey().check, object()) - self.assertRaises(TypeError, PKey().check, 1) - - - def test_check_public_key(self): - """ - :py:meth:`PKeyType.check` raises :py:exc:`TypeError` if only the public - part of the key is available. - """ - # A trick to get a public-only key - key = PKey() - key.generate_key(TYPE_RSA, 512) - cert = X509() - cert.set_pubkey(key) - pub = cert.get_pubkey() - self.assertRaises(TypeError, pub.check) - - - -class X509NameTests(TestCase): - """ - Unit tests for :py:class:`OpenSSL.crypto.X509Name`. - """ - def _x509name(self, **attrs): - # XXX There's no other way to get a new X509Name yet. - name = X509().get_subject() - attrs = list(attrs.items()) - # Make the order stable - order matters! - def key(attr): - return attr[1] - attrs.sort(key=key) - for k, v in attrs: - setattr(name, k, v) - return name - - - def test_type(self): - """ - The type of X509Name objects is :py:class:`X509NameType`. - """ - self.assertIdentical(X509Name, X509NameType) - self.assertEqual(X509NameType.__name__, 'X509Name') - self.assertTrue(isinstance(X509NameType, type)) - - name = self._x509name() - self.assertTrue( - isinstance(name, X509NameType), - "%r is of type %r, should be %r" % ( - name, type(name), X509NameType)) - - - def test_onlyStringAttributes(self): - """ - Attempting to set a non-:py:data:`str` attribute name on an :py:class:`X509NameType` - instance causes :py:exc:`TypeError` to be raised. - """ - name = self._x509name() - # Beyond these cases, you may also think that unicode should be - # rejected. Sorry, you're wrong. unicode is automatically converted to - # str outside of the control of X509Name, so there's no way to reject - # it. - - # Also, this used to test str subclasses, but that test is less relevant - # now that the implementation is in Python instead of C. Also PyPy - # automatically converts str subclasses to str when they are passed to - # setattr, so we can't test it on PyPy. Apparently CPython does this - # sometimes as well. - self.assertRaises(TypeError, setattr, name, None, "hello") - self.assertRaises(TypeError, setattr, name, 30, "hello") - - - def test_setInvalidAttribute(self): - """ - Attempting to set any attribute name on an :py:class:`X509NameType` instance for - which no corresponding NID is defined causes :py:exc:`AttributeError` to be - raised. - """ - name = self._x509name() - self.assertRaises(AttributeError, setattr, name, "no such thing", None) - - - def test_attributes(self): - """ - :py:class:`X509NameType` instances have attributes for each standard (?) - X509Name field. - """ - name = self._x509name() - name.commonName = "foo" - self.assertEqual(name.commonName, "foo") - self.assertEqual(name.CN, "foo") - name.CN = "baz" - self.assertEqual(name.commonName, "baz") - self.assertEqual(name.CN, "baz") - name.commonName = "bar" - self.assertEqual(name.commonName, "bar") - self.assertEqual(name.CN, "bar") - name.CN = "quux" - self.assertEqual(name.commonName, "quux") - self.assertEqual(name.CN, "quux") - - - def test_copy(self): - """ - :py:class:`X509Name` creates a new :py:class:`X509NameType` instance with all the same - attributes as an existing :py:class:`X509NameType` instance when called with - one. - """ - name = self._x509name(commonName="foo", emailAddress="bar@example.com") - - copy = X509Name(name) - self.assertEqual(copy.commonName, "foo") - self.assertEqual(copy.emailAddress, "bar@example.com") - - # Mutate the copy and ensure the original is unmodified. - copy.commonName = "baz" - self.assertEqual(name.commonName, "foo") - - # Mutate the original and ensure the copy is unmodified. - name.emailAddress = "quux@example.com" - self.assertEqual(copy.emailAddress, "bar@example.com") - - - def test_repr(self): - """ - :py:func:`repr` passed an :py:class:`X509NameType` instance should return a string - containing a description of the type and the NIDs which have been set - on it. - """ - name = self._x509name(commonName="foo", emailAddress="bar") - self.assertEqual( - repr(name), - "<X509Name object '/emailAddress=bar/CN=foo'>") - - - def test_comparison(self): - """ - :py:class:`X509NameType` instances should compare based on their NIDs. - """ - def _equality(a, b, assertTrue, assertFalse): - assertTrue(a == b, "(%r == %r) --> False" % (a, b)) - assertFalse(a != b) - assertTrue(b == a) - assertFalse(b != a) - - def assertEqual(a, b): - _equality(a, b, self.assertTrue, self.assertFalse) - - # Instances compare equal to themselves. - name = self._x509name() - assertEqual(name, name) - - # Empty instances should compare equal to each other. - assertEqual(self._x509name(), self._x509name()) - - # Instances with equal NIDs should compare equal to each other. - assertEqual(self._x509name(commonName="foo"), - self._x509name(commonName="foo")) - - # Instance with equal NIDs set using different aliases should compare - # equal to each other. - assertEqual(self._x509name(commonName="foo"), - self._x509name(CN="foo")) - - # Instances with more than one NID with the same values should compare - # equal to each other. - assertEqual(self._x509name(CN="foo", organizationalUnitName="bar"), - self._x509name(commonName="foo", OU="bar")) - - def assertNotEqual(a, b): - _equality(a, b, self.assertFalse, self.assertTrue) - - # Instances with different values for the same NID should not compare - # equal to each other. - assertNotEqual(self._x509name(CN="foo"), - self._x509name(CN="bar")) - - # Instances with different NIDs should not compare equal to each other. - assertNotEqual(self._x509name(CN="foo"), - self._x509name(OU="foo")) - - def _inequality(a, b, assertTrue, assertFalse): - assertTrue(a < b) - assertTrue(a <= b) - assertTrue(b > a) - assertTrue(b >= a) - assertFalse(a > b) - assertFalse(a >= b) - assertFalse(b < a) - assertFalse(b <= a) - - def assertLessThan(a, b): - _inequality(a, b, self.assertTrue, self.assertFalse) - - # An X509Name with a NID with a value which sorts less than the value - # of the same NID on another X509Name compares less than the other - # X509Name. - assertLessThan(self._x509name(CN="abc"), - self._x509name(CN="def")) - - def assertGreaterThan(a, b): - _inequality(a, b, self.assertFalse, self.assertTrue) - - # An X509Name with a NID with a value which sorts greater than the - # value of the same NID on another X509Name compares greater than the - # other X509Name. - assertGreaterThan(self._x509name(CN="def"), - self._x509name(CN="abc")) - - - def test_hash(self): - """ - :py:meth:`X509Name.hash` returns an integer hash based on the value of the - name. - """ - a = self._x509name(CN="foo") - b = self._x509name(CN="foo") - self.assertEqual(a.hash(), b.hash()) - a.CN = "bar" - self.assertNotEqual(a.hash(), b.hash()) - - - def test_der(self): - """ - :py:meth:`X509Name.der` returns the DER encoded form of the name. - """ - a = self._x509name(CN="foo", C="US") - self.assertEqual( - a.der(), - b('0\x1b1\x0b0\t\x06\x03U\x04\x06\x13\x02US' - '1\x0c0\n\x06\x03U\x04\x03\x13\x03foo')) - - - def test_get_components(self): - """ - :py:meth:`X509Name.get_components` returns a :py:data:`list` of - two-tuples of :py:data:`str` - giving the NIDs and associated values which make up the name. - """ - a = self._x509name() - self.assertEqual(a.get_components(), []) - a.CN = "foo" - self.assertEqual(a.get_components(), [(b("CN"), b("foo"))]) - a.organizationalUnitName = "bar" - self.assertEqual( - a.get_components(), - [(b("CN"), b("foo")), (b("OU"), b("bar"))]) - - - def test_load_nul_byte_attribute(self): - """ - An :py:class:`OpenSSL.crypto.X509Name` from an - :py:class:`OpenSSL.crypto.X509` instance loaded from a file can have a - NUL byte in the value of one of its attributes. - """ - cert = load_certificate(FILETYPE_PEM, nulbyteSubjectAltNamePEM) - subject = cert.get_subject() - self.assertEqual( - "null.python.org\x00example.org", subject.commonName) - - - def test_setAttributeFailure(self): - """ - If the value of an attribute cannot be set for some reason then - :py:class:`OpenSSL.crypto.Error` is raised. - """ - name = self._x509name() - # This value is too long - self.assertRaises(Error, setattr, name, "O", b"x" * 512) - - - -class _PKeyInteractionTestsMixin: - """ - Tests which involve another thing and a PKey. - """ - def signable(self): - """ - Return something with a :py:meth:`set_pubkey`, :py:meth:`set_pubkey`, - and :py:meth:`sign` method. - """ - raise NotImplementedError() - - - def test_signWithUngenerated(self): - """ - :py:meth:`X509Req.sign` raises :py:exc:`ValueError` when pass a - :py:class:`PKey` with no parts. - """ - request = self.signable() - key = PKey() - self.assertRaises(ValueError, request.sign, key, GOOD_DIGEST) - - - def test_signWithPublicKey(self): - """ - :py:meth:`X509Req.sign` raises :py:exc:`ValueError` when pass a - :py:class:`PKey` with no private part as the signing key. - """ - request = self.signable() - key = PKey() - key.generate_key(TYPE_RSA, 512) - request.set_pubkey(key) - pub = request.get_pubkey() - self.assertRaises(ValueError, request.sign, pub, GOOD_DIGEST) - - - def test_signWithUnknownDigest(self): - """ - :py:meth:`X509Req.sign` raises :py:exc:`ValueError` when passed a digest name which is - not known. - """ - request = self.signable() - key = PKey() - key.generate_key(TYPE_RSA, 512) - self.assertRaises(ValueError, request.sign, key, BAD_DIGEST) - - - def test_sign(self): - """ - :py:meth:`X509Req.sign` succeeds when passed a private key object and a valid - digest function. :py:meth:`X509Req.verify` can be used to check the signature. - """ - request = self.signable() - key = PKey() - key.generate_key(TYPE_RSA, 512) - request.set_pubkey(key) - request.sign(key, GOOD_DIGEST) - # If the type has a verify method, cover that too. - if getattr(request, 'verify', None) is not None: - pub = request.get_pubkey() - self.assertTrue(request.verify(pub)) - # Make another key that won't verify. - key = PKey() - key.generate_key(TYPE_RSA, 512) - self.assertRaises(Error, request.verify, key) - - - - -class X509ReqTests(TestCase, _PKeyInteractionTestsMixin): - """ - Tests for :py:class:`OpenSSL.crypto.X509Req`. - """ - def signable(self): - """ - Create and return a new :py:class:`X509Req`. - """ - return X509Req() - - - def test_type(self): - """ - :py:obj:`X509Req` and :py:obj:`X509ReqType` refer to the same type object and can be - used to create instances of that type. - """ - self.assertIdentical(X509Req, X509ReqType) - self.assertConsistentType(X509Req, 'X509Req') - - - def test_construction(self): - """ - :py:obj:`X509Req` takes no arguments and returns an :py:obj:`X509ReqType` instance. - """ - request = X509Req() - self.assertTrue( - isinstance(request, X509ReqType), - "%r is of type %r, should be %r" % (request, type(request), X509ReqType)) - - - def test_version(self): - """ - :py:obj:`X509ReqType.set_version` sets the X.509 version of the certificate - request. :py:obj:`X509ReqType.get_version` returns the X.509 version of - the certificate request. The initial value of the version is 0. - """ - request = X509Req() - self.assertEqual(request.get_version(), 0) - request.set_version(1) - self.assertEqual(request.get_version(), 1) - request.set_version(3) - self.assertEqual(request.get_version(), 3) - - - def test_version_wrong_args(self): - """ - :py:obj:`X509ReqType.set_version` raises :py:obj:`TypeError` if called with the wrong - number of arguments or with a non-:py:obj:`int` argument. - :py:obj:`X509ReqType.get_version` raises :py:obj:`TypeError` if called with any - arguments. - """ - request = X509Req() - self.assertRaises(TypeError, request.set_version) - self.assertRaises(TypeError, request.set_version, "foo") - self.assertRaises(TypeError, request.set_version, 1, 2) - self.assertRaises(TypeError, request.get_version, None) - - - def test_get_subject(self): - """ - :py:obj:`X509ReqType.get_subject` returns an :py:obj:`X509Name` for the subject of - the request and which is valid even after the request object is - otherwise dead. - """ - request = X509Req() - subject = request.get_subject() - self.assertTrue( - isinstance(subject, X509NameType), - "%r is of type %r, should be %r" % (subject, type(subject), X509NameType)) - subject.commonName = "foo" - self.assertEqual(request.get_subject().commonName, "foo") - del request - subject.commonName = "bar" - self.assertEqual(subject.commonName, "bar") - - - def test_get_subject_wrong_args(self): - """ - :py:obj:`X509ReqType.get_subject` raises :py:obj:`TypeError` if called with any - arguments. - """ - request = X509Req() - self.assertRaises(TypeError, request.get_subject, None) - - - def test_add_extensions(self): - """ - :py:obj:`X509Req.add_extensions` accepts a :py:obj:`list` of :py:obj:`X509Extension` - instances and adds them to the X509 request. - """ - request = X509Req() - request.add_extensions([ - X509Extension(b('basicConstraints'), True, b('CA:false'))]) - exts = request.get_extensions() - self.assertEqual(len(exts), 1) - self.assertEqual(exts[0].get_short_name(), b('basicConstraints')) - self.assertEqual(exts[0].get_critical(), 1) - self.assertEqual(exts[0].get_data(), b('0\x00')) - - - def test_get_extensions(self): - """ - :py:obj:`X509Req.get_extensions` returns a :py:obj:`list` of - extensions added to this X509 request. - """ - request = X509Req() - exts = request.get_extensions() - self.assertEqual(exts, []) - request.add_extensions([ - X509Extension(b('basicConstraints'), True, b('CA:true')), - X509Extension(b('keyUsage'), False, b('digitalSignature'))]) - exts = request.get_extensions() - self.assertEqual(len(exts), 2) - self.assertEqual(exts[0].get_short_name(), b('basicConstraints')) - self.assertEqual(exts[0].get_critical(), 1) - self.assertEqual(exts[0].get_data(), b('0\x03\x01\x01\xff')) - self.assertEqual(exts[1].get_short_name(), b('keyUsage')) - self.assertEqual(exts[1].get_critical(), 0) - self.assertEqual(exts[1].get_data(), b('\x03\x02\x07\x80')) - - - def test_add_extensions_wrong_args(self): - """ - :py:obj:`X509Req.add_extensions` raises :py:obj:`TypeError` if called with the wrong - number of arguments or with a non-:py:obj:`list`. Or it raises :py:obj:`ValueError` - if called with a :py:obj:`list` containing objects other than :py:obj:`X509Extension` - instances. - """ - request = X509Req() - self.assertRaises(TypeError, request.add_extensions) - self.assertRaises(TypeError, request.add_extensions, object()) - self.assertRaises(ValueError, request.add_extensions, [object()]) - self.assertRaises(TypeError, request.add_extensions, [], None) - - - def test_verify_wrong_args(self): - """ - :py:obj:`X509Req.verify` raises :py:obj:`TypeError` if called with zero - arguments or more than one argument or if passed anything other than a - :py:obj:`PKey` instance as its single argument. - """ - request = X509Req() - self.assertRaises(TypeError, request.verify) - self.assertRaises(TypeError, request.verify, object()) - self.assertRaises(TypeError, request.verify, PKey(), object()) - - - def test_verify_uninitialized_key(self): - """ - :py:obj:`X509Req.verify` raises :py:obj:`OpenSSL.crypto.Error` if called - with a :py:obj:`OpenSSL.crypto.PKey` which contains no key data. - """ - request = X509Req() - pkey = PKey() - self.assertRaises(Error, request.verify, pkey) - - - def test_verify_wrong_key(self): - """ - :py:obj:`X509Req.verify` raises :py:obj:`OpenSSL.crypto.Error` if called - with a :py:obj:`OpenSSL.crypto.PKey` which does not represent the public - part of the key which signed the request. - """ - request = X509Req() - pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - request.sign(pkey, GOOD_DIGEST) - another_pkey = load_privatekey(FILETYPE_PEM, client_key_pem) - self.assertRaises(Error, request.verify, another_pkey) - - - def test_verify_success(self): - """ - :py:obj:`X509Req.verify` returns :py:obj:`True` if called with a - :py:obj:`OpenSSL.crypto.PKey` which represents the public part of the key - which signed the request. - """ - request = X509Req() - pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - request.sign(pkey, GOOD_DIGEST) - self.assertEqual(True, request.verify(pkey)) - - - -class X509Tests(TestCase, _PKeyInteractionTestsMixin): - """ - Tests for :py:obj:`OpenSSL.crypto.X509`. - """ - pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM - - extpem = """ ------BEGIN CERTIFICATE----- -MIIC3jCCAkegAwIBAgIJAJHFjlcCgnQzMA0GCSqGSIb3DQEBBQUAMEcxCzAJBgNV -BAYTAlNFMRUwEwYDVQQIEwxXZXN0ZXJib3R0b20xEjAQBgNVBAoTCUNhdGFsb2dp -eDENMAsGA1UEAxMEUm9vdDAeFw0wODA0MjIxNDQ1MzhaFw0wOTA0MjIxNDQ1Mzha -MFQxCzAJBgNVBAYTAlNFMQswCQYDVQQIEwJXQjEUMBIGA1UEChMLT3Blbk1ldGFk -aXIxIjAgBgNVBAMTGW5vZGUxLm9tMi5vcGVubWV0YWRpci5vcmcwgZ8wDQYJKoZI -hvcNAQEBBQADgY0AMIGJAoGBAPIcQMrwbk2nESF/0JKibj9i1x95XYAOwP+LarwT -Op4EQbdlI9SY+uqYqlERhF19w7CS+S6oyqx0DRZSk4Y9dZ9j9/xgm2u/f136YS1u -zgYFPvfUs6PqYLPSM8Bw+SjJ+7+2+TN+Tkiof9WP1cMjodQwOmdsiRbR0/J7+b1B -hec1AgMBAAGjgcQwgcEwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNT -TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFIdHsBcMVVMbAO7j6NCj -03HgLnHaMB8GA1UdIwQYMBaAFL2h9Bf9Mre4vTdOiHTGAt7BRY/8MEYGA1UdEQQ/ -MD2CDSouZXhhbXBsZS5vcmeCESoub20yLmV4bWFwbGUuY29thwSC7wgKgRNvbTJA -b3Blbm1ldGFkaXIub3JnMA0GCSqGSIb3DQEBBQUAA4GBALd7WdXkp2KvZ7/PuWZA -MPlIxyjS+Ly11+BNE0xGQRp9Wz+2lABtpgNqssvU156+HkKd02rGheb2tj7MX9hG -uZzbwDAZzJPjzDQDD7d3cWsrVcfIdqVU7epHqIadnOF+X0ghJ39pAm6VVadnSXCt -WpOdIpB8KksUTCzV591Nr1wd ------END CERTIFICATE----- - """ - def signable(self): - """ - Create and return a new :py:obj:`X509`. - """ - return X509() - - - def test_type(self): - """ - :py:obj:`X509` and :py:obj:`X509Type` refer to the same type object and can be used - to create instances of that type. - """ - self.assertIdentical(X509, X509Type) - self.assertConsistentType(X509, 'X509') - - - def test_construction(self): - """ - :py:obj:`X509` takes no arguments and returns an instance of :py:obj:`X509Type`. - """ - certificate = X509() - self.assertTrue( - isinstance(certificate, X509Type), - "%r is of type %r, should be %r" % (certificate, - type(certificate), - X509Type)) - self.assertEqual(type(X509Type).__name__, 'type') - self.assertEqual(type(certificate).__name__, 'X509') - self.assertEqual(type(certificate), X509Type) - self.assertEqual(type(certificate), X509) - - - def test_get_version_wrong_args(self): - """ - :py:obj:`X509.get_version` raises :py:obj:`TypeError` if invoked with any arguments. - """ - cert = X509() - self.assertRaises(TypeError, cert.get_version, None) - - - def test_set_version_wrong_args(self): - """ - :py:obj:`X509.set_version` raises :py:obj:`TypeError` if invoked with the wrong number - of arguments or an argument not of type :py:obj:`int`. - """ - cert = X509() - self.assertRaises(TypeError, cert.set_version) - self.assertRaises(TypeError, cert.set_version, None) - self.assertRaises(TypeError, cert.set_version, 1, None) - - - def test_version(self): - """ - :py:obj:`X509.set_version` sets the certificate version number. - :py:obj:`X509.get_version` retrieves it. - """ - cert = X509() - cert.set_version(1234) - self.assertEquals(cert.get_version(), 1234) - - - def test_get_serial_number_wrong_args(self): - """ - :py:obj:`X509.get_serial_number` raises :py:obj:`TypeError` if invoked with any - arguments. - """ - cert = X509() - self.assertRaises(TypeError, cert.get_serial_number, None) - - - def test_serial_number(self): - """ - The serial number of an :py:obj:`X509Type` can be retrieved and modified with - :py:obj:`X509Type.get_serial_number` and :py:obj:`X509Type.set_serial_number`. - """ - certificate = X509() - self.assertRaises(TypeError, certificate.set_serial_number) - self.assertRaises(TypeError, certificate.set_serial_number, 1, 2) - self.assertRaises(TypeError, certificate.set_serial_number, "1") - self.assertRaises(TypeError, certificate.set_serial_number, 5.5) - self.assertEqual(certificate.get_serial_number(), 0) - certificate.set_serial_number(1) - self.assertEqual(certificate.get_serial_number(), 1) - certificate.set_serial_number(2 ** 32 + 1) - self.assertEqual(certificate.get_serial_number(), 2 ** 32 + 1) - certificate.set_serial_number(2 ** 64 + 1) - self.assertEqual(certificate.get_serial_number(), 2 ** 64 + 1) - certificate.set_serial_number(2 ** 128 + 1) - self.assertEqual(certificate.get_serial_number(), 2 ** 128 + 1) - - - def _setBoundTest(self, which): - """ - :py:obj:`X509Type.set_notBefore` takes a string in the format of an ASN1 - GENERALIZEDTIME and sets the beginning of the certificate's validity - period to it. - """ - certificate = X509() - set = getattr(certificate, 'set_not' + which) - get = getattr(certificate, 'get_not' + which) - - # Starts with no value. - self.assertEqual(get(), None) - - # GMT (Or is it UTC?) -exarkun - when = b("20040203040506Z") - set(when) - self.assertEqual(get(), when) - - # A plus two hours and thirty minutes offset - when = b("20040203040506+0530") - set(when) - self.assertEqual(get(), when) - - # A minus one hour fifteen minutes offset - when = b("20040203040506-0115") - set(when) - self.assertEqual(get(), when) - - # An invalid string results in a ValueError - self.assertRaises(ValueError, set, b("foo bar")) - - # The wrong number of arguments results in a TypeError. - self.assertRaises(TypeError, set) - self.assertRaises(TypeError, set, b("20040203040506Z"), b("20040203040506Z")) - self.assertRaises(TypeError, get, b("foo bar")) - - - # XXX ASN1_TIME (not GENERALIZEDTIME) - - def test_set_notBefore(self): - """ - :py:obj:`X509Type.set_notBefore` takes a string in the format of an ASN1 - GENERALIZEDTIME and sets the beginning of the certificate's validity - period to it. - """ - self._setBoundTest("Before") - - - def test_set_notAfter(self): - """ - :py:obj:`X509Type.set_notAfter` takes a string in the format of an ASN1 - GENERALIZEDTIME and sets the end of the certificate's validity period - to it. - """ - self._setBoundTest("After") - - - def test_get_notBefore(self): - """ - :py:obj:`X509Type.get_notBefore` returns a string in the format of an ASN1 - GENERALIZEDTIME even for certificates which store it as UTCTIME - internally. - """ - cert = load_certificate(FILETYPE_PEM, self.pemData) - self.assertEqual(cert.get_notBefore(), b("20090325123658Z")) - - - def test_get_notAfter(self): - """ - :py:obj:`X509Type.get_notAfter` returns a string in the format of an ASN1 - GENERALIZEDTIME even for certificates which store it as UTCTIME - internally. - """ - cert = load_certificate(FILETYPE_PEM, self.pemData) - self.assertEqual(cert.get_notAfter(), b("20170611123658Z")) - - - def test_gmtime_adj_notBefore_wrong_args(self): - """ - :py:obj:`X509Type.gmtime_adj_notBefore` raises :py:obj:`TypeError` if called with the - wrong number of arguments or a non-:py:obj:`int` argument. - """ - cert = X509() - self.assertRaises(TypeError, cert.gmtime_adj_notBefore) - self.assertRaises(TypeError, cert.gmtime_adj_notBefore, None) - self.assertRaises(TypeError, cert.gmtime_adj_notBefore, 123, None) - - - def test_gmtime_adj_notBefore(self): - """ - :py:obj:`X509Type.gmtime_adj_notBefore` changes the not-before timestamp to be - the current time plus the number of seconds passed in. - """ - cert = load_certificate(FILETYPE_PEM, self.pemData) - now = datetime.utcnow() + timedelta(seconds=100) - cert.gmtime_adj_notBefore(100) - self.assertEqual(cert.get_notBefore(), b(now.strftime("%Y%m%d%H%M%SZ"))) - - - def test_gmtime_adj_notAfter_wrong_args(self): - """ - :py:obj:`X509Type.gmtime_adj_notAfter` raises :py:obj:`TypeError` if called with the - wrong number of arguments or a non-:py:obj:`int` argument. - """ - cert = X509() - self.assertRaises(TypeError, cert.gmtime_adj_notAfter) - self.assertRaises(TypeError, cert.gmtime_adj_notAfter, None) - self.assertRaises(TypeError, cert.gmtime_adj_notAfter, 123, None) - - - def test_gmtime_adj_notAfter(self): - """ - :py:obj:`X509Type.gmtime_adj_notAfter` changes the not-after timestamp to be - the current time plus the number of seconds passed in. - """ - cert = load_certificate(FILETYPE_PEM, self.pemData) - now = datetime.utcnow() + timedelta(seconds=100) - cert.gmtime_adj_notAfter(100) - self.assertEqual(cert.get_notAfter(), b(now.strftime("%Y%m%d%H%M%SZ"))) - - - def test_has_expired_wrong_args(self): - """ - :py:obj:`X509Type.has_expired` raises :py:obj:`TypeError` if called with any - arguments. - """ - cert = X509() - self.assertRaises(TypeError, cert.has_expired, None) - - - def test_has_expired(self): - """ - :py:obj:`X509Type.has_expired` returns :py:obj:`True` if the certificate's not-after - time is in the past. - """ - cert = X509() - cert.gmtime_adj_notAfter(-1) - self.assertTrue(cert.has_expired()) - - - def test_has_not_expired(self): - """ - :py:obj:`X509Type.has_expired` returns :py:obj:`False` if the certificate's not-after - time is in the future. - """ - cert = X509() - cert.gmtime_adj_notAfter(2) - self.assertFalse(cert.has_expired()) - - - def test_digest(self): - """ - :py:obj:`X509.digest` returns a string giving ":"-separated hex-encoded words - of the digest of the certificate. - """ - cert = X509() - self.assertEqual( - # This is MD5 instead of GOOD_DIGEST because the digest algorithm - # actually matters to the assertion (ie, another arbitrary, good - # digest will not product the same digest). - cert.digest("MD5"), - b("A8:EB:07:F8:53:25:0A:F2:56:05:C5:A5:C4:C4:C7:15")) - - - def _extcert(self, pkey, extensions): - cert = X509() - cert.set_pubkey(pkey) - cert.get_subject().commonName = "Unit Tests" - cert.get_issuer().commonName = "Unit Tests" - when = b(datetime.now().strftime("%Y%m%d%H%M%SZ")) - cert.set_notBefore(when) - cert.set_notAfter(when) - - cert.add_extensions(extensions) - return load_certificate( - FILETYPE_PEM, dump_certificate(FILETYPE_PEM, cert)) - - - def test_extension_count(self): - """ - :py:obj:`X509.get_extension_count` returns the number of extensions that are - present in the certificate. - """ - pkey = load_privatekey(FILETYPE_PEM, client_key_pem) - ca = X509Extension(b('basicConstraints'), True, b('CA:FALSE')) - key = X509Extension(b('keyUsage'), True, b('digitalSignature')) - subjectAltName = X509Extension( - b('subjectAltName'), True, b('DNS:example.com')) - - # Try a certificate with no extensions at all. - c = self._extcert(pkey, []) - self.assertEqual(c.get_extension_count(), 0) - - # And a certificate with one - c = self._extcert(pkey, [ca]) - self.assertEqual(c.get_extension_count(), 1) - - # And a certificate with several - c = self._extcert(pkey, [ca, key, subjectAltName]) - self.assertEqual(c.get_extension_count(), 3) - - - def test_get_extension(self): - """ - :py:obj:`X509.get_extension` takes an integer and returns an :py:obj:`X509Extension` - corresponding to the extension at that index. - """ - pkey = load_privatekey(FILETYPE_PEM, client_key_pem) - ca = X509Extension(b('basicConstraints'), True, b('CA:FALSE')) - key = X509Extension(b('keyUsage'), True, b('digitalSignature')) - subjectAltName = X509Extension( - b('subjectAltName'), False, b('DNS:example.com')) - - cert = self._extcert(pkey, [ca, key, subjectAltName]) - - ext = cert.get_extension(0) - self.assertTrue(isinstance(ext, X509Extension)) - self.assertTrue(ext.get_critical()) - self.assertEqual(ext.get_short_name(), b('basicConstraints')) - - ext = cert.get_extension(1) - self.assertTrue(isinstance(ext, X509Extension)) - self.assertTrue(ext.get_critical()) - self.assertEqual(ext.get_short_name(), b('keyUsage')) - - ext = cert.get_extension(2) - self.assertTrue(isinstance(ext, X509Extension)) - self.assertFalse(ext.get_critical()) - self.assertEqual(ext.get_short_name(), b('subjectAltName')) - - self.assertRaises(IndexError, cert.get_extension, -1) - self.assertRaises(IndexError, cert.get_extension, 4) - self.assertRaises(TypeError, cert.get_extension, "hello") - - - def test_nullbyte_subjectAltName(self): - """ - The fields of a `subjectAltName` extension on an X509 may contain NUL - bytes and this value is reflected in the string representation of the - extension object. - """ - cert = load_certificate(FILETYPE_PEM, nulbyteSubjectAltNamePEM) - - ext = cert.get_extension(3) - self.assertEqual(ext.get_short_name(), b('subjectAltName')) - self.assertEqual( - b("DNS:altnull.python.org\x00example.com, " - "email:null@python.org\x00user@example.org, " - "URI:http://null.python.org\x00http://example.org, " - "IP Address:192.0.2.1, IP Address:2001:DB8:0:0:0:0:0:1\n"), - b(str(ext))) - - - def test_invalid_digest_algorithm(self): - """ - :py:obj:`X509.digest` raises :py:obj:`ValueError` if called with an unrecognized hash - algorithm. - """ - cert = X509() - self.assertRaises(ValueError, cert.digest, BAD_DIGEST) - - - def test_get_subject_wrong_args(self): - """ - :py:obj:`X509.get_subject` raises :py:obj:`TypeError` if called with any arguments. - """ - cert = X509() - self.assertRaises(TypeError, cert.get_subject, None) - - - def test_get_subject(self): - """ - :py:obj:`X509.get_subject` returns an :py:obj:`X509Name` instance. - """ - cert = load_certificate(FILETYPE_PEM, self.pemData) - subj = cert.get_subject() - self.assertTrue(isinstance(subj, X509Name)) - self.assertEquals( - subj.get_components(), - [(b('C'), b('US')), (b('ST'), b('IL')), (b('L'), b('Chicago')), - (b('O'), b('Testing')), (b('CN'), b('Testing Root CA'))]) - - - def test_set_subject_wrong_args(self): - """ - :py:obj:`X509.set_subject` raises a :py:obj:`TypeError` if called with the wrong - number of arguments or an argument not of type :py:obj:`X509Name`. - """ - cert = X509() - self.assertRaises(TypeError, cert.set_subject) - self.assertRaises(TypeError, cert.set_subject, None) - self.assertRaises(TypeError, cert.set_subject, cert.get_subject(), None) - - - def test_set_subject(self): - """ - :py:obj:`X509.set_subject` changes the subject of the certificate to the one - passed in. - """ - cert = X509() - name = cert.get_subject() - name.C = 'AU' - name.O = 'Unit Tests' - cert.set_subject(name) - self.assertEquals( - cert.get_subject().get_components(), - [(b('C'), b('AU')), (b('O'), b('Unit Tests'))]) - - - def test_get_issuer_wrong_args(self): - """ - :py:obj:`X509.get_issuer` raises :py:obj:`TypeError` if called with any arguments. - """ - cert = X509() - self.assertRaises(TypeError, cert.get_issuer, None) - - - def test_get_issuer(self): - """ - :py:obj:`X509.get_issuer` returns an :py:obj:`X509Name` instance. - """ - cert = load_certificate(FILETYPE_PEM, self.pemData) - subj = cert.get_issuer() - self.assertTrue(isinstance(subj, X509Name)) - comp = subj.get_components() - self.assertEquals( - comp, - [(b('C'), b('US')), (b('ST'), b('IL')), (b('L'), b('Chicago')), - (b('O'), b('Testing')), (b('CN'), b('Testing Root CA'))]) - - - def test_set_issuer_wrong_args(self): - """ - :py:obj:`X509.set_issuer` raises a :py:obj:`TypeError` if called with the wrong - number of arguments or an argument not of type :py:obj:`X509Name`. - """ - cert = X509() - self.assertRaises(TypeError, cert.set_issuer) - self.assertRaises(TypeError, cert.set_issuer, None) - self.assertRaises(TypeError, cert.set_issuer, cert.get_issuer(), None) - - - def test_set_issuer(self): - """ - :py:obj:`X509.set_issuer` changes the issuer of the certificate to the one - passed in. - """ - cert = X509() - name = cert.get_issuer() - name.C = 'AU' - name.O = 'Unit Tests' - cert.set_issuer(name) - self.assertEquals( - cert.get_issuer().get_components(), - [(b('C'), b('AU')), (b('O'), b('Unit Tests'))]) - - - def test_get_pubkey_uninitialized(self): - """ - When called on a certificate with no public key, :py:obj:`X509.get_pubkey` - raises :py:obj:`OpenSSL.crypto.Error`. - """ - cert = X509() - self.assertRaises(Error, cert.get_pubkey) - - - def test_subject_name_hash_wrong_args(self): - """ - :py:obj:`X509.subject_name_hash` raises :py:obj:`TypeError` if called with any - arguments. - """ - cert = X509() - self.assertRaises(TypeError, cert.subject_name_hash, None) - - - def test_subject_name_hash(self): - """ - :py:obj:`X509.subject_name_hash` returns the hash of the certificate's subject - name. - """ - cert = load_certificate(FILETYPE_PEM, self.pemData) - self.assertIn( - cert.subject_name_hash(), - [3350047874, # OpenSSL 0.9.8, MD5 - 3278919224, # OpenSSL 1.0.0, SHA1 - ]) - - - def test_get_signature_algorithm(self): - """ - :py:obj:`X509Type.get_signature_algorithm` returns a string which means - the algorithm used to sign the certificate. - """ - cert = load_certificate(FILETYPE_PEM, self.pemData) - self.assertEqual( - b("sha1WithRSAEncryption"), cert.get_signature_algorithm()) - - - def test_get_undefined_signature_algorithm(self): - """ - :py:obj:`X509Type.get_signature_algorithm` raises :py:obj:`ValueError` if the - signature algorithm is undefined or unknown. - """ - # This certificate has been modified to indicate a bogus OID in the - # signature algorithm field so that OpenSSL does not recognize it. - certPEM = b("""\ ------BEGIN CERTIFICATE----- -MIIC/zCCAmigAwIBAgIBATAGBgJ8BQUAMHsxCzAJBgNVBAYTAlNHMREwDwYDVQQK -EwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5 -cHRvIENlcnRpZmljYXRlIE1hc3RlcjEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0 -MS5jb20wHhcNMDAwOTEwMDk1MTMwWhcNMDIwOTEwMDk1MTMwWjBTMQswCQYDVQQG -EwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQBgNVBAMTCWxvY2FsaG9zdDEdMBsG -CSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBI -AkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5AdNgLzJ1/MfsQQJ7hHVeHmTAjM664V -+fXvwUGJLziCeBo1ysWLRnl8CQIDAQABo4IBBDCCAQAwCQYDVR0TBAIwADAsBglg -hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O -BBYEFM+EgpK+eyZiwFU1aOPSbczbPSpVMIGlBgNVHSMEgZ0wgZqAFPuHI2nrnDqT -FeXFvylRT/7tKDgBoX+kfTB7MQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlw -dG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtNMkNyeXB0byBDZXJ0 -aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tggEA -MA0GCSqGSIb3DQEBBAUAA4GBADv8KpPo+gfJxN2ERK1Y1l17sz/ZhzoGgm5XCdbx -jEY7xKfpQngV599k1xhl11IMqizDwu0855agrckg2MCTmOI9DZzDD77tAYb+Dk0O -PEVk0Mk/V0aIsDE9bolfCi/i/QWZ3N8s5nTWMNyBBBmoSliWCm4jkkRZRD0ejgTN -tgI5 ------END CERTIFICATE----- -""") - cert = load_certificate(FILETYPE_PEM, certPEM) - self.assertRaises(ValueError, cert.get_signature_algorithm) - - - -class X509StoreTests(TestCase): - """ - Test for :py:obj:`OpenSSL.crypto.X509Store`. - """ - def test_type(self): - """ - :py:obj:`X509StoreType` is a type object. - """ - self.assertIdentical(X509Store, X509StoreType) - self.assertConsistentType(X509Store, 'X509Store') - - - def test_add_cert_wrong_args(self): - store = X509Store() - self.assertRaises(TypeError, store.add_cert) - self.assertRaises(TypeError, store.add_cert, object()) - self.assertRaises(TypeError, store.add_cert, X509(), object()) - - - def test_add_cert(self): - """ - :py:obj:`X509Store.add_cert` adds a :py:obj:`X509` instance to the - certificate store. - """ - cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) - store = X509Store() - store.add_cert(cert) - - - def test_add_cert_rejects_duplicate(self): - """ - :py:obj:`X509Store.add_cert` raises :py:obj:`OpenSSL.crypto.Error` if an - attempt is made to add the same certificate to the store more than once. - """ - cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) - store = X509Store() - store.add_cert(cert) - self.assertRaises(Error, store.add_cert, cert) - - - -class PKCS12Tests(TestCase): - """ - Test for :py:obj:`OpenSSL.crypto.PKCS12` and :py:obj:`OpenSSL.crypto.load_pkcs12`. - """ - pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM - - def test_type(self): - """ - :py:obj:`PKCS12Type` is a type object. - """ - self.assertIdentical(PKCS12, PKCS12Type) - self.assertConsistentType(PKCS12, 'PKCS12') - - - def test_empty_construction(self): - """ - :py:obj:`PKCS12` returns a new instance of :py:obj:`PKCS12` with no certificate, - private key, CA certificates, or friendly name. - """ - p12 = PKCS12() - self.assertEqual(None, p12.get_certificate()) - self.assertEqual(None, p12.get_privatekey()) - self.assertEqual(None, p12.get_ca_certificates()) - self.assertEqual(None, p12.get_friendlyname()) - - - def test_type_errors(self): - """ - The :py:obj:`PKCS12` setter functions (:py:obj:`set_certificate`, :py:obj:`set_privatekey`, - :py:obj:`set_ca_certificates`, and :py:obj:`set_friendlyname`) raise :py:obj:`TypeError` - when passed objects of types other than those expected. - """ - p12 = PKCS12() - self.assertRaises(TypeError, p12.set_certificate, 3) - self.assertRaises(TypeError, p12.set_certificate, PKey()) - self.assertRaises(TypeError, p12.set_certificate, X509) - self.assertRaises(TypeError, p12.set_privatekey, 3) - self.assertRaises(TypeError, p12.set_privatekey, 'legbone') - self.assertRaises(TypeError, p12.set_privatekey, X509()) - self.assertRaises(TypeError, p12.set_ca_certificates, 3) - self.assertRaises(TypeError, p12.set_ca_certificates, X509()) - self.assertRaises(TypeError, p12.set_ca_certificates, (3, 4)) - self.assertRaises(TypeError, p12.set_ca_certificates, ( PKey(), )) - self.assertRaises(TypeError, p12.set_friendlyname, 6) - self.assertRaises(TypeError, p12.set_friendlyname, ('foo', 'bar')) - - - def test_key_only(self): - """ - A :py:obj:`PKCS12` with only a private key can be exported using - :py:obj:`PKCS12.export` and loaded again using :py:obj:`load_pkcs12`. - """ - passwd = b"blah" - p12 = PKCS12() - pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - p12.set_privatekey(pkey) - self.assertEqual(None, p12.get_certificate()) - self.assertEqual(pkey, p12.get_privatekey()) - try: - dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3) - except Error: - # Some versions of OpenSSL will throw an exception - # for this nearly useless PKCS12 we tried to generate: - # [('PKCS12 routines', 'PKCS12_create', 'invalid null argument')] - return - p12 = load_pkcs12(dumped_p12, passwd) - self.assertEqual(None, p12.get_ca_certificates()) - self.assertEqual(None, p12.get_certificate()) - - # OpenSSL fails to bring the key back to us. So sad. Perhaps in the - # future this will be improved. - self.assertTrue(isinstance(p12.get_privatekey(), (PKey, type(None)))) - - - def test_cert_only(self): - """ - A :py:obj:`PKCS12` with only a certificate can be exported using - :py:obj:`PKCS12.export` and loaded again using :py:obj:`load_pkcs12`. - """ - passwd = b"blah" - p12 = PKCS12() - cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) - p12.set_certificate(cert) - self.assertEqual(cert, p12.get_certificate()) - self.assertEqual(None, p12.get_privatekey()) - try: - dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3) - except Error: - # Some versions of OpenSSL will throw an exception - # for this nearly useless PKCS12 we tried to generate: - # [('PKCS12 routines', 'PKCS12_create', 'invalid null argument')] - return - p12 = load_pkcs12(dumped_p12, passwd) - self.assertEqual(None, p12.get_privatekey()) - - # OpenSSL fails to bring the cert back to us. Groany mcgroan. - self.assertTrue(isinstance(p12.get_certificate(), (X509, type(None)))) - - # Oh ho. It puts the certificate into the ca certificates list, in - # fact. Totally bogus, I would think. Nevertheless, let's exploit - # that to check to see if it reconstructed the certificate we expected - # it to. At some point, hopefully this will change so that - # p12.get_certificate() is actually what returns the loaded - # certificate. - self.assertEqual( - cleartextCertificatePEM, - dump_certificate(FILETYPE_PEM, p12.get_ca_certificates()[0])) - - - def gen_pkcs12(self, cert_pem=None, key_pem=None, ca_pem=None, friendly_name=None): - """ - Generate a PKCS12 object with components from PEM. Verify that the set - functions return None. - """ - p12 = PKCS12() - if cert_pem: - ret = p12.set_certificate(load_certificate(FILETYPE_PEM, cert_pem)) - self.assertEqual(ret, None) - if key_pem: - ret = p12.set_privatekey(load_privatekey(FILETYPE_PEM, key_pem)) - self.assertEqual(ret, None) - if ca_pem: - ret = p12.set_ca_certificates((load_certificate(FILETYPE_PEM, ca_pem),)) - self.assertEqual(ret, None) - if friendly_name: - ret = p12.set_friendlyname(friendly_name) - self.assertEqual(ret, None) - return p12 - - - def check_recovery(self, p12_str, key=None, cert=None, ca=None, passwd=b"", - extra=()): - """ - Use openssl program to confirm three components are recoverable from a - PKCS12 string. - """ - if key: - recovered_key = _runopenssl( - p12_str, b"pkcs12", b"-nocerts", b"-nodes", b"-passin", - b"pass:" + passwd, *extra) - self.assertEqual(recovered_key[-len(key):], key) - if cert: - recovered_cert = _runopenssl( - p12_str, b"pkcs12", b"-clcerts", b"-nodes", b"-passin", - b"pass:" + passwd, b"-nokeys", *extra) - self.assertEqual(recovered_cert[-len(cert):], cert) - if ca: - recovered_cert = _runopenssl( - p12_str, b"pkcs12", b"-cacerts", b"-nodes", b"-passin", - b"pass:" + passwd, b"-nokeys", *extra) - self.assertEqual(recovered_cert[-len(ca):], ca) - - - def verify_pkcs12_container(self, p12): - """ - Verify that the PKCS#12 container contains the correct client - certificate and private key. - - :param p12: The PKCS12 instance to verify. - :type p12: :py:class:`PKCS12` - """ - cert_pem = dump_certificate(FILETYPE_PEM, p12.get_certificate()) - key_pem = dump_privatekey(FILETYPE_PEM, p12.get_privatekey()) - self.assertEqual( - (client_cert_pem, client_key_pem, None), - (cert_pem, key_pem, p12.get_ca_certificates())) - - - def test_load_pkcs12(self): - """ - A PKCS12 string generated using the openssl command line can be loaded - with :py:obj:`load_pkcs12` and its components extracted and examined. - """ - passwd = b"whatever" - pem = client_key_pem + client_cert_pem - p12_str = _runopenssl( - pem, b"pkcs12", b"-export", b"-clcerts", b"-passout", b"pass:" + passwd) - p12 = load_pkcs12(p12_str, passphrase=passwd) - self.verify_pkcs12_container(p12) - - - def test_load_pkcs12_text_passphrase(self): - """ - A PKCS12 string generated using the openssl command line can be loaded - with :py:obj:`load_pkcs12` and its components extracted and examined. - Using text as passphrase instead of bytes. DeprecationWarning expected. - """ - pem = client_key_pem + client_cert_pem - passwd = b"whatever" - p12_str = _runopenssl(pem, b"pkcs12", b"-export", b"-clcerts", - b"-passout", b"pass:" + passwd) - with catch_warnings(record=True) as w: - simplefilter("always") - p12 = load_pkcs12(p12_str, passphrase=b"whatever".decode("ascii")) - - self.assertEqual( - "{0} for passphrase is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ), - str(w[-1].message) - ) - self.assertIs(w[-1].category, DeprecationWarning) - - self.verify_pkcs12_container(p12) - - - def test_load_pkcs12_no_passphrase(self): - """ - A PKCS12 string generated using openssl command line can be loaded with - :py:obj:`load_pkcs12` without a passphrase and its components extracted - and examined. - """ - pem = client_key_pem + client_cert_pem - p12_str = _runopenssl( - pem, b"pkcs12", b"-export", b"-clcerts", b"-passout", b"pass:") - p12 = load_pkcs12(p12_str) - self.verify_pkcs12_container(p12) - - - def _dump_and_load(self, dump_passphrase, load_passphrase): - """ - A helper method to dump and load a PKCS12 object. - """ - p12 = self.gen_pkcs12(client_cert_pem, client_key_pem) - dumped_p12 = p12.export(passphrase=dump_passphrase, iter=2, maciter=3) - return load_pkcs12(dumped_p12, passphrase=load_passphrase) - - - def test_load_pkcs12_null_passphrase_load_empty(self): - """ - A PKCS12 string can be dumped with a null passphrase, loaded with an - empty passphrase with :py:obj:`load_pkcs12`, and its components - extracted and examined. - """ - self.verify_pkcs12_container( - self._dump_and_load(dump_passphrase=None, load_passphrase=b'')) - - - def test_load_pkcs12_null_passphrase_load_null(self): - """ - A PKCS12 string can be dumped with a null passphrase, loaded with a - null passphrase with :py:obj:`load_pkcs12`, and its components - extracted and examined. - """ - self.verify_pkcs12_container( - self._dump_and_load(dump_passphrase=None, load_passphrase=None)) - - - def test_load_pkcs12_empty_passphrase_load_empty(self): - """ - A PKCS12 string can be dumped with an empty passphrase, loaded with an - empty passphrase with :py:obj:`load_pkcs12`, and its components - extracted and examined. - """ - self.verify_pkcs12_container( - self._dump_and_load(dump_passphrase=b'', load_passphrase=b'')) - - - def test_load_pkcs12_empty_passphrase_load_null(self): - """ - A PKCS12 string can be dumped with an empty passphrase, loaded with a - null passphrase with :py:obj:`load_pkcs12`, and its components - extracted and examined. - """ - self.verify_pkcs12_container( - self._dump_and_load(dump_passphrase=b'', load_passphrase=None)) - - - def test_load_pkcs12_garbage(self): - """ - :py:obj:`load_pkcs12` raises :py:obj:`OpenSSL.crypto.Error` when passed a string - which is not a PKCS12 dump. - """ - passwd = 'whatever' - e = self.assertRaises(Error, load_pkcs12, b'fruit loops', passwd) - self.assertEqual( e.args[0][0][0], 'asn1 encoding routines') - self.assertEqual( len(e.args[0][0]), 3) - - - def test_replace(self): - """ - :py:obj:`PKCS12.set_certificate` replaces the certificate in a PKCS12 cluster. - :py:obj:`PKCS12.set_privatekey` replaces the private key. - :py:obj:`PKCS12.set_ca_certificates` replaces the CA certificates. - """ - p12 = self.gen_pkcs12(client_cert_pem, client_key_pem, root_cert_pem) - p12.set_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) - p12.set_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) - root_cert = load_certificate(FILETYPE_PEM, root_cert_pem) - client_cert = load_certificate(FILETYPE_PEM, client_cert_pem) - p12.set_ca_certificates([root_cert]) # not a tuple - self.assertEqual(1, len(p12.get_ca_certificates())) - self.assertEqual(root_cert, p12.get_ca_certificates()[0]) - p12.set_ca_certificates([client_cert, root_cert]) - self.assertEqual(2, len(p12.get_ca_certificates())) - self.assertEqual(client_cert, p12.get_ca_certificates()[0]) - self.assertEqual(root_cert, p12.get_ca_certificates()[1]) - - - def test_friendly_name(self): - """ - The *friendlyName* of a PKCS12 can be set and retrieved via - :py:obj:`PKCS12.get_friendlyname` and :py:obj:`PKCS12_set_friendlyname`, and a - :py:obj:`PKCS12` with a friendly name set can be dumped with :py:obj:`PKCS12.export`. - """ - passwd = b'Dogmeat[]{}!@#$%^&*()~`?/.,<>-_+=";:' - p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) - for friendly_name in [b('Serverlicious'), None, b('###')]: - p12.set_friendlyname(friendly_name) - self.assertEqual(p12.get_friendlyname(), friendly_name) - dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3) - reloaded_p12 = load_pkcs12(dumped_p12, passwd) - self.assertEqual( - p12.get_friendlyname(), reloaded_p12.get_friendlyname()) - # We would use the openssl program to confirm the friendly - # name, but it is not possible. The pkcs12 command - # does not store the friendly name in the cert's - # alias, which we could then extract. - self.check_recovery( - dumped_p12, key=server_key_pem, cert=server_cert_pem, - ca=root_cert_pem, passwd=passwd) - - - def test_various_empty_passphrases(self): - """ - Test that missing, None, and '' passphrases are identical for PKCS12 - export. - """ - p12 = self.gen_pkcs12(client_cert_pem, client_key_pem, root_cert_pem) - passwd = b"" - dumped_p12_empty = p12.export(iter=2, maciter=0, passphrase=passwd) - dumped_p12_none = p12.export(iter=3, maciter=2, passphrase=None) - dumped_p12_nopw = p12.export(iter=9, maciter=4) - for dumped_p12 in [dumped_p12_empty, dumped_p12_none, dumped_p12_nopw]: - self.check_recovery( - dumped_p12, key=client_key_pem, cert=client_cert_pem, - ca=root_cert_pem, passwd=passwd) - - - def test_removing_ca_cert(self): - """ - Passing :py:obj:`None` to :py:obj:`PKCS12.set_ca_certificates` removes all CA - certificates. - """ - p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) - p12.set_ca_certificates(None) - self.assertEqual(None, p12.get_ca_certificates()) - - - def test_export_without_mac(self): - """ - Exporting a PKCS12 with a :py:obj:`maciter` of ``-1`` excludes the MAC - entirely. - """ - passwd = b"Lake Michigan" - p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) - dumped_p12 = p12.export(maciter=-1, passphrase=passwd, iter=2) - self.check_recovery( - dumped_p12, key=server_key_pem, cert=server_cert_pem, - passwd=passwd, extra=(b"-nomacver",)) - - - def test_load_without_mac(self): - """ - Loading a PKCS12 without a MAC does something other than crash. - """ - passwd = b"Lake Michigan" - p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) - dumped_p12 = p12.export(maciter=-1, passphrase=passwd, iter=2) - try: - recovered_p12 = load_pkcs12(dumped_p12, passwd) - # The person who generated this PCKS12 should be flogged, - # or better yet we should have a means to determine - # whether a PCKS12 had a MAC that was verified. - # Anyway, libopenssl chooses to allow it, so the - # pyopenssl binding does as well. - self.assertTrue(isinstance(recovered_p12, PKCS12)) - except Error: - # Failing here with an exception is preferred as some openssl - # versions do. - pass - - - def test_zero_len_list_for_ca(self): - """ - A PKCS12 with an empty CA certificates list can be exported. - """ - passwd = 'Hobie 18' - p12 = self.gen_pkcs12(server_cert_pem, server_key_pem) - # p12.set_ca_certificates([]) - # self.assertEqual((), p12.get_ca_certificates()) - # dumped_p12 = p12.export(passphrase=passwd, iter=3) - # self.check_recovery( - # dumped_p12, key=server_key_pem, cert=server_cert_pem, - # passwd=passwd) - - - def test_export_without_args(self): - """ - All the arguments to :py:obj:`PKCS12.export` are optional. - """ - p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) - dumped_p12 = p12.export() # no args - self.check_recovery( - dumped_p12, key=server_key_pem, cert=server_cert_pem, passwd=b"") - - - def test_export_without_bytes(self): - """ - Test :py:obj:`PKCS12.export` with text not bytes as passphrase - """ - p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) - - with catch_warnings(record=True) as w: - simplefilter("always") - dumped_p12 = p12.export(passphrase=b"randomtext".decode("ascii")) - self.assertEqual( - "{0} for passphrase is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ), - str(w[-1].message) - ) - self.assertIs(w[-1].category, DeprecationWarning) - self.check_recovery( - dumped_p12, key=server_key_pem, cert=server_cert_pem, passwd=b"randomtext") - - - def test_key_cert_mismatch(self): - """ - :py:obj:`PKCS12.export` raises an exception when a key and certificate - mismatch. - """ - p12 = self.gen_pkcs12(server_cert_pem, client_key_pem, root_cert_pem) - self.assertRaises(Error, p12.export) - - - -# These quoting functions taken directly from Twisted's twisted.python.win32. -_cmdLineQuoteRe = re.compile(br'(\\*)"') -_cmdLineQuoteRe2 = re.compile(br'(\\+)\Z') -def cmdLineQuote(s): - """ - Internal method for quoting a single command-line argument. - - See http://www.perlmonks.org/?node_id=764004 - - :type: :py:obj:`str` - :param s: A single unquoted string to quote for something that is expecting - cmd.exe-style quoting - - :rtype: :py:obj:`str` - :return: A cmd.exe-style quoted string - """ - s = _cmdLineQuoteRe2.sub(br"\1\1", _cmdLineQuoteRe.sub(br'\1\1\\"', s)) - return b'"' + s + b'"' - - - -def quoteArguments(arguments): - """ - Quote an iterable of command-line arguments for passing to CreateProcess or - a similar API. This allows the list passed to :py:obj:`reactor.spawnProcess` to - match the child process's :py:obj:`sys.argv` properly. - - :type arguments: :py:obj:`iterable` of :py:obj:`str` - :param arguments: An iterable of unquoted arguments to quote - - :rtype: :py:obj:`str` - :return: A space-delimited string containing quoted versions of :py:obj:`arguments` - """ - return b' '.join(map(cmdLineQuote, arguments)) - - - -def _runopenssl(pem, *args): - """ - Run the command line openssl tool with the given arguments and write - the given PEM to its stdin. Not safe for quotes. - """ - if os.name == 'posix': - command = b"openssl " + b" ".join([ - (b"'" + arg.replace(b"'", b"'\\''") + b"'") - for arg in args]) - else: - command = b"openssl " + quoteArguments(args) - proc = Popen(native(command), shell=True, stdin=PIPE, stdout=PIPE) - proc.stdin.write(pem) - proc.stdin.close() - output = proc.stdout.read() - proc.stdout.close() - proc.wait() - return output - - - -class FunctionTests(TestCase): - """ - Tests for free-functions in the :py:obj:`OpenSSL.crypto` module. - """ - - def test_load_privatekey_invalid_format(self): - """ - :py:obj:`load_privatekey` raises :py:obj:`ValueError` if passed an unknown filetype. - """ - self.assertRaises(ValueError, load_privatekey, 100, root_key_pem) - - - def test_load_privatekey_invalid_passphrase_type(self): - """ - :py:obj:`load_privatekey` raises :py:obj:`TypeError` if passed a passphrase that is - neither a :py:obj:`str` nor a callable. - """ - self.assertRaises( - TypeError, - load_privatekey, - FILETYPE_PEM, encryptedPrivateKeyPEMPassphrase, object()) - - - def test_load_privatekey_wrong_args(self): - """ - :py:obj:`load_privatekey` raises :py:obj:`TypeError` if called with the wrong number - of arguments. - """ - self.assertRaises(TypeError, load_privatekey) - - - def test_load_privatekey_wrongPassphrase(self): - """ - :py:obj:`load_privatekey` raises :py:obj:`OpenSSL.crypto.Error` when it is passed an - encrypted PEM and an incorrect passphrase. - """ - self.assertRaises( - Error, - load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, b("quack")) - - - def test_load_privatekey_passphraseWrongType(self): - """ - :py:obj:`load_privatekey` raises :py:obj:`ValueError` when it is passed a passphrase - with a private key encoded in a format, that doesn't support - encryption. - """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - blob = dump_privatekey(FILETYPE_ASN1, key) - self.assertRaises(ValueError, - load_privatekey, FILETYPE_ASN1, blob, "secret") - - - def test_load_privatekey_passphrase(self): - """ - :py:obj:`load_privatekey` can create a :py:obj:`PKey` object from an encrypted PEM - string if given the passphrase. - """ - key = load_privatekey( - FILETYPE_PEM, encryptedPrivateKeyPEM, - encryptedPrivateKeyPEMPassphrase) - self.assertTrue(isinstance(key, PKeyType)) - - - def test_load_privatekey_passphrase_exception(self): - """ - If the passphrase callback raises an exception, that exception is raised - by :py:obj:`load_privatekey`. - """ - def cb(ignored): - raise ArithmeticError - - self.assertRaises(ArithmeticError, - load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb) - - - def test_load_privatekey_wrongPassphraseCallback(self): - """ - :py:obj:`load_privatekey` raises :py:obj:`OpenSSL.crypto.Error` when it - is passed an encrypted PEM and a passphrase callback which returns an - incorrect passphrase. - """ - called = [] - def cb(*a): - called.append(None) - return b("quack") - self.assertRaises( - Error, - load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb) - self.assertTrue(called) - - - def test_load_privatekey_passphraseCallback(self): - """ - :py:obj:`load_privatekey` can create a :py:obj:`PKey` object from an encrypted PEM - string if given a passphrase callback which returns the correct - password. - """ - called = [] - def cb(writing): - called.append(writing) - return encryptedPrivateKeyPEMPassphrase - key = load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, cb) - self.assertTrue(isinstance(key, PKeyType)) - self.assertEqual(called, [False]) - - - def test_load_privatekey_passphrase_wrong_return_type(self): - """ - :py:obj:`load_privatekey` raises :py:obj:`ValueError` if the passphrase - callback returns something other than a byte string. - """ - self.assertRaises( - ValueError, - load_privatekey, - FILETYPE_PEM, encryptedPrivateKeyPEM, lambda *args: 3) - - - def test_dump_privatekey_wrong_args(self): - """ - :py:obj:`dump_privatekey` raises :py:obj:`TypeError` if called with the wrong number - of arguments. - """ - self.assertRaises(TypeError, dump_privatekey) - # If cipher name is given, password is required. - self.assertRaises( - TypeError, dump_privatekey, FILETYPE_PEM, PKey(), GOOD_CIPHER) - - - def test_dump_privatekey_unknown_cipher(self): - """ - :py:obj:`dump_privatekey` raises :py:obj:`ValueError` if called with an unrecognized - cipher name. - """ - key = PKey() - key.generate_key(TYPE_RSA, 512) - self.assertRaises( - ValueError, dump_privatekey, - FILETYPE_PEM, key, BAD_CIPHER, "passphrase") - - - def test_dump_privatekey_invalid_passphrase_type(self): - """ - :py:obj:`dump_privatekey` raises :py:obj:`TypeError` if called with a passphrase which - is neither a :py:obj:`str` nor a callable. - """ - key = PKey() - key.generate_key(TYPE_RSA, 512) - self.assertRaises( - TypeError, - dump_privatekey, FILETYPE_PEM, key, GOOD_CIPHER, object()) - - - def test_dump_privatekey_invalid_filetype(self): - """ - :py:obj:`dump_privatekey` raises :py:obj:`ValueError` if called with an unrecognized - filetype. - """ - key = PKey() - key.generate_key(TYPE_RSA, 512) - self.assertRaises(ValueError, dump_privatekey, 100, key) - - - def test_load_privatekey_passphraseCallbackLength(self): - """ - :py:obj:`crypto.load_privatekey` should raise an error when the passphrase - provided by the callback is too long, not silently truncate it. - """ - def cb(ignored): - return "a" * 1025 - - self.assertRaises(ValueError, - load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb) - - - def test_dump_privatekey_passphrase(self): - """ - :py:obj:`dump_privatekey` writes an encrypted PEM when given a passphrase. - """ - passphrase = b("foo") - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, passphrase) - self.assertTrue(isinstance(pem, binary_type)) - loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase) - self.assertTrue(isinstance(loadedKey, PKeyType)) - self.assertEqual(loadedKey.type(), key.type()) - self.assertEqual(loadedKey.bits(), key.bits()) - - - def test_dump_privatekey_passphraseWrongType(self): - """ - :py:obj:`dump_privatekey` raises :py:obj:`ValueError` when it is passed a passphrase - with a private key encoded in a format, that doesn't support - encryption. - """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - self.assertRaises(ValueError, - dump_privatekey, FILETYPE_ASN1, key, GOOD_CIPHER, "secret") - - - def test_dump_certificate(self): - """ - :py:obj:`dump_certificate` writes PEM, DER, and text. - """ - pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM - cert = load_certificate(FILETYPE_PEM, pemData) - dumped_pem = dump_certificate(FILETYPE_PEM, cert) - self.assertEqual(dumped_pem, cleartextCertificatePEM) - dumped_der = dump_certificate(FILETYPE_ASN1, cert) - good_der = _runopenssl(dumped_pem, b"x509", b"-outform", b"DER") - self.assertEqual(dumped_der, good_der) - cert2 = load_certificate(FILETYPE_ASN1, dumped_der) - dumped_pem2 = dump_certificate(FILETYPE_PEM, cert2) - self.assertEqual(dumped_pem2, cleartextCertificatePEM) - dumped_text = dump_certificate(FILETYPE_TEXT, cert) - good_text = _runopenssl(dumped_pem, b"x509", b"-noout", b"-text") - self.assertEqual(dumped_text, good_text) - - - def test_dump_privatekey_pem(self): - """ - :py:obj:`dump_privatekey` writes a PEM - """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - self.assertTrue(key.check()) - dumped_pem = dump_privatekey(FILETYPE_PEM, key) - self.assertEqual(dumped_pem, cleartextPrivateKeyPEM) - - - def test_dump_privatekey_asn1(self): - """ - :py:obj:`dump_privatekey` writes a DER - """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - dumped_pem = dump_privatekey(FILETYPE_PEM, key) - - dumped_der = dump_privatekey(FILETYPE_ASN1, key) - # XXX This OpenSSL call writes "writing RSA key" to standard out. Sad. - good_der = _runopenssl(dumped_pem, b"rsa", b"-outform", b"DER") - self.assertEqual(dumped_der, good_der) - key2 = load_privatekey(FILETYPE_ASN1, dumped_der) - dumped_pem2 = dump_privatekey(FILETYPE_PEM, key2) - self.assertEqual(dumped_pem2, cleartextPrivateKeyPEM) - - - def test_dump_privatekey_text(self): - """ - :py:obj:`dump_privatekey` writes a text - """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - dumped_pem = dump_privatekey(FILETYPE_PEM, key) - - dumped_text = dump_privatekey(FILETYPE_TEXT, key) - good_text = _runopenssl(dumped_pem, b"rsa", b"-noout", b"-text") - self.assertEqual(dumped_text, good_text) - - - def test_dump_certificate_request(self): - """ - :py:obj:`dump_certificate_request` writes a PEM, DER, and text. - """ - req = load_certificate_request(FILETYPE_PEM, cleartextCertificateRequestPEM) - dumped_pem = dump_certificate_request(FILETYPE_PEM, req) - self.assertEqual(dumped_pem, cleartextCertificateRequestPEM) - dumped_der = dump_certificate_request(FILETYPE_ASN1, req) - good_der = _runopenssl(dumped_pem, b"req", b"-outform", b"DER") - self.assertEqual(dumped_der, good_der) - req2 = load_certificate_request(FILETYPE_ASN1, dumped_der) - dumped_pem2 = dump_certificate_request(FILETYPE_PEM, req2) - self.assertEqual(dumped_pem2, cleartextCertificateRequestPEM) - dumped_text = dump_certificate_request(FILETYPE_TEXT, req) - good_text = _runopenssl(dumped_pem, b"req", b"-noout", b"-text") - self.assertEqual(dumped_text, good_text) - self.assertRaises(ValueError, dump_certificate_request, 100, req) - - - def test_dump_privatekey_passphraseCallback(self): - """ - :py:obj:`dump_privatekey` writes an encrypted PEM when given a callback which - returns the correct passphrase. - """ - passphrase = b("foo") - called = [] - def cb(writing): - called.append(writing) - return passphrase - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, cb) - self.assertTrue(isinstance(pem, binary_type)) - self.assertEqual(called, [True]) - loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase) - self.assertTrue(isinstance(loadedKey, PKeyType)) - self.assertEqual(loadedKey.type(), key.type()) - self.assertEqual(loadedKey.bits(), key.bits()) - - - def test_dump_privatekey_passphrase_exception(self): - """ - :py:obj:`dump_privatekey` should not overwrite the exception raised - by the passphrase callback. - """ - def cb(ignored): - raise ArithmeticError - - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - self.assertRaises(ArithmeticError, - dump_privatekey, FILETYPE_PEM, key, GOOD_CIPHER, cb) - - - def test_dump_privatekey_passphraseCallbackLength(self): - """ - :py:obj:`crypto.dump_privatekey` should raise an error when the passphrase - provided by the callback is too long, not silently truncate it. - """ - def cb(ignored): - return "a" * 1025 - - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - self.assertRaises(ValueError, - dump_privatekey, FILETYPE_PEM, key, GOOD_CIPHER, cb) - - - def test_load_pkcs7_data_pem(self): - """ - :py:obj:`load_pkcs7_data` accepts a PKCS#7 string and returns an instance of - :py:obj:`PKCS7Type`. - """ - pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - self.assertTrue(isinstance(pkcs7, PKCS7Type)) - - - def test_load_pkcs7_data_asn1(self): - """ - :py:obj:`load_pkcs7_data` accepts a bytes containing ASN1 data - representing PKCS#7 and returns an instance of :py:obj`PKCS7Type`. - """ - pkcs7 = load_pkcs7_data(FILETYPE_ASN1, pkcs7DataASN1) - self.assertTrue(isinstance(pkcs7, PKCS7Type)) - - - def test_load_pkcs7_data_invalid(self): - """ - If the data passed to :py:obj:`load_pkcs7_data` is invalid, - :py:obj:`Error` is raised. - """ - self.assertRaises(Error, load_pkcs7_data, FILETYPE_PEM, b"foo") - - - -class LoadCertificateTests(TestCase): - """ - Tests for :py:obj:`load_certificate_request`. - """ - def test_badFileType(self): - """ - If the file type passed to :py:obj:`load_certificate_request` is - neither :py:obj:`FILETYPE_PEM` nor :py:obj:`FILETYPE_ASN1` then - :py:class:`ValueError` is raised. - """ - self.assertRaises(ValueError, load_certificate_request, object(), b"") - - - -class PKCS7Tests(TestCase): - """ - Tests for :py:obj:`PKCS7Type`. - """ - def test_type(self): - """ - :py:obj:`PKCS7Type` is a type object. - """ - self.assertTrue(isinstance(PKCS7Type, type)) - self.assertEqual(PKCS7Type.__name__, 'PKCS7') - - # XXX This doesn't currently work. - # self.assertIdentical(PKCS7, PKCS7Type) - - - # XXX Opposite results for all these following methods - - def test_type_is_signed_wrong_args(self): - """ - :py:obj:`PKCS7Type.type_is_signed` raises :py:obj:`TypeError` if called with any - arguments. - """ - pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - self.assertRaises(TypeError, pkcs7.type_is_signed, None) - - - def test_type_is_signed(self): - """ - :py:obj:`PKCS7Type.type_is_signed` returns :py:obj:`True` if the PKCS7 object is of - the type *signed*. - """ - pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - self.assertTrue(pkcs7.type_is_signed()) - - - def test_type_is_enveloped_wrong_args(self): - """ - :py:obj:`PKCS7Type.type_is_enveloped` raises :py:obj:`TypeError` if called with any - arguments. - """ - pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - self.assertRaises(TypeError, pkcs7.type_is_enveloped, None) - - - def test_type_is_enveloped(self): - """ - :py:obj:`PKCS7Type.type_is_enveloped` returns :py:obj:`False` if the PKCS7 object is - not of the type *enveloped*. - """ - pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - self.assertFalse(pkcs7.type_is_enveloped()) - - - def test_type_is_signedAndEnveloped_wrong_args(self): - """ - :py:obj:`PKCS7Type.type_is_signedAndEnveloped` raises :py:obj:`TypeError` if called - with any arguments. - """ - pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - self.assertRaises(TypeError, pkcs7.type_is_signedAndEnveloped, None) - - - def test_type_is_signedAndEnveloped(self): - """ - :py:obj:`PKCS7Type.type_is_signedAndEnveloped` returns :py:obj:`False` if the PKCS7 - object is not of the type *signed and enveloped*. - """ - pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - self.assertFalse(pkcs7.type_is_signedAndEnveloped()) - - - def test_type_is_data(self): - """ - :py:obj:`PKCS7Type.type_is_data` returns :py:obj:`False` if the PKCS7 object is not of - the type data. - """ - pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - self.assertFalse(pkcs7.type_is_data()) - - - def test_type_is_data_wrong_args(self): - """ - :py:obj:`PKCS7Type.type_is_data` raises :py:obj:`TypeError` if called with any - arguments. - """ - pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - self.assertRaises(TypeError, pkcs7.type_is_data, None) - - - def test_get_type_name_wrong_args(self): - """ - :py:obj:`PKCS7Type.get_type_name` raises :py:obj:`TypeError` if called with any - arguments. - """ - pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - self.assertRaises(TypeError, pkcs7.get_type_name, None) - - - def test_get_type_name(self): - """ - :py:obj:`PKCS7Type.get_type_name` returns a :py:obj:`str` giving the type name. - """ - pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - self.assertEquals(pkcs7.get_type_name(), b('pkcs7-signedData')) - - - def test_attribute(self): - """ - If an attribute other than one of the methods tested here is accessed on - an instance of :py:obj:`PKCS7Type`, :py:obj:`AttributeError` is raised. - """ - pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - self.assertRaises(AttributeError, getattr, pkcs7, "foo") - - - -class NetscapeSPKITests(TestCase, _PKeyInteractionTestsMixin): - """ - Tests for :py:obj:`OpenSSL.crypto.NetscapeSPKI`. - """ - def signable(self): - """ - Return a new :py:obj:`NetscapeSPKI` for use with signing tests. - """ - return NetscapeSPKI() - - - def test_type(self): - """ - :py:obj:`NetscapeSPKI` and :py:obj:`NetscapeSPKIType` refer to the same type object - and can be used to create instances of that type. - """ - self.assertIdentical(NetscapeSPKI, NetscapeSPKIType) - self.assertConsistentType(NetscapeSPKI, 'NetscapeSPKI') - - - def test_construction(self): - """ - :py:obj:`NetscapeSPKI` returns an instance of :py:obj:`NetscapeSPKIType`. - """ - nspki = NetscapeSPKI() - self.assertTrue(isinstance(nspki, NetscapeSPKIType)) - - - def test_invalid_attribute(self): - """ - Accessing a non-existent attribute of a :py:obj:`NetscapeSPKI` instance causes - an :py:obj:`AttributeError` to be raised. - """ - nspki = NetscapeSPKI() - self.assertRaises(AttributeError, lambda: nspki.foo) - - - def test_b64_encode(self): - """ - :py:obj:`NetscapeSPKI.b64_encode` encodes the certificate to a base64 blob. - """ - nspki = NetscapeSPKI() - blob = nspki.b64_encode() - self.assertTrue(isinstance(blob, binary_type)) - - - -class RevokedTests(TestCase): - """ - Tests for :py:obj:`OpenSSL.crypto.Revoked` - """ - def test_construction(self): - """ - Confirm we can create :py:obj:`OpenSSL.crypto.Revoked`. Check - that it is empty. - """ - revoked = Revoked() - self.assertTrue(isinstance(revoked, Revoked)) - self.assertEquals(type(revoked), Revoked) - self.assertEquals(revoked.get_serial(), b('00')) - self.assertEquals(revoked.get_rev_date(), None) - self.assertEquals(revoked.get_reason(), None) - - - def test_construction_wrong_args(self): - """ - Calling :py:obj:`OpenSSL.crypto.Revoked` with any arguments results - in a :py:obj:`TypeError` being raised. - """ - self.assertRaises(TypeError, Revoked, None) - self.assertRaises(TypeError, Revoked, 1) - self.assertRaises(TypeError, Revoked, "foo") - - - def test_serial(self): - """ - Confirm we can set and get serial numbers from - :py:obj:`OpenSSL.crypto.Revoked`. Confirm errors are handled - with grace. - """ - revoked = Revoked() - ret = revoked.set_serial(b('10b')) - self.assertEquals(ret, None) - ser = revoked.get_serial() - self.assertEquals(ser, b('010B')) - - revoked.set_serial(b('31ppp')) # a type error would be nice - ser = revoked.get_serial() - self.assertEquals(ser, b('31')) - - self.assertRaises(ValueError, revoked.set_serial, b('pqrst')) - self.assertRaises(TypeError, revoked.set_serial, 100) - self.assertRaises(TypeError, revoked.get_serial, 1) - self.assertRaises(TypeError, revoked.get_serial, None) - self.assertRaises(TypeError, revoked.get_serial, "") - - - def test_date(self): - """ - Confirm we can set and get revocation dates from - :py:obj:`OpenSSL.crypto.Revoked`. Confirm errors are handled - with grace. - """ - revoked = Revoked() - date = revoked.get_rev_date() - self.assertEquals(date, None) - - now = b(datetime.now().strftime("%Y%m%d%H%M%SZ")) - ret = revoked.set_rev_date(now) - self.assertEqual(ret, None) - date = revoked.get_rev_date() - self.assertEqual(date, now) - - - def test_reason(self): - """ - Confirm we can set and get revocation reasons from - :py:obj:`OpenSSL.crypto.Revoked`. The "get" need to work - as "set". Likewise, each reason of all_reasons() must work. - """ - revoked = Revoked() - for r in revoked.all_reasons(): - for x in range(2): - ret = revoked.set_reason(r) - self.assertEquals(ret, None) - reason = revoked.get_reason() - self.assertEquals( - reason.lower().replace(b(' '), b('')), - r.lower().replace(b(' '), b(''))) - r = reason # again with the resp of get - - revoked.set_reason(None) - self.assertEqual(revoked.get_reason(), None) - - - def test_set_reason_wrong_arguments(self): - """ - Calling :py:obj:`OpenSSL.crypto.Revoked.set_reason` with other than - one argument, or an argument which isn't a valid reason, - results in :py:obj:`TypeError` or :py:obj:`ValueError` being raised. - """ - revoked = Revoked() - self.assertRaises(TypeError, revoked.set_reason, 100) - self.assertRaises(ValueError, revoked.set_reason, b('blue')) - - - def test_get_reason_wrong_arguments(self): - """ - Calling :py:obj:`OpenSSL.crypto.Revoked.get_reason` with any - arguments results in :py:obj:`TypeError` being raised. - """ - revoked = Revoked() - self.assertRaises(TypeError, revoked.get_reason, None) - self.assertRaises(TypeError, revoked.get_reason, 1) - self.assertRaises(TypeError, revoked.get_reason, "foo") - - - -class CRLTests(TestCase): - """ - Tests for :py:obj:`OpenSSL.crypto.CRL` - """ - cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) - pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - - def test_construction(self): - """ - Confirm we can create :py:obj:`OpenSSL.crypto.CRL`. Check - that it is empty - """ - crl = CRL() - self.assertTrue( isinstance(crl, CRL) ) - self.assertEqual(crl.get_revoked(), None) - - - def test_construction_wrong_args(self): - """ - Calling :py:obj:`OpenSSL.crypto.CRL` with any number of arguments - results in a :py:obj:`TypeError` being raised. - """ - self.assertRaises(TypeError, CRL, 1) - self.assertRaises(TypeError, CRL, "") - self.assertRaises(TypeError, CRL, None) - - - def _get_crl(self): - """ - Get a new ``CRL`` with a revocation. - """ - crl = CRL() - revoked = Revoked() - now = b(datetime.now().strftime("%Y%m%d%H%M%SZ")) - revoked.set_rev_date(now) - revoked.set_serial(b('3ab')) - revoked.set_reason(b('sUpErSeDEd')) - crl.add_revoked(revoked) - return crl - - - def test_export_pem(self): - """ - If not passed a format, ``CRL.export`` returns a "PEM" format string - representing a serial number, a revoked reason, and certificate issuer - information. - """ - crl = self._get_crl() - # PEM format - dumped_crl = crl.export(self.cert, self.pkey, days=20) - text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") - - # These magic values are based on the way the CRL above was constructed - # and with what certificate it was exported. - text.index(b('Serial Number: 03AB')) - text.index(b('Superseded')) - text.index( - b('Issuer: /C=US/ST=IL/L=Chicago/O=Testing/CN=Testing Root CA') - ) - - - def test_export_der(self): - """ - If passed ``FILETYPE_ASN1`` for the format, ``CRL.export`` returns a - "DER" format string representing a serial number, a revoked reason, and - certificate issuer information. - """ - crl = self._get_crl() - - # DER format - dumped_crl = crl.export(self.cert, self.pkey, FILETYPE_ASN1) - text = _runopenssl( - dumped_crl, b"crl", b"-noout", b"-text", b"-inform", b"DER" - ) - text.index(b('Serial Number: 03AB')) - text.index(b('Superseded')) - text.index( - b('Issuer: /C=US/ST=IL/L=Chicago/O=Testing/CN=Testing Root CA') - ) - - - def test_export_text(self): - """ - If passed ``FILETYPE_TEXT`` for the format, ``CRL.export`` returns a - text format string like the one produced by the openssl command line - tool. - """ - crl = self._get_crl() - - dumped_crl = crl.export(self.cert, self.pkey, FILETYPE_ASN1) - text = _runopenssl( - dumped_crl, b"crl", b"-noout", b"-text", b"-inform", b"DER" - ) - - # text format - dumped_text = crl.export(self.cert, self.pkey, type=FILETYPE_TEXT) - self.assertEqual(text, dumped_text) - - - def test_export_custom_digest(self): - """ - If passed the name of a digest function, ``CRL.export`` uses a - signature algorithm based on that digest function. - """ - crl = self._get_crl() - dumped_crl = crl.export(self.cert, self.pkey, digest=b"sha1") - text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") - text.index(b('Signature Algorithm: sha1')) - - - def test_export_md5_digest(self): - """ - If passed md5 as the digest function, ``CRL.export`` uses md5 and does - not emit a deprecation warning. - """ - crl = self._get_crl() - with catch_warnings(record=True) as catcher: - simplefilter("always") - self.assertEqual(0, len(catcher)) - dumped_crl = crl.export(self.cert, self.pkey, digest=b"md5") - text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") - text.index(b('Signature Algorithm: md5')) - - - def test_export_default_digest(self): - """ - If not passed the name of a digest function, ``CRL.export`` uses a - signature algorithm based on MD5 and emits a deprecation warning. - """ - crl = self._get_crl() - with catch_warnings(record=True) as catcher: - simplefilter("always") - dumped_crl = crl.export(self.cert, self.pkey) - self.assertEqual( - "The default message digest (md5) is deprecated. " - "Pass the name of a message digest explicitly.", - str(catcher[0].message), - ) - text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") - text.index(b('Signature Algorithm: md5')) - - - def test_export_invalid(self): - """ - If :py:obj:`CRL.export` is used with an uninitialized :py:obj:`X509` - instance, :py:obj:`OpenSSL.crypto.Error` is raised. - """ - crl = CRL() - self.assertRaises(Error, crl.export, X509(), PKey()) - - - def test_add_revoked_keyword(self): - """ - :py:obj:`OpenSSL.CRL.add_revoked` accepts its single argument as the - ``revoked`` keyword argument. - """ - crl = CRL() - revoked = Revoked() - crl.add_revoked(revoked=revoked) - self.assertTrue(isinstance(crl.get_revoked()[0], Revoked)) - - - def test_export_wrong_args(self): - """ - Calling :py:obj:`OpenSSL.CRL.export` with fewer than two or more than - four arguments, or with arguments other than the certificate, - private key, integer file type, and integer number of days it - expects, results in a :py:obj:`TypeError` being raised. - """ - crl = CRL() - self.assertRaises(TypeError, crl.export) - self.assertRaises(TypeError, crl.export, self.cert) - self.assertRaises(TypeError, crl.export, self.cert, self.pkey, FILETYPE_PEM, 10, "md5", "foo") - - self.assertRaises(TypeError, crl.export, None, self.pkey, FILETYPE_PEM, 10) - self.assertRaises(TypeError, crl.export, self.cert, None, FILETYPE_PEM, 10) - self.assertRaises(TypeError, crl.export, self.cert, self.pkey, None, 10) - self.assertRaises(TypeError, crl.export, self.cert, FILETYPE_PEM, None) - - - def test_export_unknown_filetype(self): - """ - Calling :py:obj:`OpenSSL.CRL.export` with a file type other than - :py:obj:`FILETYPE_PEM`, :py:obj:`FILETYPE_ASN1`, or :py:obj:`FILETYPE_TEXT` results - in a :py:obj:`ValueError` being raised. - """ - crl = CRL() - self.assertRaises(ValueError, crl.export, self.cert, self.pkey, 100, 10) - - - def test_export_unknown_digest(self): - """ - Calling :py:obj:`OpenSSL.CRL.export` with a unsupported digest results - in a :py:obj:`ValueError` being raised. - """ - crl = CRL() - self.assertRaises( - ValueError, - crl.export, - self.cert, self.pkey, FILETYPE_PEM, 10, b"strange-digest" - ) - - - def test_get_revoked(self): - """ - Use python to create a simple CRL with two revocations. - Get back the :py:obj:`Revoked` using :py:obj:`OpenSSL.CRL.get_revoked` and - verify them. - """ - crl = CRL() - - revoked = Revoked() - now = b(datetime.now().strftime("%Y%m%d%H%M%SZ")) - revoked.set_rev_date(now) - revoked.set_serial(b('3ab')) - crl.add_revoked(revoked) - revoked.set_serial(b('100')) - revoked.set_reason(b('sUpErSeDEd')) - crl.add_revoked(revoked) - - revs = crl.get_revoked() - self.assertEqual(len(revs), 2) - self.assertEqual(type(revs[0]), Revoked) - self.assertEqual(type(revs[1]), Revoked) - self.assertEqual(revs[0].get_serial(), b('03AB')) - self.assertEqual(revs[1].get_serial(), b('0100')) - self.assertEqual(revs[0].get_rev_date(), now) - self.assertEqual(revs[1].get_rev_date(), now) - - - def test_get_revoked_wrong_args(self): - """ - Calling :py:obj:`OpenSSL.CRL.get_revoked` with any arguments results - in a :py:obj:`TypeError` being raised. - """ - crl = CRL() - self.assertRaises(TypeError, crl.get_revoked, None) - self.assertRaises(TypeError, crl.get_revoked, 1) - self.assertRaises(TypeError, crl.get_revoked, "") - self.assertRaises(TypeError, crl.get_revoked, "", 1, None) - - - def test_add_revoked_wrong_args(self): - """ - Calling :py:obj:`OpenSSL.CRL.add_revoked` with other than one - argument results in a :py:obj:`TypeError` being raised. - """ - crl = CRL() - self.assertRaises(TypeError, crl.add_revoked) - self.assertRaises(TypeError, crl.add_revoked, 1, 2) - self.assertRaises(TypeError, crl.add_revoked, "foo", "bar") - - - def test_load_crl(self): - """ - Load a known CRL and inspect its revocations. Both - PEM and DER formats are loaded. - """ - crl = load_crl(FILETYPE_PEM, crlData) - revs = crl.get_revoked() - self.assertEqual(len(revs), 2) - self.assertEqual(revs[0].get_serial(), b('03AB')) - self.assertEqual(revs[0].get_reason(), None) - self.assertEqual(revs[1].get_serial(), b('0100')) - self.assertEqual(revs[1].get_reason(), b('Superseded')) - - der = _runopenssl(crlData, b"crl", b"-outform", b"DER") - crl = load_crl(FILETYPE_ASN1, der) - revs = crl.get_revoked() - self.assertEqual(len(revs), 2) - self.assertEqual(revs[0].get_serial(), b('03AB')) - self.assertEqual(revs[0].get_reason(), None) - self.assertEqual(revs[1].get_serial(), b('0100')) - self.assertEqual(revs[1].get_reason(), b('Superseded')) - - - def test_load_crl_wrong_args(self): - """ - Calling :py:obj:`OpenSSL.crypto.load_crl` with other than two - arguments results in a :py:obj:`TypeError` being raised. - """ - self.assertRaises(TypeError, load_crl) - self.assertRaises(TypeError, load_crl, FILETYPE_PEM) - self.assertRaises(TypeError, load_crl, FILETYPE_PEM, crlData, None) - - - def test_load_crl_bad_filetype(self): - """ - Calling :py:obj:`OpenSSL.crypto.load_crl` with an unknown file type - raises a :py:obj:`ValueError`. - """ - self.assertRaises(ValueError, load_crl, 100, crlData) - - - def test_load_crl_bad_data(self): - """ - Calling :py:obj:`OpenSSL.crypto.load_crl` with file data which can't - be loaded raises a :py:obj:`OpenSSL.crypto.Error`. - """ - self.assertRaises(Error, load_crl, FILETYPE_PEM, b"hello, world") - - - -class X509StoreContextTests(TestCase): - """ - Tests for :py:obj:`OpenSSL.crypto.X509StoreContext`. - """ - root_cert = load_certificate(FILETYPE_PEM, root_cert_pem) - intermediate_cert = load_certificate(FILETYPE_PEM, intermediate_cert_pem) - intermediate_server_cert = load_certificate(FILETYPE_PEM, intermediate_server_cert_pem) - - def test_valid(self): - """ - :py:obj:`verify_certificate` returns ``None`` when called with a certificate - and valid chain. - """ - store = X509Store() - store.add_cert(self.root_cert) - store.add_cert(self.intermediate_cert) - store_ctx = X509StoreContext(store, self.intermediate_server_cert) - self.assertEqual(store_ctx.verify_certificate(), None) - - - def test_reuse(self): - """ - :py:obj:`verify_certificate` can be called multiple times with the same - ``X509StoreContext`` instance to produce the same result. - """ - store = X509Store() - store.add_cert(self.root_cert) - store.add_cert(self.intermediate_cert) - store_ctx = X509StoreContext(store, self.intermediate_server_cert) - self.assertEqual(store_ctx.verify_certificate(), None) - self.assertEqual(store_ctx.verify_certificate(), None) - - - def test_trusted_self_signed(self): - """ - :py:obj:`verify_certificate` returns ``None`` when called with a self-signed - certificate and itself in the chain. - """ - store = X509Store() - store.add_cert(self.root_cert) - store_ctx = X509StoreContext(store, self.root_cert) - self.assertEqual(store_ctx.verify_certificate(), None) - - - def test_untrusted_self_signed(self): - """ - :py:obj:`verify_certificate` raises error when a self-signed certificate is - verified without itself in the chain. - """ - store = X509Store() - store_ctx = X509StoreContext(store, self.root_cert) - e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate) - self.assertEqual(e.args[0][2], 'self signed certificate') - self.assertEqual(e.certificate.get_subject().CN, 'Testing Root CA') - - - def test_invalid_chain_no_root(self): - """ - :py:obj:`verify_certificate` raises error when a root certificate is missing - from the chain. - """ - store = X509Store() - store.add_cert(self.intermediate_cert) - store_ctx = X509StoreContext(store, self.intermediate_server_cert) - e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate) - self.assertEqual(e.args[0][2], 'unable to get issuer certificate') - self.assertEqual(e.certificate.get_subject().CN, 'intermediate') - - - def test_invalid_chain_no_intermediate(self): - """ - :py:obj:`verify_certificate` raises error when an intermediate certificate is - missing from the chain. - """ - store = X509Store() - store.add_cert(self.root_cert) - store_ctx = X509StoreContext(store, self.intermediate_server_cert) - e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate) - self.assertEqual(e.args[0][2], 'unable to get local issuer certificate') - self.assertEqual(e.certificate.get_subject().CN, 'intermediate-service') - - - def test_modification_pre_verify(self): - """ - :py:obj:`verify_certificate` can use a store context modified after - instantiation. - """ - store_bad = X509Store() - store_bad.add_cert(self.intermediate_cert) - store_good = X509Store() - store_good.add_cert(self.root_cert) - store_good.add_cert(self.intermediate_cert) - store_ctx = X509StoreContext(store_bad, self.intermediate_server_cert) - e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate) - self.assertEqual(e.args[0][2], 'unable to get issuer certificate') - self.assertEqual(e.certificate.get_subject().CN, 'intermediate') - store_ctx.set_store(store_good) - self.assertEqual(store_ctx.verify_certificate(), None) - - - -class SignVerifyTests(TestCase): - """ - Tests for :py:obj:`OpenSSL.crypto.sign` and :py:obj:`OpenSSL.crypto.verify`. - """ - def test_sign_verify(self): - """ - :py:obj:`sign` generates a cryptographic signature which :py:obj:`verify` can check. - """ - content = b( - "It was a bright cold day in April, and the clocks were striking " - "thirteen. Winston Smith, his chin nuzzled into his breast in an " - "effort to escape the vile wind, slipped quickly through the " - "glass doors of Victory Mansions, though not quickly enough to " - "prevent a swirl of gritty dust from entering along with him.") - - # sign the content with this private key - priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) - # verify the content with this cert - good_cert = load_certificate(FILETYPE_PEM, root_cert_pem) - # certificate unrelated to priv_key, used to trigger an error - bad_cert = load_certificate(FILETYPE_PEM, server_cert_pem) - - for digest in ['md5', 'sha1']: - sig = sign(priv_key, content, digest) - - # Verify the signature of content, will throw an exception if error. - verify(good_cert, sig, content, digest) - - # This should fail because the certificate doesn't match the - # private key that was used to sign the content. - self.assertRaises(Error, verify, bad_cert, sig, content, digest) - - # This should fail because we've "tainted" the content after - # signing it. - self.assertRaises( - Error, verify, - good_cert, sig, content + b("tainted"), digest) - - # test that unknown digest types fail - self.assertRaises( - ValueError, sign, priv_key, content, "strange-digest") - self.assertRaises( - ValueError, verify, good_cert, sig, content, "strange-digest") - - - def test_sign_verify_with_text(self): - """ - :py:obj:`sign` generates a cryptographic signature which :py:obj:`verify` can check. - Deprecation warnings raised because using text instead of bytes as content - """ - content = ( - b"It was a bright cold day in April, and the clocks were striking " - b"thirteen. Winston Smith, his chin nuzzled into his breast in an " - b"effort to escape the vile wind, slipped quickly through the " - b"glass doors of Victory Mansions, though not quickly enough to " - b"prevent a swirl of gritty dust from entering along with him." - ).decode("ascii") - - priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) - cert = load_certificate(FILETYPE_PEM, root_cert_pem) - for digest in ['md5', 'sha1']: - with catch_warnings(record=True) as w: - simplefilter("always") - sig = sign(priv_key, content, digest) - - self.assertEqual( - "{0} for data is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ), - str(w[-1].message) - ) - self.assertIs(w[-1].category, DeprecationWarning) - - with catch_warnings(record=True) as w: - simplefilter("always") - verify(cert, sig, content, digest) - - self.assertEqual( - "{0} for data is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ), - str(w[-1].message) - ) - self.assertIs(w[-1].category, DeprecationWarning) - - - def test_sign_nulls(self): - """ - :py:obj:`sign` produces a signature for a string with embedded nulls. - """ - content = b("Watch out! \0 Did you see it?") - priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) - good_cert = load_certificate(FILETYPE_PEM, root_cert_pem) - sig = sign(priv_key, content, "sha1") - verify(good_cert, sig, content, "sha1") - - - -class EllipticCurveTests(TestCase): - """ - Tests for :py:class:`_EllipticCurve`, :py:obj:`get_elliptic_curve`, and - :py:obj:`get_elliptic_curves`. - """ - def test_set(self): - """ - :py:obj:`get_elliptic_curves` returns a :py:obj:`set`. - """ - self.assertIsInstance(get_elliptic_curves(), set) - - - def test_some_curves(self): - """ - If :py:mod:`cryptography` has elliptic curve support then the set - returned by :py:obj:`get_elliptic_curves` has some elliptic curves in - it. - - There could be an OpenSSL that violates this assumption. If so, this - test will fail and we'll find out. - """ - curves = get_elliptic_curves() - if lib.Cryptography_HAS_EC: - self.assertTrue(curves) - else: - self.assertFalse(curves) - - - def test_a_curve(self): - """ - :py:obj:`get_elliptic_curve` can be used to retrieve a particular - supported curve. - """ - curves = get_elliptic_curves() - if curves: - curve = next(iter(curves)) - self.assertEqual(curve.name, get_elliptic_curve(curve.name).name) - else: - self.assertRaises(ValueError, get_elliptic_curve, u("prime256v1")) - - - def test_not_a_curve(self): - """ - :py:obj:`get_elliptic_curve` raises :py:class:`ValueError` if called - with a name which does not identify a supported curve. - """ - self.assertRaises( - ValueError, get_elliptic_curve, u("this curve was just invented")) - - - def test_repr(self): - """ - The string representation of a curve object includes simply states the - object is a curve and what its name is. - """ - curves = get_elliptic_curves() - if curves: - curve = next(iter(curves)) - self.assertEqual("<Curve %r>" % (curve.name,), repr(curve)) - - - def test_to_EC_KEY(self): - """ - The curve object can export a version of itself as an EC_KEY* via the - private :py:meth:`_EllipticCurve._to_EC_KEY`. - """ - curves = get_elliptic_curves() - if curves: - curve = next(iter(curves)) - # It's not easy to assert anything about this object. However, see - # leakcheck/crypto.py for a test that demonstrates it at least does - # not leak memory. - curve._to_EC_KEY() - - - -class EllipticCurveFactory(object): - """ - A helper to get the names of two curves. - """ - def __init__(self): - curves = iter(get_elliptic_curves()) - try: - self.curve_name = next(curves).name - self.another_curve_name = next(curves).name - except StopIteration: - self.curve_name = self.another_curve_name = None - - - -class EllipticCurveEqualityTests(TestCase, EqualityTestsMixin): - """ - Tests :py:type:`_EllipticCurve`\ 's implementation of ``==`` and ``!=``. - """ - curve_factory = EllipticCurveFactory() - - if curve_factory.curve_name is None: - skip = "There are no curves available there can be no curve objects." - - - def anInstance(self): - """ - Get the curve object for an arbitrary curve supported by the system. - """ - return get_elliptic_curve(self.curve_factory.curve_name) - - - def anotherInstance(self): - """ - Get the curve object for an arbitrary curve supported by the system - - but not the one returned by C{anInstance}. - """ - return get_elliptic_curve(self.curve_factory.another_curve_name) - - - -class EllipticCurveHashTests(TestCase): - """ - Tests for :py:type:`_EllipticCurve`\ 's implementation of hashing (thus use - as an item in a :py:type:`dict` or :py:type:`set`). - """ - curve_factory = EllipticCurveFactory() - - if curve_factory.curve_name is None: - skip = "There are no curves available there can be no curve objects." - - - def test_contains(self): - """ - The ``in`` operator reports that a :py:type:`set` containing a curve - does contain that curve. - """ - curve = get_elliptic_curve(self.curve_factory.curve_name) - curves = set([curve]) - self.assertIn(curve, curves) - - - def test_does_not_contain(self): - """ - The ``in`` operator reports that a :py:type:`set` not containing a - curve does not contain that curve. - """ - curve = get_elliptic_curve(self.curve_factory.curve_name) - curves = set([get_elliptic_curve(self.curve_factory.another_curve_name)]) - self.assertNotIn(curve, curves) - - - -if __name__ == '__main__': - main() diff --git a/lib/OpenSSL/test/test_rand.py b/lib/OpenSSL/test/test_rand.py deleted file mode 100644 index 3d5c2906dfe370db61e3b35eab27d0adafc120b1..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/test/test_rand.py +++ /dev/null @@ -1,223 +0,0 @@ -# Copyright (c) Frederick Dean -# See LICENSE for details. - -""" -Unit tests for :py:obj:`OpenSSL.rand`. -""" - -from unittest import main -import os -import stat -import sys - -from OpenSSL.test.util import NON_ASCII, TestCase, b -from OpenSSL import rand - - -class RandTests(TestCase): - def test_bytes_wrong_args(self): - """ - :py:obj:`OpenSSL.rand.bytes` raises :py:obj:`TypeError` if called with the wrong - number of arguments or with a non-:py:obj:`int` argument. - """ - self.assertRaises(TypeError, rand.bytes) - self.assertRaises(TypeError, rand.bytes, None) - self.assertRaises(TypeError, rand.bytes, 3, None) - - - def test_insufficientMemory(self): - """ - :py:obj:`OpenSSL.rand.bytes` raises :py:obj:`MemoryError` if more bytes - are requested than will fit in memory. - """ - self.assertRaises(MemoryError, rand.bytes, sys.maxsize) - - - def test_bytes(self): - """ - Verify that we can obtain bytes from rand_bytes() and - that they are different each time. Test the parameter - of rand_bytes() for bad values. - """ - b1 = rand.bytes(50) - self.assertEqual(len(b1), 50) - b2 = rand.bytes(num_bytes=50) # parameter by name - self.assertNotEqual(b1, b2) # Hip, Hip, Horay! FIPS complaince - b3 = rand.bytes(num_bytes=0) - self.assertEqual(len(b3), 0) - exc = self.assertRaises(ValueError, rand.bytes, -1) - self.assertEqual(str(exc), "num_bytes must not be negative") - - - def test_add_wrong_args(self): - """ - When called with the wrong number of arguments, or with arguments not of - type :py:obj:`str` and :py:obj:`int`, :py:obj:`OpenSSL.rand.add` raises :py:obj:`TypeError`. - """ - self.assertRaises(TypeError, rand.add) - self.assertRaises(TypeError, rand.add, b("foo"), None) - self.assertRaises(TypeError, rand.add, None, 3) - self.assertRaises(TypeError, rand.add, b("foo"), 3, None) - - - def test_add(self): - """ - :py:obj:`OpenSSL.rand.add` adds entropy to the PRNG. - """ - rand.add(b('hamburger'), 3) - - - def test_seed_wrong_args(self): - """ - When called with the wrong number of arguments, or with a non-:py:obj:`str` - argument, :py:obj:`OpenSSL.rand.seed` raises :py:obj:`TypeError`. - """ - self.assertRaises(TypeError, rand.seed) - self.assertRaises(TypeError, rand.seed, None) - self.assertRaises(TypeError, rand.seed, b("foo"), None) - - - def test_seed(self): - """ - :py:obj:`OpenSSL.rand.seed` adds entropy to the PRNG. - """ - rand.seed(b('milk shake')) - - - def test_status_wrong_args(self): - """ - :py:obj:`OpenSSL.rand.status` raises :py:obj:`TypeError` when called with any - arguments. - """ - self.assertRaises(TypeError, rand.status, None) - - - def test_status(self): - """ - :py:obj:`OpenSSL.rand.status` returns :py:obj:`True` if the PRNG has sufficient - entropy, :py:obj:`False` otherwise. - """ - # It's hard to know what it is actually going to return. Different - # OpenSSL random engines decide differently whether they have enough - # entropy or not. - self.assertTrue(rand.status() in (1, 2)) - - - def test_egd_wrong_args(self): - """ - :py:obj:`OpenSSL.rand.egd` raises :py:obj:`TypeError` when called with the wrong - number of arguments or with arguments not of type :py:obj:`str` and :py:obj:`int`. - """ - self.assertRaises(TypeError, rand.egd) - self.assertRaises(TypeError, rand.egd, None) - self.assertRaises(TypeError, rand.egd, "foo", None) - self.assertRaises(TypeError, rand.egd, None, 3) - self.assertRaises(TypeError, rand.egd, "foo", 3, None) - - - def test_egd_missing(self): - """ - :py:obj:`OpenSSL.rand.egd` returns :py:obj:`0` or :py:obj:`-1` if the - EGD socket passed to it does not exist. - """ - result = rand.egd(self.mktemp()) - expected = (-1, 0) - self.assertTrue( - result in expected, - "%r not in %r" % (result, expected)) - - - def test_egd_missing_and_bytes(self): - """ - :py:obj:`OpenSSL.rand.egd` returns :py:obj:`0` or :py:obj:`-1` if the - EGD socket passed to it does not exist even if a size argument is - explicitly passed. - """ - result = rand.egd(self.mktemp(), 1024) - expected = (-1, 0) - self.assertTrue( - result in expected, - "%r not in %r" % (result, expected)) - - - def test_cleanup_wrong_args(self): - """ - :py:obj:`OpenSSL.rand.cleanup` raises :py:obj:`TypeError` when called with any - arguments. - """ - self.assertRaises(TypeError, rand.cleanup, None) - - - def test_cleanup(self): - """ - :py:obj:`OpenSSL.rand.cleanup` releases the memory used by the PRNG and returns - :py:obj:`None`. - """ - self.assertIdentical(rand.cleanup(), None) - - - def test_load_file_wrong_args(self): - """ - :py:obj:`OpenSSL.rand.load_file` raises :py:obj:`TypeError` when called the wrong - number of arguments or arguments not of type :py:obj:`str` and :py:obj:`int`. - """ - self.assertRaises(TypeError, rand.load_file) - self.assertRaises(TypeError, rand.load_file, "foo", None) - self.assertRaises(TypeError, rand.load_file, None, 1) - self.assertRaises(TypeError, rand.load_file, "foo", 1, None) - - - def test_write_file_wrong_args(self): - """ - :py:obj:`OpenSSL.rand.write_file` raises :py:obj:`TypeError` when called with the - wrong number of arguments or a non-:py:obj:`str` argument. - """ - self.assertRaises(TypeError, rand.write_file) - self.assertRaises(TypeError, rand.write_file, None) - self.assertRaises(TypeError, rand.write_file, "foo", None) - - def _read_write_test(self, path): - """ - Verify that ``rand.write_file`` and ``rand.load_file`` can be used. - """ - # Create the file so cleanup is more straightforward - with open(path, "w"): - pass - - try: - # Write random bytes to a file - rand.write_file(path) - - # Verify length of written file - size = os.stat(path)[stat.ST_SIZE] - self.assertEqual(1024, size) - - # Read random bytes from file - rand.load_file(path) - rand.load_file(path, 4) # specify a length - finally: - # Cleanup - os.unlink(path) - - - def test_bytes_paths(self): - """ - Random data can be saved and loaded to files with paths specified as - bytes. - """ - path = self.mktemp() - path += NON_ASCII.encode(sys.getfilesystemencoding()) - self._read_write_test(path) - - - def test_unicode_paths(self): - """ - Random data can be saved and loaded to files with paths specified as - unicode. - """ - path = self.mktemp().decode('utf-8') + NON_ASCII - self._read_write_test(path) - - -if __name__ == '__main__': - main() diff --git a/lib/OpenSSL/test/test_ssl.py b/lib/OpenSSL/test/test_ssl.py deleted file mode 100644 index bb1c9aedf9315f72f3394acf22474b2001a20d37..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/test/test_ssl.py +++ /dev/null @@ -1,3775 +0,0 @@ -# Copyright (C) Jean-Paul Calderone -# See LICENSE for details. - -""" -Unit tests for :py:obj:`OpenSSL.SSL`. -""" - -from gc import collect, get_referrers -from errno import ECONNREFUSED, EINPROGRESS, EWOULDBLOCK, EPIPE, ESHUTDOWN -from sys import platform, getfilesystemencoding -from socket import SHUT_RDWR, error, socket -from os import makedirs -from os.path import join -from unittest import main -from weakref import ref -from warnings import catch_warnings, simplefilter - -from six import PY3, text_type, u - -from OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM -from OpenSSL.crypto import PKey, X509, X509Extension, X509Store -from OpenSSL.crypto import dump_privatekey, load_privatekey -from OpenSSL.crypto import dump_certificate, load_certificate -from OpenSSL.crypto import get_elliptic_curves - -from OpenSSL.SSL import OPENSSL_VERSION_NUMBER, SSLEAY_VERSION, SSLEAY_CFLAGS -from OpenSSL.SSL import SSLEAY_PLATFORM, SSLEAY_DIR, SSLEAY_BUILT_ON -from OpenSSL.SSL import SENT_SHUTDOWN, RECEIVED_SHUTDOWN -from OpenSSL.SSL import ( - SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD, - TLSv1_1_METHOD, TLSv1_2_METHOD) -from OpenSSL.SSL import OP_SINGLE_DH_USE, OP_NO_SSLv2, OP_NO_SSLv3 -from OpenSSL.SSL import ( - VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE, VERIFY_NONE) - -from OpenSSL.SSL import ( - SESS_CACHE_OFF, SESS_CACHE_CLIENT, SESS_CACHE_SERVER, SESS_CACHE_BOTH, - SESS_CACHE_NO_AUTO_CLEAR, SESS_CACHE_NO_INTERNAL_LOOKUP, - SESS_CACHE_NO_INTERNAL_STORE, SESS_CACHE_NO_INTERNAL) - -from OpenSSL.SSL import ( - Error, SysCallError, WantReadError, WantWriteError, ZeroReturnError) -from OpenSSL.SSL import ( - Context, ContextType, Session, Connection, ConnectionType, SSLeay_version) - -from OpenSSL._util import lib as _lib - -from OpenSSL.test.util import WARNING_TYPE_EXPECTED, NON_ASCII, TestCase, b -from OpenSSL.test.test_crypto import ( - cleartextCertificatePEM, cleartextPrivateKeyPEM, - client_cert_pem, client_key_pem, server_cert_pem, server_key_pem, - root_cert_pem) - -try: - from OpenSSL.SSL import OP_NO_QUERY_MTU -except ImportError: - OP_NO_QUERY_MTU = None -try: - from OpenSSL.SSL import OP_COOKIE_EXCHANGE -except ImportError: - OP_COOKIE_EXCHANGE = None -try: - from OpenSSL.SSL import OP_NO_TICKET -except ImportError: - OP_NO_TICKET = None - -try: - from OpenSSL.SSL import OP_NO_COMPRESSION -except ImportError: - OP_NO_COMPRESSION = None - -try: - from OpenSSL.SSL import MODE_RELEASE_BUFFERS -except ImportError: - MODE_RELEASE_BUFFERS = None - -try: - from OpenSSL.SSL import OP_NO_TLSv1, OP_NO_TLSv1_1, OP_NO_TLSv1_2 -except ImportError: - OP_NO_TLSv1 = OP_NO_TLSv1_1 = OP_NO_TLSv1_2 = None - -from OpenSSL.SSL import ( - SSL_ST_CONNECT, SSL_ST_ACCEPT, SSL_ST_MASK, SSL_ST_INIT, SSL_ST_BEFORE, - SSL_ST_OK, SSL_ST_RENEGOTIATE, - SSL_CB_LOOP, SSL_CB_EXIT, SSL_CB_READ, SSL_CB_WRITE, SSL_CB_ALERT, - SSL_CB_READ_ALERT, SSL_CB_WRITE_ALERT, SSL_CB_ACCEPT_LOOP, - SSL_CB_ACCEPT_EXIT, SSL_CB_CONNECT_LOOP, SSL_CB_CONNECT_EXIT, - SSL_CB_HANDSHAKE_START, SSL_CB_HANDSHAKE_DONE) - -# openssl dhparam 128 -out dh-128.pem (note that 128 is a small number of bits -# to use) -dhparam = """\ ------BEGIN DH PARAMETERS----- -MBYCEQCobsg29c9WZP/54oAPcwiDAgEC ------END DH PARAMETERS----- -""" - - -def join_bytes_or_unicode(prefix, suffix): - """ - Join two path components of either ``bytes`` or ``unicode``. - - The return type is the same as the type of ``prefix``. - """ - # If the types are the same, nothing special is necessary. - if type(prefix) == type(suffix): - return join(prefix, suffix) - - # Otherwise, coerce suffix to the type of prefix. - if isinstance(prefix, text_type): - return join(prefix, suffix.decode(getfilesystemencoding())) - else: - return join(prefix, suffix.encode(getfilesystemencoding())) - - -def verify_cb(conn, cert, errnum, depth, ok): - return ok - - -def socket_pair(): - """ - Establish and return a pair of network sockets connected to each other. - """ - # Connect a pair of sockets - port = socket() - port.bind(('', 0)) - port.listen(1) - client = socket() - client.setblocking(False) - client.connect_ex(("127.0.0.1", port.getsockname()[1])) - client.setblocking(True) - server = port.accept()[0] - - # Let's pass some unencrypted data to make sure our socket connection is - # fine. Just one byte, so we don't have to worry about buffers getting - # filled up or fragmentation. - server.send(b("x")) - assert client.recv(1024) == b("x") - client.send(b("y")) - assert server.recv(1024) == b("y") - - # Most of our callers want non-blocking sockets, make it easy for them. - server.setblocking(False) - client.setblocking(False) - - return (server, client) - - - -def handshake(client, server): - conns = [client, server] - while conns: - for conn in conns: - try: - conn.do_handshake() - except WantReadError: - pass - else: - conns.remove(conn) - - -def _create_certificate_chain(): - """ - Construct and return a chain of certificates. - - 1. A new self-signed certificate authority certificate (cacert) - 2. A new intermediate certificate signed by cacert (icert) - 3. A new server certificate signed by icert (scert) - """ - caext = X509Extension(b('basicConstraints'), False, b('CA:true')) - - # Step 1 - cakey = PKey() - cakey.generate_key(TYPE_RSA, 512) - cacert = X509() - cacert.get_subject().commonName = "Authority Certificate" - cacert.set_issuer(cacert.get_subject()) - cacert.set_pubkey(cakey) - cacert.set_notBefore(b("20000101000000Z")) - cacert.set_notAfter(b("20200101000000Z")) - cacert.add_extensions([caext]) - cacert.set_serial_number(0) - cacert.sign(cakey, "sha1") - - # Step 2 - ikey = PKey() - ikey.generate_key(TYPE_RSA, 512) - icert = X509() - icert.get_subject().commonName = "Intermediate Certificate" - icert.set_issuer(cacert.get_subject()) - icert.set_pubkey(ikey) - icert.set_notBefore(b("20000101000000Z")) - icert.set_notAfter(b("20200101000000Z")) - icert.add_extensions([caext]) - icert.set_serial_number(0) - icert.sign(cakey, "sha1") - - # Step 3 - skey = PKey() - skey.generate_key(TYPE_RSA, 512) - scert = X509() - scert.get_subject().commonName = "Server Certificate" - scert.set_issuer(icert.get_subject()) - scert.set_pubkey(skey) - scert.set_notBefore(b("20000101000000Z")) - scert.set_notAfter(b("20200101000000Z")) - scert.add_extensions([ - X509Extension(b('basicConstraints'), True, b('CA:false'))]) - scert.set_serial_number(0) - scert.sign(ikey, "sha1") - - return [(cakey, cacert), (ikey, icert), (skey, scert)] - - - -class _LoopbackMixin: - """ - Helper mixin which defines methods for creating a connected socket pair and - for forcing two connected SSL sockets to talk to each other via memory BIOs. - """ - def _loopbackClientFactory(self, socket): - client = Connection(Context(TLSv1_METHOD), socket) - client.set_connect_state() - return client - - - def _loopbackServerFactory(self, socket): - ctx = Context(TLSv1_METHOD) - ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) - ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) - server = Connection(ctx, socket) - server.set_accept_state() - return server - - - def _loopback(self, serverFactory=None, clientFactory=None): - if serverFactory is None: - serverFactory = self._loopbackServerFactory - if clientFactory is None: - clientFactory = self._loopbackClientFactory - - (server, client) = socket_pair() - server = serverFactory(server) - client = clientFactory(client) - - handshake(client, server) - - server.setblocking(True) - client.setblocking(True) - return server, client - - - def _interactInMemory(self, client_conn, server_conn): - """ - Try to read application bytes from each of the two :py:obj:`Connection` - objects. Copy bytes back and forth between their send/receive buffers - for as long as there is anything to copy. When there is nothing more - to copy, return :py:obj:`None`. If one of them actually manages to deliver - some application bytes, return a two-tuple of the connection from which - the bytes were read and the bytes themselves. - """ - wrote = True - while wrote: - # Loop until neither side has anything to say - wrote = False - - # Copy stuff from each side's send buffer to the other side's - # receive buffer. - for (read, write) in [(client_conn, server_conn), - (server_conn, client_conn)]: - - # Give the side a chance to generate some more bytes, or - # succeed. - try: - data = read.recv(2 ** 16) - except WantReadError: - # It didn't succeed, so we'll hope it generated some - # output. - pass - else: - # It did succeed, so we'll stop now and let the caller deal - # with it. - return (read, data) - - while True: - # Keep copying as long as there's more stuff there. - try: - dirty = read.bio_read(4096) - except WantReadError: - # Okay, nothing more waiting to be sent. Stop - # processing this send buffer. - break - else: - # Keep track of the fact that someone generated some - # output. - wrote = True - write.bio_write(dirty) - - - def _handshakeInMemory(self, client_conn, server_conn): - """ - Perform the TLS handshake between two :py:class:`Connection` instances - connected to each other via memory BIOs. - """ - client_conn.set_connect_state() - server_conn.set_accept_state() - - for conn in [client_conn, server_conn]: - try: - conn.do_handshake() - except WantReadError: - pass - - self._interactInMemory(client_conn, server_conn) - - - -class VersionTests(TestCase): - """ - Tests for version information exposed by - :py:obj:`OpenSSL.SSL.SSLeay_version` and - :py:obj:`OpenSSL.SSL.OPENSSL_VERSION_NUMBER`. - """ - def test_OPENSSL_VERSION_NUMBER(self): - """ - :py:obj:`OPENSSL_VERSION_NUMBER` is an integer with status in the low - byte and the patch, fix, minor, and major versions in the - nibbles above that. - """ - self.assertTrue(isinstance(OPENSSL_VERSION_NUMBER, int)) - - - def test_SSLeay_version(self): - """ - :py:obj:`SSLeay_version` takes a version type indicator and returns - one of a number of version strings based on that indicator. - """ - versions = {} - for t in [SSLEAY_VERSION, SSLEAY_CFLAGS, SSLEAY_BUILT_ON, - SSLEAY_PLATFORM, SSLEAY_DIR]: - version = SSLeay_version(t) - versions[version] = t - self.assertTrue(isinstance(version, bytes)) - self.assertEqual(len(versions), 5) - - - -class ContextTests(TestCase, _LoopbackMixin): - """ - Unit tests for :py:obj:`OpenSSL.SSL.Context`. - """ - def test_method(self): - """ - :py:obj:`Context` can be instantiated with one of :py:obj:`SSLv2_METHOD`, - :py:obj:`SSLv3_METHOD`, :py:obj:`SSLv23_METHOD`, :py:obj:`TLSv1_METHOD`, - :py:obj:`TLSv1_1_METHOD`, or :py:obj:`TLSv1_2_METHOD`. - """ - methods = [ - SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD] - for meth in methods: - Context(meth) - - - maybe = [SSLv2_METHOD, TLSv1_1_METHOD, TLSv1_2_METHOD] - for meth in maybe: - try: - Context(meth) - except (Error, ValueError): - # Some versions of OpenSSL have SSLv2 / TLSv1.1 / TLSv1.2, some - # don't. Difficult to say in advance. - pass - - self.assertRaises(TypeError, Context, "") - self.assertRaises(ValueError, Context, 10) - - - if not PY3: - def test_method_long(self): - """ - On Python 2 :py:class:`Context` accepts values of type - :py:obj:`long` as well as :py:obj:`int`. - """ - Context(long(TLSv1_METHOD)) - - - - def test_type(self): - """ - :py:obj:`Context` and :py:obj:`ContextType` refer to the same type object and can be - used to create instances of that type. - """ - self.assertIdentical(Context, ContextType) - self.assertConsistentType(Context, 'Context', TLSv1_METHOD) - - - def test_use_privatekey(self): - """ - :py:obj:`Context.use_privatekey` takes an :py:obj:`OpenSSL.crypto.PKey` instance. - """ - key = PKey() - key.generate_key(TYPE_RSA, 128) - ctx = Context(TLSv1_METHOD) - ctx.use_privatekey(key) - self.assertRaises(TypeError, ctx.use_privatekey, "") - - - def test_use_privatekey_file_missing(self): - """ - :py:obj:`Context.use_privatekey_file` raises :py:obj:`OpenSSL.SSL.Error` - when passed the name of a file which does not exist. - """ - ctx = Context(TLSv1_METHOD) - self.assertRaises(Error, ctx.use_privatekey_file, self.mktemp()) - - - def _use_privatekey_file_test(self, pemfile, filetype): - """ - Verify that calling ``Context.use_privatekey_file`` with the given - arguments does not raise an exception. - """ - key = PKey() - key.generate_key(TYPE_RSA, 128) - - with open(pemfile, "wt") as pem: - pem.write( - dump_privatekey(FILETYPE_PEM, key).decode("ascii") - ) - - ctx = Context(TLSv1_METHOD) - ctx.use_privatekey_file(pemfile, filetype) - - - def test_use_privatekey_file_bytes(self): - """ - A private key can be specified from a file by passing a ``bytes`` - instance giving the file name to ``Context.use_privatekey_file``. - """ - self._use_privatekey_file_test( - self.mktemp() + NON_ASCII.encode(getfilesystemencoding()), - FILETYPE_PEM, - ) - - - def test_use_privatekey_file_unicode(self): - """ - A private key can be specified from a file by passing a ``unicode`` - instance giving the file name to ``Context.use_privatekey_file``. - """ - self._use_privatekey_file_test( - self.mktemp().decode(getfilesystemencoding()) + NON_ASCII, - FILETYPE_PEM, - ) - - - if not PY3: - def test_use_privatekey_file_long(self): - """ - On Python 2 :py:obj:`Context.use_privatekey_file` accepts a - filetype of type :py:obj:`long` as well as :py:obj:`int`. - """ - self._use_privatekey_file_test(self.mktemp(), long(FILETYPE_PEM)) - - - def test_use_certificate_wrong_args(self): - """ - :py:obj:`Context.use_certificate_wrong_args` raises :py:obj:`TypeError` - when not passed exactly one :py:obj:`OpenSSL.crypto.X509` instance as an - argument. - """ - ctx = Context(TLSv1_METHOD) - self.assertRaises(TypeError, ctx.use_certificate) - self.assertRaises(TypeError, ctx.use_certificate, "hello, world") - self.assertRaises(TypeError, ctx.use_certificate, X509(), "hello, world") - - - def test_use_certificate_uninitialized(self): - """ - :py:obj:`Context.use_certificate` raises :py:obj:`OpenSSL.SSL.Error` - when passed a :py:obj:`OpenSSL.crypto.X509` instance which has not been - initialized (ie, which does not actually have any certificate data). - """ - ctx = Context(TLSv1_METHOD) - self.assertRaises(Error, ctx.use_certificate, X509()) - - - def test_use_certificate(self): - """ - :py:obj:`Context.use_certificate` sets the certificate which will be - used to identify connections created using the context. - """ - # TODO - # Hard to assert anything. But we could set a privatekey then ask - # OpenSSL if the cert and key agree using check_privatekey. Then as - # long as check_privatekey works right we're good... - ctx = Context(TLSv1_METHOD) - ctx.use_certificate(load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) - - - def test_use_certificate_file_wrong_args(self): - """ - :py:obj:`Context.use_certificate_file` raises :py:obj:`TypeError` if - called with zero arguments or more than two arguments, or if the first - argument is not a byte string or the second argumnent is not an integer. - """ - ctx = Context(TLSv1_METHOD) - self.assertRaises(TypeError, ctx.use_certificate_file) - self.assertRaises(TypeError, ctx.use_certificate_file, b"somefile", object()) - self.assertRaises( - TypeError, ctx.use_certificate_file, b"somefile", FILETYPE_PEM, object()) - self.assertRaises( - TypeError, ctx.use_certificate_file, object(), FILETYPE_PEM) - self.assertRaises( - TypeError, ctx.use_certificate_file, b"somefile", object()) - - - def test_use_certificate_file_missing(self): - """ - :py:obj:`Context.use_certificate_file` raises - `:py:obj:`OpenSSL.SSL.Error` if passed the name of a file which does not - exist. - """ - ctx = Context(TLSv1_METHOD) - self.assertRaises(Error, ctx.use_certificate_file, self.mktemp()) - - - def _use_certificate_file_test(self, certificate_file): - """ - Verify that calling ``Context.use_certificate_file`` with the given - filename doesn't raise an exception. - """ - # TODO - # Hard to assert anything. But we could set a privatekey then ask - # OpenSSL if the cert and key agree using check_privatekey. Then as - # long as check_privatekey works right we're good... - with open(certificate_file, "wb") as pem_file: - pem_file.write(cleartextCertificatePEM) - - ctx = Context(TLSv1_METHOD) - ctx.use_certificate_file(certificate_file) - - - def test_use_certificate_file_bytes(self): - """ - :py:obj:`Context.use_certificate_file` sets the certificate (given as a - ``bytes`` filename) which will be used to identify connections created - using the context. - """ - filename = self.mktemp() + NON_ASCII.encode(getfilesystemencoding()) - self._use_certificate_file_test(filename) - - - def test_use_certificate_file_unicode(self): - """ - :py:obj:`Context.use_certificate_file` sets the certificate (given as a - ``bytes`` filename) which will be used to identify connections created - using the context. - """ - filename = self.mktemp().decode(getfilesystemencoding()) + NON_ASCII - self._use_certificate_file_test(filename) - - - if not PY3: - def test_use_certificate_file_long(self): - """ - On Python 2 :py:obj:`Context.use_certificate_file` accepts a - filetype of type :py:obj:`long` as well as :py:obj:`int`. - """ - pem_filename = self.mktemp() - with open(pem_filename, "wb") as pem_file: - pem_file.write(cleartextCertificatePEM) - - ctx = Context(TLSv1_METHOD) - ctx.use_certificate_file(pem_filename, long(FILETYPE_PEM)) - - - def test_check_privatekey_valid(self): - """ - :py:obj:`Context.check_privatekey` returns :py:obj:`None` if the - :py:obj:`Context` instance has been configured to use a matched key and - certificate pair. - """ - key = load_privatekey(FILETYPE_PEM, client_key_pem) - cert = load_certificate(FILETYPE_PEM, client_cert_pem) - context = Context(TLSv1_METHOD) - context.use_privatekey(key) - context.use_certificate(cert) - self.assertIs(None, context.check_privatekey()) - - - def test_check_privatekey_invalid(self): - """ - :py:obj:`Context.check_privatekey` raises :py:obj:`Error` if the - :py:obj:`Context` instance has been configured to use a key and - certificate pair which don't relate to each other. - """ - key = load_privatekey(FILETYPE_PEM, client_key_pem) - cert = load_certificate(FILETYPE_PEM, server_cert_pem) - context = Context(TLSv1_METHOD) - context.use_privatekey(key) - context.use_certificate(cert) - self.assertRaises(Error, context.check_privatekey) - - - def test_check_privatekey_wrong_args(self): - """ - :py:obj:`Context.check_privatekey` raises :py:obj:`TypeError` if called - with other than no arguments. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.check_privatekey, object()) - - - def test_set_app_data_wrong_args(self): - """ - :py:obj:`Context.set_app_data` raises :py:obj:`TypeError` if called with other than - one argument. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_app_data) - self.assertRaises(TypeError, context.set_app_data, None, None) - - - def test_get_app_data_wrong_args(self): - """ - :py:obj:`Context.get_app_data` raises :py:obj:`TypeError` if called with any - arguments. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.get_app_data, None) - - - def test_app_data(self): - """ - :py:obj:`Context.set_app_data` stores an object for later retrieval using - :py:obj:`Context.get_app_data`. - """ - app_data = object() - context = Context(TLSv1_METHOD) - context.set_app_data(app_data) - self.assertIdentical(context.get_app_data(), app_data) - - - def test_set_options_wrong_args(self): - """ - :py:obj:`Context.set_options` raises :py:obj:`TypeError` if called with the wrong - number of arguments or a non-:py:obj:`int` argument. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_options) - self.assertRaises(TypeError, context.set_options, None) - self.assertRaises(TypeError, context.set_options, 1, None) - - - def test_set_options(self): - """ - :py:obj:`Context.set_options` returns the new options value. - """ - context = Context(TLSv1_METHOD) - options = context.set_options(OP_NO_SSLv2) - self.assertTrue(OP_NO_SSLv2 & options) - - - if not PY3: - def test_set_options_long(self): - """ - On Python 2 :py:obj:`Context.set_options` accepts values of type - :py:obj:`long` as well as :py:obj:`int`. - """ - context = Context(TLSv1_METHOD) - options = context.set_options(long(OP_NO_SSLv2)) - self.assertTrue(OP_NO_SSLv2 & options) - - - def test_set_mode_wrong_args(self): - """ - :py:obj:`Context.set`mode} raises :py:obj:`TypeError` if called with the wrong - number of arguments or a non-:py:obj:`int` argument. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_mode) - self.assertRaises(TypeError, context.set_mode, None) - self.assertRaises(TypeError, context.set_mode, 1, None) - - - if MODE_RELEASE_BUFFERS is not None: - def test_set_mode(self): - """ - :py:obj:`Context.set_mode` accepts a mode bitvector and returns the newly - set mode. - """ - context = Context(TLSv1_METHOD) - self.assertTrue( - MODE_RELEASE_BUFFERS & context.set_mode(MODE_RELEASE_BUFFERS)) - - if not PY3: - def test_set_mode_long(self): - """ - On Python 2 :py:obj:`Context.set_mode` accepts values of type - :py:obj:`long` as well as :py:obj:`int`. - """ - context = Context(TLSv1_METHOD) - mode = context.set_mode(long(MODE_RELEASE_BUFFERS)) - self.assertTrue(MODE_RELEASE_BUFFERS & mode) - else: - "MODE_RELEASE_BUFFERS unavailable - OpenSSL version may be too old" - - - def test_set_timeout_wrong_args(self): - """ - :py:obj:`Context.set_timeout` raises :py:obj:`TypeError` if called with the wrong - number of arguments or a non-:py:obj:`int` argument. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_timeout) - self.assertRaises(TypeError, context.set_timeout, None) - self.assertRaises(TypeError, context.set_timeout, 1, None) - - - def test_get_timeout_wrong_args(self): - """ - :py:obj:`Context.get_timeout` raises :py:obj:`TypeError` if called with any arguments. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.get_timeout, None) - - - def test_timeout(self): - """ - :py:obj:`Context.set_timeout` sets the session timeout for all connections - created using the context object. :py:obj:`Context.get_timeout` retrieves this - value. - """ - context = Context(TLSv1_METHOD) - context.set_timeout(1234) - self.assertEquals(context.get_timeout(), 1234) - - - if not PY3: - def test_timeout_long(self): - """ - On Python 2 :py:obj:`Context.set_timeout` accepts values of type - `long` as well as int. - """ - context = Context(TLSv1_METHOD) - context.set_timeout(long(1234)) - self.assertEquals(context.get_timeout(), 1234) - - - def test_set_verify_depth_wrong_args(self): - """ - :py:obj:`Context.set_verify_depth` raises :py:obj:`TypeError` if called with the wrong - number of arguments or a non-:py:obj:`int` argument. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_verify_depth) - self.assertRaises(TypeError, context.set_verify_depth, None) - self.assertRaises(TypeError, context.set_verify_depth, 1, None) - - - def test_get_verify_depth_wrong_args(self): - """ - :py:obj:`Context.get_verify_depth` raises :py:obj:`TypeError` if called with any arguments. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.get_verify_depth, None) - - - def test_verify_depth(self): - """ - :py:obj:`Context.set_verify_depth` sets the number of certificates in a chain - to follow before giving up. The value can be retrieved with - :py:obj:`Context.get_verify_depth`. - """ - context = Context(TLSv1_METHOD) - context.set_verify_depth(11) - self.assertEquals(context.get_verify_depth(), 11) - - - if not PY3: - def test_verify_depth_long(self): - """ - On Python 2 :py:obj:`Context.set_verify_depth` accepts values of - type `long` as well as int. - """ - context = Context(TLSv1_METHOD) - context.set_verify_depth(long(11)) - self.assertEquals(context.get_verify_depth(), 11) - - - def _write_encrypted_pem(self, passphrase): - """ - Write a new private key out to a new file, encrypted using the given - passphrase. Return the path to the new file. - """ - key = PKey() - key.generate_key(TYPE_RSA, 128) - pemFile = self.mktemp() - fObj = open(pemFile, 'w') - pem = dump_privatekey(FILETYPE_PEM, key, "blowfish", passphrase) - fObj.write(pem.decode('ascii')) - fObj.close() - return pemFile - - - def test_set_passwd_cb_wrong_args(self): - """ - :py:obj:`Context.set_passwd_cb` raises :py:obj:`TypeError` if called with the - wrong arguments or with a non-callable first argument. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_passwd_cb) - self.assertRaises(TypeError, context.set_passwd_cb, None) - self.assertRaises(TypeError, context.set_passwd_cb, lambda: None, None, None) - - - def test_set_passwd_cb(self): - """ - :py:obj:`Context.set_passwd_cb` accepts a callable which will be invoked when - a private key is loaded from an encrypted PEM. - """ - passphrase = b("foobar") - pemFile = self._write_encrypted_pem(passphrase) - calledWith = [] - def passphraseCallback(maxlen, verify, extra): - calledWith.append((maxlen, verify, extra)) - return passphrase - context = Context(TLSv1_METHOD) - context.set_passwd_cb(passphraseCallback) - context.use_privatekey_file(pemFile) - self.assertTrue(len(calledWith), 1) - self.assertTrue(isinstance(calledWith[0][0], int)) - self.assertTrue(isinstance(calledWith[0][1], int)) - self.assertEqual(calledWith[0][2], None) - - - def test_passwd_callback_exception(self): - """ - :py:obj:`Context.use_privatekey_file` propagates any exception raised by the - passphrase callback. - """ - pemFile = self._write_encrypted_pem(b("monkeys are nice")) - def passphraseCallback(maxlen, verify, extra): - raise RuntimeError("Sorry, I am a fail.") - - context = Context(TLSv1_METHOD) - context.set_passwd_cb(passphraseCallback) - self.assertRaises(RuntimeError, context.use_privatekey_file, pemFile) - - - def test_passwd_callback_false(self): - """ - :py:obj:`Context.use_privatekey_file` raises :py:obj:`OpenSSL.SSL.Error` if the - passphrase callback returns a false value. - """ - pemFile = self._write_encrypted_pem(b("monkeys are nice")) - def passphraseCallback(maxlen, verify, extra): - return b"" - - context = Context(TLSv1_METHOD) - context.set_passwd_cb(passphraseCallback) - self.assertRaises(Error, context.use_privatekey_file, pemFile) - - - def test_passwd_callback_non_string(self): - """ - :py:obj:`Context.use_privatekey_file` raises :py:obj:`OpenSSL.SSL.Error` if the - passphrase callback returns a true non-string value. - """ - pemFile = self._write_encrypted_pem(b("monkeys are nice")) - def passphraseCallback(maxlen, verify, extra): - return 10 - - context = Context(TLSv1_METHOD) - context.set_passwd_cb(passphraseCallback) - self.assertRaises(ValueError, context.use_privatekey_file, pemFile) - - - def test_passwd_callback_too_long(self): - """ - If the passphrase returned by the passphrase callback returns a string - longer than the indicated maximum length, it is truncated. - """ - # A priori knowledge! - passphrase = b("x") * 1024 - pemFile = self._write_encrypted_pem(passphrase) - def passphraseCallback(maxlen, verify, extra): - assert maxlen == 1024 - return passphrase + b("y") - - context = Context(TLSv1_METHOD) - context.set_passwd_cb(passphraseCallback) - # This shall succeed because the truncated result is the correct - # passphrase. - context.use_privatekey_file(pemFile) - - - def test_set_info_callback(self): - """ - :py:obj:`Context.set_info_callback` accepts a callable which will be invoked - when certain information about an SSL connection is available. - """ - (server, client) = socket_pair() - - clientSSL = Connection(Context(TLSv1_METHOD), client) - clientSSL.set_connect_state() - - called = [] - def info(conn, where, ret): - called.append((conn, where, ret)) - context = Context(TLSv1_METHOD) - context.set_info_callback(info) - context.use_certificate( - load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) - context.use_privatekey( - load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) - - serverSSL = Connection(context, server) - serverSSL.set_accept_state() - - handshake(clientSSL, serverSSL) - - # The callback must always be called with a Connection instance as the - # first argument. It would probably be better to split this into - # separate tests for client and server side info callbacks so we could - # assert it is called with the right Connection instance. It would - # also be good to assert *something* about `where` and `ret`. - notConnections = [ - conn for (conn, where, ret) in called - if not isinstance(conn, Connection)] - self.assertEqual( - [], notConnections, - "Some info callback arguments were not Connection instaces.") - - - def _load_verify_locations_test(self, *args): - """ - Create a client context which will verify the peer certificate and call - its :py:obj:`load_verify_locations` method with the given arguments. - Then connect it to a server and ensure that the handshake succeeds. - """ - (server, client) = socket_pair() - - clientContext = Context(TLSv1_METHOD) - clientContext.load_verify_locations(*args) - # Require that the server certificate verify properly or the - # connection will fail. - clientContext.set_verify( - VERIFY_PEER, - lambda conn, cert, errno, depth, preverify_ok: preverify_ok) - - clientSSL = Connection(clientContext, client) - clientSSL.set_connect_state() - - serverContext = Context(TLSv1_METHOD) - serverContext.use_certificate( - load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) - serverContext.use_privatekey( - load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) - - serverSSL = Connection(serverContext, server) - serverSSL.set_accept_state() - - # Without load_verify_locations above, the handshake - # will fail: - # Error: [('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', - # 'certificate verify failed')] - handshake(clientSSL, serverSSL) - - cert = clientSSL.get_peer_certificate() - self.assertEqual(cert.get_subject().CN, 'Testing Root CA') - - - def _load_verify_cafile(self, cafile): - """ - Verify that if path to a file containing a certificate is passed to - ``Context.load_verify_locations`` for the ``cafile`` parameter, that - certificate is used as a trust root for the purposes of verifying - connections created using that ``Context``. - """ - fObj = open(cafile, 'w') - fObj.write(cleartextCertificatePEM.decode('ascii')) - fObj.close() - - self._load_verify_locations_test(cafile) - - - def test_load_verify_bytes_cafile(self): - """ - :py:obj:`Context.load_verify_locations` accepts a file name as a - ``bytes`` instance and uses the certificates within for verification - purposes. - """ - cafile = self.mktemp() + NON_ASCII.encode(getfilesystemencoding()) - self._load_verify_cafile(cafile) - - - def test_load_verify_unicode_cafile(self): - """ - :py:obj:`Context.load_verify_locations` accepts a file name as a - ``unicode`` instance and uses the certificates within for verification - purposes. - """ - self._load_verify_cafile( - self.mktemp().decode(getfilesystemencoding()) + NON_ASCII - ) - - - def test_load_verify_invalid_file(self): - """ - :py:obj:`Context.load_verify_locations` raises :py:obj:`Error` when passed a - non-existent cafile. - """ - clientContext = Context(TLSv1_METHOD) - self.assertRaises( - Error, clientContext.load_verify_locations, self.mktemp()) - - - def _load_verify_directory_locations_capath(self, capath): - """ - Verify that if path to a directory containing certificate files is - passed to ``Context.load_verify_locations`` for the ``capath`` - parameter, those certificates are used as trust roots for the purposes - of verifying connections created using that ``Context``. - """ - makedirs(capath) - # Hash values computed manually with c_rehash to avoid depending on - # c_rehash in the test suite. One is from OpenSSL 0.9.8, the other - # from OpenSSL 1.0.0. - for name in [b'c7adac82.0', b'c3705638.0']: - cafile = join_bytes_or_unicode(capath, name) - with open(cafile, 'w') as fObj: - fObj.write(cleartextCertificatePEM.decode('ascii')) - - self._load_verify_locations_test(None, capath) - - - def test_load_verify_directory_bytes_capath(self): - """ - :py:obj:`Context.load_verify_locations` accepts a directory name as a - ``bytes`` instance and uses the certificates within for verification - purposes. - """ - self._load_verify_directory_locations_capath( - self.mktemp() + NON_ASCII.encode(getfilesystemencoding()) - ) - - - def test_load_verify_directory_unicode_capath(self): - """ - :py:obj:`Context.load_verify_locations` accepts a directory name as a - ``unicode`` instance and uses the certificates within for verification - purposes. - """ - self._load_verify_directory_locations_capath( - self.mktemp().decode(getfilesystemencoding()) + NON_ASCII - ) - - - def test_load_verify_locations_wrong_args(self): - """ - :py:obj:`Context.load_verify_locations` raises :py:obj:`TypeError` if called with - the wrong number of arguments or with non-:py:obj:`str` arguments. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.load_verify_locations) - self.assertRaises(TypeError, context.load_verify_locations, object()) - self.assertRaises(TypeError, context.load_verify_locations, object(), object()) - self.assertRaises(TypeError, context.load_verify_locations, None, None, None) - - - if platform == "win32": - "set_default_verify_paths appears not to work on Windows. " - "See LP#404343 and LP#404344." - else: - def test_set_default_verify_paths(self): - """ - :py:obj:`Context.set_default_verify_paths` causes the platform-specific CA - certificate locations to be used for verification purposes. - """ - # Testing this requires a server with a certificate signed by one of - # the CAs in the platform CA location. Getting one of those costs - # money. Fortunately (or unfortunately, depending on your - # perspective), it's easy to think of a public server on the - # internet which has such a certificate. Connecting to the network - # in a unit test is bad, but it's the only way I can think of to - # really test this. -exarkun - - # Arg, verisign.com doesn't speak anything newer than TLS 1.0 - context = Context(TLSv1_METHOD) - context.set_default_verify_paths() - context.set_verify( - VERIFY_PEER, - lambda conn, cert, errno, depth, preverify_ok: preverify_ok) - - client = socket() - client.connect(('verisign.com', 443)) - clientSSL = Connection(context, client) - clientSSL.set_connect_state() - clientSSL.do_handshake() - clientSSL.send(b"GET / HTTP/1.0\r\n\r\n") - self.assertTrue(clientSSL.recv(1024)) - - - def test_set_default_verify_paths_signature(self): - """ - :py:obj:`Context.set_default_verify_paths` takes no arguments and raises - :py:obj:`TypeError` if given any. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_default_verify_paths, None) - self.assertRaises(TypeError, context.set_default_verify_paths, 1) - self.assertRaises(TypeError, context.set_default_verify_paths, "") - - - def test_add_extra_chain_cert_invalid_cert(self): - """ - :py:obj:`Context.add_extra_chain_cert` raises :py:obj:`TypeError` if called with - other than one argument or if called with an object which is not an - instance of :py:obj:`X509`. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.add_extra_chain_cert) - self.assertRaises(TypeError, context.add_extra_chain_cert, object()) - self.assertRaises(TypeError, context.add_extra_chain_cert, object(), object()) - - - def _handshake_test(self, serverContext, clientContext): - """ - Verify that a client and server created with the given contexts can - successfully handshake and communicate. - """ - serverSocket, clientSocket = socket_pair() - - server = Connection(serverContext, serverSocket) - server.set_accept_state() - - client = Connection(clientContext, clientSocket) - client.set_connect_state() - - # Make them talk to each other. - # self._interactInMemory(client, server) - for i in range(3): - for s in [client, server]: - try: - s.do_handshake() - except WantReadError: - pass - - - def test_set_verify_callback_connection_argument(self): - """ - The first argument passed to the verify callback is the - :py:class:`Connection` instance for which verification is taking place. - """ - serverContext = Context(TLSv1_METHOD) - serverContext.use_privatekey( - load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) - serverContext.use_certificate( - load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) - serverConnection = Connection(serverContext, None) - - class VerifyCallback(object): - def callback(self, connection, *args): - self.connection = connection - return 1 - - verify = VerifyCallback() - clientContext = Context(TLSv1_METHOD) - clientContext.set_verify(VERIFY_PEER, verify.callback) - clientConnection = Connection(clientContext, None) - clientConnection.set_connect_state() - - self._handshakeInMemory(clientConnection, serverConnection) - - self.assertIdentical(verify.connection, clientConnection) - - - def test_set_verify_callback_exception(self): - """ - If the verify callback passed to :py:obj:`Context.set_verify` raises an - exception, verification fails and the exception is propagated to the - caller of :py:obj:`Connection.do_handshake`. - """ - serverContext = Context(TLSv1_METHOD) - serverContext.use_privatekey( - load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) - serverContext.use_certificate( - load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) - - clientContext = Context(TLSv1_METHOD) - def verify_callback(*args): - raise Exception("silly verify failure") - clientContext.set_verify(VERIFY_PEER, verify_callback) - - exc = self.assertRaises( - Exception, self._handshake_test, serverContext, clientContext) - self.assertEqual("silly verify failure", str(exc)) - - - def test_add_extra_chain_cert(self): - """ - :py:obj:`Context.add_extra_chain_cert` accepts an :py:obj:`X509` instance to add to - the certificate chain. - - See :py:obj:`_create_certificate_chain` for the details of the certificate - chain tested. - - The chain is tested by starting a server with scert and connecting - to it with a client which trusts cacert and requires verification to - succeed. - """ - chain = _create_certificate_chain() - [(cakey, cacert), (ikey, icert), (skey, scert)] = chain - - # Dump the CA certificate to a file because that's the only way to load - # it as a trusted CA in the client context. - for cert, name in [(cacert, 'ca.pem'), (icert, 'i.pem'), (scert, 's.pem')]: - fObj = open(name, 'w') - fObj.write(dump_certificate(FILETYPE_PEM, cert).decode('ascii')) - fObj.close() - - for key, name in [(cakey, 'ca.key'), (ikey, 'i.key'), (skey, 's.key')]: - fObj = open(name, 'w') - fObj.write(dump_privatekey(FILETYPE_PEM, key).decode('ascii')) - fObj.close() - - # Create the server context - serverContext = Context(TLSv1_METHOD) - serverContext.use_privatekey(skey) - serverContext.use_certificate(scert) - # The client already has cacert, we only need to give them icert. - serverContext.add_extra_chain_cert(icert) - - # Create the client - clientContext = Context(TLSv1_METHOD) - clientContext.set_verify( - VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) - clientContext.load_verify_locations(b"ca.pem") - - # Try it out. - self._handshake_test(serverContext, clientContext) - - - def _use_certificate_chain_file_test(self, certdir): - """ - Verify that :py:obj:`Context.use_certificate_chain_file` reads a - certificate chain from a specified file. - - The chain is tested by starting a server with scert and connecting to - it with a client which trusts cacert and requires verification to - succeed. - """ - chain = _create_certificate_chain() - [(cakey, cacert), (ikey, icert), (skey, scert)] = chain - - makedirs(certdir) - - chainFile = join_bytes_or_unicode(certdir, "chain.pem") - caFile = join_bytes_or_unicode(certdir, "ca.pem") - - # Write out the chain file. - with open(chainFile, 'wb') as fObj: - # Most specific to least general. - fObj.write(dump_certificate(FILETYPE_PEM, scert)) - fObj.write(dump_certificate(FILETYPE_PEM, icert)) - fObj.write(dump_certificate(FILETYPE_PEM, cacert)) - - with open(caFile, 'w') as fObj: - fObj.write(dump_certificate(FILETYPE_PEM, cacert).decode('ascii')) - - serverContext = Context(TLSv1_METHOD) - serverContext.use_certificate_chain_file(chainFile) - serverContext.use_privatekey(skey) - - clientContext = Context(TLSv1_METHOD) - clientContext.set_verify( - VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) - clientContext.load_verify_locations(caFile) - - self._handshake_test(serverContext, clientContext) - - - def test_use_certificate_chain_file_bytes(self): - """ - ``Context.use_certificate_chain_file`` accepts the name of a file (as - an instance of ``bytes``) to specify additional certificates to use to - construct and verify a trust chain. - """ - self._use_certificate_chain_file_test( - self.mktemp() + NON_ASCII.encode(getfilesystemencoding()) - ) - - - def test_use_certificate_chain_file_unicode(self): - """ - ``Context.use_certificate_chain_file`` accepts the name of a file (as - an instance of ``unicode``) to specify additional certificates to use - to construct and verify a trust chain. - """ - self._use_certificate_chain_file_test( - self.mktemp().decode(getfilesystemencoding()) + NON_ASCII - ) - - - def test_use_certificate_chain_file_wrong_args(self): - """ - :py:obj:`Context.use_certificate_chain_file` raises :py:obj:`TypeError` - if passed zero or more than one argument or when passed a non-byte - string single argument. It also raises :py:obj:`OpenSSL.SSL.Error` when - passed a bad chain file name (for example, the name of a file which does - not exist). - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.use_certificate_chain_file) - self.assertRaises(TypeError, context.use_certificate_chain_file, object()) - self.assertRaises(TypeError, context.use_certificate_chain_file, b"foo", object()) - - self.assertRaises(Error, context.use_certificate_chain_file, self.mktemp()) - - # XXX load_client_ca - # XXX set_session_id - - def test_get_verify_mode_wrong_args(self): - """ - :py:obj:`Context.get_verify_mode` raises :py:obj:`TypeError` if called with any - arguments. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.get_verify_mode, None) - - - def test_set_verify_mode(self): - """ - :py:obj:`Context.get_verify_mode` returns the verify mode flags previously - passed to :py:obj:`Context.set_verify`. - """ - context = Context(TLSv1_METHOD) - self.assertEquals(context.get_verify_mode(), 0) - context.set_verify( - VERIFY_PEER | VERIFY_CLIENT_ONCE, lambda *args: None) - self.assertEquals( - context.get_verify_mode(), VERIFY_PEER | VERIFY_CLIENT_ONCE) - - - if not PY3: - def test_set_verify_mode_long(self): - """ - On Python 2 :py:obj:`Context.set_verify_mode` accepts values of - type :py:obj:`long` as well as :py:obj:`int`. - """ - context = Context(TLSv1_METHOD) - self.assertEquals(context.get_verify_mode(), 0) - context.set_verify( - long(VERIFY_PEER | VERIFY_CLIENT_ONCE), lambda *args: None) - self.assertEquals( - context.get_verify_mode(), VERIFY_PEER | VERIFY_CLIENT_ONCE) - - - def test_load_tmp_dh_wrong_args(self): - """ - :py:obj:`Context.load_tmp_dh` raises :py:obj:`TypeError` if called with the wrong - number of arguments or with a non-:py:obj:`str` argument. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.load_tmp_dh) - self.assertRaises(TypeError, context.load_tmp_dh, "foo", None) - self.assertRaises(TypeError, context.load_tmp_dh, object()) - - - def test_load_tmp_dh_missing_file(self): - """ - :py:obj:`Context.load_tmp_dh` raises :py:obj:`OpenSSL.SSL.Error` if the specified file - does not exist. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(Error, context.load_tmp_dh, b"hello") - - - def _load_tmp_dh_test(self, dhfilename): - """ - Verify that calling ``Context.load_tmp_dh`` with the given filename - does not raise an exception. - """ - context = Context(TLSv1_METHOD) - with open(dhfilename, "w") as dhfile: - dhfile.write(dhparam) - - context.load_tmp_dh(dhfilename) - # XXX What should I assert here? -exarkun - - - def test_load_tmp_dh_bytes(self): - """ - :py:obj:`Context.load_tmp_dh` loads Diffie-Hellman parameters from the - specified file (given as ``bytes``). - """ - self._load_tmp_dh_test( - self.mktemp() + NON_ASCII.encode(getfilesystemencoding()), - ) - - - def test_load_tmp_dh_unicode(self): - """ - :py:obj:`Context.load_tmp_dh` loads Diffie-Hellman parameters from the - specified file (given as ``unicode``). - """ - self._load_tmp_dh_test( - self.mktemp().decode(getfilesystemencoding()) + NON_ASCII, - ) - - - def test_set_tmp_ecdh(self): - """ - :py:obj:`Context.set_tmp_ecdh` sets the elliptic curve for - Diffie-Hellman to the specified curve. - """ - context = Context(TLSv1_METHOD) - for curve in get_elliptic_curves(): - # The only easily "assertable" thing is that it does not raise an - # exception. - context.set_tmp_ecdh(curve) - - - def test_set_cipher_list_bytes(self): - """ - :py:obj:`Context.set_cipher_list` accepts a :py:obj:`bytes` naming the - ciphers which connections created with the context object will be able - to choose from. - """ - context = Context(TLSv1_METHOD) - context.set_cipher_list(b"hello world:EXP-RC4-MD5") - conn = Connection(context, None) - self.assertEquals(conn.get_cipher_list(), ["EXP-RC4-MD5"]) - - - def test_set_cipher_list_text(self): - """ - :py:obj:`Context.set_cipher_list` accepts a :py:obj:`unicode` naming - the ciphers which connections created with the context object will be - able to choose from. - """ - context = Context(TLSv1_METHOD) - context.set_cipher_list(u("hello world:EXP-RC4-MD5")) - conn = Connection(context, None) - self.assertEquals(conn.get_cipher_list(), ["EXP-RC4-MD5"]) - - - def test_set_cipher_list_wrong_args(self): - """ - :py:obj:`Context.set_cipher_list` raises :py:obj:`TypeError` when - passed zero arguments or more than one argument or when passed a - non-string single argument and raises :py:obj:`OpenSSL.SSL.Error` when - passed an incorrect cipher list string. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_cipher_list) - self.assertRaises(TypeError, context.set_cipher_list, object()) - self.assertRaises(TypeError, context.set_cipher_list, b"EXP-RC4-MD5", object()) - - self.assertRaises(Error, context.set_cipher_list, "imaginary-cipher") - - - def test_set_session_cache_mode_wrong_args(self): - """ - :py:obj:`Context.set_session_cache_mode` raises :py:obj:`TypeError` if - called with other than one integer argument. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_session_cache_mode) - self.assertRaises(TypeError, context.set_session_cache_mode, object()) - - - def test_get_session_cache_mode_wrong_args(self): - """ - :py:obj:`Context.get_session_cache_mode` raises :py:obj:`TypeError` if - called with any arguments. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.get_session_cache_mode, 1) - - - def test_session_cache_mode(self): - """ - :py:obj:`Context.set_session_cache_mode` specifies how sessions are - cached. The setting can be retrieved via - :py:obj:`Context.get_session_cache_mode`. - """ - context = Context(TLSv1_METHOD) - context.set_session_cache_mode(SESS_CACHE_OFF) - off = context.set_session_cache_mode(SESS_CACHE_BOTH) - self.assertEqual(SESS_CACHE_OFF, off) - self.assertEqual(SESS_CACHE_BOTH, context.get_session_cache_mode()) - - if not PY3: - def test_session_cache_mode_long(self): - """ - On Python 2 :py:obj:`Context.set_session_cache_mode` accepts values - of type :py:obj:`long` as well as :py:obj:`int`. - """ - context = Context(TLSv1_METHOD) - context.set_session_cache_mode(long(SESS_CACHE_BOTH)) - self.assertEqual( - SESS_CACHE_BOTH, context.get_session_cache_mode()) - - - def test_get_cert_store(self): - """ - :py:obj:`Context.get_cert_store` returns a :py:obj:`X509Store` instance. - """ - context = Context(TLSv1_METHOD) - store = context.get_cert_store() - self.assertIsInstance(store, X509Store) - - - -class ServerNameCallbackTests(TestCase, _LoopbackMixin): - """ - Tests for :py:obj:`Context.set_tlsext_servername_callback` and its interaction with - :py:obj:`Connection`. - """ - def test_wrong_args(self): - """ - :py:obj:`Context.set_tlsext_servername_callback` raises :py:obj:`TypeError` if called - with other than one argument. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_tlsext_servername_callback) - self.assertRaises( - TypeError, context.set_tlsext_servername_callback, 1, 2) - - - def test_old_callback_forgotten(self): - """ - If :py:obj:`Context.set_tlsext_servername_callback` is used to specify a new - callback, the one it replaces is dereferenced. - """ - def callback(connection): - pass - - def replacement(connection): - pass - - context = Context(TLSv1_METHOD) - context.set_tlsext_servername_callback(callback) - - tracker = ref(callback) - del callback - - context.set_tlsext_servername_callback(replacement) - - # One run of the garbage collector happens to work on CPython. PyPy - # doesn't collect the underlying object until a second run for whatever - # reason. That's fine, it still demonstrates our code has properly - # dropped the reference. - collect() - collect() - - callback = tracker() - if callback is not None: - referrers = get_referrers(callback) - if len(referrers) > 1: - self.fail("Some references remain: %r" % (referrers,)) - - - def test_no_servername(self): - """ - When a client specifies no server name, the callback passed to - :py:obj:`Context.set_tlsext_servername_callback` is invoked and the result of - :py:obj:`Connection.get_servername` is :py:obj:`None`. - """ - args = [] - def servername(conn): - args.append((conn, conn.get_servername())) - context = Context(TLSv1_METHOD) - context.set_tlsext_servername_callback(servername) - - # Lose our reference to it. The Context is responsible for keeping it - # alive now. - del servername - collect() - - # Necessary to actually accept the connection - context.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) - context.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) - - # Do a little connection to trigger the logic - server = Connection(context, None) - server.set_accept_state() - - client = Connection(Context(TLSv1_METHOD), None) - client.set_connect_state() - - self._interactInMemory(server, client) - - self.assertEqual([(server, None)], args) - - - def test_servername(self): - """ - When a client specifies a server name in its hello message, the callback - passed to :py:obj:`Contexts.set_tlsext_servername_callback` is invoked and the - result of :py:obj:`Connection.get_servername` is that server name. - """ - args = [] - def servername(conn): - args.append((conn, conn.get_servername())) - context = Context(TLSv1_METHOD) - context.set_tlsext_servername_callback(servername) - - # Necessary to actually accept the connection - context.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) - context.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) - - # Do a little connection to trigger the logic - server = Connection(context, None) - server.set_accept_state() - - client = Connection(Context(TLSv1_METHOD), None) - client.set_connect_state() - client.set_tlsext_host_name(b("foo1.example.com")) - - self._interactInMemory(server, client) - - self.assertEqual([(server, b("foo1.example.com"))], args) - - -class NextProtoNegotiationTests(TestCase, _LoopbackMixin): - """ - Test for Next Protocol Negotiation in PyOpenSSL. - """ - if _lib.Cryptography_HAS_NEXTPROTONEG: - def test_npn_success(self): - """ - Tests that clients and servers that agree on the negotiated next - protocol can correct establish a connection, and that the agreed - protocol is reported by the connections. - """ - advertise_args = [] - select_args = [] - def advertise(conn): - advertise_args.append((conn,)) - return [b'http/1.1', b'spdy/2'] - def select(conn, options): - select_args.append((conn, options)) - return b'spdy/2' - - server_context = Context(TLSv1_METHOD) - server_context.set_npn_advertise_callback(advertise) - - client_context = Context(TLSv1_METHOD) - client_context.set_npn_select_callback(select) - - # Necessary to actually accept the connection - server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) - server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) - - # Do a little connection to trigger the logic - server = Connection(server_context, None) - server.set_accept_state() - - client = Connection(client_context, None) - client.set_connect_state() - - self._interactInMemory(server, client) - - self.assertEqual([(server,)], advertise_args) - self.assertEqual([(client, [b'http/1.1', b'spdy/2'])], select_args) - - self.assertEqual(server.get_next_proto_negotiated(), b'spdy/2') - self.assertEqual(client.get_next_proto_negotiated(), b'spdy/2') - - - def test_npn_client_fail(self): - """ - Tests that when clients and servers cannot agree on what protocol - to use next that the TLS connection does not get established. - """ - advertise_args = [] - select_args = [] - def advertise(conn): - advertise_args.append((conn,)) - return [b'http/1.1', b'spdy/2'] - def select(conn, options): - select_args.append((conn, options)) - return b'' - - server_context = Context(TLSv1_METHOD) - server_context.set_npn_advertise_callback(advertise) - - client_context = Context(TLSv1_METHOD) - client_context.set_npn_select_callback(select) - - # Necessary to actually accept the connection - server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) - server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) - - # Do a little connection to trigger the logic - server = Connection(server_context, None) - server.set_accept_state() - - client = Connection(client_context, None) - client.set_connect_state() - - # If the client doesn't return anything, the connection will fail. - self.assertRaises(Error, self._interactInMemory, server, client) - - self.assertEqual([(server,)], advertise_args) - self.assertEqual([(client, [b'http/1.1', b'spdy/2'])], select_args) - - - def test_npn_select_error(self): - """ - Test that we can handle exceptions in the select callback. If - select fails it should be fatal to the connection. - """ - advertise_args = [] - def advertise(conn): - advertise_args.append((conn,)) - return [b'http/1.1', b'spdy/2'] - def select(conn, options): - raise TypeError - - server_context = Context(TLSv1_METHOD) - server_context.set_npn_advertise_callback(advertise) - - client_context = Context(TLSv1_METHOD) - client_context.set_npn_select_callback(select) - - # Necessary to actually accept the connection - server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) - server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) - - # Do a little connection to trigger the logic - server = Connection(server_context, None) - server.set_accept_state() - - client = Connection(client_context, None) - client.set_connect_state() - - # If the callback throws an exception it should be raised here. - self.assertRaises( - TypeError, self._interactInMemory, server, client - ) - self.assertEqual([(server,)], advertise_args) - - - def test_npn_advertise_error(self): - """ - Test that we can handle exceptions in the advertise callback. If - advertise fails no NPN is advertised to the client. - """ - select_args = [] - def advertise(conn): - raise TypeError - def select(conn, options): - select_args.append((conn, options)) - return b'' - - server_context = Context(TLSv1_METHOD) - server_context.set_npn_advertise_callback(advertise) - - client_context = Context(TLSv1_METHOD) - client_context.set_npn_select_callback(select) - - # Necessary to actually accept the connection - server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) - server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) - - # Do a little connection to trigger the logic - server = Connection(server_context, None) - server.set_accept_state() - - client = Connection(client_context, None) - client.set_connect_state() - - # If the client doesn't return anything, the connection will fail. - self.assertRaises( - TypeError, self._interactInMemory, server, client - ) - self.assertEqual([], select_args) - - else: - # No NPN. - def test_npn_not_implemented(self): - # Test the context methods first. - context = Context(TLSv1_METHOD) - fail_methods = [ - context.set_npn_advertise_callback, - context.set_npn_select_callback, - ] - for method in fail_methods: - self.assertRaises( - NotImplementedError, method, None - ) - - # Now test a connection. - conn = Connection(context) - fail_methods = [ - conn.get_next_proto_negotiated, - ] - for method in fail_methods: - self.assertRaises(NotImplementedError, method) - - - -class ApplicationLayerProtoNegotiationTests(TestCase, _LoopbackMixin): - """ - Tests for ALPN in PyOpenSSL. - """ - # Skip tests on versions that don't support ALPN. - if _lib.Cryptography_HAS_ALPN: - - def test_alpn_success(self): - """ - Clients and servers that agree on the negotiated ALPN protocol can - correct establish a connection, and the agreed protocol is reported - by the connections. - """ - select_args = [] - def select(conn, options): - select_args.append((conn, options)) - return b'spdy/2' - - client_context = Context(TLSv1_METHOD) - client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) - - server_context = Context(TLSv1_METHOD) - server_context.set_alpn_select_callback(select) - - # Necessary to actually accept the connection - server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) - server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) - - # Do a little connection to trigger the logic - server = Connection(server_context, None) - server.set_accept_state() - - client = Connection(client_context, None) - client.set_connect_state() - - self._interactInMemory(server, client) - - self.assertEqual([(server, [b'http/1.1', b'spdy/2'])], select_args) - - self.assertEqual(server.get_alpn_proto_negotiated(), b'spdy/2') - self.assertEqual(client.get_alpn_proto_negotiated(), b'spdy/2') - - - def test_alpn_set_on_connection(self): - """ - The same as test_alpn_success, but setting the ALPN protocols on - the connection rather than the context. - """ - select_args = [] - def select(conn, options): - select_args.append((conn, options)) - return b'spdy/2' - - # Setup the client context but don't set any ALPN protocols. - client_context = Context(TLSv1_METHOD) - - server_context = Context(TLSv1_METHOD) - server_context.set_alpn_select_callback(select) - - # Necessary to actually accept the connection - server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) - server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) - - # Do a little connection to trigger the logic - server = Connection(server_context, None) - server.set_accept_state() - - # Set the ALPN protocols on the client connection. - client = Connection(client_context, None) - client.set_alpn_protos([b'http/1.1', b'spdy/2']) - client.set_connect_state() - - self._interactInMemory(server, client) - - self.assertEqual([(server, [b'http/1.1', b'spdy/2'])], select_args) - - self.assertEqual(server.get_alpn_proto_negotiated(), b'spdy/2') - self.assertEqual(client.get_alpn_proto_negotiated(), b'spdy/2') - - - def test_alpn_server_fail(self): - """ - When clients and servers cannot agree on what protocol to use next - the TLS connection does not get established. - """ - select_args = [] - def select(conn, options): - select_args.append((conn, options)) - return b'' - - client_context = Context(TLSv1_METHOD) - client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) - - server_context = Context(TLSv1_METHOD) - server_context.set_alpn_select_callback(select) - - # Necessary to actually accept the connection - server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) - server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) - - # Do a little connection to trigger the logic - server = Connection(server_context, None) - server.set_accept_state() - - client = Connection(client_context, None) - client.set_connect_state() - - # If the client doesn't return anything, the connection will fail. - self.assertRaises(Error, self._interactInMemory, server, client) - - self.assertEqual([(server, [b'http/1.1', b'spdy/2'])], select_args) - - - def test_alpn_no_server(self): - """ - When clients and servers cannot agree on what protocol to use next - because the server doesn't offer ALPN, no protocol is negotiated. - """ - client_context = Context(TLSv1_METHOD) - client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) - - server_context = Context(TLSv1_METHOD) - - # Necessary to actually accept the connection - server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) - server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) - - # Do a little connection to trigger the logic - server = Connection(server_context, None) - server.set_accept_state() - - client = Connection(client_context, None) - client.set_connect_state() - - # Do the dance. - self._interactInMemory(server, client) - - self.assertEqual(client.get_alpn_proto_negotiated(), b'') - - - def test_alpn_callback_exception(self): - """ - We can handle exceptions in the ALPN select callback. - """ - select_args = [] - def select(conn, options): - select_args.append((conn, options)) - raise TypeError() - - client_context = Context(TLSv1_METHOD) - client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) - - server_context = Context(TLSv1_METHOD) - server_context.set_alpn_select_callback(select) - - # Necessary to actually accept the connection - server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) - server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) - - # Do a little connection to trigger the logic - server = Connection(server_context, None) - server.set_accept_state() - - client = Connection(client_context, None) - client.set_connect_state() - - self.assertRaises( - TypeError, self._interactInMemory, server, client - ) - self.assertEqual([(server, [b'http/1.1', b'spdy/2'])], select_args) - - else: - # No ALPN. - def test_alpn_not_implemented(self): - """ - If ALPN is not in OpenSSL, we should raise NotImplementedError. - """ - # Test the context methods first. - context = Context(TLSv1_METHOD) - self.assertRaises( - NotImplementedError, context.set_alpn_protos, None - ) - self.assertRaises( - NotImplementedError, context.set_alpn_select_callback, None - ) - - # Now test a connection. - conn = Connection(context) - self.assertRaises( - NotImplementedError, context.set_alpn_protos, None - ) - - - -class SessionTests(TestCase): - """ - Unit tests for :py:obj:`OpenSSL.SSL.Session`. - """ - def test_construction(self): - """ - :py:class:`Session` can be constructed with no arguments, creating a new - instance of that type. - """ - new_session = Session() - self.assertTrue(isinstance(new_session, Session)) - - - def test_construction_wrong_args(self): - """ - If any arguments are passed to :py:class:`Session`, :py:obj:`TypeError` - is raised. - """ - self.assertRaises(TypeError, Session, 123) - self.assertRaises(TypeError, Session, "hello") - self.assertRaises(TypeError, Session, object()) - - - -class ConnectionTests(TestCase, _LoopbackMixin): - """ - Unit tests for :py:obj:`OpenSSL.SSL.Connection`. - """ - # XXX get_peer_certificate -> None - # XXX sock_shutdown - # XXX master_key -> TypeError - # XXX server_random -> TypeError - # XXX state_string - # XXX connect -> TypeError - # XXX connect_ex -> TypeError - # XXX set_connect_state -> TypeError - # XXX set_accept_state -> TypeError - # XXX renegotiate_pending - # XXX do_handshake -> TypeError - # XXX bio_read -> TypeError - # XXX recv -> TypeError - # XXX send -> TypeError - # XXX bio_write -> TypeError - - def test_type(self): - """ - :py:obj:`Connection` and :py:obj:`ConnectionType` refer to the same type object and - can be used to create instances of that type. - """ - self.assertIdentical(Connection, ConnectionType) - ctx = Context(TLSv1_METHOD) - self.assertConsistentType(Connection, 'Connection', ctx, None) - - - def test_get_context(self): - """ - :py:obj:`Connection.get_context` returns the :py:obj:`Context` instance used to - construct the :py:obj:`Connection` instance. - """ - context = Context(TLSv1_METHOD) - connection = Connection(context, None) - self.assertIdentical(connection.get_context(), context) - - - def test_get_context_wrong_args(self): - """ - :py:obj:`Connection.get_context` raises :py:obj:`TypeError` if called with any - arguments. - """ - connection = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(TypeError, connection.get_context, None) - - - def test_set_context_wrong_args(self): - """ - :py:obj:`Connection.set_context` raises :py:obj:`TypeError` if called with a - non-:py:obj:`Context` instance argument or with any number of arguments other - than 1. - """ - ctx = Context(TLSv1_METHOD) - connection = Connection(ctx, None) - self.assertRaises(TypeError, connection.set_context) - self.assertRaises(TypeError, connection.set_context, object()) - self.assertRaises(TypeError, connection.set_context, "hello") - self.assertRaises(TypeError, connection.set_context, 1) - self.assertRaises(TypeError, connection.set_context, 1, 2) - self.assertRaises( - TypeError, connection.set_context, Context(TLSv1_METHOD), 2) - self.assertIdentical(ctx, connection.get_context()) - - - def test_set_context(self): - """ - :py:obj:`Connection.set_context` specifies a new :py:obj:`Context` instance to be used - for the connection. - """ - original = Context(SSLv23_METHOD) - replacement = Context(TLSv1_METHOD) - connection = Connection(original, None) - connection.set_context(replacement) - self.assertIdentical(replacement, connection.get_context()) - # Lose our references to the contexts, just in case the Connection isn't - # properly managing its own contributions to their reference counts. - del original, replacement - collect() - - - def test_set_tlsext_host_name_wrong_args(self): - """ - If :py:obj:`Connection.set_tlsext_host_name` is called with a non-byte string - argument or a byte string with an embedded NUL or other than one - argument, :py:obj:`TypeError` is raised. - """ - conn = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(TypeError, conn.set_tlsext_host_name) - self.assertRaises(TypeError, conn.set_tlsext_host_name, object()) - self.assertRaises(TypeError, conn.set_tlsext_host_name, 123, 456) - self.assertRaises( - TypeError, conn.set_tlsext_host_name, b("with\0null")) - - if PY3: - # On Python 3.x, don't accidentally implicitly convert from text. - self.assertRaises( - TypeError, - conn.set_tlsext_host_name, b("example.com").decode("ascii")) - - - def test_get_servername_wrong_args(self): - """ - :py:obj:`Connection.get_servername` raises :py:obj:`TypeError` if called with any - arguments. - """ - connection = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(TypeError, connection.get_servername, object()) - self.assertRaises(TypeError, connection.get_servername, 1) - self.assertRaises(TypeError, connection.get_servername, "hello") - - - def test_pending(self): - """ - :py:obj:`Connection.pending` returns the number of bytes available for - immediate read. - """ - connection = Connection(Context(TLSv1_METHOD), None) - self.assertEquals(connection.pending(), 0) - - - def test_pending_wrong_args(self): - """ - :py:obj:`Connection.pending` raises :py:obj:`TypeError` if called with any arguments. - """ - connection = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(TypeError, connection.pending, None) - - - def test_connect_wrong_args(self): - """ - :py:obj:`Connection.connect` raises :py:obj:`TypeError` if called with a non-address - argument or with the wrong number of arguments. - """ - connection = Connection(Context(TLSv1_METHOD), socket()) - self.assertRaises(TypeError, connection.connect, None) - self.assertRaises(TypeError, connection.connect) - self.assertRaises(TypeError, connection.connect, ("127.0.0.1", 1), None) - - - def test_connect_refused(self): - """ - :py:obj:`Connection.connect` raises :py:obj:`socket.error` if the underlying socket - connect method raises it. - """ - client = socket() - context = Context(TLSv1_METHOD) - clientSSL = Connection(context, client) - exc = self.assertRaises(error, clientSSL.connect, ("127.0.0.1", 1)) - self.assertEquals(exc.args[0], ECONNREFUSED) - - - def test_connect(self): - """ - :py:obj:`Connection.connect` establishes a connection to the specified address. - """ - port = socket() - port.bind(('', 0)) - port.listen(3) - - clientSSL = Connection(Context(TLSv1_METHOD), socket()) - clientSSL.connect(('127.0.0.1', port.getsockname()[1])) - # XXX An assertion? Or something? - - - if platform == "darwin": - "connect_ex sometimes causes a kernel panic on OS X 10.6.4" - else: - def test_connect_ex(self): - """ - If there is a connection error, :py:obj:`Connection.connect_ex` returns the - errno instead of raising an exception. - """ - port = socket() - port.bind(('', 0)) - port.listen(3) - - clientSSL = Connection(Context(TLSv1_METHOD), socket()) - clientSSL.setblocking(False) - result = clientSSL.connect_ex(port.getsockname()) - expected = (EINPROGRESS, EWOULDBLOCK) - self.assertTrue( - result in expected, "%r not in %r" % (result, expected)) - - - def test_accept_wrong_args(self): - """ - :py:obj:`Connection.accept` raises :py:obj:`TypeError` if called with any arguments. - """ - connection = Connection(Context(TLSv1_METHOD), socket()) - self.assertRaises(TypeError, connection.accept, None) - - - def test_accept(self): - """ - :py:obj:`Connection.accept` accepts a pending connection attempt and returns a - tuple of a new :py:obj:`Connection` (the accepted client) and the address the - connection originated from. - """ - ctx = Context(TLSv1_METHOD) - ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) - ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) - port = socket() - portSSL = Connection(ctx, port) - portSSL.bind(('', 0)) - portSSL.listen(3) - - clientSSL = Connection(Context(TLSv1_METHOD), socket()) - - # Calling portSSL.getsockname() here to get the server IP address sounds - # great, but frequently fails on Windows. - clientSSL.connect(('127.0.0.1', portSSL.getsockname()[1])) - - serverSSL, address = portSSL.accept() - - self.assertTrue(isinstance(serverSSL, Connection)) - self.assertIdentical(serverSSL.get_context(), ctx) - self.assertEquals(address, clientSSL.getsockname()) - - - def test_shutdown_wrong_args(self): - """ - :py:obj:`Connection.shutdown` raises :py:obj:`TypeError` if called with the wrong - number of arguments or with arguments other than integers. - """ - connection = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(TypeError, connection.shutdown, None) - self.assertRaises(TypeError, connection.get_shutdown, None) - self.assertRaises(TypeError, connection.set_shutdown) - self.assertRaises(TypeError, connection.set_shutdown, None) - self.assertRaises(TypeError, connection.set_shutdown, 0, 1) - - - def test_shutdown(self): - """ - :py:obj:`Connection.shutdown` performs an SSL-level connection shutdown. - """ - server, client = self._loopback() - self.assertFalse(server.shutdown()) - self.assertEquals(server.get_shutdown(), SENT_SHUTDOWN) - self.assertRaises(ZeroReturnError, client.recv, 1024) - self.assertEquals(client.get_shutdown(), RECEIVED_SHUTDOWN) - client.shutdown() - self.assertEquals(client.get_shutdown(), SENT_SHUTDOWN|RECEIVED_SHUTDOWN) - self.assertRaises(ZeroReturnError, server.recv, 1024) - self.assertEquals(server.get_shutdown(), SENT_SHUTDOWN|RECEIVED_SHUTDOWN) - - - def test_shutdown_closed(self): - """ - If the underlying socket is closed, :py:obj:`Connection.shutdown` propagates the - write error from the low level write call. - """ - server, client = self._loopback() - server.sock_shutdown(2) - exc = self.assertRaises(SysCallError, server.shutdown) - if platform == "win32": - self.assertEqual(exc.args[0], ESHUTDOWN) - else: - self.assertEqual(exc.args[0], EPIPE) - - - def test_shutdown_truncated(self): - """ - If the underlying connection is truncated, :obj:`Connection.shutdown` - raises an :obj:`Error`. - """ - server_ctx = Context(TLSv1_METHOD) - client_ctx = Context(TLSv1_METHOD) - server_ctx.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) - server_ctx.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) - server = Connection(server_ctx, None) - client = Connection(client_ctx, None) - self._handshakeInMemory(client, server) - self.assertEqual(server.shutdown(), False) - self.assertRaises(WantReadError, server.shutdown) - server.bio_shutdown() - self.assertRaises(Error, server.shutdown) - - - def test_set_shutdown(self): - """ - :py:obj:`Connection.set_shutdown` sets the state of the SSL connection shutdown - process. - """ - connection = Connection(Context(TLSv1_METHOD), socket()) - connection.set_shutdown(RECEIVED_SHUTDOWN) - self.assertEquals(connection.get_shutdown(), RECEIVED_SHUTDOWN) - - - if not PY3: - def test_set_shutdown_long(self): - """ - On Python 2 :py:obj:`Connection.set_shutdown` accepts an argument - of type :py:obj:`long` as well as :py:obj:`int`. - """ - connection = Connection(Context(TLSv1_METHOD), socket()) - connection.set_shutdown(long(RECEIVED_SHUTDOWN)) - self.assertEquals(connection.get_shutdown(), RECEIVED_SHUTDOWN) - - - def test_app_data_wrong_args(self): - """ - :py:obj:`Connection.set_app_data` raises :py:obj:`TypeError` if called with other than - one argument. :py:obj:`Connection.get_app_data` raises :py:obj:`TypeError` if called - with any arguments. - """ - conn = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(TypeError, conn.get_app_data, None) - self.assertRaises(TypeError, conn.set_app_data) - self.assertRaises(TypeError, conn.set_app_data, None, None) - - - def test_app_data(self): - """ - Any object can be set as app data by passing it to - :py:obj:`Connection.set_app_data` and later retrieved with - :py:obj:`Connection.get_app_data`. - """ - conn = Connection(Context(TLSv1_METHOD), None) - app_data = object() - conn.set_app_data(app_data) - self.assertIdentical(conn.get_app_data(), app_data) - - - def test_makefile(self): - """ - :py:obj:`Connection.makefile` is not implemented and calling that method raises - :py:obj:`NotImplementedError`. - """ - conn = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(NotImplementedError, conn.makefile) - - - def test_get_peer_cert_chain_wrong_args(self): - """ - :py:obj:`Connection.get_peer_cert_chain` raises :py:obj:`TypeError` if called with any - arguments. - """ - conn = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(TypeError, conn.get_peer_cert_chain, 1) - self.assertRaises(TypeError, conn.get_peer_cert_chain, "foo") - self.assertRaises(TypeError, conn.get_peer_cert_chain, object()) - self.assertRaises(TypeError, conn.get_peer_cert_chain, []) - - - def test_get_peer_cert_chain(self): - """ - :py:obj:`Connection.get_peer_cert_chain` returns a list of certificates which - the connected server returned for the certification verification. - """ - chain = _create_certificate_chain() - [(cakey, cacert), (ikey, icert), (skey, scert)] = chain - - serverContext = Context(TLSv1_METHOD) - serverContext.use_privatekey(skey) - serverContext.use_certificate(scert) - serverContext.add_extra_chain_cert(icert) - serverContext.add_extra_chain_cert(cacert) - server = Connection(serverContext, None) - server.set_accept_state() - - # Create the client - clientContext = Context(TLSv1_METHOD) - clientContext.set_verify(VERIFY_NONE, verify_cb) - client = Connection(clientContext, None) - client.set_connect_state() - - self._interactInMemory(client, server) - - chain = client.get_peer_cert_chain() - self.assertEqual(len(chain), 3) - self.assertEqual( - "Server Certificate", chain[0].get_subject().CN) - self.assertEqual( - "Intermediate Certificate", chain[1].get_subject().CN) - self.assertEqual( - "Authority Certificate", chain[2].get_subject().CN) - - - def test_get_peer_cert_chain_none(self): - """ - :py:obj:`Connection.get_peer_cert_chain` returns :py:obj:`None` if the peer sends no - certificate chain. - """ - ctx = Context(TLSv1_METHOD) - ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) - ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) - server = Connection(ctx, None) - server.set_accept_state() - client = Connection(Context(TLSv1_METHOD), None) - client.set_connect_state() - self._interactInMemory(client, server) - self.assertIdentical(None, server.get_peer_cert_chain()) - - - def test_get_session_wrong_args(self): - """ - :py:obj:`Connection.get_session` raises :py:obj:`TypeError` if called - with any arguments. - """ - ctx = Context(TLSv1_METHOD) - server = Connection(ctx, None) - self.assertRaises(TypeError, server.get_session, 123) - self.assertRaises(TypeError, server.get_session, "hello") - self.assertRaises(TypeError, server.get_session, object()) - - - def test_get_session_unconnected(self): - """ - :py:obj:`Connection.get_session` returns :py:obj:`None` when used with - an object which has not been connected. - """ - ctx = Context(TLSv1_METHOD) - server = Connection(ctx, None) - session = server.get_session() - self.assertIdentical(None, session) - - - def test_server_get_session(self): - """ - On the server side of a connection, :py:obj:`Connection.get_session` - returns a :py:class:`Session` instance representing the SSL session for - that connection. - """ - server, client = self._loopback() - session = server.get_session() - self.assertIsInstance(session, Session) - - - def test_client_get_session(self): - """ - On the client side of a connection, :py:obj:`Connection.get_session` - returns a :py:class:`Session` instance representing the SSL session for - that connection. - """ - server, client = self._loopback() - session = client.get_session() - self.assertIsInstance(session, Session) - - - def test_set_session_wrong_args(self): - """ - If called with an object that is not an instance of :py:class:`Session`, - or with other than one argument, :py:obj:`Connection.set_session` raises - :py:obj:`TypeError`. - """ - ctx = Context(TLSv1_METHOD) - connection = Connection(ctx, None) - self.assertRaises(TypeError, connection.set_session) - self.assertRaises(TypeError, connection.set_session, 123) - self.assertRaises(TypeError, connection.set_session, "hello") - self.assertRaises(TypeError, connection.set_session, object()) - self.assertRaises( - TypeError, connection.set_session, Session(), Session()) - - - def test_client_set_session(self): - """ - :py:obj:`Connection.set_session`, when used prior to a connection being - established, accepts a :py:class:`Session` instance and causes an - attempt to re-use the session it represents when the SSL handshake is - performed. - """ - key = load_privatekey(FILETYPE_PEM, server_key_pem) - cert = load_certificate(FILETYPE_PEM, server_cert_pem) - ctx = Context(TLSv1_METHOD) - ctx.use_privatekey(key) - ctx.use_certificate(cert) - ctx.set_session_id("unity-test") - - def makeServer(socket): - server = Connection(ctx, socket) - server.set_accept_state() - return server - - originalServer, originalClient = self._loopback( - serverFactory=makeServer) - originalSession = originalClient.get_session() - - def makeClient(socket): - client = self._loopbackClientFactory(socket) - client.set_session(originalSession) - return client - resumedServer, resumedClient = self._loopback( - serverFactory=makeServer, - clientFactory=makeClient) - - # This is a proxy: in general, we have no access to any unique - # identifier for the session (new enough versions of OpenSSL expose a - # hash which could be usable, but "new enough" is very, very new). - # Instead, exploit the fact that the master key is re-used if the - # session is re-used. As long as the master key for the two connections - # is the same, the session was re-used! - self.assertEqual( - originalServer.master_key(), resumedServer.master_key()) - - - def test_set_session_wrong_method(self): - """ - If :py:obj:`Connection.set_session` is passed a :py:class:`Session` - instance associated with a context using a different SSL method than the - :py:obj:`Connection` is using, a :py:class:`OpenSSL.SSL.Error` is - raised. - """ - key = load_privatekey(FILETYPE_PEM, server_key_pem) - cert = load_certificate(FILETYPE_PEM, server_cert_pem) - ctx = Context(TLSv1_METHOD) - ctx.use_privatekey(key) - ctx.use_certificate(cert) - ctx.set_session_id("unity-test") - - def makeServer(socket): - server = Connection(ctx, socket) - server.set_accept_state() - return server - - originalServer, originalClient = self._loopback( - serverFactory=makeServer) - originalSession = originalClient.get_session() - - def makeClient(socket): - # Intentionally use a different, incompatible method here. - client = Connection(Context(SSLv3_METHOD), socket) - client.set_connect_state() - client.set_session(originalSession) - return client - - self.assertRaises( - Error, - self._loopback, clientFactory=makeClient, serverFactory=makeServer) - - - def test_wantWriteError(self): - """ - :py:obj:`Connection` methods which generate output raise - :py:obj:`OpenSSL.SSL.WantWriteError` if writing to the connection's BIO - fail indicating a should-write state. - """ - client_socket, server_socket = socket_pair() - # Fill up the client's send buffer so Connection won't be able to write - # anything. Only write a single byte at a time so we can be sure we - # completely fill the buffer. Even though the socket API is allowed to - # signal a short write via its return value it seems this doesn't - # always happen on all platforms (FreeBSD and OS X particular) for the - # very last bit of available buffer space. - msg = b"x" - for i in range(1024 * 1024 * 4): - try: - client_socket.send(msg) - except error as e: - if e.errno == EWOULDBLOCK: - break - raise - else: - self.fail( - "Failed to fill socket buffer, cannot test BIO want write") - - ctx = Context(TLSv1_METHOD) - conn = Connection(ctx, client_socket) - # Client's speak first, so make it an SSL client - conn.set_connect_state() - self.assertRaises(WantWriteError, conn.do_handshake) - - # XXX want_read - - def test_get_finished_before_connect(self): - """ - :py:obj:`Connection.get_finished` returns :py:obj:`None` before TLS - handshake is completed. - """ - ctx = Context(TLSv1_METHOD) - connection = Connection(ctx, None) - self.assertEqual(connection.get_finished(), None) - - - def test_get_peer_finished_before_connect(self): - """ - :py:obj:`Connection.get_peer_finished` returns :py:obj:`None` before - TLS handshake is completed. - """ - ctx = Context(TLSv1_METHOD) - connection = Connection(ctx, None) - self.assertEqual(connection.get_peer_finished(), None) - - - def test_get_finished(self): - """ - :py:obj:`Connection.get_finished` method returns the TLS Finished - message send from client, or server. Finished messages are send during - TLS handshake. - """ - - server, client = self._loopback() - - self.assertNotEqual(server.get_finished(), None) - self.assertTrue(len(server.get_finished()) > 0) - - - def test_get_peer_finished(self): - """ - :py:obj:`Connection.get_peer_finished` method returns the TLS Finished - message received from client, or server. Finished messages are send - during TLS handshake. - """ - server, client = self._loopback() - - self.assertNotEqual(server.get_peer_finished(), None) - self.assertTrue(len(server.get_peer_finished()) > 0) - - - def test_tls_finished_message_symmetry(self): - """ - The TLS Finished message send by server must be the TLS Finished message - received by client. - - The TLS Finished message send by client must be the TLS Finished message - received by server. - """ - server, client = self._loopback() - - self.assertEqual(server.get_finished(), client.get_peer_finished()) - self.assertEqual(client.get_finished(), server.get_peer_finished()) - - - def test_get_cipher_name_before_connect(self): - """ - :py:obj:`Connection.get_cipher_name` returns :py:obj:`None` if no - connection has been established. - """ - ctx = Context(TLSv1_METHOD) - conn = Connection(ctx, None) - self.assertIdentical(conn.get_cipher_name(), None) - - - def test_get_cipher_name(self): - """ - :py:obj:`Connection.get_cipher_name` returns a :py:class:`unicode` - string giving the name of the currently used cipher. - """ - server, client = self._loopback() - server_cipher_name, client_cipher_name = \ - server.get_cipher_name(), client.get_cipher_name() - - self.assertIsInstance(server_cipher_name, text_type) - self.assertIsInstance(client_cipher_name, text_type) - - self.assertEqual(server_cipher_name, client_cipher_name) - - - def test_get_cipher_version_before_connect(self): - """ - :py:obj:`Connection.get_cipher_version` returns :py:obj:`None` if no - connection has been established. - """ - ctx = Context(TLSv1_METHOD) - conn = Connection(ctx, None) - self.assertIdentical(conn.get_cipher_version(), None) - - - def test_get_cipher_version(self): - """ - :py:obj:`Connection.get_cipher_version` returns a :py:class:`unicode` - string giving the protocol name of the currently used cipher. - """ - server, client = self._loopback() - server_cipher_version, client_cipher_version = \ - server.get_cipher_version(), client.get_cipher_version() - - self.assertIsInstance(server_cipher_version, text_type) - self.assertIsInstance(client_cipher_version, text_type) - - self.assertEqual(server_cipher_version, client_cipher_version) - - - def test_get_cipher_bits_before_connect(self): - """ - :py:obj:`Connection.get_cipher_bits` returns :py:obj:`None` if no - connection has been established. - """ - ctx = Context(TLSv1_METHOD) - conn = Connection(ctx, None) - self.assertIdentical(conn.get_cipher_bits(), None) - - - def test_get_cipher_bits(self): - """ - :py:obj:`Connection.get_cipher_bits` returns the number of secret bits - of the currently used cipher. - """ - server, client = self._loopback() - server_cipher_bits, client_cipher_bits = \ - server.get_cipher_bits(), client.get_cipher_bits() - - self.assertIsInstance(server_cipher_bits, int) - self.assertIsInstance(client_cipher_bits, int) - - self.assertEqual(server_cipher_bits, client_cipher_bits) - - - -class ConnectionGetCipherListTests(TestCase): - """ - Tests for :py:obj:`Connection.get_cipher_list`. - """ - def test_wrong_args(self): - """ - :py:obj:`Connection.get_cipher_list` raises :py:obj:`TypeError` if called with any - arguments. - """ - connection = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(TypeError, connection.get_cipher_list, None) - - - def test_result(self): - """ - :py:obj:`Connection.get_cipher_list` returns a :py:obj:`list` of - :py:obj:`bytes` giving the names of the ciphers which might be used. - """ - connection = Connection(Context(TLSv1_METHOD), None) - ciphers = connection.get_cipher_list() - self.assertTrue(isinstance(ciphers, list)) - for cipher in ciphers: - self.assertTrue(isinstance(cipher, str)) - - - -class ConnectionSendTests(TestCase, _LoopbackMixin): - """ - Tests for :py:obj:`Connection.send` - """ - def test_wrong_args(self): - """ - When called with arguments other than string argument for its first - parameter or more than two arguments, :py:obj:`Connection.send` raises - :py:obj:`TypeError`. - """ - connection = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(TypeError, connection.send) - self.assertRaises(TypeError, connection.send, object()) - self.assertRaises(TypeError, connection.send, "foo", object(), "bar") - - - def test_short_bytes(self): - """ - When passed a short byte string, :py:obj:`Connection.send` transmits all of it - and returns the number of bytes sent. - """ - server, client = self._loopback() - count = server.send(b('xy')) - self.assertEquals(count, 2) - self.assertEquals(client.recv(2), b('xy')) - - - def test_text(self): - """ - When passed a text, :py:obj:`Connection.send` transmits all of it and - returns the number of bytes sent. It also raises a DeprecationWarning. - """ - server, client = self._loopback() - with catch_warnings(record=True) as w: - simplefilter("always") - count = server.send(b"xy".decode("ascii")) - self.assertEqual( - "{0} for buf is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ), - str(w[-1].message) - ) - self.assertIs(w[-1].category, DeprecationWarning) - self.assertEquals(count, 2) - self.assertEquals(client.recv(2), b"xy") - - try: - memoryview - except NameError: - "cannot test sending memoryview without memoryview" - else: - def test_short_memoryview(self): - """ - When passed a memoryview onto a small number of bytes, - :py:obj:`Connection.send` transmits all of them and returns the number of - bytes sent. - """ - server, client = self._loopback() - count = server.send(memoryview(b('xy'))) - self.assertEquals(count, 2) - self.assertEquals(client.recv(2), b('xy')) - - - try: - buffer - except NameError: - "cannot test sending buffer without buffer" - else: - def test_short_buffer(self): - """ - When passed a buffer containing a small number of bytes, - :py:obj:`Connection.send` transmits all of them and returns the number of - bytes sent. - """ - server, client = self._loopback() - count = server.send(buffer(b('xy'))) - self.assertEquals(count, 2) - self.assertEquals(client.recv(2), b('xy')) - - - -def _make_memoryview(size): - """ - Create a new ``memoryview`` wrapped around a ``bytearray`` of the given - size. - """ - return memoryview(bytearray(size)) - - - -class ConnectionRecvIntoTests(TestCase, _LoopbackMixin): - """ - Tests for :py:obj:`Connection.recv_into` - """ - def _no_length_test(self, factory): - """ - Assert that when the given buffer is passed to - ``Connection.recv_into``, whatever bytes are available to be received - that fit into that buffer are written into that buffer. - """ - output_buffer = factory(5) - - server, client = self._loopback() - server.send(b('xy')) - - self.assertEqual(client.recv_into(output_buffer), 2) - self.assertEqual(output_buffer, bytearray(b('xy\x00\x00\x00'))) - - - def test_bytearray_no_length(self): - """ - :py:obj:`Connection.recv_into` can be passed a ``bytearray`` instance - and data in the receive buffer is written to it. - """ - self._no_length_test(bytearray) - - - def _respects_length_test(self, factory): - """ - Assert that when the given buffer is passed to ``Connection.recv_into`` - along with a value for ``nbytes`` that is less than the size of that - buffer, only ``nbytes`` bytes are written into the buffer. - """ - output_buffer = factory(10) - - server, client = self._loopback() - server.send(b('abcdefghij')) - - self.assertEqual(client.recv_into(output_buffer, 5), 5) - self.assertEqual( - output_buffer, bytearray(b('abcde\x00\x00\x00\x00\x00')) - ) - - - def test_bytearray_respects_length(self): - """ - When called with a ``bytearray`` instance, - :py:obj:`Connection.recv_into` respects the ``nbytes`` parameter and - doesn't copy in more than that number of bytes. - """ - self._respects_length_test(bytearray) - - - def _doesnt_overfill_test(self, factory): - """ - Assert that if there are more bytes available to be read from the - receive buffer than would fit into the buffer passed to - :py:obj:`Connection.recv_into`, only as many as fit are written into - it. - """ - output_buffer = factory(5) - - server, client = self._loopback() - server.send(b('abcdefghij')) - - self.assertEqual(client.recv_into(output_buffer), 5) - self.assertEqual(output_buffer, bytearray(b('abcde'))) - rest = client.recv(5) - self.assertEqual(b('fghij'), rest) - - - def test_bytearray_doesnt_overfill(self): - """ - When called with a ``bytearray`` instance, - :py:obj:`Connection.recv_into` respects the size of the array and - doesn't write more bytes into it than will fit. - """ - self._doesnt_overfill_test(bytearray) - - - def _really_doesnt_overfill_test(self, factory): - """ - Assert that if the value given by ``nbytes`` is greater than the actual - size of the output buffer passed to :py:obj:`Connection.recv_into`, the - behavior is as if no value was given for ``nbytes`` at all. - """ - output_buffer = factory(5) - - server, client = self._loopback() - server.send(b('abcdefghij')) - - self.assertEqual(client.recv_into(output_buffer, 50), 5) - self.assertEqual(output_buffer, bytearray(b('abcde'))) - rest = client.recv(5) - self.assertEqual(b('fghij'), rest) - - - def test_bytearray_really_doesnt_overfill(self): - """ - When called with a ``bytearray`` instance and an ``nbytes`` value that - is too large, :py:obj:`Connection.recv_into` respects the size of the - array and not the ``nbytes`` value and doesn't write more bytes into - the buffer than will fit. - """ - self._doesnt_overfill_test(bytearray) - - - try: - memoryview - except NameError: - "cannot test recv_into memoryview without memoryview" - else: - def test_memoryview_no_length(self): - """ - :py:obj:`Connection.recv_into` can be passed a ``memoryview`` - instance and data in the receive buffer is written to it. - """ - self._no_length_test(_make_memoryview) - - - def test_memoryview_respects_length(self): - """ - When called with a ``memoryview`` instance, - :py:obj:`Connection.recv_into` respects the ``nbytes`` parameter - and doesn't copy more than that number of bytes in. - """ - self._respects_length_test(_make_memoryview) - - - def test_memoryview_doesnt_overfill(self): - """ - When called with a ``memoryview`` instance, - :py:obj:`Connection.recv_into` respects the size of the array and - doesn't write more bytes into it than will fit. - """ - self._doesnt_overfill_test(_make_memoryview) - - - def test_memoryview_really_doesnt_overfill(self): - """ - When called with a ``memoryview`` instance and an ``nbytes`` value - that is too large, :py:obj:`Connection.recv_into` respects the size - of the array and not the ``nbytes`` value and doesn't write more - bytes into the buffer than will fit. - """ - self._doesnt_overfill_test(_make_memoryview) - - - -class ConnectionSendallTests(TestCase, _LoopbackMixin): - """ - Tests for :py:obj:`Connection.sendall`. - """ - def test_wrong_args(self): - """ - When called with arguments other than a string argument for its first - parameter or with more than two arguments, :py:obj:`Connection.sendall` - raises :py:obj:`TypeError`. - """ - connection = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(TypeError, connection.sendall) - self.assertRaises(TypeError, connection.sendall, object()) - self.assertRaises( - TypeError, connection.sendall, "foo", object(), "bar") - - - def test_short(self): - """ - :py:obj:`Connection.sendall` transmits all of the bytes in the string passed to - it. - """ - server, client = self._loopback() - server.sendall(b('x')) - self.assertEquals(client.recv(1), b('x')) - - - def test_text(self): - """ - :py:obj:`Connection.sendall` transmits all the content in the string - passed to it raising a DeprecationWarning in case of this being a text. - """ - server, client = self._loopback() - with catch_warnings(record=True) as w: - simplefilter("always") - server.sendall(b"x".decode("ascii")) - self.assertEqual( - "{0} for buf is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ), - str(w[-1].message) - ) - self.assertIs(w[-1].category, DeprecationWarning) - self.assertEquals(client.recv(1), b"x") - - - try: - memoryview - except NameError: - "cannot test sending memoryview without memoryview" - else: - def test_short_memoryview(self): - """ - When passed a memoryview onto a small number of bytes, - :py:obj:`Connection.sendall` transmits all of them. - """ - server, client = self._loopback() - server.sendall(memoryview(b('x'))) - self.assertEquals(client.recv(1), b('x')) - - - try: - buffer - except NameError: - "cannot test sending buffers without buffers" - else: - def test_short_buffers(self): - """ - When passed a buffer containing a small number of bytes, - :py:obj:`Connection.sendall` transmits all of them. - """ - server, client = self._loopback() - server.sendall(buffer(b('x'))) - self.assertEquals(client.recv(1), b('x')) - - - def test_long(self): - """ - :py:obj:`Connection.sendall` transmits all of the bytes in the string passed to - it even if this requires multiple calls of an underlying write function. - """ - server, client = self._loopback() - # Should be enough, underlying SSL_write should only do 16k at a time. - # On Windows, after 32k of bytes the write will block (forever - because - # no one is yet reading). - message = b('x') * (1024 * 32 - 1) + b('y') - server.sendall(message) - accum = [] - received = 0 - while received < len(message): - data = client.recv(1024) - accum.append(data) - received += len(data) - self.assertEquals(message, b('').join(accum)) - - - def test_closed(self): - """ - If the underlying socket is closed, :py:obj:`Connection.sendall` propagates the - write error from the low level write call. - """ - server, client = self._loopback() - server.sock_shutdown(2) - exc = self.assertRaises(SysCallError, server.sendall, b"hello, world") - if platform == "win32": - self.assertEqual(exc.args[0], ESHUTDOWN) - else: - self.assertEqual(exc.args[0], EPIPE) - - - -class ConnectionRenegotiateTests(TestCase, _LoopbackMixin): - """ - Tests for SSL renegotiation APIs. - """ - def test_renegotiate_wrong_args(self): - """ - :py:obj:`Connection.renegotiate` raises :py:obj:`TypeError` if called with any - arguments. - """ - connection = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(TypeError, connection.renegotiate, None) - - - def test_total_renegotiations_wrong_args(self): - """ - :py:obj:`Connection.total_renegotiations` raises :py:obj:`TypeError` if called with - any arguments. - """ - connection = Connection(Context(TLSv1_METHOD), None) - self.assertRaises(TypeError, connection.total_renegotiations, None) - - - def test_total_renegotiations(self): - """ - :py:obj:`Connection.total_renegotiations` returns :py:obj:`0` before any - renegotiations have happened. - """ - connection = Connection(Context(TLSv1_METHOD), None) - self.assertEquals(connection.total_renegotiations(), 0) - - -# def test_renegotiate(self): -# """ -# """ -# server, client = self._loopback() - -# server.send("hello world") -# self.assertEquals(client.recv(len("hello world")), "hello world") - -# self.assertEquals(server.total_renegotiations(), 0) -# self.assertTrue(server.renegotiate()) - -# server.setblocking(False) -# client.setblocking(False) -# while server.renegotiate_pending(): -# client.do_handshake() -# server.do_handshake() - -# self.assertEquals(server.total_renegotiations(), 1) - - - - -class ErrorTests(TestCase): - """ - Unit tests for :py:obj:`OpenSSL.SSL.Error`. - """ - def test_type(self): - """ - :py:obj:`Error` is an exception type. - """ - self.assertTrue(issubclass(Error, Exception)) - self.assertEqual(Error.__name__, 'Error') - - - -class ConstantsTests(TestCase): - """ - Tests for the values of constants exposed in :py:obj:`OpenSSL.SSL`. - - These are values defined by OpenSSL intended only to be used as flags to - OpenSSL APIs. The only assertions it seems can be made about them is - their values. - """ - # unittest.TestCase has no skip mechanism - if OP_NO_QUERY_MTU is not None: - def test_op_no_query_mtu(self): - """ - The value of :py:obj:`OpenSSL.SSL.OP_NO_QUERY_MTU` is 0x1000, the value of - :py:const:`SSL_OP_NO_QUERY_MTU` defined by :file:`openssl/ssl.h`. - """ - self.assertEqual(OP_NO_QUERY_MTU, 0x1000) - else: - "OP_NO_QUERY_MTU unavailable - OpenSSL version may be too old" - - - if OP_COOKIE_EXCHANGE is not None: - def test_op_cookie_exchange(self): - """ - The value of :py:obj:`OpenSSL.SSL.OP_COOKIE_EXCHANGE` is 0x2000, the value - of :py:const:`SSL_OP_COOKIE_EXCHANGE` defined by :file:`openssl/ssl.h`. - """ - self.assertEqual(OP_COOKIE_EXCHANGE, 0x2000) - else: - "OP_COOKIE_EXCHANGE unavailable - OpenSSL version may be too old" - - - if OP_NO_TICKET is not None: - def test_op_no_ticket(self): - """ - The value of :py:obj:`OpenSSL.SSL.OP_NO_TICKET` is 0x4000, the value of - :py:const:`SSL_OP_NO_TICKET` defined by :file:`openssl/ssl.h`. - """ - self.assertEqual(OP_NO_TICKET, 0x4000) - else: - "OP_NO_TICKET unavailable - OpenSSL version may be too old" - - - if OP_NO_COMPRESSION is not None: - def test_op_no_compression(self): - """ - The value of :py:obj:`OpenSSL.SSL.OP_NO_COMPRESSION` is 0x20000, the value - of :py:const:`SSL_OP_NO_COMPRESSION` defined by :file:`openssl/ssl.h`. - """ - self.assertEqual(OP_NO_COMPRESSION, 0x20000) - else: - "OP_NO_COMPRESSION unavailable - OpenSSL version may be too old" - - - def test_sess_cache_off(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_OFF` 0x0, the value of - :py:obj:`SSL_SESS_CACHE_OFF` defined by ``openssl/ssl.h``. - """ - self.assertEqual(0x0, SESS_CACHE_OFF) - - - def test_sess_cache_client(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_CLIENT` 0x1, the value of - :py:obj:`SSL_SESS_CACHE_CLIENT` defined by ``openssl/ssl.h``. - """ - self.assertEqual(0x1, SESS_CACHE_CLIENT) - - - def test_sess_cache_server(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_SERVER` 0x2, the value of - :py:obj:`SSL_SESS_CACHE_SERVER` defined by ``openssl/ssl.h``. - """ - self.assertEqual(0x2, SESS_CACHE_SERVER) - - - def test_sess_cache_both(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_BOTH` 0x3, the value of - :py:obj:`SSL_SESS_CACHE_BOTH` defined by ``openssl/ssl.h``. - """ - self.assertEqual(0x3, SESS_CACHE_BOTH) - - - def test_sess_cache_no_auto_clear(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_AUTO_CLEAR` 0x80, the - value of :py:obj:`SSL_SESS_CACHE_NO_AUTO_CLEAR` defined by - ``openssl/ssl.h``. - """ - self.assertEqual(0x80, SESS_CACHE_NO_AUTO_CLEAR) - - - def test_sess_cache_no_internal_lookup(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_INTERNAL_LOOKUP` 0x100, - the value of :py:obj:`SSL_SESS_CACHE_NO_INTERNAL_LOOKUP` defined by - ``openssl/ssl.h``. - """ - self.assertEqual(0x100, SESS_CACHE_NO_INTERNAL_LOOKUP) - - - def test_sess_cache_no_internal_store(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_INTERNAL_STORE` 0x200, - the value of :py:obj:`SSL_SESS_CACHE_NO_INTERNAL_STORE` defined by - ``openssl/ssl.h``. - """ - self.assertEqual(0x200, SESS_CACHE_NO_INTERNAL_STORE) - - - def test_sess_cache_no_internal(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_INTERNAL` 0x300, the - value of :py:obj:`SSL_SESS_CACHE_NO_INTERNAL` defined by - ``openssl/ssl.h``. - """ - self.assertEqual(0x300, SESS_CACHE_NO_INTERNAL) - - - -class MemoryBIOTests(TestCase, _LoopbackMixin): - """ - Tests for :py:obj:`OpenSSL.SSL.Connection` using a memory BIO. - """ - def _server(self, sock): - """ - Create a new server-side SSL :py:obj:`Connection` object wrapped around - :py:obj:`sock`. - """ - # Create the server side Connection. This is mostly setup boilerplate - # - use TLSv1, use a particular certificate, etc. - server_ctx = Context(TLSv1_METHOD) - server_ctx.set_options(OP_NO_SSLv2 | OP_NO_SSLv3 | OP_SINGLE_DH_USE ) - server_ctx.set_verify(VERIFY_PEER|VERIFY_FAIL_IF_NO_PEER_CERT|VERIFY_CLIENT_ONCE, verify_cb) - server_store = server_ctx.get_cert_store() - server_ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) - server_ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) - server_ctx.check_privatekey() - server_store.add_cert(load_certificate(FILETYPE_PEM, root_cert_pem)) - # Here the Connection is actually created. If None is passed as the 2nd - # parameter, it indicates a memory BIO should be created. - server_conn = Connection(server_ctx, sock) - server_conn.set_accept_state() - return server_conn - - - def _client(self, sock): - """ - Create a new client-side SSL :py:obj:`Connection` object wrapped around - :py:obj:`sock`. - """ - # Now create the client side Connection. Similar boilerplate to the - # above. - client_ctx = Context(TLSv1_METHOD) - client_ctx.set_options(OP_NO_SSLv2 | OP_NO_SSLv3 | OP_SINGLE_DH_USE ) - client_ctx.set_verify(VERIFY_PEER|VERIFY_FAIL_IF_NO_PEER_CERT|VERIFY_CLIENT_ONCE, verify_cb) - client_store = client_ctx.get_cert_store() - client_ctx.use_privatekey(load_privatekey(FILETYPE_PEM, client_key_pem)) - client_ctx.use_certificate(load_certificate(FILETYPE_PEM, client_cert_pem)) - client_ctx.check_privatekey() - client_store.add_cert(load_certificate(FILETYPE_PEM, root_cert_pem)) - client_conn = Connection(client_ctx, sock) - client_conn.set_connect_state() - return client_conn - - - def test_memoryConnect(self): - """ - Two :py:obj:`Connection`s which use memory BIOs can be manually connected by - reading from the output of each and writing those bytes to the input of - the other and in this way establish a connection and exchange - application-level bytes with each other. - """ - server_conn = self._server(None) - client_conn = self._client(None) - - # There should be no key or nonces yet. - self.assertIdentical(server_conn.master_key(), None) - self.assertIdentical(server_conn.client_random(), None) - self.assertIdentical(server_conn.server_random(), None) - - # First, the handshake needs to happen. We'll deliver bytes back and - # forth between the client and server until neither of them feels like - # speaking any more. - self.assertIdentical( - self._interactInMemory(client_conn, server_conn), None) - - # Now that the handshake is done, there should be a key and nonces. - self.assertNotIdentical(server_conn.master_key(), None) - self.assertNotIdentical(server_conn.client_random(), None) - self.assertNotIdentical(server_conn.server_random(), None) - self.assertEquals(server_conn.client_random(), client_conn.client_random()) - self.assertEquals(server_conn.server_random(), client_conn.server_random()) - self.assertNotEquals(server_conn.client_random(), server_conn.server_random()) - self.assertNotEquals(client_conn.client_random(), client_conn.server_random()) - - # Here are the bytes we'll try to send. - important_message = b('One if by land, two if by sea.') - - server_conn.write(important_message) - self.assertEquals( - self._interactInMemory(client_conn, server_conn), - (client_conn, important_message)) - - client_conn.write(important_message[::-1]) - self.assertEquals( - self._interactInMemory(client_conn, server_conn), - (server_conn, important_message[::-1])) - - - def test_socketConnect(self): - """ - Just like :py:obj:`test_memoryConnect` but with an actual socket. - - This is primarily to rule out the memory BIO code as the source of - any problems encountered while passing data over a :py:obj:`Connection` (if - this test fails, there must be a problem outside the memory BIO - code, as no memory BIO is involved here). Even though this isn't a - memory BIO test, it's convenient to have it here. - """ - server_conn, client_conn = self._loopback() - - important_message = b("Help me Obi Wan Kenobi, you're my only hope.") - client_conn.send(important_message) - msg = server_conn.recv(1024) - self.assertEqual(msg, important_message) - - # Again in the other direction, just for fun. - important_message = important_message[::-1] - server_conn.send(important_message) - msg = client_conn.recv(1024) - self.assertEqual(msg, important_message) - - - def test_socketOverridesMemory(self): - """ - Test that :py:obj:`OpenSSL.SSL.bio_read` and :py:obj:`OpenSSL.SSL.bio_write` don't - work on :py:obj:`OpenSSL.SSL.Connection`() that use sockets. - """ - context = Context(SSLv3_METHOD) - client = socket() - clientSSL = Connection(context, client) - self.assertRaises( TypeError, clientSSL.bio_read, 100) - self.assertRaises( TypeError, clientSSL.bio_write, "foo") - self.assertRaises( TypeError, clientSSL.bio_shutdown ) - - - def test_outgoingOverflow(self): - """ - If more bytes than can be written to the memory BIO are passed to - :py:obj:`Connection.send` at once, the number of bytes which were written is - returned and that many bytes from the beginning of the input can be - read from the other end of the connection. - """ - server = self._server(None) - client = self._client(None) - - self._interactInMemory(client, server) - - size = 2 ** 15 - sent = client.send(b"x" * size) - # Sanity check. We're trying to test what happens when the entire - # input can't be sent. If the entire input was sent, this test is - # meaningless. - self.assertTrue(sent < size) - - receiver, received = self._interactInMemory(client, server) - self.assertIdentical(receiver, server) - - # We can rely on all of these bytes being received at once because - # _loopback passes 2 ** 16 to recv - more than 2 ** 15. - self.assertEquals(len(received), sent) - - - def test_shutdown(self): - """ - :py:obj:`Connection.bio_shutdown` signals the end of the data stream from - which the :py:obj:`Connection` reads. - """ - server = self._server(None) - server.bio_shutdown() - e = self.assertRaises(Error, server.recv, 1024) - # We don't want WantReadError or ZeroReturnError or anything - it's a - # handshake failure. - self.assertEquals(e.__class__, Error) - - - def test_unexpectedEndOfFile(self): - """ - If the connection is lost before an orderly SSL shutdown occurs, - :py:obj:`OpenSSL.SSL.SysCallError` is raised with a message of - "Unexpected EOF". - """ - server_conn, client_conn = self._loopback() - client_conn.sock_shutdown(SHUT_RDWR) - exc = self.assertRaises(SysCallError, server_conn.recv, 1024) - self.assertEqual(exc.args, (-1, "Unexpected EOF")) - - - def _check_client_ca_list(self, func): - """ - Verify the return value of the :py:obj:`get_client_ca_list` method for server and client connections. - - :param func: A function which will be called with the server context - before the client and server are connected to each other. This - function should specify a list of CAs for the server to send to the - client and return that same list. The list will be used to verify - that :py:obj:`get_client_ca_list` returns the proper value at various - times. - """ - server = self._server(None) - client = self._client(None) - self.assertEqual(client.get_client_ca_list(), []) - self.assertEqual(server.get_client_ca_list(), []) - ctx = server.get_context() - expected = func(ctx) - self.assertEqual(client.get_client_ca_list(), []) - self.assertEqual(server.get_client_ca_list(), expected) - self._interactInMemory(client, server) - self.assertEqual(client.get_client_ca_list(), expected) - self.assertEqual(server.get_client_ca_list(), expected) - - - def test_set_client_ca_list_errors(self): - """ - :py:obj:`Context.set_client_ca_list` raises a :py:obj:`TypeError` if called with a - non-list or a list that contains objects other than X509Names. - """ - ctx = Context(TLSv1_METHOD) - self.assertRaises(TypeError, ctx.set_client_ca_list, "spam") - self.assertRaises(TypeError, ctx.set_client_ca_list, ["spam"]) - self.assertIdentical(ctx.set_client_ca_list([]), None) - - - def test_set_empty_ca_list(self): - """ - If passed an empty list, :py:obj:`Context.set_client_ca_list` configures the - context to send no CA names to the client and, on both the server and - client sides, :py:obj:`Connection.get_client_ca_list` returns an empty list - after the connection is set up. - """ - def no_ca(ctx): - ctx.set_client_ca_list([]) - return [] - self._check_client_ca_list(no_ca) - - - def test_set_one_ca_list(self): - """ - If passed a list containing a single X509Name, - :py:obj:`Context.set_client_ca_list` configures the context to send that CA - name to the client and, on both the server and client sides, - :py:obj:`Connection.get_client_ca_list` returns a list containing that - X509Name after the connection is set up. - """ - cacert = load_certificate(FILETYPE_PEM, root_cert_pem) - cadesc = cacert.get_subject() - def single_ca(ctx): - ctx.set_client_ca_list([cadesc]) - return [cadesc] - self._check_client_ca_list(single_ca) - - - def test_set_multiple_ca_list(self): - """ - If passed a list containing multiple X509Name objects, - :py:obj:`Context.set_client_ca_list` configures the context to send those CA - names to the client and, on both the server and client sides, - :py:obj:`Connection.get_client_ca_list` returns a list containing those - X509Names after the connection is set up. - """ - secert = load_certificate(FILETYPE_PEM, server_cert_pem) - clcert = load_certificate(FILETYPE_PEM, server_cert_pem) - - sedesc = secert.get_subject() - cldesc = clcert.get_subject() - - def multiple_ca(ctx): - L = [sedesc, cldesc] - ctx.set_client_ca_list(L) - return L - self._check_client_ca_list(multiple_ca) - - - def test_reset_ca_list(self): - """ - If called multiple times, only the X509Names passed to the final call - of :py:obj:`Context.set_client_ca_list` are used to configure the CA names - sent to the client. - """ - cacert = load_certificate(FILETYPE_PEM, root_cert_pem) - secert = load_certificate(FILETYPE_PEM, server_cert_pem) - clcert = load_certificate(FILETYPE_PEM, server_cert_pem) - - cadesc = cacert.get_subject() - sedesc = secert.get_subject() - cldesc = clcert.get_subject() - - def changed_ca(ctx): - ctx.set_client_ca_list([sedesc, cldesc]) - ctx.set_client_ca_list([cadesc]) - return [cadesc] - self._check_client_ca_list(changed_ca) - - - def test_mutated_ca_list(self): - """ - If the list passed to :py:obj:`Context.set_client_ca_list` is mutated - afterwards, this does not affect the list of CA names sent to the - client. - """ - cacert = load_certificate(FILETYPE_PEM, root_cert_pem) - secert = load_certificate(FILETYPE_PEM, server_cert_pem) - - cadesc = cacert.get_subject() - sedesc = secert.get_subject() - - def mutated_ca(ctx): - L = [cadesc] - ctx.set_client_ca_list([cadesc]) - L.append(sedesc) - return [cadesc] - self._check_client_ca_list(mutated_ca) - - - def test_add_client_ca_errors(self): - """ - :py:obj:`Context.add_client_ca` raises :py:obj:`TypeError` if called with a non-X509 - object or with a number of arguments other than one. - """ - ctx = Context(TLSv1_METHOD) - cacert = load_certificate(FILETYPE_PEM, root_cert_pem) - self.assertRaises(TypeError, ctx.add_client_ca) - self.assertRaises(TypeError, ctx.add_client_ca, "spam") - self.assertRaises(TypeError, ctx.add_client_ca, cacert, cacert) - - - def test_one_add_client_ca(self): - """ - A certificate's subject can be added as a CA to be sent to the client - with :py:obj:`Context.add_client_ca`. - """ - cacert = load_certificate(FILETYPE_PEM, root_cert_pem) - cadesc = cacert.get_subject() - def single_ca(ctx): - ctx.add_client_ca(cacert) - return [cadesc] - self._check_client_ca_list(single_ca) - - - def test_multiple_add_client_ca(self): - """ - Multiple CA names can be sent to the client by calling - :py:obj:`Context.add_client_ca` with multiple X509 objects. - """ - cacert = load_certificate(FILETYPE_PEM, root_cert_pem) - secert = load_certificate(FILETYPE_PEM, server_cert_pem) - - cadesc = cacert.get_subject() - sedesc = secert.get_subject() - - def multiple_ca(ctx): - ctx.add_client_ca(cacert) - ctx.add_client_ca(secert) - return [cadesc, sedesc] - self._check_client_ca_list(multiple_ca) - - - def test_set_and_add_client_ca(self): - """ - A call to :py:obj:`Context.set_client_ca_list` followed by a call to - :py:obj:`Context.add_client_ca` results in using the CA names from the first - call and the CA name from the second call. - """ - cacert = load_certificate(FILETYPE_PEM, root_cert_pem) - secert = load_certificate(FILETYPE_PEM, server_cert_pem) - clcert = load_certificate(FILETYPE_PEM, server_cert_pem) - - cadesc = cacert.get_subject() - sedesc = secert.get_subject() - cldesc = clcert.get_subject() - - def mixed_set_add_ca(ctx): - ctx.set_client_ca_list([cadesc, sedesc]) - ctx.add_client_ca(clcert) - return [cadesc, sedesc, cldesc] - self._check_client_ca_list(mixed_set_add_ca) - - - def test_set_after_add_client_ca(self): - """ - A call to :py:obj:`Context.set_client_ca_list` after a call to - :py:obj:`Context.add_client_ca` replaces the CA name specified by the former - call with the names specified by the latter cal. - """ - cacert = load_certificate(FILETYPE_PEM, root_cert_pem) - secert = load_certificate(FILETYPE_PEM, server_cert_pem) - clcert = load_certificate(FILETYPE_PEM, server_cert_pem) - - cadesc = cacert.get_subject() - sedesc = secert.get_subject() - - def set_replaces_add_ca(ctx): - ctx.add_client_ca(clcert) - ctx.set_client_ca_list([cadesc]) - ctx.add_client_ca(secert) - return [cadesc, sedesc] - self._check_client_ca_list(set_replaces_add_ca) - - - -class ConnectionBIOTests(TestCase): - """ - Tests for :py:obj:`Connection.bio_read` and :py:obj:`Connection.bio_write`. - """ - def test_wantReadError(self): - """ - :py:obj:`Connection.bio_read` raises :py:obj:`OpenSSL.SSL.WantReadError` - if there are no bytes available to be read from the BIO. - """ - ctx = Context(TLSv1_METHOD) - conn = Connection(ctx, None) - self.assertRaises(WantReadError, conn.bio_read, 1024) - - - def test_buffer_size(self): - """ - :py:obj:`Connection.bio_read` accepts an integer giving the maximum - number of bytes to read and return. - """ - ctx = Context(TLSv1_METHOD) - conn = Connection(ctx, None) - conn.set_connect_state() - try: - conn.do_handshake() - except WantReadError: - pass - data = conn.bio_read(2) - self.assertEqual(2, len(data)) - - - if not PY3: - def test_buffer_size_long(self): - """ - On Python 2 :py:obj:`Connection.bio_read` accepts values of type - :py:obj:`long` as well as :py:obj:`int`. - """ - ctx = Context(TLSv1_METHOD) - conn = Connection(ctx, None) - conn.set_connect_state() - try: - conn.do_handshake() - except WantReadError: - pass - data = conn.bio_read(long(2)) - self.assertEqual(2, len(data)) - - - - -class InfoConstantTests(TestCase): - """ - Tests for assorted constants exposed for use in info callbacks. - """ - def test_integers(self): - """ - All of the info constants are integers. - - This is a very weak test. It would be nice to have one that actually - verifies that as certain info events happen, the value passed to the - info callback matches up with the constant exposed by OpenSSL.SSL. - """ - for const in [ - SSL_ST_CONNECT, SSL_ST_ACCEPT, SSL_ST_MASK, SSL_ST_INIT, - SSL_ST_BEFORE, SSL_ST_OK, SSL_ST_RENEGOTIATE, - SSL_CB_LOOP, SSL_CB_EXIT, SSL_CB_READ, SSL_CB_WRITE, SSL_CB_ALERT, - SSL_CB_READ_ALERT, SSL_CB_WRITE_ALERT, SSL_CB_ACCEPT_LOOP, - SSL_CB_ACCEPT_EXIT, SSL_CB_CONNECT_LOOP, SSL_CB_CONNECT_EXIT, - SSL_CB_HANDSHAKE_START, SSL_CB_HANDSHAKE_DONE]: - - self.assertTrue(isinstance(const, int)) - - -if __name__ == '__main__': - main() diff --git a/lib/OpenSSL/test/test_tsafe.py b/lib/OpenSSL/test/test_tsafe.py deleted file mode 100644 index 04569574da6801b3aa63f73a4f6c1301e5800b28..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/test/test_tsafe.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (C) Jean-Paul Calderone -# See LICENSE for details. - -""" -Unit tests for :py:obj:`OpenSSL.tsafe`. -""" - -from OpenSSL.SSL import TLSv1_METHOD, Context -from OpenSSL.tsafe import Connection -from OpenSSL.test.util import TestCase - - -class ConnectionTest(TestCase): - """ - Tests for :py:obj:`OpenSSL.tsafe.Connection`. - """ - def test_instantiation(self): - """ - :py:obj:`OpenSSL.tsafe.Connection` can be instantiated. - """ - # The following line should not throw an error. This isn't an ideal - # test. It would be great to refactor the other Connection tests so - # they could automatically be applied to this class too. - Connection(Context(TLSv1_METHOD), None) diff --git a/lib/OpenSSL/test/test_util.py b/lib/OpenSSL/test/test_util.py deleted file mode 100644 index 8d92a3c756ab62e95b14bbc029a841f050a46c55..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/test/test_util.py +++ /dev/null @@ -1,17 +0,0 @@ -from OpenSSL._util import exception_from_error_queue, lib -from OpenSSL.test.util import TestCase - - - -class ErrorTests(TestCase): - """ - Tests for handling of certain OpenSSL error cases. - """ - def test_exception_from_error_queue_nonexistent_reason(self): - """ - :py:func:`exception_from_error_queue` raises ``ValueError`` when it - encounters an OpenSSL error code which does not have a reason string. - """ - lib.ERR_put_error(lib.ERR_LIB_EVP, 0, 1112, b"", 10) - exc = self.assertRaises(ValueError, exception_from_error_queue, ValueError) - self.assertEqual(exc.args[0][0][2], "") diff --git a/lib/OpenSSL/test/util.py b/lib/OpenSSL/test/util.py deleted file mode 100644 index b8be91deb873472e2c7acb810c7f54b8596830ac..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/test/util.py +++ /dev/null @@ -1,463 +0,0 @@ -# Copyright (C) Jean-Paul Calderone -# Copyright (C) Twisted Matrix Laboratories. -# See LICENSE for details. - -""" -Helpers for the OpenSSL test suite, largely copied from -U{Twisted<http://twistedmatrix.com/>}. -""" - -import shutil -import traceback -import os, os.path -from tempfile import mktemp -from unittest import TestCase -import sys - -from six import PY3 - -from OpenSSL._util import exception_from_error_queue -from OpenSSL.crypto import Error - -try: - import memdbg -except Exception: - class _memdbg(object): heap = None - memdbg = _memdbg() - -from OpenSSL._util import ffi, lib, byte_string as b - - -# This is the UTF-8 encoding of the SNOWMAN unicode code point. -NON_ASCII = b("\xe2\x98\x83").decode("utf-8") - - -class TestCase(TestCase): - """ - :py:class:`TestCase` adds useful testing functionality beyond what is available - from the standard library :py:class:`unittest.TestCase`. - """ - def run(self, result): - run = super(TestCase, self).run - if memdbg.heap is None: - return run(result) - - # Run the test as usual - before = set(memdbg.heap) - run(result) - - # Clean up some long-lived allocations so they won't be reported as - # memory leaks. - lib.CRYPTO_cleanup_all_ex_data() - lib.ERR_remove_thread_state(ffi.NULL) - after = set(memdbg.heap) - - if not after - before: - # No leaks, fast succeed - return - - if result.wasSuccessful(): - # If it passed, run it again with memory debugging - before = set(memdbg.heap) - run(result) - - # Clean up some long-lived allocations so they won't be reported as - # memory leaks. - lib.CRYPTO_cleanup_all_ex_data() - lib.ERR_remove_thread_state(ffi.NULL) - - after = set(memdbg.heap) - - self._reportLeaks(after - before, result) - - - def _reportLeaks(self, leaks, result): - def format_leak(p): - stacks = memdbg.heap[p] - # Eventually look at multiple stacks for the realloc() case. For - # now just look at the original allocation location. - (size, python_stack, c_stack) = stacks[0] - - stack = traceback.format_list(python_stack)[:-1] - - # c_stack looks something like this (interesting parts indicated - # with inserted arrows not part of the data): - # - # /home/exarkun/Projects/pyOpenSSL/branches/use-opentls/__pycache__/_cffi__x89095113xb9185b9b.so(+0x12cf) [0x7fe2e20582cf] - # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a] - # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52] - # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e] - # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419] - # /home/exarkun/Projects/cpython/2.7/python() [0x4d6129] - # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e] - # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalCodeEx+0x1043) [0x4d3726] - # /home/exarkun/Projects/cpython/2.7/python() [0x55fd51] - # /home/exarkun/Projects/cpython/2.7/python(PyObject_Call+0x7e) [0x420ee6] - # /home/exarkun/Projects/cpython/2.7/python(PyEval_CallObjectWithKeywords+0x158) [0x4d56ec] - # /home/exarkun/.local/lib/python2.7/site-packages/cffi-0.5-py2.7-linux-x86_64.egg/_cffi_backend.so(+0xe96e) [0x7fe2e38be96e] - # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64_inner+0x1b9) [0x7fe2e36ad819] - # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64+0x46) [0x7fe2e36adb7c] - # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(CRYPTO_malloc+0x64) [0x7fe2e1cef784] <------ end interesting - # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(lh_insert+0x16b) [0x7fe2e1d6a24b] . - # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x61c18) [0x7fe2e1cf0c18] . - # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x625ec) [0x7fe2e1cf15ec] . - # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_new_method+0xe6) [0x7fe2e1d524d6] . - # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_generate_parameters+0x3a) [0x7fe2e1d5364a] <------ begin interesting - # /home/exarkun/Projects/opentls/trunk/tls/c/__pycache__/_cffi__x305d4698xb539baaa.so(+0x1f397) [0x7fe2df84d397] - # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a] - # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52] - # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e] - # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419] - # ... - # - # Notice the stack is upside down compared to a Python traceback. - # Identify the start and end of interesting bits and stuff it into the stack we report. - - saved = list(c_stack) - - # Figure the first interesting frame will be after a the cffi-compiled module - while c_stack and '/__pycache__/_cffi__' not in c_stack[-1]: - c_stack.pop() - - # Figure the last interesting frame will always be CRYPTO_malloc, - # since that's where we hooked in to things. - while c_stack and 'CRYPTO_malloc' not in c_stack[0] and 'CRYPTO_realloc' not in c_stack[0]: - c_stack.pop(0) - - if c_stack: - c_stack.reverse() - else: - c_stack = saved[::-1] - stack.extend([frame + "\n" for frame in c_stack]) - - stack.insert(0, "Leaked (%s) at:\n") - return "".join(stack) - - if leaks: - unique_leaks = {} - for p in leaks: - size = memdbg.heap[p][-1][0] - new_leak = format_leak(p) - if new_leak not in unique_leaks: - unique_leaks[new_leak] = [(size, p)] - else: - unique_leaks[new_leak].append((size, p)) - memdbg.free(p) - - for (stack, allocs) in unique_leaks.iteritems(): - allocs_accum = [] - for (size, pointer) in allocs: - - addr = int(ffi.cast('uintptr_t', pointer)) - allocs_accum.append("%d@0x%x" % (size, addr)) - allocs_report = ", ".join(sorted(allocs_accum)) - - result.addError( - self, - (None, Exception(stack % (allocs_report,)), None)) - - - def tearDown(self): - """ - Clean up any files or directories created using :py:meth:`TestCase.mktemp`. - Subclasses must invoke this method if they override it or the - cleanup will not occur. - """ - if False and self._temporaryFiles is not None: - for temp in self._temporaryFiles: - if os.path.isdir(temp): - shutil.rmtree(temp) - elif os.path.exists(temp): - os.unlink(temp) - try: - exception_from_error_queue(Error) - except Error: - e = sys.exc_info()[1] - if e.args != ([],): - self.fail("Left over errors in OpenSSL error queue: " + repr(e)) - - - def assertIsInstance(self, instance, classOrTuple, message=None): - """ - Fail if C{instance} is not an instance of the given class or of - one of the given classes. - - @param instance: the object to test the type (first argument of the - C{isinstance} call). - @type instance: any. - @param classOrTuple: the class or classes to test against (second - argument of the C{isinstance} call). - @type classOrTuple: class, type, or tuple. - - @param message: Custom text to include in the exception text if the - assertion fails. - """ - if not isinstance(instance, classOrTuple): - if message is None: - suffix = "" - else: - suffix = ": " + message - self.fail("%r is not an instance of %s%s" % ( - instance, classOrTuple, suffix)) - - - def failUnlessIn(self, containee, container, msg=None): - """ - Fail the test if :py:data:`containee` is not found in :py:data:`container`. - - :param containee: the value that should be in :py:class:`container` - :param container: a sequence type, or in the case of a mapping type, - will follow semantics of 'if key in dict.keys()' - :param msg: if msg is None, then the failure message will be - '%r not in %r' % (first, second) - """ - if containee not in container: - raise self.failureException(msg or "%r not in %r" - % (containee, container)) - return containee - assertIn = failUnlessIn - - def assertNotIn(self, containee, container, msg=None): - """ - Fail the test if C{containee} is found in C{container}. - - @param containee: the value that should not be in C{container} - @param container: a sequence type, or in the case of a mapping type, - will follow semantics of 'if key in dict.keys()' - @param msg: if msg is None, then the failure message will be - '%r in %r' % (first, second) - """ - if containee in container: - raise self.failureException(msg or "%r in %r" - % (containee, container)) - return containee - failIfIn = assertNotIn - - - def assertIs(self, first, second, msg=None): - """ - Fail the test if :py:data:`first` is not :py:data:`second`. This is an - obect-identity-equality test, not an object equality - (i.e. :py:func:`__eq__`) test. - - :param msg: if msg is None, then the failure message will be - '%r is not %r' % (first, second) - """ - if first is not second: - raise self.failureException(msg or '%r is not %r' % (first, second)) - return first - assertIdentical = failUnlessIdentical = assertIs - - - def assertIsNot(self, first, second, msg=None): - """ - Fail the test if :py:data:`first` is :py:data:`second`. This is an - obect-identity-equality test, not an object equality - (i.e. :py:func:`__eq__`) test. - - :param msg: if msg is None, then the failure message will be - '%r is %r' % (first, second) - """ - if first is second: - raise self.failureException(msg or '%r is %r' % (first, second)) - return first - assertNotIdentical = failIfIdentical = assertIsNot - - - def failUnlessRaises(self, exception, f, *args, **kwargs): - """ - Fail the test unless calling the function :py:data:`f` with the given - :py:data:`args` and :py:data:`kwargs` raises :py:data:`exception`. The - failure will report the traceback and call stack of the unexpected - exception. - - :param exception: exception type that is to be expected - :param f: the function to call - - :return: The raised exception instance, if it is of the given type. - :raise self.failureException: Raised if the function call does - not raise an exception or if it raises an exception of a - different type. - """ - try: - result = f(*args, **kwargs) - except exception: - inst = sys.exc_info()[1] - return inst - except: - raise self.failureException('%s raised instead of %s' - % (sys.exc_info()[0], - exception.__name__, - )) - else: - raise self.failureException('%s not raised (%r returned)' - % (exception.__name__, result)) - assertRaises = failUnlessRaises - - - _temporaryFiles = None - def mktemp(self): - """ - Pathetic substitute for twisted.trial.unittest.TestCase.mktemp. - """ - if self._temporaryFiles is None: - self._temporaryFiles = [] - temp = b(mktemp(dir=".")) - self._temporaryFiles.append(temp) - return temp - - - # Other stuff - def assertConsistentType(self, theType, name, *constructionArgs): - """ - Perform various assertions about :py:data:`theType` to ensure that it is a - well-defined type. This is useful for extension types, where it's - pretty easy to do something wacky. If something about the type is - unusual, an exception will be raised. - - :param theType: The type object about which to make assertions. - :param name: A string giving the name of the type. - :param constructionArgs: Positional arguments to use with :py:data:`theType` to - create an instance of it. - """ - self.assertEqual(theType.__name__, name) - self.assertTrue(isinstance(theType, type)) - instance = theType(*constructionArgs) - self.assertIdentical(type(instance), theType) - - - -class EqualityTestsMixin(object): - """ - A mixin defining tests for the standard implementation of C{==} and C{!=}. - """ - def anInstance(self): - """ - Return an instance of the class under test. Each call to this method - must return a different object. All objects returned must be equal to - each other. - """ - raise NotImplementedError() - - - def anotherInstance(self): - """ - Return an instance of the class under test. Each call to this method - must return a different object. The objects must not be equal to the - objects returned by C{anInstance}. They may or may not be equal to - each other (they will not be compared against each other). - """ - raise NotImplementedError() - - - def test_identicalEq(self): - """ - An object compares equal to itself using the C{==} operator. - """ - o = self.anInstance() - self.assertTrue(o == o) - - - def test_identicalNe(self): - """ - An object doesn't compare not equal to itself using the C{!=} operator. - """ - o = self.anInstance() - self.assertFalse(o != o) - - - def test_sameEq(self): - """ - Two objects that are equal to each other compare equal to each other - using the C{==} operator. - """ - a = self.anInstance() - b = self.anInstance() - self.assertTrue(a == b) - - - def test_sameNe(self): - """ - Two objects that are equal to each other do not compare not equal to - each other using the C{!=} operator. - """ - a = self.anInstance() - b = self.anInstance() - self.assertFalse(a != b) - - - def test_differentEq(self): - """ - Two objects that are not equal to each other do not compare equal to - each other using the C{==} operator. - """ - a = self.anInstance() - b = self.anotherInstance() - self.assertFalse(a == b) - - - def test_differentNe(self): - """ - Two objects that are not equal to each other compare not equal to each - other using the C{!=} operator. - """ - a = self.anInstance() - b = self.anotherInstance() - self.assertTrue(a != b) - - - def test_anotherTypeEq(self): - """ - The object does not compare equal to an object of an unrelated type - (which does not implement the comparison) using the C{==} operator. - """ - a = self.anInstance() - b = object() - self.assertFalse(a == b) - - - def test_anotherTypeNe(self): - """ - The object compares not equal to an object of an unrelated type (which - does not implement the comparison) using the C{!=} operator. - """ - a = self.anInstance() - b = object() - self.assertTrue(a != b) - - - def test_delegatedEq(self): - """ - The result of comparison using C{==} is delegated to the right-hand - operand if it is of an unrelated type. - """ - class Delegate(object): - def __eq__(self, other): - # Do something crazy and obvious. - return [self] - - a = self.anInstance() - b = Delegate() - self.assertEqual(a == b, [b]) - - - def test_delegateNe(self): - """ - The result of comparison using C{!=} is delegated to the right-hand - operand if it is of an unrelated type. - """ - class Delegate(object): - def __ne__(self, other): - # Do something crazy and obvious. - return [self] - - a = self.anInstance() - b = Delegate() - self.assertEqual(a != b, [b]) - - -# The type name expected in warnings about using the wrong string type. -if PY3: - WARNING_TYPE_EXPECTED = "str" -else: - WARNING_TYPE_EXPECTED = "unicode" diff --git a/lib/OpenSSL/tsafe.py b/lib/OpenSSL/tsafe.py deleted file mode 100644 index 3a9c71035149b947e3804fce2965928d1aa71f20..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/tsafe.py +++ /dev/null @@ -1,28 +0,0 @@ -from OpenSSL import SSL -_ssl = SSL -del SSL - -import threading -_RLock = threading.RLock -del threading - -class Connection: - def __init__(self, *args): - self._ssl_conn = _ssl.Connection(*args) - self._lock = _RLock() - - for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read', - 'renegotiate', 'bind', 'listen', 'connect', 'accept', - 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list', - 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', - 'makefile', 'get_app_data', 'set_app_data', 'state_string', - 'sock_shutdown', 'get_peer_certificate', 'get_peer_cert_chain', 'want_read', - 'want_write', 'set_connect_state', 'set_accept_state', - 'connect_ex', 'sendall'): - exec("""def %s(self, *args): - self._lock.acquire() - try: - return self._ssl_conn.%s(*args) - finally: - self._lock.release()\n""" % (f, f)) - diff --git a/lib/OpenSSL/version.py b/lib/OpenSSL/version.py deleted file mode 100644 index eb3b736e0072632886f69508a39072cb5778a40d..0000000000000000000000000000000000000000 --- a/lib/OpenSSL/version.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (C) AB Strakt -# Copyright (C) Jean-Paul Calderone -# See LICENSE for details. - -""" -pyOpenSSL - A simple wrapper around the OpenSSL library -""" - -__version__ = '0.15.1' diff --git a/readme.md b/readme.md index 0302171dbc214e7d2c4c2bfeb4554557703c9a45..35445a570020fb0177da73bbe370742fdafd2f8f 100644 --- a/readme.md +++ b/readme.md @@ -26,7 +26,7 @@ Video File Manager for TV Shows, It watches for new episodes of your favorite sh -[Mobile](http://imgur.com/a/WPyG6) ## Dependencies - To run SickRage from source you will need Python 2.6+ and Cheetah 2.1.0+. + To run SickRage from source you will need Python 2.6+, Cheetah 2.1.0+, and PyOpenSSL 0.13.1 or lower. ## Forums Any questions or setup info your looking for can be found at out forums https://www.sickrage.tv diff --git a/sickbeard/common.py b/sickbeard/common.py index 443e487d2befc069759b941a9c5c8f12d15abe04..cafe17c4b1cac597f43b0647c34c586550ec5f4c 100644 --- a/sickbeard/common.py +++ b/sickbeard/common.py @@ -103,11 +103,11 @@ class Quality: qualityStrings = {NONE: "N/A", UNKNOWN: "Unknown", - SDTV: "SD TV", + SDTV: "SDTV", SDDVD: "SD DVD", - HDTV: "HD TV", - RAWHDTV: "RawHD TV", - FULLHDTV: "1080p HD TV", + HDTV: "HDTV", + RAWHDTV: "RawHD", + FULLHDTV: "1080p HDTV", HDWEBDL: "720p WEB-DL", FULLHDWEBDL: "1080p WEB-DL", HDBLURAY: "720p BluRay", @@ -156,21 +156,25 @@ class Quality: If no quality is achieved it will try sceneQuality regex """ + #Try Scene names first + quality = Quality.sceneQuality(name, anime) + if quality != Quality.UNKNOWN: + return quality + name = os.path.basename(name) # if we have our exact text then assume we put it there for x in sorted(Quality.qualityStrings.keys(), reverse=True): - if x == Quality.UNKNOWN: + if x == Quality.UNKNOWN or x == Quality.NONE: continue - if x == Quality.NONE: #Last chance - return Quality.sceneQuality(name, anime) - regex = '\W' + Quality.qualityStrings[x].replace(' ', '\W') + '\W' regex_match = re.search(regex, name, re.I) if regex_match: return x + return Quality.UNKNOWN + @staticmethod def sceneQuality(name, anime=False): """ diff --git a/sickbeard/indexers/indexer_config.py b/sickbeard/indexers/indexer_config.py index 1a61357f171a380d5418933a1273dfa89c0f7663..a568f580f2da48cbb87d20df6101609c1483787c 100644 --- a/sickbeard/indexers/indexer_config.py +++ b/sickbeard/indexers/indexer_config.py @@ -48,6 +48,6 @@ indexerConfig[INDEXER_TVDB]['base_url'] = 'http://thetvdb.com/api/%(apikey)s/ser indexerConfig[INDEXER_TVRAGE]['trakt_id'] = 'tvrage_id' indexerConfig[INDEXER_TVRAGE]['xem_origin'] = 'rage' indexerConfig[INDEXER_TVRAGE]['icon'] = 'tvrage16.png' -indexerConfig[INDEXER_TVRAGE]['scene_url'] = 'https://raw.githubusercontent.com/echel0n/sb_tvrage_scene_exceptions/master/exceptions.txt' +indexerConfig[INDEXER_TVRAGE]['scene_url'] = 'https://raw.githubusercontent.com/SiCKRAGETV/sr_tvrage_scene_exceptions/master/exceptions.txt' indexerConfig[INDEXER_TVRAGE]['show_url'] = 'http://tvrage.com/shows/id-' -indexerConfig[INDEXER_TVRAGE]['base_url'] = 'http://tvrage.com/showinfo.php?key=%(apikey)s&sid=' % indexerConfig[INDEXER_TVRAGE]['api_params'] \ No newline at end of file +indexerConfig[INDEXER_TVRAGE]['base_url'] = 'http://tvrage.com/showinfo.php?key=%(apikey)s&sid=' % indexerConfig[INDEXER_TVRAGE]['api_params'] diff --git a/sickbeard/logger.py b/sickbeard/logger.py index 9ff72da2e80683298b59472be2eb2b4528de727f..f5e0d92ffbbda5e0fd300a9ff53922cd24fe0155 100644 --- a/sickbeard/logger.py +++ b/sickbeard/logger.py @@ -62,7 +62,7 @@ class CensoredFormatter(logging.Formatter, object): if v and len(v) > 0 and v in msg: msg = msg.replace(v, len(v) * '*') # Needed because Newznab apikey isn't stored as key=value in a section. - msg = re.sub(r'(r|apikey|api_key)=[^&]*([&\w]?)',r'\1=**********\2', msg) + msg = re.sub(r'([&?]r|[&?]apikey|[&?]api_key)=[^&]*([&\w]?)',r'\1=**********\2', msg) return msg diff --git a/sickbeard/providers/eztv.py b/sickbeard/providers/eztv.py index c5527214beb6242008b09f3cc39f462b1af950b3..c21512174593926fb6ac5a32c13ecbd1289ce2d8 100644 --- a/sickbeard/providers/eztv.py +++ b/sickbeard/providers/eztv.py @@ -100,7 +100,7 @@ class EZTVProvider(generic.TorrentProvider): try: with BS4Parser(HTML, features=["html5lib", "permissive"]) as parsedHTML: - resultsTable = parsedHTML.find_all('tr', attrs={'name': 'hover', 'class': 'header_brd'}) + resultsTable = parsedHTML.find_all('tr', attrs={'name': 'hover', 'class': 'forum_header_border'}) if not resultsTable: logger.log(u"The Data returned from " + self.name + " do not contains any torrent", @@ -109,7 +109,11 @@ class EZTVProvider(generic.TorrentProvider): for entries in resultsTable: title = entries.find('a', attrs={'class': 'epinfo'}).contents[0] - link = entries.find('a', attrs={'class': 'magnet'}).get('href') + link = entries.find('a', attrs={'class': 'magnet'}) or entries.find('a', attrs={'class': 'download_1'}) + if link: + link = link.get('href') + else: + continue item = { 'title': title, @@ -143,12 +147,27 @@ class EZTVProvider(generic.TorrentProvider): for quality in episode['torrents'].keys(): link = episode['torrents'][quality]['url'] - getTitle = re.search('&dn=(.*?)&', link) - if getTitle: - title = getTitle.group(1) - else: + if not re.match('magnet', link) and not re.match('http', link): continue + # Get title from link: + # 1) try magnet link + # 2) try rarbg link + # 3) try extratorrent link + # 4) try '([^/]+$)' : everything after last slash character (not accurate) + # 5) fallback, title is equal to link + if re.match('.*&dn=(.*?)&', link): + title = re.match('.*&dn=(.*?)&', link).group(1) + elif re.match('http://rarbg.to', link): + title = re.search('([^=]+$)', link).group(0) + elif re.match('http://extratorrent.cc', link): + title = re.search('([^/]+$)', link).group(0) + elif re.search('([^/]+$)', link): + title = re.search('([^/]+$)', link).group(0) + else: + title = link + + title = title.replace('+', '.').replace('%20', '.').replace('%5B', '[').replace('%5D', ']') item = { 'title': title, 'link': link, diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py index f6ec5abe59afc92be0a0134e9a2ba3b7dc8b9d66..078b47fe9120e7879792ed8b28bdd86b27d05fd0 100644 --- a/sickbeard/providers/generic.py +++ b/sickbeard/providers/generic.py @@ -156,8 +156,9 @@ class GenericProvider: urls = [ 'http://torcache.net/torrent/' + torrent_hash + '.torrent', - 'http://zoink.ch/torrent/' + torrent_hash + '.torrent', - 'http://torrage.com/torrent/' + torrent_hash.lower() + '.torrent', + #zoink.ch misconfigured, torrage.com domain expired. + #'http://zoink.ch/torrent/' + torrent_hash + '.torrent', + #'http://torrage.com/torrent/' + torrent_hash.lower() + '.torrent', ] except: urls = [result.url] diff --git a/sickbeard/show_name_helpers.py b/sickbeard/show_name_helpers.py index 9858ac19dd12ab6b666cb122e58575c1881ca094..9f3a9ea8bfd85f6341abec4d4f8726e9ed1d0bb5 100644 --- a/sickbeard/show_name_helpers.py +++ b/sickbeard/show_name_helpers.py @@ -200,9 +200,11 @@ def makeSceneSeasonSearchString(show, ep_obj, extraSearchType=None): # for providers that don't allow multiple searches in one request we only search for Sxx style stuff else: for cur_season in seasonStrings: - if len(show.release_groups.whitelist) > 0: - for keyword in show.release_groups.whitelist: - toReturn.append(keyword + '.' + curShow+ "." + cur_season) + if ep_obj.show.is_anime: + if ep_obj.show.release_groups is not None: + if len(show.release_groups.whitelist) > 0: + for keyword in show.release_groups.whitelist: + toReturn.append(keyword + '.' + curShow+ "." + cur_season) else: toReturn.append(curShow + "." + cur_season) @@ -237,9 +239,11 @@ def makeSceneSearchString(show, ep_obj): for curShow in showNames: for curEpString in epStrings: - if len(ep_obj.show.release_groups.whitelist) > 0: - for keyword in ep_obj.show.release_groups.whitelist: - toReturn.append(keyword + '.' + curShow + '.' + curEpString) + if ep_obj.show.is_anime: + if ep_obj.show.release_groups is not None: + if len(ep_obj.show.release_groups.whitelist) > 0: + for keyword in ep_obj.show.release_groups.whitelist: + toReturn.append(keyword + '.' + curShow + '.' + curEpString) else: toReturn.append(curShow + '.' + curEpString) diff --git a/tests/all_tests.py b/tests/all_tests.py index 747551c70df96d5a6b546818fdf46fd5ef9065a1..b20c0c35745b87dc98d73856639cdf10603b4f33 100755 --- a/tests/all_tests.py +++ b/tests/all_tests.py @@ -30,7 +30,7 @@ sys.path.insert(1, os.path.join(tests_dir, '..')) class AllTests(unittest.TestCase): #Block issue_submitter_tests to avoid issue tracker spam on every build #Block feedparser_tests because http://lolo.sickbeard.com/ has changed api, which makes the test fail - blacklist = [tests_dir + 'all_tests.py',tests_dir + 'issue_submitter_tests.py', tests_dir + 'feedparser_tests.py'] + blacklist = [tests_dir + 'all_tests.py', tests_dir + 'issue_submitter_tests.py'] def setUp(self): self.test_file_strings = [ x for x in glob.glob(tests_dir + '*_tests.py') if not x in self.blacklist ] self.module_strings = [file_string[len(tests_dir):len(file_string) - 3] for file_string in self.test_file_strings] diff --git a/tests/common_tests.py b/tests/common_tests.py index 5c01a8143d94046054ff152abdd4cc1374709eab..62922c84d61d8e152e89f965cd0ddc73dcc00a53 100644 --- a/tests/common_tests.py +++ b/tests/common_tests.py @@ -88,11 +88,11 @@ class QualityTests(unittest.TestCase): self.assertEqual(common.Quality.UNKNOWN, common.Quality.nameQuality("Test.Show.S01E02-SiCKBEARD")) def test_reverse_parsing(self): - self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test Show - S01E02 - SD TV - GROUP")) + self.assertEqual(common.Quality.SDTV, common.Quality.nameQuality("Test Show - S01E02 - SDTV - GROUP")) self.assertEqual(common.Quality.SDDVD, common.Quality.nameQuality("Test Show - S01E02 - SD DVD - GROUP")) - self.assertEqual(common.Quality.HDTV, common.Quality.nameQuality("Test Show - S01E02 - HD TV - GROUP")) - self.assertEqual(common.Quality.RAWHDTV, common.Quality.nameQuality("Test Show - S01E02 - RawHD TV - GROUP")) - self.assertEqual(common.Quality.FULLHDTV, common.Quality.nameQuality("Test Show - S01E02 - 1080p HD TV - GROUP")) + self.assertEqual(common.Quality.HDTV, common.Quality.nameQuality("Test Show - S01E02 - HDTV - GROUP")) + self.assertEqual(common.Quality.RAWHDTV, common.Quality.nameQuality("Test Show - S01E02 - RawHD - GROUP")) + self.assertEqual(common.Quality.FULLHDTV, common.Quality.nameQuality("Test Show - S01E02 - 1080p HDTV - GROUP")) self.assertEqual(common.Quality.HDWEBDL, common.Quality.nameQuality("Test Show - S01E02 - 720p WEB-DL - GROUP")) self.assertEqual(common.Quality.FULLHDWEBDL, common.Quality.nameQuality("Test Show - S01E02 - 1080p WEB-DL - GROUP")) self.assertEqual(common.Quality.HDBLURAY, common.Quality.nameQuality("Test Show - S01E02 - 720p BluRay - GROUP")) diff --git a/tests/ssl_sni_tests.py b/tests/ssl_sni_tests.py index 6156f7c448e90f48826761b013bca53af74c011d..cce6a23edbf0f96406c847ca6f10643c991ec85b 100644 --- a/tests/ssl_sni_tests.py +++ b/tests/ssl_sni_tests.py @@ -32,12 +32,21 @@ if sys.version_info < (2, 7, 9): try: import cryptography except ImportError: - enabled_sni = False + try: + from OpenSSL.version import __version__ as pyOpenSSL_Version + if int(pyOpenSSL_Version.replace('.', '')[:3]) > 13: + raise ImportError + except ImportError: + enabled_sni = False + class SNI_Tests(unittest.TestCase): def test_SNI_URLS(self): if not enabled_sni: - print("\nSNI is disabled when the cryptography module is missing, you may encounter SSL errors!") + print('\nSNI is disabled with pyOpenSSL >= 0.14 when the cryptography module is missing,\n' + + 'you will encounter SSL errors with HTTPS! To fix this issue:\n' + + 'pip install pyopenssl==0.13.1 (easy) or pip install cryptography (pita)') + print else: for provider in [ torrentday, rarbg, sceneaccess ]: #print 'Checking ' + provider.name