/*!========================================================================
  @ingroup      Sec
  @file         RTESec_SCRAMMD5.c
  @author       ChristophB

  @brief

  @since        2004-08-18  16:34
  @sa

  ==========================================================================

    The following sample source code implements SCRAM itself for both
    server and client.

    Please note the lines marked "XXX: ..." as they need to be
    translated from English to computer readable code.

    A client implementation simply calls SCRAMMD5Gen() with the
    passphrase after receiving the first server reply.  The cproof
    parameter will hold the message to send to the server and the
    sproof parameter will hold the expected server mutual
    authentication.  The clidata parameter holds the client's security
    layer selections.  A client may also call HMACMD5Precalc() to
    turn a passphrase into CRAM/SCRAM credentials for later use in
    SCRAMMD5Gen().

    A server implementation simply calls SCRAMMD5Gen() with the
    stored verifier, the second client message and the SCRAM_VERIFY
    option.  Server verifiers are generated by creating a random salt
    and calling SCRAMMD5GenVerfier() with either the passphrase or
    CRAM/SCRAM credentials.

    If integrity protection is desired, the integrity_key parameter may
    be provided to SCRAMMD5Gen() and will receive the resulting
    key. Generating an verifying the integrity protection is left as
    an exercise to the reader.

 ============================================================================*/

/*===========================================================================*
 *  INCLUDES                                                                 *
 *===========================================================================*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "RunTime/Security/RTESec_MD5.h"
#include "RunTime/Security/RTESec_HMACMD5.h"
#include "RunTime/Security/RTESec_SCRAMMD5.h"
#include "RunTime/Security/RTESec_DumpHex.hpp"

/*===========================================================================*
 *  DEFINES                                                                  *
 *===========================================================================*/

/*===========================================================================*
 *  MACROS                                                                   *
 *===========================================================================*/

/*===========================================================================*
 *  LOCAL CLASSES, STRUCTURES, TYPES, UNIONS ...                             *
 *===========================================================================*/

/*===========================================================================*
 *  GLOBAL VARIABLES                                                         *
 *===========================================================================*/

/*===========================================================================*
 *  STATIC/INLINE FUNCTION PROTOTYPES                                        *
 *===========================================================================*/

/*===========================================================================*
 *  PUBLIC FUNCTIONS (CODE)                                                  *
 *===========================================================================*/

void SCRAMMD5GenVerifier (SCRAMMD5_VRFY *vptr,
                     const unsigned char *salt,
                     const char *password,
                     int pwdlen,
                     unsigned char *clientkey)
{
    HMACMD5_CTX hctx;

    if (clientkey == 0) {
        clientkey = vptr->clidata;
    }

    if (pwdlen == -1) {
        pwdlen = strlen (password);
    }
    RTESec_DumpHex ("SCRAMMD5GenVerifier: salt", salt, SCRAMMD5_SALTSIZE);
    RTESec_DumpHex ("SCRAMMD5GenVerifier: password", password, pwdlen);
    /* get context */
    HMACMD5Init (&hctx, (const unsigned char *) password, pwdlen);
    RTESec_DumpHex ("SCRAMMD5GenVerifier: HMACMD5Init", &hctx, sizeof (hctx));

    /* generate salted passphrase */
    HMACMD5Update (&hctx, salt, SCRAMMD5_SALTSIZE);
    RTESec_DumpHex ("SCRAMMD5GenVerifier: HMACMD5Update", &hctx, sizeof (hctx));
    HMACMD5Final (vptr->clidata, &hctx);
    RTESec_DumpHex ("SCRAMMD5GenVerifier: HMACMD5Final", vptr->clidata, sizeof (vptr->clidata));

    /* generate server proof */
    HMACMD5 (salt, SCRAMMD5_SALTSIZE, vptr->clidata,
    sizeof (vptr->clidata), vptr->svrdata);

    /* generate client key and client verifier */
    RTESec_MD5Init (&hctx.ictx);
    RTESec_MD5Update (&hctx.ictx, vptr->clidata, sizeof (vptr->clidata));
    RTESec_MD5Final (clientkey, &hctx.ictx);
    RTESec_MD5Init (&hctx.ictx);
    RTESec_MD5Update (&hctx.ictx, clientkey, SCRAMMD5_DATASIZE);
    RTESec_MD5Final (vptr->clidata, &hctx.ictx);

    /* copy salt to verifier */
    if (salt != vptr->salt)
        memcpy (vptr->salt, salt, SCRAMMD5_SALTSIZE);
}

int SCRAMMD5GenClient (SCRAMMD5_CLIENT *clidata,
                       const char *cchal, int cchallen,
                       const char *salt, int saltlen,
                       const char *schal, int schallen,
                       const char *password, int pwdlen,
                       unsigned char *sproof)
{
    HMACMD5_CTX hctx;
    SCRAMMD5_VRFY verifier[1];
    unsigned char clientkey[HMACMD5_SIZE];
    unsigned char sharedkey[HMACMD5_SIZE];
    int i;
    int result = 0;

    RTESec_DumpHex ("SCRAMMD5GenClient: client challenge", cchal, cchallen);
    RTESec_DumpHex ("SCRAMMD5GenClient: server salt     ", salt, saltlen);
    RTESec_DumpHex ("SCRAMMD5GenClient: server challenge", schal, schallen);
    /* check params */
    if (saltlen < SCRAMMD5_SALTSIZE)
        return -2;

    /* get verifier */
    SCRAMMD5GenVerifier (verifier, (const unsigned char *) salt,
        password, pwdlen, clientkey);
    RTESec_DumpHex ("SCRAMMD5Gen: verifier", &verifier, sizeof (SCRAMMD5_VRFY));

    /* calculate shared key */
    HMACMD5Init (&hctx, verifier->clidata, sizeof (verifier->clidata));
    RTESec_DumpHex ("SCRAMMD5GenClient: HMACMD5Init", &hctx, sizeof (hctx));
    HMACMD5Update (&hctx, (unsigned char *) salt, saltlen);
    HMACMD5Update (&hctx, (unsigned char *) schal, schallen);
    RTESec_DumpHex ("SCRAMMD5GenClient: HMACMD5Update server", &hctx, sizeof (hctx));
    HMACMD5Update (&hctx, (unsigned char *) cchal, cchallen);
    RTESec_DumpHex ("SCRAMMD5GenClient: HMACMD5Update client", &hctx, sizeof (hctx));
    HMACMD5Final (sharedkey, &hctx);
    RTESec_DumpHex ("SCRAMMD5GenClient: HMACMD5Final", sharedkey, sizeof (sharedkey));

    /* generate client proof */
    for (i = 0; i < HMACMD5_SIZE; ++i) {
        /* XXX:
         * the line which belongs here is omitted due to
         * U.S. export regulations, but it exclusive-ors the
         * "sharedkey" with the "clientkey" and places the
         * result in "clidata->cproof" (see step (G) above)
         */
        clidata->cproof[i] = sharedkey[i] ^ clientkey[i];
    }

    RTESec_DumpHex ("SCRAMMD5GenClient: cproof:", clidata->cproof, sizeof (clidata->cproof));
    /* calculate server result */
    if (result == 0) {
        HMACMD5Init (&hctx, verifier->svrdata, HMACMD5_SIZE);
        HMACMD5Update (&hctx, (unsigned char *) cchal, cchallen);
        HMACMD5Update (&hctx, (unsigned char *) schal, schallen);
        HMACMD5Final (sproof, &hctx);
    }

    /* cleanup workspace */
    memset (clientkey, 0, sizeof (clientkey));
    memset (sharedkey, 0, sizeof (sharedkey));
    memset (verifier, 0, sizeof (verifier));

    return (result);
}

int SCRAMMD5Gen (SCRAMMD5_CLIENT *clidata,
                 const char *cchal, int cchallen,
                 const char *salt, int saltlen,
                 const char *schal, int schallen,
                 const char *secret, int secretlen,
                 int action,
                 unsigned char *sproof)
{
    SCRAMMD5_VRFY verifier, *vptr;
    HMACMD5_CTX hctx;
    unsigned char clientkey[HMACMD5_SIZE];
    unsigned char sharedkey[HMACMD5_SIZE];
    int i, result = 0;

    RTESec_DumpHex ("SCRAMMD5Gen: client challenge:", cchal, cchallen);
    RTESec_DumpHex ("SCRAMMD5Gen: server challenge:", schal, schallen);
    /* check params */
    if (action == SCRAM_CREDENTIAL && secretlen != sizeof (HMACMD5_STATE))
        return (-2);

    if (action == SCRAM_VERIFY && secretlen != sizeof (verifier))
        return (-2);

    if (saltlen < SCRAMMD5_SALTSIZE)
        return (-2);

    /* get verifier */
    if (action == SCRAM_VERIFY) {
        vptr = (SCRAMMD5_VRFY *) secret;
        RTESec_DumpHex ("SCRAMMD5Gen: verifier", vptr, sizeof (SCRAMMD5_VRFY));
    } else {
        SCRAMMD5GenVerifier (
            &verifier, (const unsigned char *) salt,
            secret, secretlen, clientkey);
        vptr = &verifier;
    }

    /* calculate shared key */
    HMACMD5Init (&hctx, vptr->clidata, sizeof (vptr->clidata));
    RTESec_DumpHex ("SCRAMMD5Gen: HMACMD5Init", &hctx, sizeof (hctx));
    HMACMD5Update (&hctx, (unsigned char *) salt, saltlen);
    HMACMD5Update (&hctx, (unsigned char *) schal, schallen);
    RTESec_DumpHex ("SCRAMMD5Gen: HMACMD5Update server", &hctx, sizeof (hctx));
    HMACMD5Update (&hctx, (unsigned char *) cchal, cchallen);
    RTESec_DumpHex ("SCRAMMD5Gen: HMACMD5Update client", &hctx, sizeof (hctx));
    HMACMD5Final (sharedkey, &hctx);
    RTESec_DumpHex ("SCRAMMD5Gen: HMACMD5Final", &sharedkey, sizeof (sharedkey));

    if (action == SCRAM_VERIFY) {
        /* verify client proof */
        for (i = 0; i < HMACMD5_SIZE; ++i) {
            /*
             * XXX:
             * the line which belongs here is omitted due to
             * U.S. export regulations, but it exclusive-ors the
             * "sharedkey" with the "clidata->cproof" and places the
             * result in "clientkey" (see step (c) above)
             */
            clientkey[i] = sharedkey[i] ^ clidata->cproof[i];
        }

        RTESec_MD5Init(&hctx.ictx);
        RTESec_MD5Update(&hctx.ictx, clientkey, sizeof (clientkey));
        RTESec_MD5Final(sharedkey, &hctx.ictx);

        if (memcmp (sharedkey, vptr->clidata, sizeof (sharedkey)) != 0)
            result = -1;
    } else {
        /* generate client proof */
        for (i = 0; i < HMACMD5_SIZE; ++i) {
            /* XXX:
             * the line which belongs here is omitted due to
             * U.S. export regulations, but it exclusive-ors the
             * "sharedkey" with the "clientkey" and places the
             * result in "clidata->cproof" (see step (G) above)
             */
            clidata->cproof[i] = sharedkey[i] ^ clientkey[i];
        }
    }

    /* calculate server result */
    if (result == 0) {
        HMACMD5Init (&hctx, vptr->svrdata, HMACMD5_SIZE);
        HMACMD5Update (&hctx, (unsigned char *) cchal, cchallen);
        HMACMD5Update (&hctx, (unsigned char *) salt, saltlen);
        HMACMD5Update (&hctx, (unsigned char *) schal, schallen);
        HMACMD5Final (sproof, &hctx);
    }

    /* cleanup workspace */
    memset (clientkey, 0, sizeof (clientkey));
    memset (sharedkey, 0, sizeof (sharedkey));
    if (vptr == &verifier)
        memset (&verifier, 0, sizeof (verifier));

    return (result);
}

