/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

#include <stdio.h>
#include <string.h>
#include "mbedtls/platform.h"
#include "mbedtls/debug.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/certs.h"
#include "iotcs_hostname_verifier.h"

#include "log/log.h"
#define IOTCSP_MODULE_LOG_CHANNEL LOG_CHANNEL_PORT_SSL
#include "log/log_template.h"

iotcs_result hostname_verifier_check(mbedtls_ssl_context *ssl, const char* host_addr) {
    int ret, cn_len = 0;
    iotcs_result result = IOTCS_RESULT_OK;
    unsigned char buf[1024];
    char *certCN = NULL;

    LOG_INFO("Host addr : %s", host_addr);

    if (!host_addr || !ssl) {
        LOG_ERRS("verifier_check: some of the input parameters are NULL.");
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    ret = mbedtls_x509_crt_info((char *) buf, sizeof ( buf) - 1, "      ", ssl->session->peer_cert);
    if (ret == -1) {
        LOG_ERR("mbedtls_x509_crt_info returned %d", ret);
        return IOTCS_RESULT_FAIL;
    } else {
        LOG_INFOS((const char*)buf);
    }

    /* e.g. subject=C=US, ST=California, O=Oracle Inc, OU=IoT Platform Group, CN=iotserver */
    ret = mbedtls_x509_dn_gets((char *) buf, sizeof ( buf) - 1, &(ssl->session->peer_cert)->subject);
    if (ret == -1) {
        LOG_ERR("mbedtls_x509_dn_gets returned %d", ret);
        return IOTCS_RESULT_FAIL;
    }
    LOG_INFO("subject=%s", buf);

    certCN = strstr((const char*)buf, "CN=");
    certCN += 3;
    result = IOTCS_RESULT_FAIL;
    for (cn_len = 0; certCN[cn_len] != 0 && certCN[cn_len] != ','; cn_len++);

    /* Check if it is wildcard certificate (we support only leading wildcard) in a form "*.XYZ" */
    if (certCN[0] == '*' && certCN[1] == '.') {
        int host_addr_len = strlen(host_addr);
        /* E.g.: CN "*.abc" should match ".abc" and "XYZ.abc".
         * In host name we should compare only trailing (cn_len - 1) chars the remaining
         * leading chars could be random and must be ignored */
        int ignored_offset = host_addr_len - (cn_len - 1);
        if (ignored_offset < 0) {
            ignored_offset = 0;
        }

        if (strcmp(certCN + 1/* skip '*' symbol */, host_addr + ignored_offset) == 0) {
            result = IOTCS_RESULT_OK;
        } else {
            result = IOTCS_RESULT_FAIL;
        }
    }

    /* Not a wildcard certificate */
    if ((certCN != NULL) && (result == IOTCS_RESULT_FAIL)) {
        if (strncmp(certCN, host_addr, cn_len) == 0 && strlen(host_addr) == cn_len) {
            LOG_INFOS("CN is OK !");
            result = IOTCS_RESULT_OK;
        }
    }
    mbedtls_x509_sequence *alt_names = &ssl->session->peer_cert->subject_alt_names;
    while (result != IOTCS_RESULT_OK && alt_names) {
        if (strncmp((const char*)alt_names->buf.p, host_addr, strlen(host_addr)) == 0) {
            LOG_INFOS("CN is OK !");
            return IOTCS_RESULT_OK;
        }
        alt_names = alt_names->next;
    }
    if (result == IOTCS_RESULT_FAIL) {
        LOG_ERR("CN is NOT OK ! |%s|%d != |%s|", certCN, cn_len, host_addr);
    }
    return result;
}
