
/******************************************************************************
**
**  Copyright (C) 2001  - the shmoo group -
**
**  This program is free software; you can redistribute it and/or
**  modify it, however, you cannot sell it.
**
**  This program is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**
**  You should have received a copy of the license attached to the
**  use of this software.  If not, visit www.shmoo.com/osiris for
**  details.
**
******************************************************************************/

/******************************************************************************
**
**    The Shmoo Group (TSG)
**
**    File:      ssl_utilities.c
**    Author:    Brian Wotring
**
**    Date:      July 28, 2002.
**    Project:   osiris
**
******************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

#include <string.h>
#include <fcntl.h>
#include <ctype.h>

#include <sys/stat.h>
#include <sys/types.h>

#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/rand.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
#include <openssl/ripemd.h>

#ifdef WIN32
#include <winsock.h>
#include <io.h>
#include <time.h>
#else
#include <dirent.h>
#endif

#include "utilities.h"
#include "ssl_utilities.h"
#include "fileapi.h"

#include "error.h"

/******************************************************************************
**
**    Function: seed_prng
**
**    Purpose:  seed prng to do more then suck stuff from /dev/urandom,
**              or in case /dev/urandom doesn't exist ( windows ).
**              Returns non-zero if good, 0 if fails.
**
******************************************************************************/

int seed_prng( int bytes )
{
    /* first, try to get stuff from /dev/random. */

    if( RAND_load_file( DEVRANDOM, bytes ) )
    {
        return 1;
    }

#ifdef USE_EGADS

    {
        int error;
        char *buffer;
        prngctx_t ctx;

        egads_init( &ctx, NULL, NULL, &error );

        if( !error )
        {
            buffer = (char *)osi_malloc( bytes );
            egads_entropy( &ctx, buffer, bytes, &error );

            if( !error )
            {
                RAND_seed( buffer, bytes );

                osi_free( buffer );
                egads_destroy( &ctx );

                return 1;
            }

            osi_free( buffer );
        }

        egads_destroy( &ctx );
    }

#endif

#ifdef USE_EGD

    {
        int i;
        char *names[] = { DEVRANDOM_EGD, NULL };

        for( i = 0; names[i]; i++ )
        {
            if( RAND_egd( names[i] ) != -1 )
            {
                return 1;
            }
        }
    }

#endif

    return 0;
}

/******************************************************************************
**
**    Function: hash_file_md5
**
**    Purpose:  hash file contents with the md5 algorithm.
**
******************************************************************************/

char * hash_file_md5( const char *file_path, FILE *file,    
                      char *buffer, int buffer_size )
{
    FILE *file_to_hash  = file;
    MD5_CTX context;
    unsigned char digest[MD5_DIGEST_LENGTH];

    int burst;
    int index;

    unsigned char hash_buffer[BUFFER_SIZE];
    char file_hash[33];

    if( ( file_path != NULL ) && ( buffer != NULL ) &&
        ( buffer_size >= MAX_CHECKSUM_LENGTH ) )
    {
        /* see if we were given a file descriptor. */

        if( file_to_hash == NULL )
        {
            file_to_hash = fopen( file_path, FILE_OPEN_READ_FLAGS );
        }

        else
        {
            fseek( file_to_hash, 0, SEEK_SET );
        }

        if( file_to_hash != NULL )
        {
            /* initialize the md5 library and our hash string */
            /* which is 32 bytes long for md5.                */

            MD5_Init( &context );

            while(1)
            {
                burst = read( fileno( file_to_hash ),
                              hash_buffer, BUFFER_SIZE );

                if( burst <= 0 )
                {
                    break;
                }

                MD5_Update( &context, hash_buffer, (unsigned long)burst );
            }

            MD5_Final( &( digest[0] ), &context );

            for ( index = 0; index < MD5_DIGEST_LENGTH; index++)
            {
                osi_snprintf( &file_hash[index * 2], 3, "%02x", digest[index] );
            }

            file_hash[32] = '\0';

            /* if we didn't open it, don't close it. */

            if( file == NULL )
            {
                fclose( file_to_hash );
            }

            osi_strlcpy( buffer, file_hash, buffer_size );

            return buffer;
        }
    }

    return NULL;
}

/******************************************************************************
**
**    Function: hash_file_sha
**
**    Purpose:  hash file contents with the sha1 algorithm.
**
******************************************************************************/

char * hash_file_sha( const char *file_path, FILE *file,
                      char *buffer, int buffer_size )
{
    FILE *file_to_hash  = file;
    SHA_CTX context;
    unsigned char digest[SHA_DIGEST_LENGTH];

    int burst;
    int index;

    unsigned char hash_buffer[BUFFER_SIZE];
    char file_hash[41];

    if( ( file_path != NULL ) && ( buffer != NULL ) &&
        ( buffer_size >= MAX_CHECKSUM_LENGTH ) )
    {
        if( file_to_hash == NULL )
        {
            file_to_hash = fopen( file_path, FILE_OPEN_READ_FLAGS );
        }

        else
        {
            fseek( file_to_hash, 0, SEEK_SET );
        }

        if( file_to_hash != NULL )
        {
            /* initialize the sha library and our hash string */
            /* which is 40 bytes long for sha.                */

            SHA1_Init( &context );

            while(1)
            {
                burst = read( fileno( file_to_hash ),
                              hash_buffer, BUFFER_SIZE );

                if( burst <= 0 )
                {
                    break;
                }

                SHA1_Update( &context, hash_buffer, (unsigned long)burst );
            }

            SHA1_Final( &( digest[0] ), &context );

            for ( index = 0; index < SHA_DIGEST_LENGTH; index++ )
            {
                osi_snprintf( &file_hash[index * 2], 3, "%02x", digest[index] );
            }

            file_hash[40] = '\0';

            /* if we didn't open it, don't close it. */

            if( file == NULL )
            {
                fclose( file_to_hash );
            }

            osi_strlcpy( buffer, file_hash, buffer_size );

            return buffer;
        }
    }

    return NULL;
}

/******************************************************************************
**
**    Function: hash_file_ripemd
**
**    Purpose:  hash file contents with the ripemd 160 bit algorithm.
**
******************************************************************************/

char * hash_file_ripemd( const char *file_path, FILE *file,
                         char *buffer, int buffer_size )
{
    FILE *file_to_hash  = file;
    RIPEMD160_CTX context;
    unsigned char digest[RIPEMD160_DIGEST_LENGTH];

    int burst;
    int index;

    unsigned char hash_buffer[BUFFER_SIZE];
    char file_hash[81];

    if( ( file_path != NULL ) && ( buffer != NULL ) &&
        ( buffer_size >= MAX_CHECKSUM_LENGTH ) )
    {
        if( file_to_hash == NULL )
        {
            file_to_hash = fopen( file_path, FILE_OPEN_READ_FLAGS );
        }

        else
        {
            fseek( file_to_hash, 0, SEEK_SET );
        }

        if( file_to_hash != NULL )
        {
            /* initialize the ripemd library and our hash string */
            /* which is 80 bytes long for ripemd.                */

            RIPEMD160_Init( &context );

            while(1)
            {
                burst = read( fileno( file_to_hash ),
                              hash_buffer, BUFFER_SIZE );

                if( burst <= 0 )
                {
                    break;
                }

                RIPEMD160_Update( &context, hash_buffer, (unsigned long)burst );
            }

            RIPEMD160_Final( &( digest[0] ), &context );

            for ( index = 0; index < RIPEMD160_DIGEST_LENGTH; index++)
            {
                osi_snprintf( &file_hash[index * 2], 3, "%02x", digest[index] );
            }

            file_hash[80] = '\0';

            /* if we didn't open it, don't close it. */

            if( file == NULL )
            {
                fclose( file_to_hash );
            }

            osi_strlcpy( buffer, file_hash, buffer_size );

            return buffer;
        }
    }

    return NULL;
}


/******************************************************************************
**
**    Function: osi_write_x509_to_path
**
**    Purpose:  given the certificate, write it in pem format to the specified
**              local file.
**
******************************************************************************/

int osi_write_x509_to_path( X509 *cert, const char *filepath, mode_t mode )
{
    int result = OSI_ERROR_NULL_ARGUMENT;
    FILE *file;

    if( ( cert != NULL ) && ( filepath != NULL ) )
    {
        file = osi_fopen( filepath, "w+", mode );

        if( file != NULL )
        {
            if( PEM_write_X509( file, cert ) )
            {
                result = OSI_OK;
            }

            else
            {
                result = OSI_ERROR_UNABLE_TO_WRITE_FILE;
            }

            fclose( file );
        }

        else
        {
            result = OSI_ERROR_UNABLE_TO_CREATE_FILE;
        }
    }

    return result;
}

/******************************************************************************
**
**    Function: osi_write_rsa_to_path
**
**    Purpose:  given the rsa key, write it to the specified local file.
**
******************************************************************************/

int osi_write_rsa_to_path( RSA *rsa, const char *filepath, mode_t mode )
{
    int result = OSI_ERROR_NULL_ARGUMENT;

    BIO *bio;
    FILE *file;

    if( ( rsa != NULL ) && ( filepath != NULL ) )
    {
        file = osi_fopen( filepath, "w+", mode );

        if( file != NULL )
        {
            bio = BIO_new_fp( file, BIO_NOCLOSE );

            if( PEM_write_bio_RSAPrivateKey( bio, rsa, NULL,
                                             NULL, 0, NULL, NULL ) )
            {
                result = OSI_OK;
            }

            else
            {
                osi_remove_file( filepath );
                result = OSI_ERROR_UNABLE_TO_WRITE_FILE;
            }

            BIO_free( bio );
            fclose( file );
        }

        else
        {
            result = OSI_ERROR_UNABLE_TO_CREATE_FILE;
        }
    }

    return result;
}


/******************************************************************************
**
**    Function: osi_write_pkey_to_path
**
**    Purpose:  given the pkey structure, write it to the specified local file.
**
******************************************************************************/

int osi_write_pkey_to_path( EVP_PKEY *pkey, const char *filepath, mode_t mode )
{
    int result = OSI_ERROR_NULL_ARGUMENT;
    FILE *file;

    if( ( pkey != NULL ) && ( filepath != NULL ) )
    {
        file = osi_fopen( filepath, "w+", mode );

        if( file != NULL )
        {
            if( PEM_write_PrivateKey( file, pkey, NULL, NULL, 0, NULL, NULL ) )
            {
                result = OSI_OK;
            }

            else
            {
                osi_remove_file( filepath );
                result = OSI_ERROR_UNABLE_TO_WRITE_FILE;
            }

            fclose( file );
        }

        else
        {
            result = OSI_ERROR_UNABLE_TO_CREATE_FILE;
        }
    }

    return result;
}

/******************************************************************************
**
**    Function: osi_get_x509_fingerprint
**
**    Purpose:  extract the MD5 checksum of the given cert, put it into octet
**   	        printable format.
**
******************************************************************************/

char * osi_get_x509_fingerprint( X509 *cert, char *buffer, int buffer_size )
{
    if( ( cert != NULL ) && ( buffer != NULL ) )
    {
        const EVP_MD *digest=EVP_md5();
        int j;

        unsigned int n;
        unsigned char md[EVP_MAX_MD_SIZE];

        if( !X509_digest( cert, digest, md, &n) )
        {
            return NULL;
        }

        // check size according to our buffer.

        if( (n*3) > (unsigned int)buffer_size )
        {
            return NULL;
        }

        memset( buffer, '\0', buffer_size );

        for( j = 0; j < (int)n; j++ )
        {
            osi_snprintf( &buffer[j*3], 4, "%02X%c", md[j],
                          ( (unsigned int)(j+1) != n ) ? ':' : '\0' );
        }
    }

    return buffer;
}


