/* Copyright (c) 2004-2007, Sara Golemon <sarag@libssh2.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 *   Redistributions of source code must retain the above
 *   copyright notice, this list of conditions and the
 *   following disclaimer.
 *
 *   Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 *   Neither the name of the copyright holder nor the names
 *   of any other contributors may be used to endorse or
 *   promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

#include "libssh2_priv.h"
#include "mac.h"

#if defined(LIBSSH2DEBUG) && defined(LIBSSH2_MAC_NONE_INSECURE)
/* mac_none_MAC
 *
 * Minimalist MAC: No MAC. DO NOT USE.
 *
 * The SSH2 Transport allows implementations to forego a message
 * authentication code.  While this is less of a security risk than using
 * a "none" cipher, it is still not recommended as disabling MAC hashes
 * removes a layer of security.
 *
 * Enabling this option will allow for "none" as a negotiable method,
 * however it still requires that the method be advertised by the remote
 * end and that no more-preferable methods are available.
 *
 */
static int
mac_none_MAC(LIBSSH2_SESSION * session, unsigned char *buf,
             uint32_t seqno, const unsigned char *packet,
             size_t packet_len, const unsigned char *addtl,
             size_t addtl_len, void **abstract)
{
    return 0;
}




static LIBSSH2_MAC_METHOD mac_method_none = {
    "none",
    0,
    0,
    NULL,
    mac_none_MAC,
    NULL,
    0
};
#endif /* defined(LIBSSH2DEBUG) && defined(LIBSSH2_MAC_NONE_INSECURE) */

/* mac_method_common_init
 * Initialize simple mac methods
 */
static int
mac_method_common_init(LIBSSH2_SESSION * session, unsigned char *key,
                       int *free_key, void **abstract)
{
    *abstract = key;
    *free_key = 0;
    (void)session;

    return 0;
}



/* mac_method_common_dtor
 * Cleanup simple mac methods
 */
static int
mac_method_common_dtor(LIBSSH2_SESSION * session, void **abstract)
{
    if(*abstract) {
        LIBSSH2_FREE(session, *abstract);
    }
    *abstract = NULL;

    return 0;
}



#if LIBSSH2_HMAC_SHA512
/* mac_method_hmac_sha512_hash
 * Calculate hash using full sha512 value
 */
static int
mac_method_hmac_sha2_512_hash(LIBSSH2_SESSION * session,
                              unsigned char *buf, uint32_t seqno,
                              const unsigned char *packet,
                              size_t packet_len,
                              const unsigned char *addtl,
                              size_t addtl_len, void **abstract)
{
    libssh2_hmac_ctx ctx;
    unsigned char seqno_buf[4];
    (void)session;

    _libssh2_htonu32(seqno_buf, seqno);

    libssh2_hmac_ctx_init(ctx);
    libssh2_hmac_sha512_init(&ctx, *abstract, 64);
    libssh2_hmac_update(ctx, seqno_buf, 4);
    libssh2_hmac_update(ctx, packet, packet_len);
    if(addtl && addtl_len) {
        libssh2_hmac_update(ctx, addtl, addtl_len);
    }
    libssh2_hmac_final(ctx, buf);
    libssh2_hmac_cleanup(&ctx);

    return 0;
}

static const LIBSSH2_MAC_METHOD mac_method_hmac_sha2_512 = {
    "hmac-sha2-512",
    64,
    64,
    mac_method_common_init,
    mac_method_hmac_sha2_512_hash,
    mac_method_common_dtor,
    0
};

static const LIBSSH2_MAC_METHOD mac_method_hmac_sha2_512_etm = {
    "hmac-sha2-512-etm@openssh.com",
    64,
    64,
    mac_method_common_init,
    mac_method_hmac_sha2_512_hash,
    mac_method_common_dtor,
    1
};

#endif



#if LIBSSH2_HMAC_SHA256
/* mac_method_hmac_sha256_hash
 * Calculate hash using full sha256 value
 */
static int
mac_method_hmac_sha2_256_hash(LIBSSH2_SESSION * session,
                              unsigned char *buf, uint32_t seqno,
                              const unsigned char *packet,
                              size_t packet_len,
                              const unsigned char *addtl,
                              size_t addtl_len, void **abstract)
{
    libssh2_hmac_ctx ctx;
    unsigned char seqno_buf[4];
    (void)session;

    _libssh2_htonu32(seqno_buf, seqno);

    libssh2_hmac_ctx_init(ctx);
    libssh2_hmac_sha256_init(&ctx, *abstract, 32);
    libssh2_hmac_update(ctx, seqno_buf, 4);
    libssh2_hmac_update(ctx, packet, packet_len);
    if(addtl && addtl_len) {
        libssh2_hmac_update(ctx, addtl, addtl_len);
    }
    libssh2_hmac_final(ctx, buf);
    libssh2_hmac_cleanup(&ctx);

    return 0;
}



static const LIBSSH2_MAC_METHOD mac_method_hmac_sha2_256 = {
    "hmac-sha2-256",
    32,
    32,
    mac_method_common_init,
    mac_method_hmac_sha2_256_hash,
    mac_method_common_dtor,
    0
};

static const LIBSSH2_MAC_METHOD mac_method_hmac_sha2_256_etm = {
    "hmac-sha2-256-etm@openssh.com",
    32,
    32,
    mac_method_common_init,
    mac_method_hmac_sha2_256_hash,
    mac_method_common_dtor,
    1
};

#endif




/* mac_method_hmac_sha1_hash
 * Calculate hash using full sha1 value
 */
static int
mac_method_hmac_sha1_hash(LIBSSH2_SESSION * session,
                          unsigned char *buf, uint32_t seqno,
                          const unsigned char *packet,
                          size_t packet_len,
                          const unsigned char *addtl,
                          size_t addtl_len, void **abstract)
{
    libssh2_hmac_ctx ctx;
    unsigned char seqno_buf[4];
    (void)session;

    _libssh2_htonu32(seqno_buf, seqno);

    libssh2_hmac_ctx_init(ctx);
    libssh2_hmac_sha1_init(&ctx, *abstract, 20);
    libssh2_hmac_update(ctx, seqno_buf, 4);
    libssh2_hmac_update(ctx, packet, packet_len);
    if(addtl && addtl_len) {
        libssh2_hmac_update(ctx, addtl, addtl_len);
    }
    libssh2_hmac_final(ctx, buf);
    libssh2_hmac_cleanup(&ctx);

    return 0;
}



static const LIBSSH2_MAC_METHOD mac_method_hmac_sha1 = {
    "hmac-sha1",
    20,
    20,
    mac_method_common_init,
    mac_method_hmac_sha1_hash,
    mac_method_common_dtor,
    0
};

static const LIBSSH2_MAC_METHOD mac_method_hmac_sha1_etm = {
    "hmac-sha1-etm@openssh.com",
    20,
    20,
    mac_method_common_init,
    mac_method_hmac_sha1_hash,
    mac_method_common_dtor,
    1
};

/* mac_method_hmac_sha1_96_hash
 * Calculate hash using first 96 bits of sha1 value
 */
static int
mac_method_hmac_sha1_96_hash(LIBSSH2_SESSION * session,
                             unsigned char *buf, uint32_t seqno,
                             const unsigned char *packet,
                             size_t packet_len,
                             const unsigned char *addtl,
                             size_t addtl_len, void **abstract)
{
    unsigned char temp[SHA_DIGEST_LENGTH];

    mac_method_hmac_sha1_hash(session, temp, seqno, packet, packet_len,
                              addtl, addtl_len, abstract);
    memcpy(buf, (char *) temp, 96 / 8);

    return 0;
}



static const LIBSSH2_MAC_METHOD mac_method_hmac_sha1_96 = {
    "hmac-sha1-96",
    12,
    20,
    mac_method_common_init,
    mac_method_hmac_sha1_96_hash,
    mac_method_common_dtor,
    0
};

#if LIBSSH2_MD5
/* mac_method_hmac_md5_hash
 * Calculate hash using full md5 value
 */
static int
mac_method_hmac_md5_hash(LIBSSH2_SESSION * session, unsigned char *buf,
                         uint32_t seqno,
                         const unsigned char *packet,
                         size_t packet_len,
                         const unsigned char *addtl,
                         size_t addtl_len, void **abstract)
{
    libssh2_hmac_ctx ctx;
    unsigned char seqno_buf[4];
    (void)session;

    _libssh2_htonu32(seqno_buf, seqno);

    libssh2_hmac_ctx_init(ctx);
    libssh2_hmac_md5_init(&ctx, *abstract, 16);
    libssh2_hmac_update(ctx, seqno_buf, 4);
    libssh2_hmac_update(ctx, packet, packet_len);
    if(addtl && addtl_len) {
        libssh2_hmac_update(ctx, addtl, addtl_len);
    }
    libssh2_hmac_final(ctx, buf);
    libssh2_hmac_cleanup(&ctx);

    return 0;
}



static const LIBSSH2_MAC_METHOD mac_method_hmac_md5 = {
    "hmac-md5",
    16,
    16,
    mac_method_common_init,
    mac_method_hmac_md5_hash,
    mac_method_common_dtor,
    0
};

/* mac_method_hmac_md5_96_hash
 * Calculate hash using first 96 bits of md5 value
 */
static int
mac_method_hmac_md5_96_hash(LIBSSH2_SESSION * session,
                            unsigned char *buf, uint32_t seqno,
                            const unsigned char *packet,
                            size_t packet_len,
                            const unsigned char *addtl,
                            size_t addtl_len, void **abstract)
{
    unsigned char temp[MD5_DIGEST_LENGTH];
    mac_method_hmac_md5_hash(session, temp, seqno, packet, packet_len,
                             addtl, addtl_len, abstract);
    memcpy(buf, (char *) temp, 96 / 8);
    return 0;
}



static const LIBSSH2_MAC_METHOD mac_method_hmac_md5_96 = {
    "hmac-md5-96",
    12,
    16,
    mac_method_common_init,
    mac_method_hmac_md5_96_hash,
    mac_method_common_dtor,
    0
};
#endif /* LIBSSH2_MD5 */

#if LIBSSH2_HMAC_RIPEMD
/* mac_method_hmac_ripemd160_hash
 * Calculate hash using ripemd160 value
 */
static int
mac_method_hmac_ripemd160_hash(LIBSSH2_SESSION * session,
                               unsigned char *buf, uint32_t seqno,
                               const unsigned char *packet,
                               size_t packet_len,
                               const unsigned char *addtl,
                               size_t addtl_len,
                               void **abstract)
{
    libssh2_hmac_ctx ctx;
    unsigned char seqno_buf[4];
    (void)session;

    _libssh2_htonu32(seqno_buf, seqno);

    libssh2_hmac_ctx_init(ctx);
    libssh2_hmac_ripemd160_init(&ctx, *abstract, 20);
    libssh2_hmac_update(ctx, seqno_buf, 4);
    libssh2_hmac_update(ctx, packet, packet_len);
    if(addtl && addtl_len) {
        libssh2_hmac_update(ctx, addtl, addtl_len);
    }
    libssh2_hmac_final(ctx, buf);
    libssh2_hmac_cleanup(&ctx);

    return 0;
}



static const LIBSSH2_MAC_METHOD mac_method_hmac_ripemd160 = {
    "hmac-ripemd160",
    20,
    20,
    mac_method_common_init,
    mac_method_hmac_ripemd160_hash,
    mac_method_common_dtor,
    0
};

static const LIBSSH2_MAC_METHOD mac_method_hmac_ripemd160_openssh_com = {
    "hmac-ripemd160@openssh.com",
    20,
    20,
    mac_method_common_init,
    mac_method_hmac_ripemd160_hash,
    mac_method_common_dtor,
    0
};
#endif /* LIBSSH2_HMAC_RIPEMD */

static const LIBSSH2_MAC_METHOD *mac_methods[] = {
#if LIBSSH2_HMAC_SHA256
    &mac_method_hmac_sha2_256,
    &mac_method_hmac_sha2_256_etm,
#endif
#if LIBSSH2_HMAC_SHA512
    &mac_method_hmac_sha2_512,
    &mac_method_hmac_sha2_512_etm,
#endif
    &mac_method_hmac_sha1,
    &mac_method_hmac_sha1_etm,
    &mac_method_hmac_sha1_96,
#if LIBSSH2_MD5
    &mac_method_hmac_md5,
    &mac_method_hmac_md5_96,
#endif
#if LIBSSH2_HMAC_RIPEMD
    &mac_method_hmac_ripemd160,
    &mac_method_hmac_ripemd160_openssh_com,
#endif /* LIBSSH2_HMAC_RIPEMD */
#if defined(LIBSSH2DEBUG) && defined(LIBSSH2_MAC_NONE_INSECURE)
    &mac_method_none,
#endif
    NULL
};

const LIBSSH2_MAC_METHOD **
_libssh2_mac_methods(void)
{
    return mac_methods;
}

#if LIBSSH2_AES_GCM
/* Stub for aes256-gcm@openssh.com crypto type, which has an integrated
   HMAC method. This must not be added to mac_methods[] since it cannot be
   negotiated separately. */
static const LIBSSH2_MAC_METHOD mac_method_hmac_aesgcm = {
    "INTEGRATED-AES-GCM",  /* made up name for display only */
    16,
    16,
    NULL,
    NULL,
    NULL,
    0
};
#endif /* LIBSSH2_AES_GCM */

/* See if the negotiated crypto method has its own authentication scheme that
 * obviates the need for a separate negotiated hmac method */
const LIBSSH2_MAC_METHOD *
_libssh2_mac_override(const LIBSSH2_CRYPT_METHOD *crypt)
{
#if LIBSSH2_AES_GCM
    if(!strcmp(crypt->name, "aes256-gcm@openssh.com") ||
       !strcmp(crypt->name, "aes128-gcm@openssh.com"))
        return &mac_method_hmac_aesgcm;
#else
    (void) crypt;
#endif /* LIBSSH2_AES_GCM */
    return NULL;
}
