Tag: security

An SSL Client Using OpenSSL

Recently I wrote about how to use OpenSSL to connect to a plain data server, this time I’m modifying the same code to perform encrypted connections.  Naturally this is more of an example for how to use the API than production ready code.  It’s main purpose is to show the very small difference between using the library as I did last time and how that example can be altered to create a basic SSL client.

The essential changes to the code below are the replacement of the connection function ‘connect_unencrypted(host_and_port)‘ with ‘connect_encrypted(host_and_port, store_path, store_type, &ctx, &ssl)‘ and the introduction of the SSL cleanup step ‘SSL_CTX_free(ctx)‘.  All other changes are purely cosmetic; which really shows how simple adding SSL to your application connections can be.  Externally you need to provide the root CA certificate for the connection to be verified by.  That’s it.

At this point I could warble through the connection function, but you should just read through it yourself and consult the SSL man pages.  Note that there is a dreadful buffer overflow possibility in this code and no real error handling, just a bit of logging.  This is to keep the example short and also because only you will know what valid handling should take place for each situation when you write your own code.

So take a look and enjoy.  To try this out yourself:

  1. Make sure that you have Firefox, GCC and OpenSSL (development sources and libraries) installed.
  2. Copy the following code to a file called ‘main.c‘ in a directory that you will be playing around in.
  3. Compile the code using ‘gcc main.c -o sslclient -lssl‘ if you are on Linux or ‘gcc main.c -o sslclient -lssl-lcrypto‘ if you are on OSX.
  4. Select an SSL (https) web site to connect to and find the Root CA’s certificate name in the site’s certificate.
  5. Either export the appropriate root CA from Firefox or obtain it directly from the CA online in pem format and copy it to a file ‘certificate.pem‘ in the same directory as the ‘sslclient‘ file.
  6. Run the following command:


'./sslclient servername:443 "GET / \r\n\r\n" certificate.pem f e'


/*
 * File:   main.cpp
 *
 * Licence: GPL2
 */

/* Standard headers */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/* OpenSSL headers */
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

/**
 * Simple log function
 */
void slog(char* message) {
    fprintf(stdout, message);
}

/**
 * Print SSL error details
 */
void print_ssl_error(char* message, FILE* out) {

    fprintf(out, message);
    fprintf(out, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
    fprintf(out, "%s\n", ERR_error_string(ERR_get_error(), NULL));
    ERR_print_errors_fp(out);
}

/**
 * Print SSL error details with inserted content
 */
void print_ssl_error_2(char* message, char* content, FILE* out) {

    fprintf(out, message, content);
    fprintf(out, "Error: %s\n", ERR_reason_error_string(ERR_get_error()));
    fprintf(out, "%s\n", ERR_error_string(ERR_get_error(), NULL));
    ERR_print_errors_fp(out);
}

/**
 * Initialise OpenSSL
 */
void init_openssl() {

    /* call the standard SSL init functions */
    SSL_load_error_strings();
    SSL_library_init();
    ERR_load_BIO_strings();
    OpenSSL_add_all_algorithms();

    /* seed the random number system - only really nessecary for systems without '/dev/random' */
    /* RAND_add(?,?,?); need to work out a cryptographically significant way of generating the seed */
}

/**
 * Close an unencrypted connection gracefully
 */
int close_connection(BIO* bio) {

    int r = 0;

    r = BIO_free(bio);
    if (r == 0) {
        /* Error unable to free BIO */
    }

    return r;
}

/**
 * Connect to a host using an unencrypted stream
 */
BIO* connect_unencrypted(char* host_and_port) {

    BIO* bio = NULL;

    /* Create a new connection */
    bio = BIO_new_connect(host_and_port);
    if (bio == NULL) {

        print_ssl_error("Unable to create a new unencrypted BIO object.\n", stdout);
        return NULL;
    }

    /* Verify successful connection */
    if (BIO_do_connect(bio) != 1) {

        print_ssl_error("Unable to connect unencrypted.\n", stdout);
        close_connection(bio);
        return NULL;
    }

    return bio;
}

/**
 * Connect to a host using an encrypted stream
 */
BIO* connect_encrypted(char* host_and_port, char* store_path, char store_type, SSL_CTX** ctx, SSL** ssl) {

    BIO* bio = NULL;
    int r = 0;

    /* Set up the SSL pointers */
    *ctx = SSL_CTX_new(SSLv23_client_method());
    *ssl = NULL;

    /* Load the trust store from the pem location in argv[2] */
    if (store_type == 'f')
        r = SSL_CTX_load_verify_locations(*ctx, store_path, NULL);
    else
        r = SSL_CTX_load_verify_locations(*ctx, NULL, store_path);
    if (r == 0) {

        print_ssl_error_2("Unable to load the trust store from %s.\n", store_path, stdout);
        return NULL;
    }

    /* Setting up the BIO SSL object */
    bio = BIO_new_ssl_connect(*ctx);
    BIO_get_ssl(bio, ssl);
    if (!(*ssl)) {

        print_ssl_error("Unable to allocate SSL pointer.\n", stdout);
        return NULL;
    }
    SSL_set_mode(*ssl, SSL_MODE_AUTO_RETRY);

    /* Attempt to connect */
    BIO_set_conn_hostname(bio, host_and_port);

    /* Verify the connection opened and perform the handshake */
    if (BIO_do_connect(bio) < 1) {

        print_ssl_error_2("Unable to connect BIO.%s\n", host_and_port, stdout);
        return NULL;
    }

    if (SSL_get_verify_result(*ssl) != X509_V_OK) {

        print_ssl_error("Unable to verify connection result.\n", stdout);
    }

    return bio;
}

/**
 * Read a from a stream and handle restarts if nessecary
 */
ssize_t read_from_stream(BIO* bio, char* buffer, ssize_t length) {

    ssize_t r = -1;

    while (r < 0) {

        r = BIO_read(bio, buffer, length);
        if (r == 0) {

            print_ssl_error("Reached the end of the data stream.\n", stdout);
            continue;

        } else if (r < 0) {

            if (!BIO_should_retry(bio)) {

                print_ssl_error("BIO_read should retry test failed.\n", stdout);
                continue;
            }

            /* It would be prudent to check the reason for the retry and handle
             * it appropriately here */
        }

    };

    return r;
}

/**
 * Write to a stream and handle restarts if nessecary
 */
int write_to_stream(BIO* bio, char* buffer, ssize_t length) {

    ssize_t r = -1;

    while (r < 0) {

        r = BIO_write(bio, buffer, length);
        if (r <= 0) {

            if (!BIO_should_retry(bio)) {

                print_ssl_error("BIO_read should retry test failed.\n", stdout);
                continue;
            }

            /* It would be prudent to check the reason for the retry and handle
             * it appropriately here */
        }

    }

    return r;
}

/**
 * Main SSL demonstration code entry point
 */
int main(int argc, char** argv) {

    char* host_and_port = argv[1]; /* localhost:4422 */
    char* server_request = argv[2]; /* "GET / \r\n\r\n" */
    char* store_path = argv[3]; /* /home/user/projects/sslclient/certificate.pem */
    char store_type = argv[4][0]; /* f = file, anything else is a directory structure */
    char connection_type = argv[5][0]; /* e = encrypted, anything else is unencrypted */

    char buffer[4096];
    buffer[0] = 0;

    BIO* bio;
    SSL_CTX* ctx = NULL;
    SSL* ssl = NULL;

    /* initilise the OpenSSL library */
    init_openssl();

    /* encrypted link */
    if (connection_type == 'e') {

        if ((bio = connect_encrypted(host_and_port, store_path, store_type, &ctx, &ssl)) == NULL)
            return (EXIT_FAILURE);
    }
        /* unencrypted link */
    else if ((bio = connect_unencrypted(host_and_port)) == NULL)
        return (EXIT_FAILURE);

    write_to_stream(bio, server_request, strlen(server_request));
    read_from_stream(bio, buffer, 4096);
    printf("%s\r\n", buffer);

    if (close_connection(bio) == 0)
        return (EXIT_FAILURE);

    /* clean up the SSL context resources for the encrypted link */
    if (connection_type == 'e')
        SSL_CTX_free(ctx);

    return (EXIT_SUCCESS);
}

SELinux and audit2allow

Using SELinux is reasonably straight forward once you get used to it.  It protects OS objects and configuration from potentially dangerous manipulation.  If an exception to its current policy is found then it is logged and either denied or allowed.  The reasons that it may be allowed depend on the policy’s construction.

For me I use a targeted, as opposed to blanket, policy and set the SELinux level to enforcing.  This means that not everything unplanned will be blocked, but it does log and alert me when something crosses the policy line and allows me to block it if I feel it is necessary.  Naturally it also can block services that I want to use too.  So in this case I will look at setting up vsftpd to access home directories.

If you run this command (you need the selinuxtools package installed) you will generate a new policy straight to the console to allow all of the exceptions found in the SELinux log ‘audit2allow < /var/log/audit/audit.log’.  Naturally this is not what we want as it will generate a policy that will allow all previously caught exceptions to proceed.  We just want to allow ftp; which is what the following statement does: ‘grep ftp /var/log/audit/audit.log | audit2allow -R’.

The result:

[foo@bar ~]# grep ftp /var/log/audit/audit.log | audit2allow -R
require {
type ftpd_t;
type home_root_t;
class capability net_raw;
class dir search;
}
#============= ftpd_t ==============
#!!!! This avc can be allowed using one of the these booleans:
#     allow_ftpd_full_access, allow_ftpd_full_access
allow ftpd_t home_root_t:dir search;
allow ftpd_t self:capability net_raw;

What is interesting here is that the audit2allow application alerts us to the fact that the same thing being achieved through the creation of this policy could also be accomplished using SELinux booleans; in this case  ‘setsebool -P allow_ftpd_full_access true’ would do the trick.  However we’re going to do this the long way as booleans are not always available, this is just the first step.

Now that we have inspected the policy we need to build it and integrate it into the active policy.  First we need to construct the new policy file using ‘grep ftp /var/log/audit/audit.log | audit2allow -M ftp’.  This will create a ‘ftp.te’ policy file and a ‘ftp.pp’ compiled policy file.  To make the new policy active simply run ‘semodule -i ftp.pp’ and its done.  This of course may reveal another denial or other OS configuration issue so you just need to perform an allow then test to see if there is anything else that needs sorting out.


Copyright © 1996-2010 Code Snips. All rights reserved.
iDream theme by Templates Next | Powered by WordPress