/*
 * scapitest.c
 *
 * Expected SUCCESSes:  2 + 10 + 1 for all tests.
 *
 * Returns:
 *      Number of FAILUREs.
 *
 *
 * ASSUMES  No key management functions return non-zero success codes.
 *
 * XXX  Split into individual modules?
 * XXX  Error/fringe conditions should be tested.
 *
 *
 * Test of sc_random.                                           SUCCESSes: 2.
 *      REQUIRES a human to spot check for obvious non-randomness...
 *
 * Test of sc_generate_keyed_hash and sc_check_keyed_hash.      SUCCESSes: 10.
 *
 * Test of sc_encrypt and sc_decrypt.                           SUCCESSes: 1.
 */

static char    *rcsid = "$Id: scapitest.c 2 2004-05-07 17:47:48Z randallc $";    /* */


#include <net-snmp/net-snmp-config.h>

#include <stdio.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#include "asn1.h"
#include "snmp_api.h"
#include "keytools.h"
#include "tools.h"
#include "scapi.h"
#include "transform_oids.h"
#include "callback.h"

#include <stdlib.h>

extern char    *optarg;
extern int      optind, optopt, opterr;

#define DEBUG                   /* */



/*
 * Globals, &c...
 */
char           *local_progname;

#define USAGE	"Usage: %s [-h][-acHr]"
#define OPTIONLIST	"achHr"

int             doalltests = 0, docrypt = 0, dokeyedhash = 0, dorandom = 0;

#define	ALLOPTIONS	(doalltests + docrypt + dokeyedhash + dorandom)



#define LOCAL_MAXBUF	(1024 * 8)
#define NL		"\n"

#define OUTPUT(o)	fprintf(stdout, "\n\n%s\n\n", o);

#define SUCCESS(s)					\
{							\
	if (!failcount)					\
		fprintf(stdout, "\nSUCCESS: %s\n", s);	\
}

#define FAILED(e, f)					\
{							\
	if (e != SNMPERR_SUCCESS) {			\
		fprintf(stdout, "\nFAILED: %s\n", f);	\
		failcount += 1;				\
	}						\
}


#define BIGSTRING							\
    "   A port may be a pleasant retreat for any mind grown weary of"	\
    "the struggle for existence.  The vast expanse of sky, the"		\
    "mobile architecture of the clouds, the chameleon coloration"	\
    "of the sea, the beacons flashing on the shore, together make"	\
    "a prism which is marvellously calculated to entertain but not"	\
    "fatigue the eye.  The lofty ships with their complex webs of"	\
    "rigging, swayed to and fro by the swell in harmonious dance,"	\
    "all help to maintain a taste for rhythm and beauty in the"		\
    "mind.  And above all there is a mysterious, aristrocratic kind"	\
    "of pleasure to be had, for those who have lost all curiosity"	\
    "or ambition, as they strech on the belvedere or lean over the"	\
    "mole to watch the arrivals and departures of other men, those"	\
    "who still have sufficient strength of purpose in them, the"	\
    "urge to travel or enrich themselves."				\
    "	-- Baudelaire"							\
    "	   From _The_Poems_in_Prose_, \"The Port\" (XLI)."

#define BIGSECRET	"Shhhh... Don't tell *anyone* about this.  Not a soul."
#define BKWDSECRET	".luos a toN  .siht tuoba *enoyna* llet t'noD ...hhhhS"

#define MLCOUNT_MAX	6       /* MAC Length Count Maximum. */



/*
 * Prototypes.
 */
void            usage(FILE * ofp);

int             test_docrypt(void);
int             test_dokeyedhash(void);
int             test_dorandom(void);




int
main(int argc, char **argv)
{
    int             rval = SNMPERR_SUCCESS, failcount = 0;
    char            ch;

    local_progname = argv[0];

    /*
     * Parse.
     */
    while ((ch = getopt(argc, argv, OPTIONLIST)) != EOF) {
        switch (ch) {
        case 'a':
            doalltests = 1;
            break;
        case 'c':
            docrypt = 1;
            break;
        case 'H':
            dokeyedhash = 1;
            break;
        case 'r':
            dorandom = 1;
            break;
        case 'h':
            rval = 0;
        default:
            usage(stdout);
            exit(rval);
        }

        argc -= 1;
        argv += 1;
        optind = 1;
    }                           /* endwhile getopt */

    if ((argc > 1)) {
        usage(stdout);
        exit(1000);

    } else if (ALLOPTIONS != 1) {
        usage(stdout);
        exit(1000);
    }


    /*
     * Test stuff.
     */
    rval = sc_init();
    FAILED(rval, "sc_init().");


    if (docrypt || doalltests) {
        failcount += test_docrypt();
    }
    if (dokeyedhash || doalltests) {
        failcount += test_dokeyedhash();
    }
    if (dorandom || doalltests) {
        failcount += test_dorandom();
    }


    /*
     * Cleanup.
     */
    rval = sc_shutdown(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_SHUTDOWN,
                       NULL, NULL);
    FAILED(rval, "sc_shutdown().");

    return failcount;

}                               /* end main() */





void
usage(FILE * ofp)
{
    fprintf(ofp,
            USAGE
            "" NL
            "	-a		All tests." NL
            "	-c		Test of sc_encrypt()/sc_decrypt()."
            NL
            "	-h		Help."
            NL
            "	-H              Test sc_{generate,check}_keyed_hash()."
            NL
            "	-r              Test sc_random()."
            NL "" NL, local_progname);

}                               /* end usage() */




#ifdef EXAMPLE
/*******************************************************************-o-******
 * test_dosomething
 *
 * Test template.
 *
 * Returns:
 *	Number of failures.
 */
int
test_dosomething(void)
{
    int             rval = SNMPERR_SUCCESS, failcount = 0;

    EM0(1, "UNIMPLEMENTED");    /* EM(1); /* */

  test_dosomething_quit:
    return failcount;

}                               /* end test_dosomething() */
#endif                          /* EXAMPLE */





/*******************************************************************-o-******
 * test_dorandom
 *
 * One large request, one set of short requests.
 *
 * Returns:
 *	Number of failures.
 *
 * XXX	probably should split up into individual options.
 */
int
test_dorandom(void)
{
    int             rval = SNMPERR_SUCCESS,
        failcount = 0,
        origrequest = (1024 * 2),
        origrequest_short = 19, nbytes = origrequest, shortcount = 7, i;
    char            buf[LOCAL_MAXBUF];

    OUTPUT("Random test -- large request:");

    rval = sc_random(buf, &nbytes);
    FAILED(rval, "sc_random().");

    if (nbytes != origrequest) {
        FAILED(SNMPERR_GENERR,
               "sc_random() returned different than requested.");
    }

    dump_chunk("scapitest", NULL, buf, nbytes);

    SUCCESS("Random test -- large request.");


    OUTPUT("Random test -- short requests:");
    origrequest_short = 16;

    for (i = 0; i < shortcount; i++) {
        nbytes = origrequest_short;
        rval = sc_random(buf, &nbytes);
        FAILED(rval, "sc_random().");

        if (nbytes != origrequest_short) {
            FAILED(SNMPERR_GENERR,
                   "sc_random() returned different " "than requested.");
        }

        dump_chunk("scapitest", NULL, buf, nbytes);
    }                           /* endfor */

    SUCCESS("Random test -- short requests.");


    return failcount;

}                               /* end test_dorandom() */



/*******************************************************************-o-******
 * test_dokeyedhash
 *
 * Returns:
 *	Number of failures.
 *
 *
 * Test keyed hashes with a variety of MAC length requests.
 *
 *
 * NOTE Both tests intentionally use the same secret
 *
 * FIX	Get input or output from some other package which hashes...
 * XXX	Could cut this in half with a little indirection...
 */
int
test_dokeyedhash(void)
{
    int             rval = SNMPERR_SUCCESS, failcount = 0, bigstring_len = strlen(BIGSTRING), secret_len = strlen(BIGSECRET), properlength, mlcount = 0,        /* MAC Length count.   */
                    hblen;      /* Hash Buffer length. */

    u_int           hashbuf_len[MLCOUNT_MAX] = {
        LOCAL_MAXBUF,
        BYTESIZE(SNMP_TRANS_AUTHLEN_HMACSHA1),
        BYTESIZE(SNMP_TRANS_AUTHLEN_HMACMD5),
        BYTESIZE(SNMP_TRANS_AUTHLEN_HMAC96),
        7,
        0,
    };

    u_char          hashbuf[LOCAL_MAXBUF];
    char           *s;

  test_dokeyedhash_again:

    OUTPUT("Keyed hash test using MD5 --");

    memset(hashbuf, 0, LOCAL_MAXBUF);
    hblen = hashbuf_len[mlcount];
    properlength = BYTESIZE(SNMP_TRANS_AUTHLEN_HMACMD5);

    rval =
        sc_generate_keyed_hash(usmHMACMD5AuthProtocol,
                               USM_LENGTH_OID_TRANSFORM, BIGSECRET,
                               secret_len, BIGSTRING, bigstring_len,
                               hashbuf, &hblen);
    FAILED(rval, "sc_generate_keyed_hash().");

    if (hashbuf_len[mlcount] > properlength) {
        if (hblen != properlength) {
            FAILED(SNMPERR_GENERR, "Wrong MD5 hash length returned.  (1)");
        }

    } else if (hblen != hashbuf_len[mlcount]) {
        FAILED(SNMPERR_GENERR, "Wrong MD5 hash length returned.  (2)");
    }

    rval =
        sc_check_keyed_hash(usmHMACMD5AuthProtocol,
                            USM_LENGTH_OID_TRANSFORM, BIGSECRET,
                            secret_len, BIGSTRING, bigstring_len, hashbuf,
                            hblen);
    FAILED(rval, "sc_check_keyed_hash().");

    binary_to_hex(hashbuf, hblen, &s);
    fprintf(stdout, "hash buffer (len=%d, request=%d):   %s\n",
            hblen, hashbuf_len[mlcount], s);
    SNMP_FREE(s);

    SUCCESS("Keyed hash test using MD5.");



    OUTPUT("Keyed hash test using SHA1 --");

    memset(hashbuf, 0, LOCAL_MAXBUF);
    hblen = hashbuf_len[mlcount];
    properlength = BYTESIZE(SNMP_TRANS_AUTHLEN_HMACSHA1);

    rval =
        sc_generate_keyed_hash(usmHMACSHA1AuthProtocol,
                               USM_LENGTH_OID_TRANSFORM, BIGSECRET,
                               secret_len, BIGSTRING, bigstring_len,
                               hashbuf, &hblen);
    FAILED(rval, "sc_generate_keyed_hash().");

    if (hashbuf_len[mlcount] > properlength) {
        if (hblen != properlength) {
            FAILED(SNMPERR_GENERR,
                   "Wrong SHA1 hash length returned.  (1)");
        }

    } else if (hblen != hashbuf_len[mlcount]) {
        FAILED(SNMPERR_GENERR, "Wrong SHA1 hash length returned.  (2)");
    }

    rval =
        sc_check_keyed_hash(usmHMACSHA1AuthProtocol,
                            USM_LENGTH_OID_TRANSFORM, BIGSECRET,
                            secret_len, BIGSTRING, bigstring_len, hashbuf,
                            hblen);
    FAILED(rval, "sc_check_keyed_hash().");

    binary_to_hex(hashbuf, hblen, &s);
    fprintf(stdout, "hash buffer (len=%d, request=%d):   %s\n",
            hblen, hashbuf_len[mlcount], s);
    SNMP_FREE(s);

    SUCCESS("Keyed hash test using SHA1.");



    /*
     * Run the basic hash tests but vary the size MAC requests.
     */
    if (hashbuf_len[++mlcount] != 0) {
        goto test_dokeyedhash_again;
    }


    return failcount;

}                               /* end test_dokeyedhash() */





/*******************************************************************-o-******
 * test_docrypt
 *
 * Returns:
 *	Number of failures.
 */
int
test_docrypt(void)
{
    int             rval = SNMPERR_SUCCESS,
        failcount = 0,
        bigstring_len = strlen(BIGSTRING),
        secret_len = BYTESIZE(SNMP_TRANS_PRIVLEN_1DES),
        iv_len = BYTESIZE(SNMP_TRANS_PRIVLEN_1DES_IV);

    u_int           buf_len = LOCAL_MAXBUF, cryptbuf_len = LOCAL_MAXBUF;

    char            buf[LOCAL_MAXBUF],
        cryptbuf[LOCAL_MAXBUF], secret[LOCAL_MAXBUF], iv[LOCAL_MAXBUF];

    OUTPUT("Test 1DES-CBC --");


    memset(buf, 0, LOCAL_MAXBUF);

    memcpy(secret, BIGSECRET, secret_len);
    memcpy(iv, BKWDSECRET, iv_len);


    rval = sc_encrypt(usmDESPrivProtocol, USM_LENGTH_OID_TRANSFORM,
                      secret, secret_len,
                      iv, iv_len,
                      BIGSTRING, bigstring_len, cryptbuf, &cryptbuf_len);
    FAILED(rval, "sc_encrypt().");

    rval = sc_decrypt(usmDESPrivProtocol, USM_LENGTH_OID_TRANSFORM,
                      secret, secret_len,
                      iv, iv_len, cryptbuf, cryptbuf_len, buf, &buf_len);
    FAILED(rval, "sc_decrypt().");

    if (buf_len != bigstring_len) {
        FAILED(SNMPERR_GENERR, "Decrypted buffer is the wrong length.");
    }
    if (memcmp(buf, BIGSTRING, bigstring_len)) {
        FAILED(SNMPERR_GENERR,
               "Decrypted buffer is not equal to original plaintext.");
    }


    SUCCESS("Test 1DES-CBC --");

    return failcount;

}                               /* end test_docrypt() */
