/*
 * Copyright (c) 2018-2020 Not for Radio, LLC
 *
 * See the file LICENSE in this distribution.
 *
 */
/* vim: set ts=4 sw=4 et: */

#include "ssl_locl.h"
#include <openssl/ets_crypto.h>
#include <openssl/ets_ssl.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>

struct ets_selected_key {
    uint16_t group_id;
    EVP_PKEY *pkey;
};

struct ets_supported_groups {
    const uint16_t *supported_groups;
    size_t supported_groups_len;
    int *groups;
    size_t ngroups;
    size_t maxgroups;
};

static int ets_choose_selected_key(void *, EVP_PKEY *);
static int ets_enumerate_supported_groups(void *, EVP_PKEY *);
static void ets_foreach_key(SSL *, int (*)(void *, EVP_PKEY *), void *);

int
ETS_limit_groups(SSL *s)
{
    struct ets_supported_groups sg;

    /*
     * In ETS required mode, we here call SSL_set1_groups with a list of
     * groups which constitutes all of the groups enumerated in the
     * VisibilityInformation attached to the certificate in
     * s->cert->key->x509.
     *
     * If the certificate has not yet been set, we cannot do such
     * restriction.  In OpenSSL's implementation of TLS 1.3 at present, the
     * certificate will be selected by this point; that could change in
     * later versions of OpenSSL or in response to later versions of TLS,
     * in which case it may be necessary to force certificate selection at
     * this point.
     *
     * This routine will be called in ETS required mode by the SSL code
     * itself.  An application may choose to call it directly if
     * preferable.  This routine assumes that it has been called in ETS
     * required mode, and leaves the decision of whether to limit the
     * groups used to the caller.
     *
     * It may make sense to call this routine in ETS required mode
     * earlier, namely as soon as a certificate has been selected.  We
     * choose to do this at the point we need the list of supported groups
     * to be restricted instead to minimize intrusion into the OpenSSL
     * codebase, as it makes no practical difference.
     */

    /*
     * First, we retrieve the currently-configured supported groups list.
     */
    tls1_get_supported_groups(s, &sg.supported_groups, &sg.supported_groups_len);

    /*
     * We allocate a list of groups up to the size of our current supported
     * groups list size, which is an effective upper bounds.
     */
    sg.maxgroups = sg.supported_groups_len;
    if (sg.maxgroups == 0) {
        SSLerr(SSL_F_ETS_LIMIT_GROUPS,
               SSL_R_NO_SHARED_GROUPS);
        return 0;
    }
    /*
     * If there were a maximum number of possible configurable groups, we
     * could avoid allocating groups on the heap.
     */
    sg.groups = OPENSSL_malloc(sizeof sg.groups[0] * sg.maxgroups);
    if (sg.groups == NULL) {
        SSLerr(SSL_F_ETS_LIMIT_GROUPS,
               ERR_R_MALLOC_FAILURE);
        return 0;
    }

    /*
     * Now we collect the list of groups for which we have usable static
     * keys configured.
     */
    sg.ngroups = 0;
    ets_foreach_key(s, ets_enumerate_supported_groups, &sg);

    /*
     * If we found no suitable groups and we are in ETS required mode, we
     * must break the connection.
     */
    if (sg.ngroups == 0) {
        OPENSSL_free(sg.groups);
        SSLerr(SSL_F_ETS_LIMIT_GROUPS,
               SSL_R_NO_SHARED_GROUPS);
        return 0;
    }

    if (!SSL_set1_groups(s, sg.groups, sg.ngroups)) {
        OPENSSL_free(sg.groups);
        return 0;
    }

    OPENSSL_free(sg.groups);
    return 1;
}

EVP_PKEY *
ETS_select_key(SSL *s)
{
    struct ets_selected_key sk;

    sk.pkey = NULL;
    sk.group_id = s->s3->group_id;

    ets_foreach_key(s, ets_choose_selected_key, &sk);

    /*
     * We pass our reference to the caller if we found a static key which
     * is usable with the selected group.
     *
     * May fail, returning NULL, if we do not have a static key for the
     * selected group, in which case the caller will enforce the
     * appropriate ETS mode: breaking the connection if ETS is required,
     * generating an ephemeral key if it is optional.
     */
    return sk.pkey;
}

/*
 * Find a static key which is usable with the chosen group.
 */
static int
ets_choose_selected_key(void *c, EVP_PKEY *pkey)
{
    struct ets_selected_key *sk;

    sk = c;

    if (tls1_get_group_id(pkey) != sk->group_id) {
        /* No match, keep looking.  */
        return 1;
    }

    /*
     * We have found a configured static key which is usable with this
     * group.
     *
     * We take a reference, pass the pkey to the caller, and stop looking.
     */
    (void)EVP_PKEY_up_ref(pkey);
    sk->pkey = pkey;
    return 0;
}

/*
 * Check whether a configured ETS static key is suitable for the supported
 * groups list, and track it as a group we are willing to use if so.
 */
static int
ets_enumerate_supported_groups(void *c, EVP_PKEY *pkey)
{
    struct ets_supported_groups *sg;
    const TLS_GROUP_INFO *ginfo;
    uint16_t group_id;
    unsigned j, k;

    sg = c;

    /*
     * If a key does not have an associated group_id and TLS_GROUP_INFO,
     * then it is not fully supported and must be skipped.
     */
    group_id = tls1_get_group_id(pkey);
    if (group_id == 0)
        return 1;

    ginfo = tls1_group_id_lookup(group_id);
    if (ginfo == NULL)
        return 1;

    /*
     * If this group is already in the output list of groups, we do not
     * need to add it.  This can happen if multiple keys with the same
     * group have are configured for a given certificate.
     *
     * Since we are bounded by the current list of supported groups, and do
     * not allow duplicates to occur in the list, this ensures that we
     * cannot end up with a list of groups larger than the current list,
     * which is the size we allocate.
     */
    for (j = 0; j < sg->ngroups; j++) {
        if (sg->groups[j] != ginfo->nid)
            continue;
        return 1;
    }

    /*
     * If a group is not in the supported_groups list currently configured,
     * it must also be skipped.
     *
     * We implement this by adding it to the list when it is found.
     */
    for (k = 0; k < sg->supported_groups_len; k++) {
        if (sg->supported_groups[k] != group_id)
            continue;
        /*
         * Note that ngroups is bounded to maxgroups due to the
         * combination of the constraint that a group must appear in
         * the current supported list and that we do not add a group to
         * the list twice.
         */
        sg->groups[sg->ngroups++] = ginfo->nid;
        break;
    }
    return 1;
}

/*
 * This routine is used to perform work on the subset of ETS static keys
 * usable on this SSL session.
 *
 * The callback is called for every ETS static key that the subject
 * certificate has an ETS Visibility Information entry for, or if no ETS
 * Visibility Information is present, for every ETS static key.  That
 * callback returns true if work should continue and false if its work is
 * finished.
 *
 * The callback is responsible for indicating failure and doing data tracking
 * through its provided context parameter.
 */
static void
ets_foreach_key(SSL *s, int (*cb)(void *, EVP_PKEY *), void *c)
{
    STACK_OF(X509_ATTRIBUTE) *ai;
    X509_ATTRIBUTE *vi_attr;
    GENERAL_NAMES *sans;
    int found_vi;
    int vi_attr_idx;
    int i;

    found_vi = 0;

    /*
     * No certificate is configured.  This cannot happen with extant
     * configurations.
     */
    if (s->cert == NULL || s->cert->key == NULL || s->cert->key->x509 == NULL)
        return;

    /*
     * If we have no available static keys, there is nothing more to do.
     */
    if (s->ctx == NULL || s->ctx->ets_static_keys == NULL)
        return;

    /*
     * Check for an associated information extension attribute containing VI.
     */
    ai = X509_get_ext_d2i(s->cert->key->x509, NID_associated_information, NULL, NULL);
    if (ai != NULL &&
        (vi_attr_idx = X509at_get_attr_by_NID(ai, NID_etsVisibility, -1)) >= 0 &&
        (vi_attr = X509at_get_attr(ai, vi_attr_idx)) != NULL) {

        for (i = 0; i < X509_ATTRIBUTE_count(vi_attr); i++) {
            ETS_VISIBILITY_INFORMATION *vi;
            const uint8_t *fp;
            EVP_PKEY *pkey;
            size_t fplen;

            vi = ASN1_TYPE_unpack_sequence(ASN1_ITEM_rptr(ETS_VISIBILITY_INFORMATION),
                                           X509_ATTRIBUTE_get0_type(vi_attr, i));
            /* Ignore invalid Visibility Information.  */
            if (vi == NULL)
                continue;

            /* We have at least one valid Visibility Information.  */
            found_vi = 1;

            fp = ASN1_STRING_get0_data(vi->fingerprint);
            fplen = ASN1_STRING_length(vi->fingerprint);

            pkey = ETS_find_key(s->ctx->ets_static_keys, fp, fplen);
            if (pkey == NULL) {
                ETS_VISIBILITY_INFORMATION_free(vi);
                continue;
            }

            ETS_VISIBILITY_INFORMATION_free(vi);

            if (cb(c, pkey)) {
                EVP_PKEY_free(pkey);
                continue;
            }
            EVP_PKEY_free(pkey);
            break;
        }

        sk_X509_ATTRIBUTE_free(ai);
    }

    /*
     * If we found valid Visibility Information, our work here is done.
     */
    if (found_vi)
        return;

    /*
     * If there was no VI encoded as an associated information extension,
     * iterate through all available subject alt names looking for VI.
     */
    sans = X509_get_ext_d2i(s->cert->key->x509, NID_subject_alt_name, NULL, NULL);
    if (sans != NULL) {
        for (i = 0; i < sk_GENERAL_NAME_num(sans); i++) {
            ETS_VISIBILITY_INFORMATION *vi;
            GENERAL_NAME *san;
            const uint8_t *fp;
            EVP_PKEY *pkey;
            OTHERNAME *on;
            size_t fplen;

            san = sk_GENERAL_NAME_value(sans, i);
            if (san->type != GEN_OTHERNAME)
                continue;

            on = san->d.otherName;
            if (OBJ_obj2nid(on->type_id) != NID_etsVisibility)
                continue;

            vi = ASN1_TYPE_unpack_sequence(ASN1_ITEM_rptr(ETS_VISIBILITY_INFORMATION), on->value);
            /* Ignore invalid Visibility Information.  */
            if (vi == NULL)
                continue;

            /* We have at least one valid Visibility Information.  */
            found_vi = 1;

            fp = ASN1_STRING_get0_data(vi->fingerprint);
            fplen = ASN1_STRING_length(vi->fingerprint);

            pkey = ETS_find_key(s->ctx->ets_static_keys, fp, fplen);
            if (pkey == NULL) {
                ETS_VISIBILITY_INFORMATION_free(vi);
                continue;
            }

            ETS_VISIBILITY_INFORMATION_free(vi);

            if (cb(c, pkey)) {
                EVP_PKEY_free(pkey);
                continue;
            }
            EVP_PKEY_free(pkey);
            break;
        }

        GENERAL_NAMES_free(sans);
    }

    /*
     * If we found valid Visibility Information, our work here is done.
     */
    if (found_vi)
        return;

    /*
     * If, however, we are using ETS but have no Visibility Information,
     * then we would prefer to use a configured static key so that our
     * middlebox can examine our traffic.  We do this by considering all
     * configured static keys.
     */
    for (i = 0; i < sk_ETS_PKEY_num(s->ctx->ets_static_keys); i++) {
        ETS_PKEY *pk;

        pk = sk_ETS_PKEY_value(s->ctx->ets_static_keys, i);
        if (cb(c, pk->pkey))
            continue;
        break;
    }
}

/* Local Variables:       */
/* c-basic-offset: 4      */
/* tab-width: 4           */
/* indent-tabs-mode: nil  */
/* End:                   */
