123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- #define _POSIX_SOURCE
- #include <stdio.h>
- #include <stdlib.h>
- #include <strings.h>
- #include <unistd.h>
- #include <errno.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netdb.h>
- #include <openssl/x509.h>
- #include <openssl/ssl.h>
- #include "qa.h"
- #define SOCKET_PROTOCOL 0
- #define INVALID_SOCKET (-1)
- /**
- * \brief Converts a uri into a tuple {host, service}.
- *
- * Parses an input string containing a host and (maybe) a service/port.
- * Valid options are:
- * - service://hostname
- * - hostname
- * - hostname:port
- * The resulting tuple will be stored in params \ref host and \ref service.
- * \note \ref uri might be modified during parsing.
- *
- * \param[in] uri The input uri.
- * \param[out] host Place where to store parsed host.
- * \param[out] service Place where to store parsed service.
- *
- * \return 0 if the string was not parsable, 1 otherwise.
- */
- int host_port(char *uri, char **host, char **service)
- {
- char* c;
- if (!(c = strchr(uri, ':'))) {
- *host = uri;
- *service = NULL;
- } else {
- *c = '\0';
- if (c[1] == '/' && c[2] == '/') {
- *service = uri;
- *host = c+3;
- } else {
- *service = c+1;
- *host = uri;
- }
- }
- return 1;
- }
- int init_client(const char *host, const char *port)
- {
- struct addrinfo hints;
- struct addrinfo *result, *rp;
- int s;
- int i;
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_flags = 0;
- hints.ai_protocol = 0;
- if ((i=getaddrinfo(host, port, NULL, &result))) {
- BIO_printf(bio_err, "Error: %s\n", gai_strerror(i));
- return -1;
- }
- for (rp=result; rp; rp = rp->ai_next) {
- s = socket(rp->ai_family,
- rp->ai_socktype,
- rp->ai_protocol);
- if (s == INVALID_SOCKET) continue;
- if (rp->ai_protocol == SOCK_STREAM) {
- i = 0;
- i = setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char*) &i, sizeof(i));
- if (i < 0) exit(EXIT_FAILURE);
- }
- if (connect(s, rp->ai_addr, rp->ai_addrlen) != -1) break;
- }
- if (!rp) return -1;
- return s;
- }
- /**
- * \brief Connection informations.
- */
- typedef struct qa_connection {
- int socket; /**< socket file descriptor. */
- SSL* ssl; /**< ssl handler for this connection. */
- SSL_CTX* ctx; /**< ssl context used in this connection. */
- } qa_connection_t;
- /**
- * \brief Destructor for a \ref qa_connection.
- *
- * Closes the socket, shuts down the connection, and frees all memory used for
- * holding the connection.
- * \note Input might be partial (ex. a socket exists, but not no ssl session).
- *
- * \param c The connection to be freed.
- */
- static void qa_connection_free(struct qa_connection* c)
- {
- if (!c) return;
- if (c->socket != -1)
- close(c->socket);
- if (c->ssl) {
- SSL_shutdown(c->ssl);
- SSL_free(c->ssl);
- }
- if (c->ctx)
- SSL_CTX_free(c->ctx);
- free(c);
- }
- static int verify_callback(int ok, X509_STORE_CTX* ctx)
- {
- return ok;
- }
- /**
- * \brief Set up a new ssl connection.
- *
- * Create a new \ref qa_connection, turning on OpenSSL (if not yet started), and
- * opening a socket with the target server over ssl.
- *
- * \param[in] conf Configuration holding informations about the target.
- * \return The new connection.
- */
- struct qa_connection* qa_connection_new(char* address)
- {
- struct qa_connection* c;
- char *host, *port;
- int err;
- /* parse input address */
- if (!host_port(address, &host, &port)) goto error;
- c = malloc(sizeof(struct qa_connection));
- if (!c) goto error;
- /* set up context, and protocol versions */
- c->ctx = SSL_CTX_new(SSLv23_client_method());
- if (!c->ctx) goto error;
- /* create the ssl session, disabling certificate verification */
- SSL_CTX_set_verify(c->ctx, SSL_VERIFY_NONE, verify_callback);
- c->ssl = SSL_new(c->ctx);
- if (!c->ssl) goto error;
- /* open the socket over ssl */
- c->socket = init_client(host, port);
- if (c->socket == -1) goto error;
- if (!SSL_set_fd(c->ssl, c->socket)) goto error;
- if (SSL_connect(c->ssl) != 1) goto error;
- SSL_set_connect_state(c->ssl);
- return c;
- error:
- /* XXX. add checks for errno, and the ssl error stack (ssl_get_error) */
- qa_connection_free(c);
- return NULL;
- }
- /**
- * \brief Fetches the certificate opening a tcp connection to the given address.
- *
- * Attempts to open a new tcp connection to the address `address`, and return
- * the X509 certificate presented by the server.
- *
- * \param address[in] the uri to which handshake a ssl connection.
- * \return a valid pointer to the X509 certificate if the handshake succeeded,
- * NULL otherwise.
- */
- X509* get_remote_cert(char *address)
- {
- X509 *crt;
- qa_connection_t *c;
- c = qa_connection_new(address);
- if (!c) return NULL;
- crt = SSL_get_peer_certificate(c->ssl);
- qa_connection_free(c);
- return crt;
- }
|