/* Copyright (C) 2020-2022 Open Information Security Foundation
 *
 * You can copy, redistribute or modify this Program under the terms of
 * the GNU General Public License version 2 as published by the Free
 * Software Foundation.
 *
 * This program 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
 * version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/**
 * \file detect-asn1.c
 *
 * Implements "asn1" keyword
 */

#include "suricata-common.h"
#include "decode.h"
#include "rust.h"

#include "detect.h"
#include "detect-parse.h"

#include "flow.h"
#include "detect-asn1.h"

#include "util-unittest.h"
#include "util-unittest-helper.h"
#include "util-byte.h"
#include "util-debug.h"

static int DetectAsn1Setup (DetectEngineCtx *, Signature *, const char *);
#ifdef UNITTESTS
static void DetectAsn1RegisterTests(void);
#endif
static void DetectAsn1Free(DetectEngineCtx *, void *);

/**
 * \brief Registration function for asn1
 */
void DetectAsn1Register(void)
{
    sigmatch_table[DETECT_ASN1].name = "asn1";
    sigmatch_table[DETECT_ASN1].Setup = DetectAsn1Setup;
    sigmatch_table[DETECT_ASN1].Free  = DetectAsn1Free;
#ifdef UNITTESTS
    sigmatch_table[DETECT_ASN1].RegisterTests = DetectAsn1RegisterTests;
#endif
}

bool DetectAsn1Match(const SigMatchData *smd, const uint8_t *buffer, const uint32_t buffer_len,
        const uint32_t offset)
{
    const DetectAsn1Data *ad = (const DetectAsn1Data *)smd->ctx;
    Asn1 *asn1 = SCAsn1Decode(buffer, buffer_len, offset, ad);
    uint8_t ret = SCAsn1Checks(asn1, ad);
    SCAsn1Free(asn1);
    return ret == 1;
}

/**
 * \brief This function is used to parse asn1 options passed via asn1: keyword
 *
 * \param asn1str pointer to the user provided asn1 options
 *
 * \retval pointer to `DetectAsn1Data` on success
 * \retval NULL on failure
 */
static DetectAsn1Data *DetectAsn1Parse(const char *asn1str)
{
    DetectAsn1Data *ad = SCAsn1DetectParse(asn1str);

    if (ad == NULL) {
        SCLogError("Malformed asn1 argument: %s", asn1str);
    }

    return ad;
}

/**
 * \brief this function is used to add the parsed asn1 data into
 *        the current signature
 *
 * \param de_ctx pointer to the detection engine context
 * \param s pointer to the current signature
 * \param asn1str pointer to the user provided asn1 options
 *
 * \retval 0 on success
 * \retval -1 on failure
 */
static int DetectAsn1Setup(DetectEngineCtx *de_ctx, Signature *s, const char *asn1str)
{
    DetectAsn1Data *ad = DetectAsn1Parse(asn1str);
    if (ad == NULL)
        return -1;

    if (SCSigMatchAppendSMToList(
                de_ctx, s, DETECT_ASN1, (SigMatchCtx *)ad, DETECT_SM_LIST_PMATCH) == NULL) {
        DetectAsn1Free(de_ctx, ad);
        return -1;
    }

    s->flags |= SIG_FLAG_REQUIRE_PACKET;
    return 0;
}

/**
 * \brief this function will free memory associated with `DetectAsn1Data`
 *
 * \param de_ctx pointer to the detection engine context
 * \param ptr point to `DetectAsn1Data`
 */
static void DetectAsn1Free(DetectEngineCtx *de_ctx, void *ptr)
{
    DetectAsn1Data *ad = (DetectAsn1Data *)ptr;
    SCAsn1DetectFree(ad);
}

#ifdef UNITTESTS

/**
 * \test DetectAsn1TestReal01 Ensure that all works together
 */
static int DetectAsn1TestReal01(void)
{
    uint8_t *buf = (uint8_t *) "\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01"
                   "P""\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111"
                   "\x31\x1F\x61\x11\x1A\x05""Susan""\x1A\x01""B""\x1A\x05"
                   "Jones""\xA0\x0A\x43\x08""19590717"
                   "\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01""P"
                   "\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111""\x31\x1F"
                   "\x61\x11\x1A\x05""Pablo""\x1A\x01""B""\x1A\x05""Jones"
                   "\xA0\x0A\x43\x08""19590717";

    uint16_t buflen = strlen((char *)buf) - 1;

    /* Check the start with AA (this is to test the relative_offset keyword) */
    uint8_t *buf2 = (uint8_t *) "AA\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01"
                   "P""\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111"
                   "\x31\x1F\x61\x11\x1A\x05""Susan""\x1A\x01""B""\x1A\x05"
                   "Jones""\xA0\x0A\x43\x08""19590717"
                   "\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01""P"
                   "\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111""\x31\x1F"
                   "\x61\x11\x1A\x05""Susan""\x1A\x01""B""\x1A\x05""Jones"
                   "\xA0\x0A\x43\x08""19590717";

    uint16_t buflen2 = strlen((char *)buf2) - 1;

    Packet *p[2];

    p[0] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_TCP);
    FAIL_IF_NULL(p[0]);
    p[1] = UTHBuildPacket((uint8_t *)buf2, buflen2, IPPROTO_TCP);
    FAIL_IF_NULL(p[1]);

    const char *sigs[3];
    sigs[0]= "alert ip any any -> any any (msg:\"Testing id 1\"; "
             "content:\"Pablo\"; asn1:absolute_offset 0, "
             "oversize_length 130; sid:1;)";
    sigs[1]= "alert ip any any -> any any (msg:\"Testing id 2\"; "
             "content:\"AA\"; asn1:relative_offset 0, "
             "oversize_length 130; sid:2;)";
    sigs[2]= "alert ip any any -> any any (msg:\"Testing id 3\"; "
             "content:\"lalala\"; asn1: oversize_length 2000; sid:3;)";

    uint32_t sid[3] = {1, 2, 3};
    uint32_t results[2][3] = {
                              /* packet 0 match sid 1 */
                              {1, 0, 0},
                              /* packet 1 match sid 2 */
                              {0, 1, 0}};
    /* None of the packets should match sid 3 */
    FAIL_IF_NOT(UTHGenericTest(p, 2, sigs, sid, (uint32_t *)results, 3) == 1);

    UTHFreePackets(p, 2);
    PASS;
}

/**
 * \test DetectAsn1TestReal02 Ensure that all works together
 */
static int DetectAsn1TestReal02(void)
{
    int result = 0;
    uint8_t *buf = (uint8_t *) "\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01"
                   "P""\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111"
                   "\x31\x1F\x61\x11\x1A\x05""Susan""\x1A\x01""B""\x1A\x05"
                   "Jones""\xA0\x0A\x43\x08""19590717"
                   "\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01""P"
                   "\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111""\x31\x1F"
                   "\x61\x11\x1A\x05""Pablo""\x1A\x01""B""\x1A\x05""Jones"
                   "\xA0\x0A\x43\x08""19590717";

    uint16_t buflen = strlen((char *)buf) - 1;

    /* Check the start with AA (this is to test the relative_offset keyword) */
    uint8_t *buf2 = (uint8_t *) "AA\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01"
                   "P""\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111"
                   "\x31\x1F\x61\x11\x1A\x05""Susan""\x1A\x01""B""\x1A\x05"
                   "Jones""\xA0\x0A\x43\x08""19590717"
                   "\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01""P"
                   "\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111""\x31\x1F"
                   "\x61\x11\x1A\x05""Susan""\x1A\x01""B""\x1A\x05""Jones"
                   "\xA0\x0A\x43\x08""19590717";

    uint16_t buflen2 = strlen((char *)buf2) - 1;

    Packet *p[2];

    p[0] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_TCP);
    p[1] = UTHBuildPacket((uint8_t *)buf2, buflen2, IPPROTO_TCP);

    if (p[0] == NULL || p[1] == NULL)
        goto end;

    const char *sigs[3];
    sigs[0]= "alert ip any any -> any any (msg:\"Testing id 1\"; "
             "content:\"Pablo\"; asn1:absolute_offset 0, "
             "oversize_length 140; sid:1;)";
    sigs[1]= "alert ip any any -> any any (msg:\"Testing id 2\"; "
             "content:\"AA\"; asn1:relative_offset 0, "
             "oversize_length 140; sid:2;)";
    sigs[2]= "alert ip any any -> any any (msg:\"Testing id 3\"; "
             "content:\"lalala\"; asn1: oversize_length 2000; sid:3;)";

    uint32_t sid[3] = {1, 2, 3};

    uint32_t results[2][3] = {
                              {0, 0, 0},
                              {0, 0, 0}};
    /* None of the packets should match */

    result = UTHGenericTest(p, 2, sigs, sid, (uint32_t *) results, 3);

    UTHFreePackets(p, 2);
end:
    return result;
}

/**
 * \test DetectAsn1TestReal03 Ensure that all works together
 */
static int DetectAsn1TestReal03(void)
{
    int result = 0;
    uint8_t buf[261] = "";
    /* universal class, primitive type, tag_num = 9 (Data type Real) */
    buf[0] = '\x09';
    /* length, definite form, 2 octets */
    buf[1] = '\x82';
    /* length is the sum of the following octets (257): */
    buf[2] = '\x01';
    buf[3] = '\x01';

    /* Fill the content of the number */
    uint16_t i = 4;
    for (; i < 257;i++)
        buf[i] = '\x05';

    uint16_t buflen = 261;

    /* Check the start with AA (this is to test the relative_offset keyword) */
    uint8_t *buf2 = (uint8_t *) "AA\x03\x01\xFF";

    uint16_t buflen2 = 5;

    Packet *p[2] = { NULL, NULL };

    p[0] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_TCP);
    p[1] = UTHBuildPacket((uint8_t *)buf2, buflen2, IPPROTO_TCP);

    if (p[0] == NULL || p[1] == NULL)
        goto end;

    const char *sigs[3];
            /* This should match the first packet */
    sigs[0]= "alert ip any any -> any any (msg:\"Testing id 1\"; "
             "asn1:absolute_offset 0, double_overflow; sid:1;)";
            /* This should match the second packet */
    sigs[1]= "alert ip any any -> any any (msg:\"Testing id 2\"; "
             "asn1:relative_offset 2, bitstring_overflow,"
             "oversize_length 140; sid:2;)";
            /* This should match no packet */
    sigs[2]= "alert ip any any -> any any (msg:\"Testing id 3\"; "
             "asn1: oversize_length 2000; sid:3;)";

    uint32_t sid[3] = {1, 2, 3};

    uint32_t results[2][3] = {{1, 0, 0},
                              {0, 1, 0}};

    result = UTHGenericTest(p, 2, sigs, sid, (uint32_t *) results, 3);

    UTHFreePackets(p, 2);
end:
    return result;
}

/**
 * \test DetectAsn1TestReal04 like the real test 02, but modified the
 *       relative offset to check negative offset values, in this case
 *       start decoding from -7 bytes respect the content match "John"
 */
static int DetectAsn1TestReal04(void)
{
    int result = 0;
    uint8_t *buf = (uint8_t *) "\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01"
                   "P""\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111"
                   "\x31\x1F\x61\x11\x1A\x05""Susan""\x1A\x01""B""\x1A\x05"
                   "Jones""\xA0\x0A\x43\x08""19590717"
                   "\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01""P"
                   "\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111""\x31\x1F"
                   "\x61\x11\x1A\x05""Pablo""\x1A\x01""B""\x1A\x05""Jones"
                   "\xA0\x0A\x43\x08""19590717";

    uint16_t buflen = strlen((char *)buf) - 1;

    /* Check the start with AA (this is to test the relative_offset keyword) */
    uint8_t *buf2 = (uint8_t *) "AA\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01"
                   "P""\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111"
                   "\x31\x1F\x61\x11\x1A\x05""Susan""\x1A\x01""B""\x1A\x05"
                   "Jones""\xA0\x0A\x43\x08""19590717"
                   "\x60\x81\x85\x61\x10\x1A\x04""John""\x1A\x01""P"
                   "\x1A\x05""Smith""\xA0\x0A\x1A\x08""Director"
                   "\x42\x01\x33\xA1\x0A\x43\x08""19710917"
                   "\xA2\x12\x61\x10\x1A\x04""Mary""\x1A\x01""T""\x1A\x05"
                   "Smith""\xA3\x42\x31\x1F\x61\x11\x1A\x05""Ralph""\x1A\x01"
                   "T""\x1A\x05""Smith""\xA0\x0A\x43\x08""19571111""\x31\x1F"
                   "\x61\x11\x1A\x05""Susan""\x1A\x01""B""\x1A\x05""Jones"
                   "\xA0\x0A\x43\x08""19590717";

    uint16_t buflen2 = strlen((char *)buf2) - 1;

    Packet *p[2];

    p[0] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_TCP);
    p[1] = UTHBuildPacket((uint8_t *)buf2, buflen2, IPPROTO_TCP);

    if (p[0] == NULL || p[1] == NULL)
        goto end;

    const char *sigs[3];
    sigs[0]= "alert ip any any -> any any (msg:\"Testing id 1\"; "
             "content:\"Pablo\"; asn1:absolute_offset 0, "
             "oversize_length 140; sid:1;)";
    sigs[1]= "alert ip any any -> any any (msg:\"Testing id 2\"; "
             "content:\"John\"; asn1:relative_offset -11, "
             "oversize_length 140; sid:2;)";
    sigs[2]= "alert ip any any -> any any (msg:\"Testing id 3\"; "
             "content:\"lalala\"; asn1: oversize_length 2000; sid:3;)";

    uint32_t sid[3] = {1, 2, 3};

    uint32_t results[2][3] = {
                              {0, 0, 0},
                              {0, 0, 0}};
    /* None of the packets should match */

    result = UTHGenericTest(p, 2, sigs, sid, (uint32_t *) results, 3);

    UTHFreePackets(p, 2);
end:
    return result;
}

/**
 * \brief this function registers unit tests for DetectAsn1
 */
static void DetectAsn1RegisterTests(void)
{
    UtRegisterTest("DetectAsn1TestReal01", DetectAsn1TestReal01);
    UtRegisterTest("DetectAsn1TestReal02", DetectAsn1TestReal02);
    UtRegisterTest("DetectAsn1TestReal03", DetectAsn1TestReal03);
    UtRegisterTest("DetectAsn1TestReal04", DetectAsn1TestReal04);
}
#endif /* UNITTESTS */
