diff options
author | Matt Caswell <matt@openssl.org> | 2023-06-02 16:31:30 +0100 |
---|---|---|
committer | Pauli <pauli@openssl.org> | 2023-06-14 13:08:37 +1000 |
commit | a5a0c6a372d2cb9fbcec627e5a338c6f79aa0b16 (patch) | |
tree | 3666a08feb2a25be8bb6d8351126e5b8aebd0671 /demos | |
parent | 4032cd9a1434610e4dc2bbde01f98d04faa615e5 (diff) | |
download | openssl-a5a0c6a372d2cb9fbcec627e5a338c6f79aa0b16.tar.gz |
Add a very simple blocking TLS client demo
This blocking client is intended to be used to explain how to implement
a simple client in the documentation.
Reviewed-by: Viktor Dukhovni <viktor@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21133)
Diffstat (limited to 'demos')
-rw-r--r-- | demos/guide/Makefile | 16 | ||||
-rw-r--r-- | demos/guide/tls-client-block.c | 273 |
2 files changed, 289 insertions, 0 deletions
diff --git a/demos/guide/Makefile b/demos/guide/Makefile new file mode 100644 index 0000000000..54c6979f43 --- /dev/null +++ b/demos/guide/Makefile @@ -0,0 +1,16 @@ +# +# To run the demos when linked with a shared library (default): +# +# LD_LIBRARY_PATH=../.. ./tls-client-block + +CFLAGS = -I../../include -g +LDFLAGS = -L../.. +LDLIBS = -lcrypto -lssl + +all: tls-client-block + +tls-client-block: tls-client-block.c + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) + +clean: + $(RM) *.o tls-client-block diff --git a/demos/guide/tls-client-block.c b/demos/guide/tls-client-block.c new file mode 100644 index 0000000000..56133aab64 --- /dev/null +++ b/demos/guide/tls-client-block.c @@ -0,0 +1,273 @@ +/* + * Copyright 2023 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * NB: Changes to this file should also be reflected in + * doc/man7/ossl-guide-tls-client-block.pod + */ + +#include <string.h> + +/* Include the appropriate header file for SOCK_STREAM */ +#ifdef _WIN32 /* Windows */ +# include <winsock2.h> +#else /* Linux/Unix */ +# include <sys/socket.h> +#endif + +#include <openssl/bio.h> +#include <openssl/ssl.h> +#include <openssl/err.h> + +/* Helper function to create a BIO connected to the server */ +static BIO *create_socket_bio(const char *hostname, const char *port) +{ + int sock = -1; + BIO_ADDRINFO *res; + const BIO_ADDRINFO *ai = NULL; + BIO *bio; + + /* + * Lookup IP address info for the server. + */ + if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_CLIENT, 0, SOCK_STREAM, 0, + &res)) { + BIO_closesocket(sock); + return NULL; + } + + /* + * Loop through all the possible addresses for the server and find one + * we can connect to. + */ + for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) { + /* + * Create a TCP socket. We could equally use non-OpenSSL calls such + * as "socket" here for this and the subsequent connect and close + * functions. But for portability reasons and also so that we get + * errors on the OpenSSL stack in the event of a failure we use + * OpenSSL's versions of these functions. + */ + sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_STREAM, 0, 0); + if (sock == -1) + continue; + + /* Connect the socket to the server's address */ + if (!BIO_connect(sock, BIO_ADDRINFO_address(ai), BIO_SOCK_NODELAY)) { + BIO_closesocket(sock); + sock = -1; + continue; + } + + /* We have a connected socket so break out of the loop */ + break; + } + + /* Free the address information resources we allocated earlier */ + BIO_ADDRINFO_free(res); + + /* If sock is -1 then we've been unable to connect to the server */ + if (sock == -1) + return NULL; + + /* Create a BIO to wrap the socket*/ + bio = BIO_new(BIO_s_socket()); + if (bio == NULL) + BIO_closesocket(sock); + + /* + * Associate the newly created BIO with the underlying socket. By + * passing BIO_CLOSE here the socket will be automatically closed when + * the BIO is freed. Alternatively you can use BIO_NOCLOSE, in which + * case you must close the socket explicitly when it is no longer + * needed. + */ + BIO_set_fd(bio, sock, BIO_CLOSE); + + return bio; +} + +/* Server hostname and port details */ +#define HOSTNAME "www.example.com" +#define PORT "443" + +/* + * Simple application to send a basic HTTP/1.0 request to a server and + * print the response on the screen. + */ +int main(void) +{ + SSL_CTX *ctx = NULL; + SSL *ssl; + BIO *bio = NULL; + int res = EXIT_FAILURE; + int ret; + const char *request = + "GET / HTTP/1.0\r\nConnection: close\r\nHost: "HOSTNAME"\r\n\r\n"; + size_t written, readbytes; + char buf[160]; + + /* + * Create an SSL_CTX which we can use to create SSL objects from. We + * want an SSL_CTX for creating clients so we use TLS_client_method() + * here. + */ + ctx = SSL_CTX_new(TLS_client_method()); + if (ctx == NULL) { + printf("Failed to create the SSL_CTX\n"); + goto end; + } + + /* + * Configure the client to abort the handshake if certificate + * verification fails. Virtually all clients should do this unless you + * really know what you are doing. + */ + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + + /* Use the default trusted certificate store */ + if (!SSL_CTX_set_default_verify_paths(ctx)) { + printf("Failed to set the default trusted certificate store\n"); + goto end; + } + + /* + * TLSv1.1 or earlier are deprecated by IETF and are generally to be + * avoided if possible. We require a mimimum TLS version of TLSv1.2. + */ + if (!SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION)) { + printf("Failed to set the minimum TLS protocol version\n"); + goto end; + } + + /* Create an SSL object to represent the TLS connection */ + ssl = SSL_new(ctx); + if (ssl == NULL) { + printf("Failed to create the SSL object\n"); + goto end; + } + + /* + * Create the underlying transport socket/BIO and associate it with the + * connection + */ + bio = create_socket_bio(HOSTNAME, PORT); + if (bio == NULL) { + printf("Failed to crete the BIO\n"); + goto end; + } + SSL_set_bio(ssl, bio, bio); + + /* + * Tell the server during the handshake which hostname we are attempting + * to connect to in case the server supports multiple hosts. + */ + if (!SSL_set_tlsext_host_name(ssl, HOSTNAME)) { + printf("Failed to set the SNI hostname\n"); + goto end; + } + + /* + * Ensure we check during certificate verification that the server has + * supplied a certificate for the hostname that we were expecting. + * Virtually all clients should do this unless you really know what you + * are doing. + */ + if (!SSL_set1_host(ssl, HOSTNAME)) { + printf("Failed to set the certificate verification hostname"); + goto end; + } + + /* Do the handshake with the server */ + if (SSL_connect(ssl) < 1) { + printf("Failed to connect to the server\n"); + /* + * If the failure is due to a verification error we can get more + * information about it from SSL_get_verify_result(). + */ + if (SSL_get_verify_result(ssl) != X509_V_OK) + printf("Verify error: %s\n", + X509_verify_cert_error_string(SSL_get_verify_result(ssl))); + goto end; + } + + /* Write an HTTP GET request to the peer */ + if (!SSL_write_ex(ssl, request, strlen(request), &written)) { + printf("Failed to write HTTP request\n"); + goto end; + } + + /* + * Get up to sizeof(buf) bytes of the response. We keep reading until the + * server closes the connection. + */ + while (SSL_read_ex(ssl, buf, sizeof(buf), &readbytes)) { + /* + * OpenSSL does not guarantee that the returned data is a string or + * that it is NUL terminated so we use fwrite() to write the exact + * number of bytes that we read. The data could be non-printable or + * have NUL characters in the middle of it. For this simple example + * we're going to print it to stdout anyway. + */ + fwrite(buf, 1, readbytes, stdout); + } + /* In case the response didn't finish with a newline we add one now */ + printf("\n"); + + /* + * Check whether we finished the while loop above normally or as the + * result of an error. The 0 argument to SSL_get_error() is the return + * code we received from the SSL_read_ex() call. It must be 0 in order + * to get here. Normal completion is indicated by SSL_ERROR_ZERO_RETURN. + */ + if (SSL_get_error(ssl, 0) != SSL_ERROR_ZERO_RETURN) { + /* + * Some error occurred other than a graceful close down by the + * peer + */ + printf ("Failed reading remaining data\n"); + goto end; + } + + /* + * The peer already shutdown gracefully (we know this because of the + * SSL_ERROR_ZERO_RETURN above). We should do the same back. + */ + ret = SSL_shutdown(ssl); + if (ret < 1) { + /* + * ret < 0 indicates an error. ret == 0 would be unexpected here + * because that means "we've sent a close_notify and we're waiting + * for one back". But we already know we got one from the peer + * because of the SSL_ERROR_ZERO_RETURN above. + */ + printf("Error shuting down\n"); + goto end; + } + + /* Success! */ + res = EXIT_SUCCESS; + end: + /* + * If something bad happened then we will dump the contents of the + * OpenSSL error stack to stderr. There might be some useful diagnostic + * information there. + */ + if (res == EXIT_FAILURE) + ERR_print_errors_fp(stderr); + + /* + * Free the resources we allocated. We do not free the BIO object here + * because ownership of it was immediately transferred to the SSL object + * via SSL_set_bio(). The BIO will be freed when we free the SSL object. + */ + SSL_free(ssl); + SSL_CTX_free(ctx); + return res; +} |