From 49cf1b53768caf146830c0eb57f3651535009221 Mon Sep 17 00:00:00 2001 From: night1rider Date: Wed, 9 Sep 2026 17:26:53 -0600 Subject: [PATCH] ocsp_nonblock: drop the curl shell command and rework both OCSP clients The plain client fetches the live www.digicert.com certificate itself and runs the non-blocking OCSP check with wolfSSL's own HTTP helpers, so no URL from a certificate ever reaches a shell. Both clients resolve the responder once, refuse loopback, private and link-local addresses, and connect to the address they checked. The cert fetch, the TCP connect, the handshake and the OCSP lookup each run non-blocking against a deadline so a stalled peer fails instead of hanging. The async client reads the OCSP reply in one parser call through a receive callback that waits against that deadline. The README and CI manifest gain a dedicated ocsp-nonblock build profile with the responder screening flag. --- .github/examples-manifest.yml | 17 +- .github/workflows/cert-expiry.yml | 4 + ocsp/ocsp_nonblock/README.md | 143 +-- .../ca_certs/DigiCert_EV_RSA_CA_G2.pem | 30 + .../ca_certs/DigiCert_Global_Root_G2.pem | 22 + ocsp/ocsp_nonblock/ca_certs/GTS_CA_1C3.pem | 32 - ocsp/ocsp_nonblock/ca_certs/GTS_Root_R1.pem | 31 - ocsp/ocsp_nonblock/google.pem | 27 - ocsp/ocsp_nonblock/ocsp_nonblock.c | 437 +++++++-- ocsp/ocsp_nonblock/ocsp_nonblock_async.c | 901 ++++++++++++------ 10 files changed, 1123 insertions(+), 521 deletions(-) create mode 100644 ocsp/ocsp_nonblock/ca_certs/DigiCert_EV_RSA_CA_G2.pem create mode 100644 ocsp/ocsp_nonblock/ca_certs/DigiCert_Global_Root_G2.pem delete mode 100644 ocsp/ocsp_nonblock/ca_certs/GTS_CA_1C3.pem delete mode 100644 ocsp/ocsp_nonblock/ca_certs/GTS_Root_R1.pem delete mode 100644 ocsp/ocsp_nonblock/google.pem diff --git a/.github/examples-manifest.yml b/.github/examples-manifest.yml index 1f0950d82..a1da412cd 100644 --- a/.github/examples-manifest.yml +++ b/.github/examples-manifest.yml @@ -204,6 +204,19 @@ profiles: --enable-ocsp-responder --enable-cert-setup-cb --enable-sessioncerts --enable-tls13 --enable-static --enable-shared + ocsp-nonblock: + # ocsp/ocsp_nonblock: two clients that fetch a live cert and check OCSP + # non-blocking. The plain one needs opensslextra+sni and KEEP_PEER_CERT + # (it calls wolfSSL_get_peer_certificate/X509_get_der); the async one adds + # asynccrypt, alpn and stapling. This profile mirrors the two README + # configure lines and adds tls13 to exercise the TLS 1.3 path against the + # live server -- neither client requires it (both still allow TLS 1.2). + flags: >- + --enable-ocsp --enable-ocspstapling --enable-ocspstapling2 + --enable-asynccrypt --enable-opensslextra --enable-sni --enable-alpn + --enable-tls13 --enable-static --enable-shared + cflags: "-DWOLFSSL_NONBLOCK_OCSP -DHAVE_IO_TIMEOUT -DKEEP_PEER_CERT -DWOLFSSL_OCSP_SCREEN_RESPONDER" + pkcs11: flags: "--enable-pkcs11 --enable-cryptocb --enable-static --enable-shared" @@ -635,9 +648,9 @@ examples: - id: ocsp-nonblock path: ocsp/ocsp_nonblock - profile: ocsp + profile: ocsp-nonblock mode: build-only - reason: "ocsp_nonblock_async connects to an external responder on :443" + reason: "both clients connect to a live OCSP responder on :443" - id: ocsp-responder path: ocsp/responder diff --git a/.github/workflows/cert-expiry.yml b/.github/workflows/cert-expiry.yml index 3472df2b4..77e643970 100644 --- a/.github/workflows/cert-expiry.yml +++ b/.github/workflows/cert-expiry.yml @@ -5,11 +5,13 @@ on: branches: [master] paths: - 'certs/**' + - 'ocsp/ocsp_nonblock/ca_certs/**' - '.github/workflows/cert-expiry.yml' pull_request: types: [opened, synchronize, reopened, ready_for_review] paths: - 'certs/**' + - 'ocsp/ocsp_nonblock/ca_certs/**' - '.github/workflows/cert-expiry.yml' # No cron: nightly.yml calls this, so the nightly stays one run and one triage writer workflow_call: @@ -38,3 +40,5 @@ jobs: - uses: actions/checkout@v5 - name: Warn on certs nearing expiry run: ./certs/check-expiry.sh certs + - name: Warn on example CA certs nearing expiry + run: ./certs/check-expiry.sh ocsp/ocsp_nonblock/ca_certs diff --git a/ocsp/ocsp_nonblock/README.md b/ocsp/ocsp_nonblock/README.md index e0a4fd0f5..ca7eb0af7 100644 --- a/ocsp/ocsp_nonblock/README.md +++ b/ocsp/ocsp_nonblock/README.md @@ -5,14 +5,20 @@ Online Certificate Status Protocol (OCSP) is used for obtaining the revocation s ## OCSP non-blocking example -This uses a google.com certificate chain to demonstrate validating revocation status using an OCSP server. +This connects to a public server, fetches its leaf certificate, and checks that +certificate's revocation status with a non-blocking OCSP lookup. The certificate +is fetched live so nothing static can expire. -The Google.com certificate defines OCSP in the X509v3 extension "Authority Information Access" section. +It uses `www.digicert.com` because that certificate still has an OCSP responder +(many CAs, including Google, have dropped OCSP). The bundled `ca_certs` hold the +DigiCert CAs used to verify it. Example usage: ```sh -$ ./configure --enable-ocsp CFLAGS="-DHAVE_IO_TIMEOUT -DWOLFSSL_NONBLOCK_OCSP" +$ ./configure --enable-ocsp --enable-opensslextra --enable-sni \ + CFLAGS="-DHAVE_IO_TIMEOUT -DWOLFSSL_NONBLOCK_OCSP -DKEEP_PEER_CERT \ + -DWOLFSSL_OCSP_SCREEN_RESPONDER" $ make $ sudo make install @@ -20,35 +26,56 @@ $ sudo make install gcc -o ocsp_nonblock ocsp_nonblock.c -Wall -I/usr/local/include -Os -L/usr/local/lib -lwolfssl % ./ocsp_nonblock +Fetched www.digicert.com certificate (1777 bytes) Loaded Trusted CA dir ca_certs (ret 1) -Convert Google.com PEM cert to DER (ret 1) -Verify Google.com cert: 1 +Verify www.digicert.com cert: 1 OCSP Lookup: - URL: http://ocsp.pki.goog/gts1c3 - Domain: ocsp.pki.goog - Path: /gts1c3 + URL: http://ocsp.digicert.com + Domain: ocsp.digicert.com + Path: / Port: 80 -OCSP Response: ret 471, nonblock count 409421 -Check OCSP for Google.com (ret 1) +OCSP Response: ret 471, nonblock count 1 +Resp ret: 471 +Check OCSP for www.digicert.com (ret 1) Ret = 1: success ``` +The `Ret = 1` line is wolfSSL's success code, not the exit status: the program +exits `0` on success and a non-zero error code on failure, so it works in scripts. + +If DigiCert's chain changes, pass an alternate CA directory as an argument, e.g. +`./ocsp_nonblock /etc/ssl/certs`. + +The certificate fetch (TCP connect plus TLS handshake) and the OCSP responder +connect each time out after 10 seconds. On a slow network, raise it at build +time with `CFLAGS="-DDEFAULT_TIMEOUT_SEC=30 ..."`. + +With `WOLFSSL_OCSP_SCREEN_RESPONDER` both clients refuse OCSP responder +addresses in loopback, private and link-local ranges, so a certificate cannot +point the lookup at an internal host. They resolve the responder once and +connect to the address they checked, so a changing DNS answer cannot get +around it. Leave the define out if your responder runs on such an address. + ## OCSP non-blocking Async example -This uses your system certificate chain to demonstrate validating revocation status using an OCSP -public server with wolfSSL's Asynchronous cryptography support. +This demonstrates validating revocation status using an OCSP public server with wolfSSL's +Asynchronous cryptography support. -**NOTE:** Before building this example the asynchronous support must be obtained from -(https://github.com/wolfSSL/wolfAsyncCrypt) and installed into wolfSSL by following the -instructions in the *README* file contained in the wolfAsyncCrypt repository. +The example uses `www.digicert.com`, whose certificate still has an OCSP +responder (many CAs, including Google, have dropped OCSP). -The example uses youtube.com as the public server. +The name lookups, the TCP connects, the TLS handshake and the OCSP lookup +inside it share one 15 second deadline (`HANDSHAKE_TIMEOUT_SEC` in the source). +The OCSP lookup is done with wolfSSL's HTTP helpers on a non-blocking socket so +it can be held to that deadline too. Name lookups use glibc's `getaddrinfo_a()`, +so this client builds on Linux. Example usage: ```sh $ ./configure --enable-ocsp --enable-asynccrypt --enable-sni --enable-alpn --enable-ocspstapling - --enable-ocspstapling2 --enable-opensslextra --enable-curve25519 CFLAGS="-DWOLFSSL_NONBLOCK_OCSP" + --enable-ocspstapling2 --enable-opensslextra \ + CFLAGS="-DWOLFSSL_NONBLOCK_OCSP -DHAVE_IO_TIMEOUT -DWOLFSSL_OCSP_SCREEN_RESPONDER" $ make $ sudo make install @@ -59,70 +86,48 @@ gcc -o ocsp_nonblock_async ocsp_nonblock_async.c -Wall -I/usr/local/include -Os WolfSSL AsyncCrypt Enabled WolfSSL AsyncCrypt with Simulation Mode Connecting... -wolfSSL_connect() returned -1 (error code -108) -wolfSSL_connect() returned -1 (error code -108) -wolfSSL_connect() returned -1 (error code -108) -ocsp_cb(): http://ocsp.pki.goog/gsr1 - simulate 'want read' -wolfSSL_connect() returned -1 (error code -408) -ocsp_cb(): http://ocsp.pki.goog/gsr1 -Running command: -curl -s --data-binary '@ocsp.req' -o 'ocsp.resp' -X POST -H 'Cache-Control: no-cache' -H 'Content-Type: application/ocsp-request' 'http://ocsp.pki.goog/gsr1' -Reading OCSP response from file... -Read 1447 bytes. -*response is (nil) -Allocating 1447 bytes... -*response is now 0x55ef7fdcb4e0 -Copying bytes... -Bytes copied. -verify_cb() - preverify_ok = 1 -wolfSSL_connect() returned -1 (error code -108) -ocsp_cb(): http://ocsp.pki.goog/gtsr1 - simulate 'want read' -wolfSSL_connect() returned -1 (error code -408) -ocsp_cb(): http://ocsp.pki.goog/gtsr1 -Running command: -curl -s --data-binary '@ocsp.req' -o 'ocsp.resp' -X POST -H 'Cache-Control: no-cache' -H 'Content-Type: application/ocsp-request' 'http://ocsp.pki.goog/gtsr1' -Reading OCSP response from file... -Read 724 bytes. -*response is (nil) -Allocating 724 bytes... -*response is now 0x55ef7fdaf030 -Copying bytes... -Bytes copied. verify_cb() preverify_ok = 1 -wolfSSL_connect() returned -1 (error code -108) -ocsp_cb(): http://ocsp.pki.goog/gts1c3 +ocsp_cb(): http://ocsp.digicert.com simulate 'want read' -wolfSSL_connect() returned -1 (error code -408) -ocsp_cb(): http://ocsp.pki.goog/gts1c3 -Running command: -curl -s --data-binary '@ocsp.req' -o 'ocsp.resp' -X POST -H 'Cache-Control: no-cache' -H 'Content-Type: application/ocsp-request' 'http://ocsp.pki.goog/gts1c3' -Reading OCSP response from file... -Read 472 bytes. -*response is (nil) -Allocating 472 bytes... -*response is now 0x55ef7fdac4b0 -Copying bytes... -Bytes copied. +ocsp_cb(): http://ocsp.digicert.com verify_cb() preverify_ok = 1 -wolfSSL_connect() returned -1 (error code -108) CONNECTED Closing connection... -wolfSSL_shutdown() failed with code 2 (error 0) CLOSED -test_connect() failed -CONNECT FAILED +CONNECT PASSED DONE ``` -The example uses `/etc/ssl/certs/ca-certificates.crt` as the system certs file by default. If your -system doesn't have this file, just run the executable with the path to your own cert file. +If you already have wolfSSL installed and do not want to change it, build a +local wolfSSL copy but skip `make install`, then compile the example straight +against that build (this leaves your existing install untouched): +```sh +# In your local wolfSSL source directory, build only (no install): +$ ./configure --enable-ocsp --enable-asynccrypt --enable-sni --enable-alpn \ + --enable-ocspstapling --enable-ocspstapling2 --enable-opensslextra \ + CFLAGS="-DWOLFSSL_NONBLOCK_OCSP -DHAVE_IO_TIMEOUT -DWOLFSSL_OCSP_SCREEN_RESPONDER" +$ make + +# Back in this example directory, point the compiler at that build: +$ WOLFSSL=/path/to/wolfssl +$ gcc -o ocsp_nonblock_async ocsp_nonblock_async.c -Wall -Os \ + -I$WOLFSSL -L$WOLFSSL/src/.libs -lwolfssl +$ LD_LIBRARY_PATH=$WOLFSSL/src/.libs ./ocsp_nonblock_async ``` -./ocsp_nonblock_asynccrypt ../../mycerts/ca.crt + +By default it loads the bundled `ca_certs/DigiCert_Global_Root_G2.pem` to check +digicert.com's certificate, so it runs on its own. digicert.com is a real server, +so its certificate can change. If the check fails, pass another cert file, such as +your system CA bundle: + +``` +./ocsp_nonblock_async /etc/ssl/certs/ca-certificates.crt ``` + +On success it prints `CONNECTED` and, at the end, `CONNECT PASSED`. The +`verify_cb` and `ocsp_cb` lines show the certificate check and OCSP lookup +running. diff --git a/ocsp/ocsp_nonblock/ca_certs/DigiCert_EV_RSA_CA_G2.pem b/ocsp/ocsp_nonblock/ca_certs/DigiCert_EV_RSA_CA_G2.pem new file mode 100644 index 000000000..5a38769b1 --- /dev/null +++ b/ocsp/ocsp_nonblock/ca_certs/DigiCert_EV_RSA_CA_G2.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFPDCCBCSgAwIBAgIQAWePH++IIlXYsKcOa3uyIDANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0yMDA3MDIxMjQyNTBaFw0zMDA3MDIxMjQyNTBaMEQxCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFURpZ2lDZXJ0IEVWIFJT +QSBDQSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK0eZsx/neTr +f4MXJz0R2fJTIDfN8AwUAu7hy4gI0vp7O8LAAHx2h3bbf8wl+pGMSxaJK9ffDDCD +63FqqFBqE9eTmo3RkgQhlu55a04LsXRLcK6crkBOO0djdonybmhrfGrtBqYvbRat +xenkv0Sg4frhRl4wYh4dnW0LOVRGhbt1G5Q19zm9CqMlq7LlUdAE+6d3a5++ppfG +cnWLmbEVEcLHPAnbl+/iKauQpQlU1Mi+wEBnjE5tK8Q778naXnF+DsedQJ7NEi+b +QoonTHEz9ryeEcUHuQTv7nApa/zCqes5lXn1pMs4LZJ3SVgbkTLj+RbBov/uiwTX +tkBEWawvZH8CAwEAAaOCAgswggIHMB0GA1UdDgQWBBRqTlC/mGidW3sgddRZAXlI +ZpIyBjAfBgNVHSMEGDAWgBROIlQgGJXm427mD/r6uRLtBhePOTAOBgNVHQ8BAf8E +BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQI +MAYBAf8CAQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz +cC5kaWdpY2VydC5jb20wewYDVR0fBHQwcjA3oDWgM4YxaHR0cDovL2NybDMuZGln +aWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA3oDWgM4YxaHR0cDov +L2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDCBzgYD +VR0gBIHGMIHDMIHABgRVHSAAMIG3MCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5k +aWdpY2VydC5jb20vQ1BTMIGKBggrBgEFBQcCAjB+DHxBbnkgdXNlIG9mIHRoaXMg +Q2VydGlmaWNhdGUgY29uc3RpdHV0ZXMgYWNjZXB0YW5jZSBvZiB0aGUgUmVseWlu +ZyBQYXJ0eSBBZ3JlZW1lbnQgbG9jYXRlZCBhdCBodHRwczovL3d3dy5kaWdpY2Vy +dC5jb20vcnBhLXVhMA0GCSqGSIb3DQEBCwUAA4IBAQBSMgrCdY2+O9spnYNvwHiG ++9lCJbyELR0UsoLwpzGpSdkHD7pVDDFJm3//B8Es+17T1o5Hat+HRDsvRr7d3MEy +o9iXkkxLhKEgApA2Ft2eZfPrTolc95PwSWnn3FZ8BhdGO4brTA4+zkPSKoMXi/X+ +WLBNN29Z/nbCS7H/qLGt7gViEvTIdU8x+H4l/XigZMUDaVmJ+B5d7cwSK7yOoQdf +oIBGmA5Mp4LhMzo52rf//kXPfE3wYIZVHqVuxxlnTkFYmffCX9/Lon7SWaGdg6Rc +k4RHhHLWtmz2lTZ5CEo2ljDsGzCFGJP7oT4q6Q8oFC38irvdKIJ95cUxYzj4tnOI +-----END CERTIFICATE----- diff --git a/ocsp/ocsp_nonblock/ca_certs/DigiCert_Global_Root_G2.pem b/ocsp/ocsp_nonblock/ca_certs/DigiCert_Global_Root_G2.pem new file mode 100644 index 000000000..798e00275 --- /dev/null +++ b/ocsp/ocsp_nonblock/ca_certs/DigiCert_Global_Root_G2.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- diff --git a/ocsp/ocsp_nonblock/ca_certs/GTS_CA_1C3.pem b/ocsp/ocsp_nonblock/ca_certs/GTS_CA_1C3.pem deleted file mode 100644 index 2acab7259..000000000 --- a/ocsp/ocsp_nonblock/ca_certs/GTS_CA_1C3.pem +++ /dev/null @@ -1,32 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFljCCA36gAwIBAgINAgO8U1lrNMcY9QFQZjANBgkqhkiG9w0BAQsFADBHMQsw -CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU -MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMjAwODEzMDAwMDQyWhcNMjcwOTMwMDAw -MDQyWjBGMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp -Y2VzIExMQzETMBEGA1UEAxMKR1RTIENBIDFDMzCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAPWI3+dijB43+DdCkH9sh9D7ZYIl/ejLa6T/belaI+KZ9hzp -kgOZE3wJCor6QtZeViSqejOEH9Hpabu5dOxXTGZok3c3VVP+ORBNtzS7XyV3NzsX -lOo85Z3VvMO0Q+sup0fvsEQRY9i0QYXdQTBIkxu/t/bgRQIh4JZCF8/ZK2VWNAcm -BA2o/X3KLu/qSHw3TT8An4Pf73WELnlXXPxXbhqW//yMmqaZviXZf5YsBvcRKgKA -gOtjGDxQSYflispfGStZloEAoPtR28p3CwvJlk/vcEnHXG0g/Zm0tOLKLnf9LdwL -tmsTDIwZKxeWmLnwi/agJ7u2441Rj72ux5uxiZ0CAwEAAaOCAYAwggF8MA4GA1Ud -DwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0T -AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUinR/r4XN7pXNPZzQ4kYU83E1HScwHwYD -VR0jBBgwFoAU5K8rJnEaK0gnhS9SZizv8IkTcT4waAYIKwYBBQUHAQEEXDBaMCYG -CCsGAQUFBzABhhpodHRwOi8vb2NzcC5wa2kuZ29vZy9ndHNyMTAwBggrBgEFBQcw -AoYkaHR0cDovL3BraS5nb29nL3JlcG8vY2VydHMvZ3RzcjEuZGVyMDQGA1UdHwQt -MCswKaAnoCWGI2h0dHA6Ly9jcmwucGtpLmdvb2cvZ3RzcjEvZ3RzcjEuY3JsMFcG -A1UdIARQME4wOAYKKwYBBAHWeQIFAzAqMCgGCCsGAQUFBwIBFhxodHRwczovL3Br -aS5nb29nL3JlcG9zaXRvcnkvMAgGBmeBDAECATAIBgZngQwBAgIwDQYJKoZIhvcN -AQELBQADggIBAIl9rCBcDDy+mqhXlRu0rvqrpXJxtDaV/d9AEQNMwkYUuxQkq/BQ -cSLbrcRuf8/xam/IgxvYzolfh2yHuKkMo5uhYpSTld9brmYZCwKWnvy15xBpPnrL -RklfRuFBsdeYTWU0AIAaP0+fbH9JAIFTQaSSIYKCGvGjRFsqUBITTcFTNvNCCK9U -+o53UxtkOCcXCb1YyRt8OS1b887U7ZfbFAO/CVMkH8IMBHmYJvJh8VNS/UKMG2Yr -PxWhu//2m+OBmgEGcYk1KCTd4b3rGS3hSMs9WYNRtHTGnXzGsYZbr8w0xNPM1IER -lQCh9BIiAfq0g3GvjLeMcySsN1PCAJA/Ef5c7TaUEDu9Ka7ixzpiO2xj2YC/WXGs -Yye5TBeg2vZzFb8q3o/zpWwygTMD0IZRcZk0upONXbVRWPeyk+gB9lm+cZv9TSjO -z23HFtz30dZGm6fKa+l3D/2gthsjgx0QGtkJAITgRNOidSOzNIb2ILCkXhAd4FJG -AJ2xDx8hcFH1mt0G/FX0Kw4zd8NLQsLxdxP8c4CU6x+7Nz/OAipmsHMdMqUybDKw -juDEI/9bfU1lcKwrmz3O2+BtjjKAvpafkmO8l7tdufThcV4q5O8DIrGKZTqPwJNl -1IXNDw9bg1kWRxYtnCQ6yICmJhSFm/Y3m6xv+cXDBlHz4n/FsRC6UfTd ------END CERTIFICATE----- \ No newline at end of file diff --git a/ocsp/ocsp_nonblock/ca_certs/GTS_Root_R1.pem b/ocsp/ocsp_nonblock/ca_certs/GTS_Root_R1.pem deleted file mode 100644 index e5aa68147..000000000 --- a/ocsp/ocsp_nonblock/ca_certs/GTS_Root_R1.pem +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFYjCCBEqgAwIBAgIQd70NbNs2+RrqIQ/E8FjTDTANBgkqhkiG9w0BAQsFADBX -MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE -CxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIwMDYx -OTAwMDA0MloXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoT -GUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFIx -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAthECix7joXebO9y/lD63 -ladAPKH9gvl9MgaCcfb2jH/76Nu8ai6Xl6OMS/kr9rH5zoQdsfnFl97vufKj6bwS -iV6nqlKr+CMny6SxnGPb15l+8Ape62im9MZaRw1NEDPjTrETo8gYbEvs/AmQ351k -KSUjB6G00j0uYODP0gmHu81I8E3CwnqIiru6z1kZ1q+PsAewnjHxgsHA3y6mbWwZ -DrXYfiYaRQM9sHmklCitD38m5agI/pboPGiUU+6DOogrFZYJsuB6jC511pzrp1Zk -j5ZPaK49l8KEj8C8QMALXL32h7M1bKwYUH+E4EzNktMg6TO8UpmvMrUpsyUqtEj5 -cuHKZPfmghCN6J3Cioj6OGaK/GP5Afl4/Xtcd/p2h/rs37EOeZVXtL0m79YB0esW -CruOC7XFxYpVq9Os6pFLKcwZpDIlTirxZUTQAs6qzkm06p98g7BAe+dDq6dso499 -iYH6TKX/1Y7DzkvgtdizjkXPdsDtQCv9Uw+wp9U7DbGKogPeMa3Md+pvez7W35Ei -Eua++tgy/BBjFFFy3l3WFpO9KWgz7zpm7AeKJt8T11dleCfeXkkUAKIAf5qoIbap -sZWwpbkNFhHax2xIPEDgfg1azVY80ZcFuctL7TlLnMQ/0lUTbiSw1nH69MG6zO0b -9f6BQdgAmD06yK56mDcYBZUCAwEAAaOCATgwggE0MA4GA1UdDwEB/wQEAwIBhjAP -BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTkrysmcRorSCeFL1JmLO/wiRNxPjAf -BgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzBgBggrBgEFBQcBAQRUMFIw -JQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnBraS5nb29nL2dzcjEwKQYIKwYBBQUH -MAKGHWh0dHA6Ly9wa2kuZ29vZy9nc3IxL2dzcjEuY3J0MDIGA1UdHwQrMCkwJ6Al -oCOGIWh0dHA6Ly9jcmwucGtpLmdvb2cvZ3NyMS9nc3IxLmNybDA7BgNVHSAENDAy -MAgGBmeBDAECATAIBgZngQwBAgIwDQYLKwYBBAHWeQIFAwIwDQYLKwYBBAHWeQIF -AwMwDQYJKoZIhvcNAQELBQADggEBADSkHrEoo9C0dhemMXoh6dFSPsjbdBZBiLg9 -NR3t5P+T4Vxfq7vqfM/b5A3Ri1fyJm9bvhdGaJQ3b2t6yMAYN/olUazsaL+yyEn9 -WprKASOshIArAoyZl+tJaox118fessmXn1hIVw41oeQa1v1vg4Fv74zPl6/AhSrw -9U5pCZEt4Wi4wStz6dTZ/CLANx8LZh1J7QJVj2fhMtfTJr9w4z30Z209fOU0iOMy -+qduBmpvvYuR7hZL6Dupszfnw0Skfths18dG9ZKb59UhvmaSGZRVbNQpsg3BZlvi -d0lIKO2d1xozclOzgjXPYovJJIultzkMu34qQb9Sz/yilrbCgj8= ------END CERTIFICATE----- \ No newline at end of file diff --git a/ocsp/ocsp_nonblock/google.pem b/ocsp/ocsp_nonblock/google.pem deleted file mode 100644 index 8d158776c..000000000 --- a/ocsp/ocsp_nonblock/google.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEhzCCA2+gAwIBAgIQOzdfWX2DYt0J79PlD6I4NTANBgkqhkiG9w0BAQsFADBG -MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM -QzETMBEGA1UEAxMKR1RTIENBIDFDMzAeFw0yMzA0MjQxMjAxMTZaFw0yMzA3MTcx -MjAxMTVaMBkxFzAVBgNVBAMTDnd3dy5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYI -KoZIzj0DAQcDQgAEjjE+XIQeTp+HYC7t1oTblWTyWA3fy8AdbKLYDxYvyCBhtU3o -hNe8xFj2yLkNzjjG5r/e+9pssNIL7///gh53JaOCAmcwggJjMA4GA1UdDwEB/wQE -AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW -BBRqOTqTkUjbMDNo/o2cnpSNng1IATAfBgNVHSMEGDAWgBSKdH+vhc3ulc09nNDi -RhTzcTUdJzBqBggrBgEFBQcBAQReMFwwJwYIKwYBBQUHMAGGG2h0dHA6Ly9vY3Nw -LnBraS5nb29nL2d0czFjMzAxBggrBgEFBQcwAoYlaHR0cDovL3BraS5nb29nL3Jl -cG8vY2VydHMvZ3RzMWMzLmRlcjAZBgNVHREEEjAQgg53d3cuZ29vZ2xlLmNvbTAh -BgNVHSAEGjAYMAgGBmeBDAECATAMBgorBgEEAdZ5AgUDMDwGA1UdHwQ1MDMwMaAv -oC2GK2h0dHA6Ly9jcmxzLnBraS5nb29nL2d0czFjMy9RT3ZKME4xc1QyQS5jcmww -ggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAAdgB6MoxU2LcttiDqOOBSHumEFnAyE4VN -O9IrwTpXo1LrUgAAAYezWq9QAAAEAwBHMEUCID7E4i4X0sXFwbQSmgi+aDvZw5Zc -D6hTicrkqA/3XlmrAiEA8qCimh33lfyW6879dtRbxZ2HTu7mZwD9BaitE2y9m44A -dgDoPtDaPvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYezWq8cAAAEAwBH -MEUCIQDt0g6qUANO5pRDTWlqVUlGvYHAavpVy1oTzaH54v2lnAIgLP5A/29sPUZX -/7txQxZyeUgnzoXidMvpvrjMkjHqL/IwDQYJKoZIhvcNAQELBQADggEBANalC0GR -lmC+Yt6aB1IG9WZn+i9lYmaoIhz1WbDF+GkX6WwMWCJZOd3P7Ul4of3DU8Ql1jhG -fpPvCtXa66Iuc9Ujzhs29Mbrunq7zdYjlB+X8yacFkUHBDkvXtr7em53BfHDHnwb -Sb5CIVm/E/HrfHeCBeK6JQJ4C8m25hUYYv5+Z2s6vDutnX+L0vwMFgE2dNX3Sh45 -rtjoG2AHUIkxR/ovAMPIy8hKuhag7yaFZO08us1cNlGXda448O3OTcVSZ+yuyHlu -cI4gZ5/W1Kem0gx3uvjnCttxZ4hR+mknD0dS+XK4sp5LNuc8pi1K40GzL3kHVK5M -suHVECOecBARAJ4= ------END CERTIFICATE----- diff --git a/ocsp/ocsp_nonblock/ocsp_nonblock.c b/ocsp/ocsp_nonblock/ocsp_nonblock.c index ba5c5ba22..bef7ccf38 100644 --- a/ocsp/ocsp_nonblock/ocsp_nonblock.c +++ b/ocsp/ocsp_nonblock/ocsp_nonblock.c @@ -20,6 +20,15 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA **/ +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -27,8 +36,11 @@ #include #include +/* Requires wolfSSL: --enable-ocsp --enable-opensslextra --enable-sni, + * CFLAGS -DWOLFSSL_NONBLOCK_OCSP -DHAVE_IO_TIMEOUT -DKEEP_PEER_CERT. */ static const char* kCACertsDir = "ca_certs"; /* put CA certs into this directory */ -static const char* kGoogleCom = "google.pem"; /* www.google.com */ +static const char* kServerHost = "www.digicert.com"; +static const word16 kServerPort = 443; #ifndef HTTP_SCRATCH_BUFFER_SIZE #define HTTP_SCRATCH_BUFFER_SIZE 512 @@ -37,7 +49,7 @@ static const char* kGoogleCom = "google.pem"; /* www.google.com */ #define MAX_URL_ITEM_SIZE 80 #endif #ifndef DEFAULT_TIMEOUT_SEC - #define DEFAULT_TIMEOUT_SEC 0 /* no timeout */ + #define DEFAULT_TIMEOUT_SEC 10 /* cert fetch / OCSP connect limit (sec) */ #endif static int io_timeout_sec = DEFAULT_TIMEOUT_SEC; @@ -50,6 +62,120 @@ static char domainName[MAX_URL_ITEM_SIZE]; static int nonBlockCnt = 0; static byte* httpBuf; +/* Switch fd to non-blocking so I/O on it can be driven against a deadline. + * Returns 0 on success, -1 on error. */ +static int set_nonblocking(SOCKET_T fd) +{ + int flags = fcntl(fd, F_GETFL, 0); + + if (flags == -1) { + return -1; + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) { + return -1; + } + return 0; +} + +/* Wait until fd is ready for the direction the caller needs, or until the + * deadline passes. Returns 0 when ready, -1 on timeout or error. */ +static int wait_for_socket(SOCKET_T fd, int wantWrite, time_t deadline) +{ + struct pollfd pfd; + time_t now = time(NULL); + int ret; + + if (now >= deadline) { + return -1; + } + pfd.fd = fd; + pfd.events = wantWrite ? POLLOUT : POLLIN; + pfd.revents = 0; + ret = poll(&pfd, 1, (int)(deadline - now) * 1000); + return (ret > 0) ? 0 : -1; +} + +#ifdef WOLFSSL_OCSP_SCREEN_RESPONDER +/* 1 if the IPv4 address is in a loopback, private, link-local or reserved + * range that an OCSP responder must not live in, else 0. */ +static int ipv4_blocked(const struct in_addr* in) +{ + const unsigned char* a = (const unsigned char*)&in->s_addr; + + return (a[0] == 0) || /* 0.0.0.0/8 */ + (a[0] == 127) || /* loopback */ + (a[0] == 10) || /* 10/8 */ + (a[0] == 172 && (a[1] & 0xF0) == 16) || /* 172.16/12 */ + (a[0] == 192 && a[1] == 168) || /* 192.168/16 */ + (a[0] == 169 && a[1] == 254) || /* link-local */ + (a[0] == 100 && (a[1] & 0xC0) == 64) || /* 100.64/10 */ + (a[0] >= 224); /* multicast, reserved */ +} +#endif + +/* Resolve host once, screen every address it returned, then connect straight + * to an accepted address within timeoutSec. Connecting to the address rather + * than the name again means a changing DNS answer cannot slip a blocked + * address past the screen. Returns 0 with *fd set, -1 on failure. */ +static int connect_responder(const char* host, word16 port, int timeoutSec, + SOCKET_T* fd) +{ + struct addrinfo hints; + struct addrinfo* res = NULL; + struct addrinfo* ai; + char portStr[6]; + time_t deadline = time(NULL) + timeoutSec; + int s = -1; + + snprintf(portStr, sizeof(portStr), "%u", (unsigned int)port); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(host, portStr, &hints, &res) != 0) { + printf("Unable to resolve OCSP responder %s\n", host); + return -1; + } + +#ifdef WOLFSSL_OCSP_SCREEN_RESPONDER + /* Reject the responder if any of its addresses is off limits. */ + for (ai = res; ai != NULL; ai = ai->ai_next) { + if (ipv4_blocked(&((struct sockaddr_in*)ai->ai_addr)->sin_addr)) { + printf("OCSP responder %s not permitted\n", host); + freeaddrinfo(res); + return -1; + } + } +#endif + + for (ai = res; ai != NULL && s == -1; ai = ai->ai_next) { + int err = 0; + socklen_t errLen = sizeof(err); + + s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (s == -1) { + continue; + } + if (set_nonblocking(s) == 0 && + (connect(s, ai->ai_addr, ai->ai_addrlen) == 0 || + (errno == EINPROGRESS && + wait_for_socket(s, 1, deadline) == 0 && + getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &errLen) == 0 && + err == 0))) { + break; + } + close(s); + s = -1; + } + freeaddrinfo(res); + + if (s == -1) { + printf("OCSP responder connection failed\n"); + return -1; + } + *fd = s; + return 0; +} + /* Return size of the OCSP response or negative for error */ static int OcspLookupNonBlockCb(void* ctx, const char* url, int urlSz, byte* ocspReqBuf, int ocspReqSz, byte** ocspRespBuf) @@ -96,12 +222,10 @@ static int OcspLookupNonBlockCb(void* ctx, const char* url, int urlSz, httpBufSz = wolfIO_HttpBuildRequestOcsp(domainName, path, ocspReqSz, httpBuf, httpBufSz); - ret = wolfIO_TcpConnect(&sfd, domainName, port, io_timeout_sec); + /* The socket comes back non-blocking, which the response read + * below relies on to report OCSP_WANT_READ. */ + ret = connect_responder(domainName, port, io_timeout_sec, &sfd); if (ret == 0) { - #if defined(WOLFSSL_NONBLOCK_OCSP) && defined(HAVE_IO_TIMEOUT) - wolfIO_SetBlockingMode(sfd, 1); /* non-blocking */ - #endif - if (wolfIO_Send(sfd, (char*)httpBuf, httpBufSz, 0) != httpBufSz) { printf("OCSP http request failed\n"); @@ -112,13 +236,13 @@ static int OcspLookupNonBlockCb(void* ctx, const char* url, int urlSz, } else { do { - ret = wolfIO_HttpProcessResponseOcsp(sfd, ocspRespBuf, + ret = wolfIO_HttpProcessResponseOcsp(sfd, ocspRespBuf, httpBuf, HTTP_SCRATCH_BUFFER_SIZE, NULL); nonBlockCnt++; if (ret == OCSP_WANT_READ) return WOLFSSL_CBIO_ERR_WANT_READ; } while (ret == OCSP_WANT_READ); - printf("OCSP Response: ret %d, nonblock count %d\n", + printf("OCSP Response: ret %d, nonblock count %d\n", ret, nonBlockCnt); } } @@ -145,92 +269,257 @@ static void OcspRespFreeCb(void* ctx, byte *resp) (void)ctx; } +/* Duplicate an X509's DER encoding into a newly allocated buffer sized to the + * cert. Returns it (caller XFREEs) and sets *derSz, or NULL on error. */ +static byte* dup_x509_der(WOLFSSL_X509* x509, int* derSz) +{ + int sz = 0; + const byte* d = wolfSSL_X509_get_der(x509, &sz); + byte* copy; + + if (d == NULL) { + return NULL; + } + if (sz <= 0) { + return NULL; + } + copy = (byte*)XMALLOC(sz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (copy == NULL) { + return NULL; + } + XMEMCPY(copy, d, sz); + *derSz = sz; + return copy; +} + +/* Drive wolfSSL_connect() on a non-blocking socket until it finishes or the + * deadline passes. wolfIO_TcpConnect() only times out the TCP connect, and + * per-call socket timeouts can be kept alive by a peer that trickles data, so + * the whole handshake is held to one absolute deadline instead. Returns + * WOLFSSL_SUCCESS, or the wolfSSL_connect() result with *timedOut set when the + * deadline passed. */ +static int connect_with_deadline(WOLFSSL* ssl, SOCKET_T fd, int timeoutSec, + int* timedOut) +{ + time_t deadline = time(NULL) + timeoutSec; + int ret; + int err; + + *timedOut = 0; + for (;;) { + ret = wolfSSL_connect(ssl); + if (ret == WOLFSSL_SUCCESS) { + break; + } + err = wolfSSL_get_error(ssl, ret); + if (err != WOLFSSL_ERROR_WANT_READ && err != WOLFSSL_ERROR_WANT_WRITE) { + break; + } + if (wait_for_socket(fd, err == WOLFSSL_ERROR_WANT_WRITE, deadline) + != 0) { + *timedOut = 1; + break; + } + } + return ret; +} + +/* Open a TLS connection to host:tlsPort and return the peer's leaf certificate + * DER in a newly allocated buffer (caller XFREEs), or NULL on error. */ +static byte* fetch_leaf_der(const char* host, word16 tlsPort, const char* caDir, + int* derSz) +{ + WOLFSSL_CTX* ctx; + WOLFSSL* ssl; + WOLFSSL_X509* peer; + SOCKET_T fd = SOCKET_INVALID; + byte* der; + int ret; + int timedOut; + + ctx = wolfSSL_CTX_new(wolfSSLv23_client_method()); + if (ctx == NULL) { + printf("Unable to create client context\n"); + return NULL; + } + /* Verify the peer during this fetch so we get the genuine host cert; an + * unverified fetch would let an attacker swap in any CA-issued cert. */ + wolfSSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + if (wolfSSL_CTX_load_verify_locations(ctx, NULL, caDir) + != WOLFSSL_SUCCESS) { + printf("Unable to load CA certs from %s\n", caDir); + wolfSSL_CTX_free(ctx); + return NULL; + } + + if (wolfIO_TcpConnect(&fd, host, tlsPort, io_timeout_sec) != 0) { + printf("TCP connect to %s failed\n", host); + wolfSSL_CTX_free(ctx); + return NULL; + } + if (set_nonblocking(fd) != 0) { + printf("Unable to make socket non-blocking\n"); + CloseSocket(fd); + wolfSSL_CTX_free(ctx); + return NULL; + } + + ssl = wolfSSL_new(ctx); + if (ssl == NULL) { + printf("Unable to create SSL object\n"); + CloseSocket(fd); + wolfSSL_CTX_free(ctx); + return NULL; + } + wolfSSL_set_fd(ssl, fd); + + if (wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, host, + (word16)XSTRLEN(host)) != WOLFSSL_SUCCESS) { + printf("wolfSSL_UseSNI failed\n"); + wolfSSL_free(ssl); + CloseSocket(fd); + wolfSSL_CTX_free(ctx); + return NULL; + } + + /* Bind the cert to the hostname; a chain check alone is not enough. */ + if (wolfSSL_check_domain_name(ssl, host) != WOLFSSL_SUCCESS) { + printf("wolfSSL_check_domain_name failed\n"); + wolfSSL_free(ssl); + CloseSocket(fd); + wolfSSL_CTX_free(ctx); + return NULL; + } + + ret = connect_with_deadline(ssl, fd, io_timeout_sec, &timedOut); + if (ret != WOLFSSL_SUCCESS) { + if (timedOut) { + printf("TLS connect to %s timed out after %d seconds\n", host, + io_timeout_sec); + } + else { + printf("TLS connect to %s failed (%d)\n", host, + wolfSSL_get_error(ssl, ret)); + } + wolfSSL_free(ssl); + CloseSocket(fd); + wolfSSL_CTX_free(ctx); + return NULL; + } + + peer = wolfSSL_get_peer_certificate(ssl); + if (peer == NULL) { + printf("No peer certificate\n"); + wolfSSL_free(ssl); + CloseSocket(fd); + wolfSSL_CTX_free(ctx); + return NULL; + } + + der = dup_x509_der(peer, derSz); + if (der == NULL) { + printf("Peer certificate DER unavailable\n"); + } + + wolfSSL_shutdown(ssl); /* clean TLS close before teardown */ + wolfSSL_FreeX509(peer); + wolfSSL_free(ssl); + CloseSocket(fd); + wolfSSL_CTX_free(ctx); + return der; +} + int main(int argc, char** argv) { int ret; WOLFSSL_CERT_MANAGER* pCm; - char pem[2048]; - int pemSz = 0; - byte der[2000]; + /* Allocated by fetch_leaf_der to the exact cert size; freed below. */ + byte* der = NULL; int derSz = 0; - FILE* file; - const char* certFile = kGoogleCom; + const char* caDir = kCACertsDir; #if 0 wolfSSL_Debugging_ON(); #endif + /* Optional argv[1] overrides the CA directory if the chain changes. */ if (argc > 1) { - certFile = argv[1]; + caDir = argv[1]; } + ret = wolfSSL_Init(); + if (ret != WOLFSSL_SUCCESS) { + printf("wolfSSL_Init failed (%d)\n", ret); + return ret; + } + + /* Fetch the live leaf certificate so nothing static can expire. */ + der = fetch_leaf_der(kServerHost, kServerPort, caDir, &derSz); + if (der == NULL) { + printf("Could not fetch %s certificate\n", kServerHost); + wolfSSL_Cleanup(); + return -1; + } + printf("Fetched %s certificate (%d bytes)\n", kServerHost, derSz); + /* Create certificate manager context */ pCm = wolfSSL_CertManagerNew(); - if (pCm) { - #ifdef HAVE_OCSP - /* Enable OCSP */ - ret = wolfSSL_CertManagerEnableOCSP(pCm, 0); - if (ret == WOLFSSL_SUCCESS) { - /* Setup callbacks for OCSP */ - ret = wolfSSL_CertManagerSetOCSP_Cb(pCm, - OcspLookupNonBlockCb, - OcspRespFreeCb, - NULL /* optional context */ - ); - } - #else - ret = WOLFSSL_SUCCESS; - #endif - if (ret == WOLFSSL_SUCCESS) { - /* Load root CAs into Certificate Manager */ - ret = wolfSSL_CertManagerLoadCA(pCm, NULL, kCACertsDir); - printf("Loaded Trusted CA dir %s (ret %d)\n", kCACertsDir, ret); - } - if (ret == WOLFSSL_SUCCESS) { - /* Load PEM to buffer */ - file = fopen(certFile, "rb"); - if (file != NULL) { - pemSz = fread(pem, 1, sizeof(pem), file); - fclose(file); - } + if (pCm == NULL) { + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_Cleanup(); + return MEMORY_E; + } - /* Convert certificate to DER/ASN.1 */ - ret = wc_CertPemToDer( - (byte*)pem, pemSz, - der, sizeof(der), CERT_TYPE); - if (ret >= 0) { - derSz = ret; - ret = WOLFSSL_SUCCESS; - } - printf("Convert Google.com PEM cert to DER (ret %d)\n", ret); - } - if (ret == WOLFSSL_SUCCESS) { - /* Load and verify certificate */ - ret = wolfSSL_CertManagerVerifyBuffer(pCm, - der, derSz, WOLFSSL_FILETYPE_ASN1); - printf("Verify Google.com cert: %d\n", ret); - } - #ifdef HAVE_OCSP - if (ret == WOLFSSL_SUCCESS) { - /* Check OCSP for certificate */ - do { - ret = wolfSSL_CertManagerCheckOCSP(pCm, - der, derSz); - } while (ret == OCSP_WANT_READ); - printf("Check OCSP for Google.com (ret %d)\n", ret); - } - #endif +#ifdef HAVE_OCSP + /* Enable OCSP */ + ret = wolfSSL_CertManagerEnableOCSP(pCm, 0); + if (ret == WOLFSSL_SUCCESS) { + /* Setup callbacks for OCSP */ + ret = wolfSSL_CertManagerSetOCSP_Cb(pCm, + OcspLookupNonBlockCb, + OcspRespFreeCb, + NULL /* optional context */ + ); + } +#else + ret = WOLFSSL_SUCCESS; +#endif - wolfSSL_CertManagerFree(pCm); + if (ret == WOLFSSL_SUCCESS) { + /* Load root CAs into Certificate Manager */ + ret = wolfSSL_CertManagerLoadCA(pCm, NULL, caDir); + printf("Loaded Trusted CA dir %s (ret %d)\n", caDir, ret); } - else { - ret = MEMORY_E; + + if (ret == WOLFSSL_SUCCESS) { + /* Verify the fetched certificate against the loaded CAs */ + ret = wolfSSL_CertManagerVerifyBuffer(pCm, der, derSz, + WOLFSSL_FILETYPE_ASN1); + printf("Verify %s cert: %d\n", kServerHost, ret); + } + +#ifdef HAVE_OCSP + if (ret == WOLFSSL_SUCCESS) { + /* Check OCSP for the certificate */ + do { + ret = wolfSSL_CertManagerCheckOCSP(pCm, der, derSz); + } while (ret == OCSP_WANT_READ); + printf("Check OCSP for %s (ret %d)\n", kServerHost, ret); } +#endif + + wolfSSL_CertManagerFree(pCm); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_Cleanup(); - printf("Ret = %d: %s\n", - ret, (ret == WOLFSSL_SUCCESS) ? - "success" : + printf("Ret = %d: %s\n", + ret, (ret == WOLFSSL_SUCCESS) ? + "success" : wc_GetErrorString(ret)); + /* Exit 0 on success so scripts and CI can tell pass from fail. */ + if (ret == WOLFSSL_SUCCESS) { + return 0; + } return ret; } diff --git a/ocsp/ocsp_nonblock/ocsp_nonblock_async.c b/ocsp/ocsp_nonblock/ocsp_nonblock_async.c index 2a363f4ad..fef256d7a 100644 --- a/ocsp/ocsp_nonblock/ocsp_nonblock_async.c +++ b/ocsp/ocsp_nonblock/ocsp_nonblock_async.c @@ -19,47 +19,242 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA **/ +#define _GNU_SOURCE /* getaddrinfo_a() */ + #include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include #include #include -#include -#define SERVER_NAME "www.youtube.com" -#define SERVER_IP "172.217.18.14" +#define SERVER_NAME "www.digicert.com" #define SERVER_PORT 443 #define ALPN_PROTOS "http/1.1" +#define HANDSHAKE_TIMEOUT_SEC 15 /* connect + handshake + OCSP lookup limit */ +#ifndef HTTP_SCRATCH_BUFFER_SIZE + #define HTTP_SCRATCH_BUFFER_SIZE 512 +#endif +#ifndef MAX_URL_ITEM_SIZE + #define MAX_URL_ITEM_SIZE 80 +#endif #if defined(WOLFSSL_ASYNC_CRYPT) && defined(HAVE_SNI) && defined(HAVE_ALPN) \ && defined(WOLFSSL_NONBLOCK_OCSP) && defined(HAVE_CERTIFICATE_STATUS_REQUEST) \ && defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) -static const char* sys_certs_file = "/etc/ssl/certs/ca-certificates.crt"; +/* Bundled root for digicert.com so the demo runs standalone. Pass a cert + * file as argv[1] if that chain changes. */ +static const char* sys_certs_file = "ca_certs/DigiCert_Global_Root_G2.pem"; -#if defined(WOLFSSL_ASYNC_CRYPT) -static int wait_async(WOLFSSL_CTX* ctx, WOLFSSL* ssl) +/* One absolute deadline for the whole connection attempt: the TCP connect, the + * TLS handshake and the OCSP responder lookup inside it. */ +static time_t deadline; + +/* Seconds left until the deadline, or 0 when it has passed. */ +static int remaining_sec(void) { - WOLF_EVENT* wolfEvent; - int n = 0; - do { - if (wolfSSL_CTX_AsyncPoll(ctx, &wolfEvent, 1, WOLF_POLL_FLAG_CHECK_HW, &n) != 0) { - fprintf(stderr, "error calling wolfSSL_CTX_AsyncPoll()\n"); - return -1; + time_t now = time(NULL); + return (now < deadline) ? (int)(deadline - now) : 0; +} + +/* Wait until sockfd is ready for the given poll events or the deadline passes. + * Returns 0 when ready, -1 on timeout or error. */ +static int wait_for_socket(int sockfd, short events) +{ + struct pollfd pfd; + int remain = remaining_sec(); + int ret; + + if (remain == 0) { + return -1; + } + pfd.fd = sockfd; + pfd.events = events; + pfd.revents = 0; + ret = poll(&pfd, 1, remain * 1000); + return (ret > 0) ? 0 : -1; +} + +/* Everything an in-flight lookup refers to, in one heap block. If the lookup + * cannot be cancelled it keeps using this after resolve_with_deadline() + * returns, so nothing here may live on the caller's stack. */ +struct resolve_req { + struct gaicb req; + struct addrinfo hints; + char host[MAX_URL_ITEM_SIZE]; + char service[6]; +}; + +/* Resolve host:service within the deadline. getaddrinfo() has no timeout, so + * use glibc's asynchronous resolver and wait only for the time that is left. + * Returns 0 with *res set (caller frees), -1 on failure. */ +static int resolve_with_deadline(const char* host, const char* service, + struct addrinfo** res) +{ + struct resolve_req* r; + struct gaicb* list[1]; + struct timespec timeout; + int ret; + + r = calloc(1, sizeof(*r)); + if (r == NULL) { + fprintf(stderr, "out of memory\n"); + return -1; + } + if (snprintf(r->host, sizeof(r->host), "%s", host) >= (int)sizeof(r->host) + || snprintf(r->service, sizeof(r->service), "%s", service) + >= (int)sizeof(r->service)) { + fprintf(stderr, "host or port too long\n"); + free(r); + return -1; + } + r->hints.ai_family = AF_INET; + r->hints.ai_socktype = SOCK_STREAM; + r->req.ar_name = r->host; + r->req.ar_service = r->service; + r->req.ar_request = &r->hints; + list[0] = &r->req; + + if (getaddrinfo_a(GAI_NOWAIT, list, 1, NULL) != 0) { + fprintf(stderr, "getaddrinfo_a() failed\n"); + free(r); + return -1; + } + + timeout.tv_sec = remaining_sec(); + timeout.tv_nsec = 0; + ret = gai_suspend((const struct gaicb* const*)list, 1, &timeout); + if (ret != 0) { + fprintf(stderr, "failed to resolve %s within %d seconds\n", host, + HANDSHAKE_TIMEOUT_SEC); + ret = gai_cancel(&r->req); + if (ret == EAI_ALLDONE && gai_error(&r->req) == 0) { + freeaddrinfo(r->req.ar_result); + } + if (ret != EAI_NOTCANCELED) { + free(r); } - } while (n == 0); - assert(n == 1); - assert(ssl == (WOLFSSL*)wolfEvent->context); + /* else still running: the block stays with it for good */ + return -1; + } + + ret = gai_error(&r->req); + if (ret != 0) { + fprintf(stderr, "failed to resolve %s: %s\n", host, gai_strerror(ret)); + free(r); + return -1; + } + *res = r->req.ar_result; + free(r); + return 0; +} + +/* Make sockfd non-blocking. Returns 0 on success, -1 on failure. */ +static int set_nonblocking(int sockfd) +{ + int flags = fcntl(sockfd, F_GETFL, 0); + + if (flags == -1) { + fprintf(stderr, "fcntl(F_GETFL) failed\n"); + return -1; + } + if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) != 0) { + fprintf(stderr, "fcntl(F_SETFL) failed\n"); + return -1; + } return 0; } -#endif + +/* Connect a non-blocking socket, waiting for completion against the deadline. + * Returns 0 on success, -1 on failure. */ +static int connect_nonblocking(int sockfd, const struct sockaddr* addr, + socklen_t addrLen) +{ + int err = 0; + socklen_t errLen = sizeof(err); + + if (connect(sockfd, addr, addrLen) == 0) { + return 0; + } + if (errno != EINPROGRESS) { + return -1; + } + if (wait_for_socket(sockfd, POLLOUT) != 0) { + return -1; + } + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &errLen) != 0) { + return -1; + } + return (err == 0) ? 0 : -1; +} + +/* Resolve SERVER_NAME and connect a non-blocking socket within the deadline. + * Returns the connected socket, or -1 on failure. */ +static int open_socket(void) +{ + struct addrinfo* res = NULL; + struct addrinfo* ai; + char portStr[6]; + int sockfd = -1; + int ret; + + ret = snprintf(portStr, sizeof(portStr), "%d", SERVER_PORT); + if (ret < 0) { + fprintf(stderr, "failed to format port %d\n", SERVER_PORT); + return -1; + } + if ((size_t)ret >= sizeof(portStr)) { + fprintf(stderr, "failed to format port %d\n", SERVER_PORT); + return -1; + } + + /* Resolve the server by name so it keeps working as the IP changes. */ + if (resolve_with_deadline(SERVER_NAME, portStr, &res) != 0) { + return -1; + } + + for (ai = res; ai != NULL; ai = ai->ai_next) { + sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sockfd == -1) { + continue; + } + /* Go non-blocking before connect() so the connect itself is held to + * the deadline rather than the kernel's own timeout. */ + if (set_nonblocking(sockfd) == 0 && + connect_nonblocking(sockfd, ai->ai_addr, ai->ai_addrlen) == 0) { + break; + } + if (close(sockfd) != 0) { + fprintf(stderr, "warning: failed to close socket\n"); + } + sockfd = -1; + } + freeaddrinfo(res); + + if (sockfd == -1) { + fprintf(stderr, "failed to connect to %s:%d within %d seconds\n", + SERVER_NAME, SERVER_PORT, HANDSHAKE_TIMEOUT_SEC); + return -1; + } + + return sockfd; +} static int verify_cb(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) { @@ -74,201 +269,305 @@ static int verify_cb(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) return preverify_ok; } -static int ocsp_cb(void* ctx, const char* url, int urlSz, unsigned char* request, int requestSz, unsigned char** response) +#ifdef WOLFSSL_OCSP_SCREEN_RESPONDER +/* 1 if the IPv4 address is in a loopback, private, link-local or reserved + * range that an OCSP responder must not live in, else 0. */ +static int ipv4_blocked(const struct in_addr* in) +{ + const unsigned char* a = (const unsigned char*)&in->s_addr; + + return (a[0] == 0) || /* 0.0.0.0/8 */ + (a[0] == 127) || /* loopback */ + (a[0] == 10) || /* 10/8 */ + (a[0] == 172 && (a[1] & 0xF0) == 16) || /* 172.16/12 */ + (a[0] == 192 && a[1] == 168) || /* 192.168/16 */ + (a[0] == 169 && a[1] == 254) || /* link-local */ + (a[0] == 100 && (a[1] & 0xC0) == 64) || /* 100.64/10 */ + (a[0] >= 224); /* multicast, reserved */ +} +#endif + +/* Resolve the responder once within the deadline, screen every address it + * returned, then connect straight to an accepted address. Connecting to the + * address rather than the name again means a changing DNS answer cannot slip + * a blocked address past the screen. Returns the socket, or -1 on failure. */ +static int connect_responder(const char* host, word16 port) { - printf("ocsp_cb(): %s\n", url); + struct addrinfo* res = NULL; + struct addrinfo* ai; + char portStr[6]; + int sockfd = -1; + + snprintf(portStr, sizeof(portStr), "%u", (unsigned int)port); + if (resolve_with_deadline(host, portStr, &res) != 0) { + return -1; + } - if (access("ocsp.req", F_OK) == 0) { - /* file exists, delete it and return (in order to simulate that it needs read I/O) */ - if (remove("ocsp.req") != 0) { +#ifdef WOLFSSL_OCSP_SCREEN_RESPONDER + /* Reject the responder if any of its addresses is off limits. */ + for (ai = res; ai != NULL; ai = ai->ai_next) { + if (ipv4_blocked(&((struct sockaddr_in*)ai->ai_addr)->sin_addr)) { + fprintf(stderr, " OCSP responder %s not permitted\n", host); + freeaddrinfo(res); return -1; } - printf(" simulate 'want read'\n"); - return WOLFSSL_CBIO_ERR_WANT_READ; - } else { - /* file doesn't exist, proceed */ } +#endif - FILE *frq = fopen("ocsp.req", "wb"); - if (frq != NULL) { - size_t nbytes = fwrite(request, 1, requestSz, frq); - if (requestSz != nbytes) { - printf("Failed to write all data. Wrote only %zu bytes.\n", nbytes); + for (ai = res; ai != NULL; ai = ai->ai_next) { + sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sockfd == -1) { + continue; } - fclose(frq); - frq = NULL; - } else { - return -1; + if (set_nonblocking(sockfd) == 0 && + connect_nonblocking(sockfd, ai->ai_addr, ai->ai_addrlen) == 0) { + break; + } + close(sockfd); + sockfd = -1; } + freeaddrinfo(res); - char cmd[1024]; - snprintf(cmd, sizeof(cmd), "curl -s --data-binary '@ocsp.req' -o 'ocsp.resp' -X POST -H 'Cache-Control: no-cache' -H 'Content-Type: application/ocsp-request' '%s'", url); - printf("Running command:\n%s\n", cmd); - int ret = system(cmd); - if (ret == 0) { - FILE *frsp = fopen("ocsp.resp", "rb"); - if (frsp != NULL) { - printf("Reading OCSP response from file...\n"); - char resp[4096]; - size_t nbytes = fread(resp, 1, sizeof(resp), frsp); - printf("Read %zu bytes.\n", nbytes); - fclose(frsp); - frsp = NULL; - - printf("*response is %p\n", *response); - printf("Allocating %zu bytes...\n", nbytes); - *response = malloc(nbytes); - if (*response == NULL) { - printf("malloc() failed\n"); - return -1; - } - printf("*response is now %p\n", *response); - printf("Copying bytes...\n"); - memcpy(*response, resp, nbytes); - printf("Bytes copied.\n"); + if (sockfd == -1) { + fprintf(stderr, " OCSP responder %s connection failed\n", host); + } + return sockfd; +} - return nbytes; +/* Send all of buf on a non-blocking socket, waiting for room against the + * deadline. Returns 0 on success, -1 on failure. */ +static int send_all(int sockfd, const unsigned char* buf, int sz) +{ + int sent = 0; + + while (sent < sz) { + int ret; + + if (wait_for_socket(sockfd, POLLOUT) != 0) { + return -1; } - return -1; - } else { - printf("Command failed with error code '%d'.\n", ret); + ret = wolfIO_Send(sockfd, (char*)buf + sent, sz - sent, 0); + if (ret < 0) { + return -1; + } + sent += ret; } - - return -1; + return 0; } -static void ocsp_free(void* ctx, unsigned char* response) +/* Receive callback for the OCSP response parser: wait for data against the + * deadline, then read. Returns the byte count, or a negative error. */ +static int recv_with_deadline(char* buf, int sz, void* ctx) { - (void)ctx; - if (response) { - free(response); + int sockfd = *(int*)ctx; + ssize_t ret; + + if (wait_for_socket(sockfd, POLLIN) != 0) { + fprintf(stderr, " OCSP lookup timed out\n"); + return WOLFSSL_CBIO_ERR_TIMEOUT; } + ret = recv(sockfd, buf, (size_t)sz, 0); + return (ret < 0) ? WOLFSSL_CBIO_ERR_GENERAL : (int)ret; } -int test_connect(WOLFSSL_CTX* ctx) +/* Fetch the OCSP response over HTTP with wolfSSL's helpers, holding every step + * to the shared deadline. Returns the response size, or a negative error. */ +static int ocsp_lookup(const char* url, int urlSz, unsigned char* request, + int requestSz, unsigned char** response) { - int ret; - int errCode; - char errBuff[WOLFSSL_MAX_ERROR_SZ]; - + char domainName[MAX_URL_ITEM_SIZE]; + char path[MAX_URL_ITEM_SIZE]; + word16 port = 0; + unsigned char* httpBuf; + int httpBufSz = HTTP_SCRATCH_BUFFER_SIZE; int sockfd; - struct sockaddr_in servAddr; - - int result = 0; - - memset(&servAddr, 0, sizeof(servAddr)); + int ret = WOLFSSL_CBIO_ERR_GENERAL; - servAddr.sin_family = AF_INET; - servAddr.sin_port = htons(SERVER_PORT); - - if (inet_pton(AF_INET, SERVER_IP, &servAddr.sin_addr) != 1) { - fprintf(stderr, "invalid address\n"); - result = -1; - goto exit; + if (wolfIO_DecodeUrl(url, urlSz, domainName, path, &port) < 0) { + fprintf(stderr, " unable to decode OCSP URL\n"); + return ret; } - - if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { - fprintf(stderr, "failed to create socket\n"); - result = -1; - goto exit; + httpBuf = malloc(httpBufSz); + if (httpBuf == NULL) { + fprintf(stderr, " out of memory\n"); + return ret; + } + httpBufSz = wolfIO_HttpBuildRequestOcsp(domainName, path, requestSz, + httpBuf, httpBufSz); + if (httpBufSz <= 0) { + fprintf(stderr, " unable to build OCSP request\n"); + free(httpBuf); + return ret; } - if (connect(sockfd, (struct sockaddr*) &servAddr, sizeof(servAddr)) == -1) { - fprintf(stderr, "failed to connect socket\n"); - result = -1; - goto exit; + sockfd = connect_responder(domainName, port); + if (sockfd == -1) { + free(httpBuf); + return ret; } - // set non-block socket - int flags = fcntl(sockfd, F_GETFL, 0); - if (flags == -1) { - fprintf(stderr, "fcntl(F_GETFL) failed\n"); - result = -1; - goto exit_closefd; + if (send_all(sockfd, httpBuf, httpBufSz) != 0 || + send_all(sockfd, request, requestSz) != 0) { + fprintf(stderr, " OCSP request send failed\n"); } - if ((flags & O_NONBLOCK) == 0) { - flags |= O_NONBLOCK; - if (fcntl(sockfd, F_SETFL, flags) != 0) { - fprintf(stderr, "fcntl(F_SETFL) failed\n"); - result = -1; - goto exit_closefd; - } + else { + /* The parser keeps no state between calls, so let it run once to the + * end and have the receive callback do the waiting. */ + ret = wolfIO_HttpProcessResponseOcspGenericIO(recv_with_deadline, + &sockfd, response, httpBuf, HTTP_SCRATCH_BUFFER_SIZE, NULL); } - WOLFSSL *ssl = wolfSSL_new(ctx); - if (ssl == NULL) { - fprintf(stderr, "wolfSSL_new() failed\n"); - result = -1; - goto exit_closefd; + close(sockfd); + free(httpBuf); + return ret; +} + +static int ocsp_cb(void* ctx, const char* url, int urlSz, + unsigned char* request, int requestSz, unsigned char** response) +{ + /* Return "want read" once per request, then look up on retry. A static + * flag holds the state, so no marker file is needed. */ + static int wantReadPending = 0; + + (void)ctx; + printf("ocsp_cb(): %.*s\n", urlSz, url); + + if (!wantReadPending) { + wantReadPending = 1; + printf(" simulate 'want read'\n"); + return WOLFSSL_CBIO_ERR_WANT_READ; } + wantReadPending = 0; - wolfSSL_set_fd(ssl, sockfd); + return ocsp_lookup(url, urlSz, request, requestSz, response); +} - wolfSSL_set_using_nonblock(ssl, 1); +static void ocsp_free(void* ctx, unsigned char* response) +{ + /* wolfIO_HttpProcessResponseOcsp() allocates the response the same way + * EmbedOcspLookup() does, so free it the same way. */ + EmbedOcspRespFree(ctx, response); +} + +/* Apply the OCSP callback, TLS version policy, SNI, and ALPN. + * Returns 0 on success, -1 on failure. */ +static int configure_ssl(WOLFSSL* ssl) +{ + const long verMask = SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | + SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2; + long opt; + int ret; ret = wolfSSL_SetOCSP_Cb(ssl, ocsp_cb, ocsp_free, NULL); if (ret != SSL_SUCCESS) { fprintf(stderr, "wolfSSL_SetOCSP_Cb() failed with code %d\n", ret); - result = -1; - goto exit_freessl; + return -1; } - long opt = wolfSSL_get_options(ssl); - const long ver_opt_mask = SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2; - opt = (opt & ~ver_opt_mask) | SSL_OP_NO_SSLv3; + opt = wolfSSL_get_options(ssl); + opt = (opt & ~verMask) | SSL_OP_NO_SSLv3; if (opt != wolfSSL_set_options(ssl, opt)) { fprintf(stderr, "Could not configure TLS versions on client stub\n"); - result = -1; - goto exit_freessl; + return -1; } - if ((ret = wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_X25519)) != SSL_SUCCESS || - (ret = wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP256R1)) != SSL_SUCCESS || - (ret = wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP384R1)) != SSL_SUCCESS || - (ret = wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP521R1)) != SSL_SUCCESS) { - fprintf(stderr, "Could not set SSL supported groups on client stub\n"); - result = -1; - goto exit_freessl; + ret = wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, SERVER_NAME, + strlen(SERVER_NAME)); + if (ret != SSL_SUCCESS) { + fprintf(stderr, "wolfSSL_UseSNI() failed with code %d\n", ret); + return -1; } - ret = wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, SERVER_NAME, strlen(SERVER_NAME)); + /* Match the peer certificate name against SERVER_NAME; verifying the + * chain alone would accept any cert issued under the loaded root. */ + ret = wolfSSL_check_domain_name(ssl, SERVER_NAME); if (ret != SSL_SUCCESS) { - fprintf(stderr, "wolfSSL_UseSNI() failed with code %d\n", ret); - result = -1; - goto exit_freessl; + fprintf(stderr, "wolfSSL_check_domain_name() failed with code %d\n", + ret); + return -1; } - ret = wolfSSL_UseALPN(ssl, ALPN_PROTOS, strlen(ALPN_PROTOS), WOLFSSL_ALPN_FAILED_ON_MISMATCH); + ret = wolfSSL_UseALPN(ssl, ALPN_PROTOS, strlen(ALPN_PROTOS), + WOLFSSL_ALPN_FAILED_ON_MISMATCH); if (ret != SSL_SUCCESS) { fprintf(stderr, "wolfSSL_UseALPN() failed with code %d\n", ret); - result = -1; - goto exit_freessl; + return -1; + } + + return 0; +} + +/* Whether a failed wolfSSL_connect() should be retried. */ +static int is_retryable(int errCode) +{ + if (errCode == SSL_ERROR_WANT_READ) { + return 1; + } + if (errCode == SSL_ERROR_WANT_WRITE) { + return 1; } + if (errCode == OCSP_WANT_READ) { + return 1; + } + if (errCode == WC_PENDING_E) { + return 1; + } + return 0; +} + +/* Drive the non-blocking handshake to completion, then shut down. + * Returns 0 on success, -1 on failure. */ +static int do_handshake(WOLFSSL* ssl) +{ + char errBuff[WOLFSSL_MAX_ERROR_SZ]; + int sockfd = wolfSSL_get_fd(ssl); + int ret; + int errCode; printf("Connecting...\n"); - do { + for (;;) { ret = wolfSSL_connect(ssl); - errCode = 0; - if (ret != SSL_SUCCESS) { - errCode = wolfSSL_get_error(ssl, ret); + if (ret == SSL_SUCCESS) { + break; + } - if (errCode != WOLFSSL_ERROR_WANT_READ) { - printf("wolfSSL_connect() returned %d (error code %d)\n", ret, errCode); - } + errCode = wolfSSL_get_error(ssl, ret); + /* Only report genuine errors; the want-read/pending codes are normal + * non-blocking progress. */ + if (!is_retryable(errCode)) { + printf("wolfSSL_connect() returned %d (error code %d)\n", ret, + errCode); + } -#if defined(WOLFSSL_ASYNC_CRYPT) - if (errCode == WC_PENDING_E) { - if (wait_async(ctx, ssl) != 0) { - fprintf(stderr, "wait_async() failed\n"); - result = -1; - goto exit_freessl; - } + if (errCode == WC_PENDING_E) { + /* Native async: finish the pending crypto op, then retry. */ + if (wolfSSL_AsyncPoll(ssl, WOLF_POLL_FLAG_CHECK_HW) < 0) { + fprintf(stderr, "wolfSSL_AsyncPoll() failed\n"); + return -1; } -#endif } - } while (ret != SSL_SUCCESS && (errCode == SSL_ERROR_WANT_READ || errCode == OCSP_WANT_READ || errCode == WC_PENDING_E)); + if (!is_retryable(errCode)) { + break; + } + + /* Wait for the socket rather than spinning, and give up when the + * deadline passes so a stalled peer fails instead of hanging. */ + if (errCode == SSL_ERROR_WANT_READ || errCode == SSL_ERROR_WANT_WRITE) { + if (wait_for_socket(sockfd, errCode == SSL_ERROR_WANT_WRITE ? + POLLOUT : POLLIN) != 0) { + fprintf(stderr, "handshake timed out after %d seconds\n", + HANDSHAKE_TIMEOUT_SEC); + return -1; + } + } + else if (remaining_sec() == 0) { + fprintf(stderr, "handshake timed out after %d seconds\n", + HANDSHAKE_TIMEOUT_SEC); + return -1; + } + } if (ret != SSL_SUCCESS) { errCode = wolfSSL_get_error(ssl, ret); @@ -277,38 +576,142 @@ int test_connect(WOLFSSL_CTX* ctx) wolfSSL_ERR_error_string((unsigned long) errCode, errBuff)); if (errCode == FATAL_ERROR) { WOLFSSL_ALERT_HISTORY hist; + const char* alertType; wolfSSL_get_alert_history(ssl, &hist); - int last_alert_code = hist.last_rx.code; - const char* last_alert_type = wolfSSL_alert_type_string_long(last_alert_code); - fprintf(stderr, "Last alert received: %d - %s\n", last_alert_code, last_alert_type); + alertType = wolfSSL_alert_type_string_long(hist.last_rx.code); + fprintf(stderr, "Last alert received: %d - %s\n", + hist.last_rx.code, alertType); } - result = -1; - goto exit_freessl; + return -1; } printf("CONNECTED\n"); - if (wolfSSL_get_error(ssl, ret) != SOCKET_PEER_CLOSED_E) { - printf(" Closing connection...\n"); - ret = wolfSSL_shutdown(ssl); - if (ret == SSL_SHUTDOWN_NOT_DONE) { - ret = wolfSSL_shutdown(ssl); - } - if (ret != SSL_SUCCESS) { - fprintf(stderr, "wolfSSL_shutdown() failed with code %d (error %d)\n", ret, wolfSSL_get_error(ssl, ret)); - result = -1; - goto exit_freessl; - } + printf(" Closing connection...\n"); + ret = wolfSSL_shutdown(ssl); + if (ret == WOLFSSL_SUCCESS) { + return 0; } + /* Our close_notify was sent; on a non-blocking socket the peer's reply may + * still be in flight, which is fine for this demo. */ + if (ret == WOLFSSL_SHUTDOWN_NOT_DONE) { + return 0; + } + errCode = wolfSSL_get_error(ssl, ret); + if (errCode == WOLFSSL_ERROR_WANT_READ) { + return 0; + } + if (errCode == WOLFSSL_ERROR_WANT_WRITE) { + return 0; + } + fprintf(stderr, "wolfSSL_shutdown() failed with code %d (error %d)\n", + ret, errCode); + return -1; +} -exit_freessl: - wolfSSL_free(ssl); +/* Create and configure the client CTX (verify, OCSP stapling, CA, async). + * Returns the CTX, or NULL on failure. */ +static WOLFSSL_CTX* create_ctx(int devId) +{ + WOLFSSL_METHOD* method; + WOLFSSL_CTX* ctx; + int err; + + method = wolfSSLv23_client_method(); + if (method == NULL) { + fprintf(stderr, "failed to get client method\n"); + return NULL; + } + + ctx = wolfSSL_CTX_new(method); + if (ctx == NULL) { + fprintf(stderr, "failed to create context object\n"); + return NULL; + } + + wolfSSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_cb); + + err = wolfSSL_CTX_UseOCSPStapling(ctx, WOLFSSL_CSR_OCSP, + WOLFSSL_CSR_OCSP_USE_NONCE); + if (err != SSL_SUCCESS) { + fprintf(stderr, "wolfSSL_CTX_UseOCSPStapling() returned %d\n", err); + wolfSSL_CTX_free(ctx); + return NULL; + } + + err = wolfSSL_CTX_UseOCSPStaplingV2(ctx, WOLFSSL_CSR2_OCSP_MULTI, + WOLFSSL_CSR2_OCSP_USE_NONCE); + if (err != SSL_SUCCESS) { + fprintf(stderr, "wolfSSL_CTX_UseOCSPStaplingV2() returned %d\n", err); + wolfSSL_CTX_free(ctx); + return NULL; + } + + err = wolfSSL_CTX_EnableOCSP(ctx, + WOLFSSL_OCSP_CHECKALL | WOLFSSL_OCSP_NO_NONCE); + if (err != SSL_SUCCESS) { + fprintf(stderr, "wolfSSL_CTX_EnableOCSP() returned %d\n", err); + wolfSSL_CTX_free(ctx); + return NULL; + } + + err = wolfSSL_CTX_UnloadCAs(ctx); + if (err != SSL_SUCCESS) { + fprintf(stderr, "wolfSSL_CTX_UnloadCAs() returned %d\n", err); + wolfSSL_CTX_free(ctx); + return NULL; + } + + err = wolfSSL_CTX_load_verify_locations(ctx, sys_certs_file, NULL); + if (err != SSL_SUCCESS) { + fprintf(stderr, "wolfSSL_CTX_load_verify_locations() returned %d\n", + err); + wolfSSL_CTX_free(ctx); + return NULL; + } + + if (wolfSSL_CTX_SetDevId(ctx, devId) != SSL_SUCCESS) { + fprintf(stderr, "Could not set AsyncCrypt device on TLS context\n"); + wolfSSL_CTX_free(ctx); + return NULL; + } + + return ctx; +} + +int test_connect(WOLFSSL_CTX* ctx) +{ + WOLFSSL* ssl; + int sockfd; + int result; + + deadline = time(NULL) + HANDSHAKE_TIMEOUT_SEC; + + sockfd = open_socket(); + if (sockfd == -1) { + return -1; + } -exit_closefd: + ssl = wolfSSL_new(ctx); + if (ssl == NULL) { + fprintf(stderr, "wolfSSL_new() failed\n"); + close(sockfd); + printf(" CLOSED\n"); + return -1; + } + + wolfSSL_set_fd(ssl, sockfd); + wolfSSL_set_using_nonblock(ssl, 1); + + result = configure_ssl(ssl); + if (result == 0) { + result = do_handshake(ssl); + } + + wolfSSL_free(ssl); close(sockfd); printf(" CLOSED\n"); -exit: return result; } #endif @@ -319,45 +722,38 @@ int main(int argc, char** argv) && defined(WOLFSSL_NONBLOCK_OCSP) && defined(HAVE_CERTIFICATE_STATUS_REQUEST) \ && defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) - int err; int result = 0; WC_RNG rng; int asyncCryptDevId = INVALID_DEVID; - pthread_t current_thread; - WOLFSSL_METHOD *method = NULL; - WOLFSSL_CTX *ctx = NULL; - - /* Check presence of sys_certs_file */ - if (access (sys_certs_file, F_OK) == -1 && argc == 1) { - fprintf(stderr, "Default system cert file /etc/ssl/certs/ca-certificates.crt doesn't exist." - " Please provide cert file path as show below.\n"); - fprintf(stderr, "./ocsp_nonblock_asynccrypt ../../mycerts/ca.crt\n"); - return -1; - } - /* Handle user provided certs file */ - else if (argc == 2) { - if (access (argv[1], F_OK) == -1) { + WOLFSSL_CTX* ctx; + + /* Use a caller-supplied cert file, else the bundled default. */ + if (argc == 2) { + if (access(argv[1], F_OK) == -1) { fprintf(stderr, "Provided cert file %s doesn't exist." " Please provide a valid path.\n", argv[1]); return -1; } - else { - sys_certs_file = argv[1]; - } + sys_certs_file = argv[1]; + } + else if (access(sys_certs_file, F_OK) == -1) { + fprintf(stderr, "Default cert file %s doesn't exist." + " Please provide a cert file path as shown below.\n", + sys_certs_file); + fprintf(stderr, + "./ocsp_nonblock_async /etc/ssl/certs/ca-certificates.crt\n"); + return -1; } wolfSSL_Debugging_ON(); - err = wolfSSL_Init(); - if (err != SSL_SUCCESS) { - fprintf(stderr, "wolfSSL_Init() failed with code %d\n", err); + if (wolfSSL_Init() != SSL_SUCCESS) { + fprintf(stderr, "wolfSSL_Init() failed\n"); return -1; } wolfSSL_load_error_strings(); -#if defined(WOLFSSL_ASYNC_CRYPT) - wolfAsync_HardwareStart(); printf("WolfSSL AsyncCrypt Enabled\n"); #if defined(HAVE_INTEL_QA) printf("WolfSSL AsyncCrypt with IntelQA Mode\n"); @@ -367,107 +763,40 @@ int main(int argc, char** argv) #error Unknown HW Acceleration device #endif - current_thread = pthread_self(); - if (wolfAsync_DevOpenThread(&asyncCryptDevId, ¤t_thread) < 0) { - fprintf(stderr, "Could not initialize AsyncCrypt device\n"); + if (wolfAsync_DevOpen(&asyncCryptDevId) < 0) { + fprintf(stderr, "Could not open AsyncCrypt device\n"); + wolfSSL_Cleanup(); return -1; } -#endif - - err = wc_InitRng(&rng); - if (err != 0) { - fprintf(stderr, "wc_InitRng() failed with code %d\n", err); - result = -1; - goto exit; - } - - method = wolfSSLv23_client_method(); - if (method == NULL) { - fprintf(stderr, "failed to get client method\n"); - result = -1; - goto exit; - } - - ctx = wolfSSL_CTX_new(method); - if (ctx == NULL) { - fprintf(stderr, "failed to create context object\n"); - result = -1; - goto exit; - } - - wolfSSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_cb); - - err = wolfSSL_CTX_UseOCSPStapling(ctx, WOLFSSL_CSR_OCSP, WOLFSSL_CSR_OCSP_USE_NONCE); - if (err != SSL_SUCCESS) { - fprintf(stderr, "wolfSSL_CTX_UseOCSPStapling() returned %d\n", err); - result = -1; - goto exit; - } - - err = wolfSSL_CTX_UseOCSPStaplingV2(ctx, WOLFSSL_CSR2_OCSP_MULTI, WOLFSSL_CSR2_OCSP_USE_NONCE); - if (err != SSL_SUCCESS) { - fprintf(stderr, "wolfSSL_CTX_UseOCSPStaplingV2() returned %d\n", err); - result = -1; - goto exit; - } - - err = wolfSSL_CTX_EnableOCSP(ctx, WOLFSSL_OCSP_CHECKALL | WOLFSSL_OCSP_NO_NONCE); - if (err != SSL_SUCCESS) { - fprintf(stderr, "wolfSSL_CTX_EnableOCSP() returned %d\n", err); + if (wc_InitRng(&rng) != 0) { + fprintf(stderr, "wc_InitRng() failed\n"); result = -1; - goto exit; - } - - err = wolfSSL_CTX_UnloadCAs(ctx); - if (err != SSL_SUCCESS) { - fprintf(stderr, "wolfSSL_CTX_UnloadCAs() returned %d\n", err); - result = -1; - goto exit; - } - - err = wolfSSL_CTX_load_verify_locations(ctx, sys_certs_file, NULL); - if (err != SSL_SUCCESS) { - fprintf(stderr, "wolfSSL_CTX_load_verify_locations() returned %d\n", err); - result = -1; - goto exit; - } - -#if defined(WOLFSSL_ASYNC_CRYPT) - if (wolfSSL_CTX_UseAsync(ctx, asyncCryptDevId) != SSL_SUCCESS ) { - fprintf(stderr, "Could not enable AsyncCrypt device on TLS context\n"); - result = -1; - goto exit; - } -#endif - - err = test_connect(ctx); - if (err != 0) { - fprintf(stderr, "test_connect() failed\n"); - fprintf(stderr, "CONNECT FAILED\n"); } else { - fprintf(stderr, "CONNECT PASSED\n"); + ctx = create_ctx(asyncCryptDevId); + if (ctx == NULL) { + result = -1; + } + else { + if (test_connect(ctx) != 0) { + fprintf(stderr, "test_connect() failed\n"); + fprintf(stderr, "CONNECT FAILED\n"); + result = -1; + } + else { + fprintf(stderr, "CONNECT PASSED\n"); + } + wolfSSL_CTX_free(ctx); + } + wc_FreeRng(&rng); } - wolfSSL_CTX_free(ctx); - ctx = NULL; - -exit: - printf("\nDONE\n\n"); - - wc_FreeRng(&rng); - -#if defined(WOLFSSL_ASYNC_CRYPT) if (asyncCryptDevId != INVALID_DEVID) { wolfAsync_DevClose(&asyncCryptDevId); } - - wolfAsync_HardwareStop(); -#endif - wolfSSL_Cleanup(); return result; @@ -475,9 +804,9 @@ int main(int argc, char** argv) (void)argc; (void)argv; - printf("Please compile wolfSSL with ./configure --enable-asynccrypt --enable-sni" + printf("Please compile wolfSSL with ./configure --enable-asynccrypt --enable-sni" " --enable-alpn --enable-ocspstapling --enable-ocspstapling2 --enable-opensslextra" - " --enable-curve25519 CFLAGS=-DWOLFSSL_NONBLOCK_OCSP"); + " CFLAGS=-DWOLFSSL_NONBLOCK_OCSP"); return -1; #endif }