/*
    Copyright 2015-2016 Amebis
    Copyright 2016 GÉANT

    This file is part of GÉANTLink.

    GÉANTLink is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    GÉANTLink 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.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with GÉANTLink. If not, see <http://www.gnu.org/licenses/>.
*/

#include "StdAfx.h"

using namespace std;
using namespace winstd;


//////////////////////////////////////////////////////////////////////
// eap::tls_version
//////////////////////////////////////////////////////////////////////

const eap::tls_version eap::tls_version_1_0 = { 3, 1 };
const eap::tls_version eap::tls_version_1_1 = { 3, 2 };
const eap::tls_version eap::tls_version_1_2 = { 3, 3 };


//////////////////////////////////////////////////////////////////////
// eap::tls_random
//////////////////////////////////////////////////////////////////////

void eap::tls_random::randomize(_In_ HCRYPTPROV cp)
{
    _time32((__time32_t*)data);
    if (!CryptGenRandom(cp, sizeof(data) - sizeof(__time32_t), data + sizeof(__time32_t)))
        throw win_runtime_error(__FUNCTION__ " Error creating randomness.");
}


//////////////////////////////////////////////////////////////////////
// eap::tls_master_secret
//////////////////////////////////////////////////////////////////////

eap::tls_master_secret::tls_master_secret()
{
}


eap::tls_master_secret::tls_master_secret(_In_ HCRYPTPROV cp, _In_ tls_version ver)
{
    data[0] = ver.major;
    data[1] = ver.minor;

    if (!CryptGenRandom(cp, sizeof(data) - 2, data + 2))
        throw win_runtime_error(__FUNCTION__ " Error creating PMS randomness.");
}


eap::tls_master_secret::tls_master_secret(_In_ const sanitizing_blob_f<48> &other) :
    sanitizing_blob_xf<48>(other)
{
}


#ifdef _DEBUG

eap::tls_master_secret::tls_master_secret(_Inout_ sanitizing_blob_zf<48> &&other) :
    sanitizing_blob_xf<48>(std::move(other))
{
}

#endif


//////////////////////////////////////////////////////////////////////
// eap::hmac_padding
//////////////////////////////////////////////////////////////////////

eap::hmac_padding::hmac_padding()
{
}


eap::hmac_padding::hmac_padding(
            _In_                               HCRYPTPROV    cp,
            _In_                               ALG_ID        alg,
            _In_bytecount_(size_secret ) const void          *secret,
            _In_                               size_t        size_secret,
            _In_opt_                           unsigned char pad)
{
    if (size_secret > sizeof(hmac_padding)) {
        // If the secret is longer than padding, use secret's hash instead.
        crypt_hash hash;
        if (!hash.create(cp, alg))
            throw win_runtime_error(__FUNCTION__ " Error creating hash.");
        if (!CryptHashData(hash, (const BYTE*)secret, (DWORD)size_secret, 0))
            throw win_runtime_error(__FUNCTION__ " Error hashing.");
        DWORD size_hash = sizeof(hmac_padding);
        if (!CryptGetHashParam(hash, HP_HASHVAL, data, &size_hash, 0))
            throw win_runtime_error(__FUNCTION__ " Error finishing hash.");
        size_secret = size_hash;
    } else
        memcpy(data, secret, size_secret);
    for (size_t i = 0; i < size_secret; i++)
        data[i] ^= pad;
    memset(data + size_secret, pad, sizeof(hmac_padding) - size_secret);
}


eap::hmac_padding::hmac_padding(_In_ const sanitizing_blob_f<64> &other) :
    sanitizing_blob_xf<64>(other)
{
}


#ifdef _DEBUG

eap::hmac_padding::hmac_padding(_Inout_ sanitizing_blob_zf<64> &&other) :
    sanitizing_blob_xf<64>(std::move(other))
{
}

#endif


//////////////////////////////////////////////////////////////////////
// eap::hmac_hash
//////////////////////////////////////////////////////////////////////

eap::hmac_hash::hmac_hash(
    _In_                               HCRYPTPROV cp,
    _In_                               ALG_ID     alg,
    _In_bytecount_(size_secret ) const void       *secret,
    _In_                               size_t     size_secret)
{
    // Prepare inner padding and forward to the other constructor.
    this->hmac_hash::hmac_hash(cp, alg, hmac_padding(cp, alg, secret, size_secret));
}


eap::hmac_hash::hmac_hash(
    _In_       HCRYPTPROV   cp,
    _In_       ALG_ID       alg,
    _In_ const hmac_padding &padding)
{
    // Create inner hash.
    if (!m_hash_inner.create(cp, alg))
        throw win_runtime_error(__FUNCTION__ " Error creating inner hash.");

    // Initialize it with the inner padding.
    if (!CryptHashData(m_hash_inner, padding.data, sizeof(hmac_padding), 0))
        throw win_runtime_error(__FUNCTION__ " Error hashing secret XOR inner padding.");

    // Convert inner padding to outer padding for final calculation.
    hmac_padding padding_out;
    for (size_t i = 0; i < sizeof(hmac_padding); i++)
        padding_out.data[i] = padding.data[i] ^ (0x36 ^ 0x5c);

    // Create outer hash.
    if (!m_hash_outer.create(cp, alg))
        throw win_runtime_error(__FUNCTION__ " Error creating outer hash.");

    // Initialize it with the outer padding.
    if (!CryptHashData(m_hash_outer, padding_out.data, sizeof(hmac_padding), 0))
        throw win_runtime_error(__FUNCTION__ " Error hashing secret XOR inner padding.");
}


//////////////////////////////////////////////////////////////////////
// eap::tls_conn_state
//////////////////////////////////////////////////////////////////////

eap::tls_conn_state::tls_conn_state()
#ifdef _DEBUG
    // Initialize state primitive members for diagnostic purposes.
    :
    m_prov_name     (NULL),
    m_prov_type     (0),
    m_alg_encrypt   (0),
    m_size_enc_key  (0),
    m_size_enc_iv   (0),
    m_size_enc_block(0),
    m_alg_mac       (0),
    m_size_mac_key  (0),
    m_size_mac_hash (0)
#endif
{
}


eap::tls_conn_state::tls_conn_state(_In_ const tls_conn_state &other) :
    m_prov_name     (other.m_prov_name     ),
    m_prov_type     (other.m_prov_type     ),
    m_alg_encrypt   (other.m_alg_encrypt   ),
    m_size_enc_key  (other.m_size_enc_key  ),
    m_size_enc_iv   (other.m_size_enc_iv   ),
    m_size_enc_block(other.m_size_enc_block),
    m_key           (other.m_key           ),
    m_alg_mac       (other.m_alg_mac       ),
    m_size_mac_key  (other.m_size_mac_key  ),
    m_size_mac_hash (other.m_size_mac_hash ),
    m_padding_hmac  (other.m_padding_hmac  )
{
}


eap::tls_conn_state::tls_conn_state(_Inout_ tls_conn_state &&other) :
    m_prov_name     (std::move(other.m_prov_name     )),
    m_prov_type     (std::move(other.m_prov_type     )),
    m_alg_encrypt   (std::move(other.m_alg_encrypt   )),
    m_size_enc_key  (std::move(other.m_size_enc_key  )),
    m_size_enc_iv   (std::move(other.m_size_enc_iv   )),
    m_size_enc_block(std::move(other.m_size_enc_block)),
    m_key           (std::move(other.m_key           )),
    m_alg_mac       (std::move(other.m_alg_mac       )),
    m_size_mac_key  (std::move(other.m_size_mac_key  )),
    m_size_mac_hash (std::move(other.m_size_mac_hash )),
    m_padding_hmac  (std::move(other.m_padding_hmac  ))
{
#ifdef _DEBUG
    // Reinitialize other state primitive members for diagnostic purposes.
    other.m_prov_name      = NULL;
    other.m_prov_type      = 0;
    other.m_alg_encrypt    = 0;
    other.m_size_enc_key   = 0;
    other.m_size_enc_iv    = 0;
    other.m_size_enc_block = 0;
    other.m_alg_mac        = 0;
    other.m_size_mac_key   = 0;
    other.m_size_mac_hash  = 0;
#endif
}


eap::tls_conn_state& eap::tls_conn_state::operator=(_In_ const tls_conn_state &other)
{
    if (this != std::addressof(other)) {
        m_prov_name      = other.m_prov_name     ;
        m_prov_type      = other.m_prov_type     ;
        m_alg_encrypt    = other.m_alg_encrypt   ;
        m_size_enc_key   = other.m_size_enc_key  ;
        m_size_enc_iv    = other.m_size_enc_iv   ;
        m_size_enc_block = other.m_size_enc_block;
        m_key            = other.m_key           ;
        m_alg_mac        = other.m_alg_mac       ;
        m_size_mac_key   = other.m_size_mac_key  ;
        m_size_mac_hash  = other.m_size_mac_hash ;
        m_padding_hmac   = other.m_padding_hmac  ;
    }

    return *this;
}


eap::tls_conn_state& eap::tls_conn_state::operator=(_Inout_ tls_conn_state &&other)
{
    if (this != std::addressof(other)) {
        m_prov_name      = std::move(other.m_prov_name     );
        m_prov_type      = std::move(other.m_prov_type     );
        m_alg_encrypt    = std::move(other.m_alg_encrypt   );
        m_size_enc_key   = std::move(other.m_size_enc_key  );
        m_size_enc_iv    = std::move(other.m_size_enc_iv   );
        m_size_enc_block = std::move(other.m_size_enc_block);
        m_key            = std::move(other.m_key           );
        m_alg_mac        = std::move(other.m_alg_mac       );
        m_size_mac_key   = std::move(other.m_size_mac_key  );
        m_size_mac_hash  = std::move(other.m_size_mac_hash );
        m_padding_hmac   = std::move(other.m_padding_hmac  );

#ifdef _DEBUG
        // Reinitialize other state primitive members for diagnostic purposes.
        other.m_prov_name      = NULL;
        other.m_prov_type      = 0;
        other.m_alg_encrypt    = 0;
        other.m_size_enc_key   = 0;
        other.m_size_enc_iv    = 0;
        other.m_size_enc_block = 0;
        other.m_alg_mac        = 0;
        other.m_size_mac_key   = 0;
        other.m_size_mac_hash  = 0;
#endif
    }

    return *this;
}


void eap::tls_conn_state::set_cipher(_In_ const unsigned char cipher[2])
{
    if (cipher[0] == 0x00 && cipher[1] == 0x0a) {
        // TLS_RSA_WITH_3DES_EDE_CBC_SHA
        m_prov_name      = NULL;
        m_prov_type      = PROV_RSA_AES;
        m_alg_encrypt    = CALG_3DES;
        m_size_enc_key   = 192/8; // 3DES 192bits
        m_size_enc_iv    = 64/8;  // 3DES 64bits
        m_size_enc_block = 64/8;  // 3DES 64bits
        m_alg_mac        = CALG_SHA1;
        m_size_mac_key   = 160/8; // SHA-1
        m_size_mac_hash  = 160/8; // SHA-1
    } else if (cipher[0] == 0x00 && cipher[1] == 0x2f) {
        // TLS_RSA_WITH_AES_128_CBC_SHA
        m_prov_name      = NULL;
        m_prov_type      = PROV_RSA_AES;
        m_alg_encrypt    = CALG_AES_128;
        m_size_enc_key   = 128/8; // AES-128
        m_size_enc_iv    = 128/8; // AES-128
        m_size_enc_block = 128/8; // AES-128
        m_alg_mac        = CALG_SHA1;
        m_size_mac_key   = 160/8; // SHA-1
        m_size_mac_hash  = 160/8; // SHA-1
    } else if (cipher[0] == 0x00 && cipher[1] == 0x3c) {
        // AES128-SHA256
        m_prov_name      = NULL;
        m_prov_type      = PROV_RSA_AES;
        m_alg_encrypt    = CALG_AES_128;
        m_size_enc_key   = 128/8; // AES-128
        m_size_enc_iv    = 128/8; // AES-128
        m_size_enc_block = 128/8; // AES-128
        m_alg_mac        = CALG_SHA_256;
        m_size_mac_key   = 256/8; // SHA-256
        m_size_mac_hash  = 256/8; // SHA-256
    } else if (cipher[0] == 0x00 && cipher[1] == 0x3d) {
        // AES256-SHA256
        m_prov_name      = MS_ENH_RSA_AES_PROV;
        m_prov_type      = PROV_RSA_AES;
        m_alg_encrypt    = CALG_AES_256;
        m_size_enc_key   = 256/8; // AES-256
        m_size_enc_iv    = 128/8; // AES-256
        m_size_enc_block = 128/8; // AES-256
        m_alg_mac        = CALG_SHA_256;
        m_size_mac_key   = 256/8; // SHA-256
        m_size_mac_hash  = 256/8; // SHA-256
    } else if (cipher[0] == 0x00 && cipher[1] == 0x40) {
        // DHE-DSS-AES128-SHA256
        m_prov_name      = MS_ENH_DSS_DH_PROV;
        m_prov_type      = PROV_DSS_DH;
        m_alg_encrypt    = CALG_AES_128;
        m_size_enc_key   = 128/8; // AES-128
        m_size_enc_iv    = 128/8; // AES-128
        m_size_enc_block = 128/8; // AES-128
        m_alg_mac        = CALG_SHA_256;
        m_size_mac_key   = 256/8; // SHA-256
        m_size_mac_hash  = 256/8; // SHA-256
    } else if (cipher[0] == 0x00 && cipher[1] == 0x67) {
        // DHE-RSA-AES128-SHA256
        m_prov_name      = MS_DEF_DH_SCHANNEL_PROV;
        m_prov_type      = PROV_DH_SCHANNEL;
        m_alg_encrypt    = CALG_AES_128;
        m_size_enc_key   = 128/8; // AES-128
        m_size_enc_iv    = 128/8; // AES-128
        m_size_enc_block = 128/8; // AES-128
        m_alg_mac        = CALG_SHA_256;
        m_size_mac_key   = 256/8; // SHA-256
        m_size_mac_hash  = 256/8; // SHA-256
    } else if (cipher[0] == 0x00 && cipher[1] == 0x6a) {
        // DHE-DSS-AES256-SHA256
        m_prov_name      = MS_ENH_DSS_DH_PROV;
        m_prov_type      = PROV_DSS_DH;
        m_alg_encrypt    = CALG_AES_256;
        m_size_enc_key   = 256/8; // AES-256
        m_size_enc_iv    = 128/8; // AES-256
        m_size_enc_block = 128/8; // AES-256
        m_alg_mac        = CALG_SHA_256;
        m_size_mac_key   = 256/8; // SHA-256
        m_size_mac_hash  = 256/8; // SHA-256
    } else if (cipher[0] == 0x00 && cipher[1] == 0x6b) {
        // DHE-RSA-AES256-SHA256
        m_prov_name      = MS_DEF_DH_SCHANNEL_PROV;
        m_prov_type      = PROV_DH_SCHANNEL;
        m_alg_encrypt    = CALG_AES_256;
        m_size_enc_key   = 256/8; // AES-256
        m_size_enc_iv    = 128/8; // AES-256
        m_size_enc_block = 128/8; // AES-256
        m_alg_mac        = CALG_SHA_256;
        m_size_mac_key   = 256/8; // SHA-256
        m_size_mac_hash  = 256/8; // SHA-256
    } else if (cipher[0] == 0xc0 && cipher[1] == 0x23) {
        // ECDHE-ECDSA-AES128-SHA256
        m_prov_name      = MS_ENH_DSS_DH_PROV;
        m_prov_type      = PROV_DSS_DH;
        m_alg_encrypt    = CALG_AES_128;
        m_size_enc_key   = 128/8; // AES-128
        m_size_enc_iv    = 128/8; // AES-128
        m_size_enc_block = 128/8; // AES-128
        m_alg_mac        = CALG_SHA_256;
        m_size_mac_key   = 256/8; // SHA-256
        m_size_mac_hash  = 256/8; // SHA-256
    } else if (cipher[0] == 0xc0 && cipher[1] == 0x24) {
        // ECDHE-ECDSA-AES256-SHA384
        m_prov_name      = MS_ENH_DSS_DH_PROV;
        m_prov_type      = PROV_DSS_DH;
        m_alg_encrypt    = CALG_AES_256;
        m_size_enc_key   = 256/8; // AES-256
        m_size_enc_iv    = 128/8; // AES-256
        m_size_enc_block = 128/8; // AES-256
        m_alg_mac        = CALG_SHA_384;
        m_size_mac_key   = 384/8; // SHA-384
        m_size_mac_hash  = 384/8; // SHA-384
    } else if (cipher[0] == 0xc0 && cipher[1] == 0x27) {
        // ECDHE-RSA-AES128-SHA256
        m_prov_name      = MS_ENH_DSS_DH_PROV;
        m_prov_type      = PROV_DSS_DH;
        m_alg_encrypt    = CALG_AES_128;
        m_size_enc_key   = 128/8; // AES-128
        m_size_enc_iv    = 128/8; // AES-128
        m_size_enc_block = 128/8; // AES-128
        m_alg_mac        = CALG_SHA_256;
        m_size_mac_key   = 256/8; // SHA-256
        m_size_mac_hash  = 256/8; // SHA-256
    } else if (cipher[0] == 0xc0 && cipher[1] == 0x28) {
        // ECDHE-RSA-AES256-SHA384
        m_prov_name      = MS_ENH_DSS_DH_PROV;
        m_prov_type      = PROV_DSS_DH;
        m_alg_encrypt    = CALG_AES_256;
        m_size_enc_key   = 256/8; // AES-256
        m_size_enc_iv    = 128/8; // AES-256
        m_size_enc_block = 128/8; // AES-256
        m_alg_mac        = CALG_SHA_384;
        m_size_mac_key   = 384/8; // SHA-384
        m_size_mac_hash  = 384/8; // SHA-384
    } else
        throw win_runtime_error(ERROR_NOT_SUPPORTED, string_printf(__FUNCTION__ " Unknown cipher (received 0x%02x%02x).", cipher[0], cipher[1]));
}
