//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program 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.
//
// 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.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// SEOS commands
//-----------------------------------------------------------------------------
#include "cmdhfseos.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>              // tolower
#include "cliparser.h"
#include "cmdparser.h"          // command_t
#include "comms.h"              // clearCommandBuffer
#include "cmdtrace.h"
#include <mbedtls/cmac.h>
#include <mbedtls/des.h>
#include "fileutils.h"
#include "crc16.h"
#include "ui.h"
#include "cmdhf14a.h"           // manufacture
#include "protocols.h"          // definitions of ISO14A/7816 protocol
#include "cardhelper.h"
#include "wiegand_formats.h"
#include "wiegand_formatutils.h"
#include "iso7816/apduinfo.h"   // GetAPDUCodeDescription
#include "crypto/asn1utils.h"   // ASN1 decode / print
#include "crypto/libpcrypto.h"  // AES decrypt
#include "commonutil.h"         // get_sw
#include "protocols.h"          // ISO7816 APDU return codes
#include "hidsio.h"

static uint8_t zeros[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

#define MAX_DIVERSIFIER_LEN 16
#define MAX_OID_LEN 32
#define MAX_SEOS_AID_LEN 16
#define SEOS_MAX_KEYS 16

static int CmdHelp(const char *Cmd);

typedef struct {
    uint8_t privEncKey[16];
    uint8_t privMacKey[16];
    uint8_t authKey[16];
} keyset_t;

#define SEOS_EMPTY_KEYSET { \
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, \
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, \
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } \
}

keyset_t keys[SEOS_MAX_KEYS] = {
    SEOS_EMPTY_KEYSET,
    SEOS_EMPTY_KEYSET,
    SEOS_EMPTY_KEYSET,
    SEOS_EMPTY_KEYSET,
    SEOS_EMPTY_KEYSET,
    SEOS_EMPTY_KEYSET,
    SEOS_EMPTY_KEYSET,
    SEOS_EMPTY_KEYSET,
    SEOS_EMPTY_KEYSET,
    SEOS_EMPTY_KEYSET,
    SEOS_EMPTY_KEYSET,
};

#undef SEOS_EMPTY_KEYSET

typedef struct {
    const int value;
    const char *name;
} known_algo_t;

static const known_algo_t known_algorithm_map[] = {
    {2, "2K3DES_CBC_MODE"},
    {4, "3K3DES_CBC_MODE"},
    {6, "SHA-1"},
    {7, "SHA-256"},
    {9, "AES-128_CBC_MODE"},
};

static const char *known_seos_aids[] = {
    "A0000004400001010001",   // STANDARD_SEOS
    "A000000382002D000101",   // MOBILE_SEOS_ADMIN_CARD
};

static int seos_get_custom_aid(CLIParserContext *ctx, int arg_index, uint8_t *aid, int *aid_len) {
    int res = CLIParamHexToBuf(arg_get_str(ctx, arg_index), aid, MAX_SEOS_AID_LEN, aid_len);
    if (res != PM3_SUCCESS) {
        return PM3_ESOFT;
    }
    return res;
}

static bool seos_parse_key_index(const char *input, int *value) {
    if (input == NULL || *input == '\0') {
        return false;
    }

    char *end = NULL;
    long parsed = strtol(input, &end, 10);
    if (*end != '\0' || parsed < 0 || parsed >= (long)ARRAYLEN(keys)) {
        return false;
    }

    *value = (int)parsed;
    return true;
}

static int create_cmac(uint8_t *key, uint8_t *input, uint8_t *out, int input_len, int output_len, int encryption_algorithm) {
    uint8_t iv[16] = {0x00};
    uint8_t mac[16] = {0x00};

    if (encryption_algorithm == 0x09) {
        // Working as expected
        aes_cmac(iv, key, input, mac, input_len);
    } else if (encryption_algorithm == 0x02) {
        // CMAC Requires a 24 byte key, but the 2k3DES uses the 1st part for the 3rd part of the key
        memcpy(&key[16], &key[0], 8);

        const mbedtls_cipher_info_t *ctx;
        ctx = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_DES_EDE3_ECB);
        mbedtls_cipher_cmac(ctx, key, 192, input, input_len, mac);
    } else {
        PrintAndLogEx(ERR, _RED_("Unknown Encryption Algorithm"));
        return PM3_ESOFT;
    }
    // Copy only requested number of bytes into output buffer
    memcpy(out, mac, output_len);
    return PM3_SUCCESS;
}

static int create_cryptogram(uint8_t *key, uint8_t *input, uint8_t *out, int input_len, int encryption_algorithm) {
    uint8_t iv[16] = {};

    if (encryption_algorithm == 0x09) {
        aes_encode(iv, key, input, out, input_len);
    } else if (encryption_algorithm == 0x02) {
        mbedtls_des3_context ctx3;
        mbedtls_des3_set2key_enc(&ctx3, key);
        mbedtls_des3_crypt_cbc(&ctx3, MBEDTLS_DES_ENCRYPT, input_len, iv, input, out);
        mbedtls_des3_free(&ctx3);
    } else {
        PrintAndLogEx(ERR, _RED_("Unknown Encryption Algorithm"));
        return PM3_ESOFT;
    }

    return PM3_SUCCESS;
}

static int decrypt_cryptogram(uint8_t *key, uint8_t *input, uint8_t *out, int input_len, int encryption_algorithm) {
    uint8_t iv[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    if (encryption_algorithm == 0x09) {
        aes_decode(iv, key, input, out, input_len);
    } else if (encryption_algorithm == 0x02) {
        mbedtls_des3_context ctx3;
        mbedtls_des3_set2key_dec(&ctx3, key);
        mbedtls_des3_crypt_cbc(&ctx3, MBEDTLS_DES_DECRYPT, input_len, iv, input, out);
        mbedtls_des3_free(&ctx3);
    } else {
        PrintAndLogEx(ERR, "Unknown Encryption Algorithm");
        return PM3_ESOFT;
    }

    return PM3_SUCCESS;
}

static void increment_command_wrapper(uint8_t *input, int input_len) {
    // Increment the end of the header by 1
    uint8_t *offset = &input[input_len - sizeof(uint32_t)];
    uint32_t value = MemBeToUint4byte(offset);
    value++;
    Uint4byteToMemBe(offset, value);
}

static void padToBlockSize(const uint8_t *input, int *inputSize, int blockSize, uint8_t *output) {
    int paddingSize = blockSize - (*inputSize % blockSize);
    memcpy(output, input, *inputSize);
    memset(output + *inputSize, 0x80, 1);
    memset(output + *inputSize + 1, 0x00, paddingSize - 1);
    *inputSize += paddingSize;
}

static void generate_command_wrapping(uint8_t *command_Header, int command_header_len, uint8_t *unencrypted_Command, int unencrypted_command_len, uint8_t *rndICC, uint8_t *rndIFD, uint8_t *diversified_enc_key, uint8_t *diversified_mac_key, int encryption_algorithm, uint8_t *command, int *command_len) {
    int block_size = 0;

    if (encryption_algorithm == 0x02) {
        block_size = 8;
    } else if (encryption_algorithm == 0x09) {
        block_size = 16;
    } else {
        PrintAndLogEx(ERR, _RED_("Unknown Encryption Algorithm"));
        return;
    }

    uint8_t rndCounter[block_size];
    memcpy(rndCounter, rndICC, block_size / 2);
    memcpy(rndCounter + block_size / 2, rndIFD, block_size / 2);
    increment_command_wrapper(rndCounter, block_size);

    // Command Header is for the APDU Command to be sent
    int padded_Command_Header_len = command_header_len;
    uint8_t padded_Command_Header[padded_Command_Header_len + block_size];
    padToBlockSize(command_Header, &padded_Command_Header_len, block_size, padded_Command_Header);

    // Unencrypted Command is our actual command data
    int padded_unencrypted_Command_len = unencrypted_command_len;
    uint8_t padded_unencrypted_Command[padded_unencrypted_Command_len + block_size];
    padToBlockSize(unencrypted_Command, &padded_unencrypted_Command_len, block_size, padded_unencrypted_Command);

    uint8_t padded_encrypted_Command[padded_unencrypted_Command_len];
    create_cryptogram(diversified_enc_key, padded_unencrypted_Command, padded_encrypted_Command, padded_unencrypted_Command_len, encryption_algorithm);

    uint8_t asn1_tag_cryptograph[3] = {0x85, 0x81, ARRAYLEN(padded_encrypted_Command)};
    uint8_t asn1_tag_mac[2] = {0x8e, 0x08};
    uint8_t command_trailer[2] = {0x97, 0x00};

    uint8_t toEncrypt[ARRAYLEN(rndCounter) + padded_Command_Header_len + ARRAYLEN(asn1_tag_cryptograph) + ARRAYLEN(padded_encrypted_Command) + ARRAYLEN(command_trailer)];

    memcpy(toEncrypt, rndCounter, ARRAYLEN(rndCounter));
    memcpy(toEncrypt + ARRAYLEN(rndCounter), padded_Command_Header, padded_Command_Header_len);
    memcpy(toEncrypt + ARRAYLEN(rndCounter) + padded_Command_Header_len, asn1_tag_cryptograph, ARRAYLEN(asn1_tag_cryptograph));
    memcpy(toEncrypt + ARRAYLEN(rndCounter) + padded_Command_Header_len + ARRAYLEN(asn1_tag_cryptograph), padded_encrypted_Command, ARRAYLEN(padded_encrypted_Command));
    memcpy(toEncrypt + ARRAYLEN(rndCounter) + padded_Command_Header_len + ARRAYLEN(asn1_tag_cryptograph) + ARRAYLEN(padded_encrypted_Command), command_trailer, ARRAYLEN(command_trailer));

    int padded_toEncrypt_len = ARRAYLEN(toEncrypt);
    uint8_t padded_toEncrypt[padded_toEncrypt_len + block_size];
    padToBlockSize(toEncrypt, &padded_toEncrypt_len, block_size, padded_toEncrypt);

    // Breakdown
    // 0181e43801010201 + 0000000000000001 + 0CCB3FFF800000000000000000000000 + 8510EB54DA90CB43AEE7FBFE816ECA25A10D + 9700 + 800000000000000000000000

    uint8_t mac[8] = { 0x00 };
    if (create_cmac(diversified_mac_key, padded_toEncrypt, mac, padded_toEncrypt_len, sizeof(mac), encryption_algorithm) != PM3_SUCCESS) {
        PrintAndLogEx(ERR, _RED_("Failed to create MAC"));
        return;
    }

    // PrintAndLogEx(SUCCESS, "Encryption Key................... " _YELLOW_("%s"), sprint_hex_inrow(diversified_enc_key, 24));
    // PrintAndLogEx(SUCCESS, "MAC Key.......................... " _YELLOW_("%s"), sprint_hex_inrow(diversified_mac_key, 24));
    // PrintAndLogEx(SUCCESS, "rndCounter....................... " _YELLOW_("%s"), sprint_hex_inrow(rndCounter,sizeof(rndCounter)));
    // PrintAndLogEx(SUCCESS, "padded_encrypted_Command......... " _YELLOW_("%s"), sprint_hex_inrow(padded_encrypted_Command,sizeof(padded_encrypted_Command)));
    // PrintAndLogEx(SUCCESS, "padded_toEncrypt................. " _YELLOW_("%s"), sprint_hex_inrow(padded_toEncrypt,padded_toEncrypt_len));
    // PrintAndLogEx(SUCCESS, "MAC.............................. " _YELLOW_("%s"), sprint_hex_inrow(mac,sizeof(mac)));

    uint8_t sizeofcommand[1] = {ARRAYLEN(asn1_tag_cryptograph) + ARRAYLEN(padded_encrypted_Command) + ARRAYLEN(command_trailer) + ARRAYLEN(asn1_tag_mac) + ARRAYLEN(mac)};
    uint8_t respondTo[1] = {0x00};

    uint8_t completedCommand[command_header_len + 1 + ARRAYLEN(asn1_tag_cryptograph) + ARRAYLEN(padded_encrypted_Command) + ARRAYLEN(command_trailer) + ARRAYLEN(asn1_tag_mac) + ARRAYLEN(mac) + 1];
    memcpy(completedCommand, command_Header, command_header_len);
    memcpy(completedCommand + command_header_len, sizeofcommand, ARRAYLEN(sizeofcommand));
    memcpy(completedCommand + command_header_len + ARRAYLEN(sizeofcommand), asn1_tag_cryptograph, ARRAYLEN(asn1_tag_cryptograph));
    memcpy(completedCommand + command_header_len + ARRAYLEN(sizeofcommand) + ARRAYLEN(asn1_tag_cryptograph), padded_encrypted_Command, ARRAYLEN(padded_encrypted_Command));
    memcpy(completedCommand + command_header_len + ARRAYLEN(sizeofcommand) + ARRAYLEN(asn1_tag_cryptograph) + ARRAYLEN(padded_encrypted_Command), command_trailer, ARRAYLEN(command_trailer));
    memcpy(completedCommand + command_header_len + ARRAYLEN(sizeofcommand) + ARRAYLEN(asn1_tag_cryptograph) + ARRAYLEN(padded_encrypted_Command) + ARRAYLEN(command_trailer), asn1_tag_mac, ARRAYLEN(asn1_tag_mac));
    memcpy(completedCommand + command_header_len + ARRAYLEN(sizeofcommand) + ARRAYLEN(asn1_tag_cryptograph) + ARRAYLEN(padded_encrypted_Command) + ARRAYLEN(command_trailer) + ARRAYLEN(asn1_tag_mac), mac, ARRAYLEN(mac));
    memcpy(completedCommand + command_header_len + ARRAYLEN(sizeofcommand) + ARRAYLEN(asn1_tag_cryptograph) + ARRAYLEN(padded_encrypted_Command) + ARRAYLEN(command_trailer) + ARRAYLEN(asn1_tag_mac) + ARRAYLEN(mac), respondTo, 1);

    // PrintAndLogEx(INFO, "--- " _CYAN_("Command Generation") " ---------------------------");
    // PrintAndLogEx(SUCCESS, "Command Header................... " _YELLOW_("%s"), sprint_hex_inrow(command_Header,sizeof(command_Header)));
    // PrintAndLogEx(SUCCESS, "Payload.......................... " _YELLOW_("%s"), sprint_hex_inrow(unencrypted_Command,sizeof(unencrypted_Command)));
    // PrintAndLogEx(SUCCESS, "completedCommand................. " _YELLOW_("%s"), sprint_hex_inrow(completedCommand,sizeof(completedCommand)));

    memcpy(command, completedCommand, ARRAYLEN(completedCommand));
    *command_len = ARRAYLEN(completedCommand);
    //return;
}

static int seos_get_data(uint8_t *rndICC, uint8_t *rndIFD, uint8_t *diversified_enc_key, uint8_t *diversified_mac_key, uint8_t *sioOutput,  int *sio_size, int encryption_algorithm, uint8_t *data_tag, int data_tag_len) {
    // Intergrating our command generation with the GetData request to make my life easier in the future

    // Command Header is for the Get Data Command using
    // `0C` - Secure messaging – ISO/IEC 7816 standard, command header authenticated (C-MAC)
    // `CB` - GET DATA
    //     uint8_t command_header[4] = {0x0c,0xcb,0x3f,0xff};
    uint8_t cla[1] = {0x0c};    // Secure Messaging Command Header
    uint8_t ins[1] = {0xcb};    // GET DATA Instruction
    uint8_t p1[1] = {0x3f};     // High order tag value (accoring to NIST.SP.800-73pt2-5.pdf, this is the hardcoded tag value)
    uint8_t p2[1] = {0xff};     // Low order tag value

    // command builder
    uint8_t command_header[ARRAYLEN(cla) + ARRAYLEN(ins) + ARRAYLEN(p1) + ARRAYLEN(p2)];
    memcpy(command_header, cla, ARRAYLEN(cla));
    memcpy(command_header + ARRAYLEN(cla), ins, ARRAYLEN(ins));
    memcpy(command_header + ARRAYLEN(cla) + ARRAYLEN(ins), p1, ARRAYLEN(p1));
    memcpy(command_header + ARRAYLEN(cla) + ARRAYLEN(ins) + ARRAYLEN(p1), p2, ARRAYLEN(p2));

    int command_header_len = ARRAYLEN(command_header);

    // Command to be sent
    // 5c [02] ff 00
    // 5c = tag list data object
    // BER-TLV tag of the data object to be retrieved
    // uint8_t unencrypted_command[4] = {0x5c,0x02,0xff,0x00};
    // Modification of the tags 2nd place from 00 can return other data

    uint8_t unencrypted_command[data_tag_len + 2];
    unencrypted_command[0] = 0x5c;
    unencrypted_command[1] = data_tag_len;
    memcpy(unencrypted_command + 2, data_tag, data_tag_len);

    int unencrypted_command_len = ARRAYLEN(unencrypted_command);

    uint8_t command_buffer[254];
    int command_len = 0;

    // PrintAndLogEx(SUCCESS, "Raw Command...................... " _YELLOW_("%s"), sprint_hex_inrow(unencrypted_command, 2+data_tag_len));
    generate_command_wrapping(command_header, command_header_len, unencrypted_command, unencrypted_command_len, rndICC, rndIFD, diversified_enc_key, diversified_mac_key, encryption_algorithm, command_buffer, &command_len);

    // Convert command from buffer to stream
    uint8_t command_convert[command_len];
    memcpy(command_convert, command_buffer, command_len);
    char completedCommandChar[command_len * 2 + 1];
    for (int i = 0; i < command_len; i++) {
        snprintf(&completedCommandChar[i * 2], 3, "%02X", command_convert[i]);
    }
    //PrintAndLogEx(SUCCESS, "Command.......................... " _YELLOW_("%s"), completedCommandChar);

    // ------------------- Send Command -------------------
    uint8_t response[PM3_CMD_DATA_SIZE];
    int resplen = 0;

    bool activate_field = false;
    bool keep_field_on = true;

    uint8_t aGET_CHALLENGE[100];
    int aGET_CHALLENGE_n = command_len;
    param_gethex_to_eol(completedCommandChar, 0, aGET_CHALLENGE, sizeof(aGET_CHALLENGE), &aGET_CHALLENGE_n);
    int res = ExchangeAPDU14a(aGET_CHALLENGE, aGET_CHALLENGE_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
    if (res != PM3_SUCCESS) {
        DropField();
        return res;
    }
    uint16_t sw = get_sw(response, resplen);
    if (sw != ISO7816_OK) {
        PrintAndLogEx(ERR, "Get Data Failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
        DropField();
        return PM3_ESOFT;
    }

    PrintAndLogEx(INFO, "--- " _CYAN_("Get Data") " ---------------------------");
    // Raw response contains a few values
    // 85 is our cryptogram response (64 bytes)
    // 99 is our status word response (2 bytes)
    // 8E is our MAC response (8 bytes)
    // PrintAndLogEx(SUCCESS, "Raw Response..................... " _YELLOW_("%s"), sprint_hex_inrow(response, (resplen - 2)));
    uint8_t cryptogram[256] = {0};
    uint8_t responseCode[2] = {0};
    uint8_t mac[8] = {0};
    size_t cryptogram_length = 0;

    // Parse the response to extract the three tags
    for (int i = 0; i < resplen - 2;) {
        if (response[i] == 0x85) {
            // Cryptogram tag
            size_t offset = i + 1;
            asn1_get_tag_length(response, &cryptogram_length, &offset, resplen);
            memcpy(cryptogram, response + offset, cryptogram_length);
            i += offset + cryptogram_length;
        } else if (response[i] == 0x99) {
            // Response code tag
            size_t len = response[i + 1];
            memcpy(responseCode, response + i + 2, len);
            i += 2 + len;
        } else if (response[i] == 0x8E) {
            // MAC tag
            size_t len = response[i + 1];
            memcpy(mac, response + i + 2, len);
            i += 2 + len;
        } else {
            i += 2 + response[i + 1];
        }
    }

    // ------------------- Cryptogram Response -------------------
    if (cryptogram_length > 0) {
        uint8_t decrypted[cryptogram_length];
        int getDataSize = 0;

        // Decrypt the response
        decrypt_cryptogram(diversified_enc_key, cryptogram, decrypted, cryptogram_length, encryption_algorithm);

        //PrintAndLogEx(SUCCESS, "Cryptogram....................... " _YELLOW_("%s"), sprint_hex_inrow(cryptogram, cryptogram_length));
        //PrintAndLogEx(SUCCESS, "Decrypted........................ " _YELLOW_("%s"), sprint_hex_inrow(decrypted, cryptogram_length));

        // Parse TLV: tag can be 1 or 2 bytes
        int offset = 0;
        uint8_t tag[2] = {0x00, 0x00};
        int tag_len = 1;

        // Check if it's a 2-byte tag (first byte has bits 5-1 all set to 1)
        if ((decrypted[offset] & 0x1F) == 0x1F) {
            tag[0] = decrypted[offset];
            tag[1] = decrypted[offset + 1];
            tag_len = 2;
            offset += 2;
        } else {
            tag[0] = decrypted[offset];
            offset += 1;
        }

        // Get length byte
        uint8_t length_byte = decrypted[offset];
        offset += 1;

        // Extract the value
        getDataSize = length_byte;
        memcpy(sioOutput, decrypted + offset, getDataSize);
        *sio_size = getDataSize;

        PrintAndLogEx(SUCCESS, "Tag.............................. " _YELLOW_("%s"), sprint_hex_inrow(tag, tag_len));
        PrintAndLogEx(SUCCESS, "Value............................ " _YELLOW_("%s"), sprint_hex_inrow(sioOutput, getDataSize));
    }

    if (responseCode[0] != 0x00 || responseCode[1] != 0x00) {
        PrintAndLogEx(SUCCESS, "Response Code.................... " _YELLOW_("%s"), sprint_hex_inrow(responseCode, ARRAYLEN(responseCode)));
    }

    // if (mac[0] != 0x00) {
    //     PrintAndLogEx(SUCCESS, "MAC.............................. " _YELLOW_("%s"), sprint_hex_inrow(mac, sizeof(mac)));
    // }

    return PM3_SUCCESS;
};

static int seos_write_data(uint8_t *rndICC, uint8_t *rndIFD, uint8_t *diversified_enc_key, uint8_t *diversified_mac_key, uint8_t *sio,  int sio_size, int encryption_algorithm, uint8_t *write_data_tag, int write_data_tag_len) {
    // Command Header is for the Update Data Command using
    // `0C` - Secure messaging – ISO/IEC 7816 standard, command header authenticated (C-MAC)
    // `DB` - UPDATE DATA
    //     uint8_t command_header[4] = {0x0c,0xdb,0x3f,0xff};
    uint8_t cla[1] = {0x0c};    // Secure Messaging Command Header
    uint8_t ins[1] = {0xdb};    // UPDATE DATA Instruction
    uint8_t p1[1] = {0x3f};     // High order tag value (accoring to NIST.SP.800-73pt2-5.pdf, this is the hardcoded tag value)
    uint8_t p2[1] = {0xff};     // Low order tag value

    // command builder
    uint8_t command_header[ARRAYLEN(cla) + ARRAYLEN(ins) + ARRAYLEN(p1) + ARRAYLEN(p2)];
    memcpy(command_header, cla, ARRAYLEN(cla));
    memcpy(command_header + ARRAYLEN(cla), ins, ARRAYLEN(ins));
    memcpy(command_header + ARRAYLEN(cla) + ARRAYLEN(ins), p1, ARRAYLEN(p1));
    memcpy(command_header + ARRAYLEN(cla) + ARRAYLEN(ins) + ARRAYLEN(p1), p2, ARRAYLEN(p2));

    int command_header_len = ARRAYLEN(command_header);

    // Command to be sent
    // ff 00 [04] 12 34 56 78
    // ff 00 = BER-TLV tag of the data object to be written

    uint8_t unencrypted_command[write_data_tag_len + 1 + sio_size];
    memcpy(unencrypted_command, write_data_tag, write_data_tag_len);

    unencrypted_command[write_data_tag_len] = sio_size;
    memcpy(unencrypted_command + write_data_tag_len + 1, sio, sio_size);

    int unencrypted_command_len = ARRAYLEN(unencrypted_command);

    uint8_t command_buffer[254];
    int command_len = 0;

    // PrintAndLogEx(SUCCESS, "Raw Command...................... " _YELLOW_("%s"), sprint_hex_inrow(unencrypted_command, unencrypted_command_len));
    generate_command_wrapping(command_header, command_header_len, unencrypted_command, unencrypted_command_len, rndICC, rndIFD, diversified_enc_key, diversified_mac_key, encryption_algorithm, command_buffer, &command_len);

    // Convert command from buffer to stream
    uint8_t command_convert[command_len];
    memcpy(command_convert, command_buffer, command_len);
    char completedCommandChar[command_len * 2 + 1];
    for (int i = 0; i < command_len; i++) {
        snprintf(&completedCommandChar[i * 2], 3, "%02X", command_convert[i]);
    }
    // PrintAndLogEx(SUCCESS, "Command.......................... " _YELLOW_("%s"), completedCommandChar);

    // ------------------- Send Command -------------------
    uint8_t response[PM3_CMD_DATA_SIZE];
    int resplen = 0;

    bool activate_field = false;
    bool keep_field_on = true;

    uint8_t aUPDATE_CHALLENGE[254];
    int aUPDATE_CHALLENGE_n = command_len;
    param_gethex_to_eol(completedCommandChar, 0, aUPDATE_CHALLENGE, sizeof(aUPDATE_CHALLENGE), &aUPDATE_CHALLENGE_n);
    int res = ExchangeAPDU14a(aUPDATE_CHALLENGE, aUPDATE_CHALLENGE_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
    if (res != PM3_SUCCESS) {
        DropField();
        return res;
    }
    uint16_t sw = get_sw(response, resplen);
    if (sw != ISO7816_OK) {
        PrintAndLogEx(ERR, "Update Data Failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
        DropField();
        return PM3_ESOFT;
    }

    PrintAndLogEx(INFO, "--- " _CYAN_("Update Data") " ---------------------------");
    // Raw response contains a few values
    // 99 is our status word response (2 bytes)
    // 8E is our MAC response (8 bytes)
    // PrintAndLogEx(SUCCESS, "Raw Response..................... " _YELLOW_("%s"), sprint_hex_inrow(response, (resplen - 2)));

    uint8_t responseCode[2];

    if (resplen >= 2 && response[0] == 0x99) {
        memcpy(responseCode, response + 2, 2);
        // PrintAndLogEx(SUCCESS, "Raw Response..................... " _YELLOW_("%s"), sprint_hex_inrow(response, (resplen - 2)));
        PrintAndLogEx(SUCCESS, "Response Code.................... " _YELLOW_("%s"), sprint_hex_inrow(responseCode, (ARRAYLEN(responseCode))));
    }

    return PM3_SUCCESS;
};

static void set_counter_big_endian(uint8_t *buffer, uint32_t counter) {
    buffer[0] = (counter >> 24) & 0xFF;
    buffer[1] = (counter >> 16) & 0xFF;
    buffer[2] = (counter >> 8) & 0xFF;
    buffer[3] = counter & 0xFF;
}

static void create_mutual_auth_key(uint8_t *KEYIFD, uint8_t *KEYICC, uint8_t *RNDICC, uint8_t *RNDIFD, uint8_t *EncryptionKey, uint8_t *MACKey, int encryptionAlgorithm, int HashingAlgorithm) {
    // Creating Mutual Authentication Keys
    // Structure
    // Prefix               = 00000000
    // keyIFD.substring(16) = 0000000000000000      IFD = Interface Device
    // keyICC.substring(16) = 0000000000000000      ICC = Integrated Circuit Card
    // hashing algorithm x2 = 09 09
    // randomICC            = 0000000000000000      ICC = Integrated Circuit Card
    // RandomIFD            = 0000000000000000      IFD = Interface Device
    // Will always be 38 bytes
    //
    // 00000000 E0EC1F2D7B000000 F0EC1F2D7B000000 09 09 B0EC1F2D7B000000B8EC1F2D7B000000

    uint8_t prefix[4] = {0x00, 0x00, 0x00, 0x00};
    uint8_t aHashingAlgorithm[2] = {encryptionAlgorithm, encryptionAlgorithm};
    uint8_t hash_in[38];

    memcpy(hash_in, prefix, 4);
    memcpy(hash_in + 4, KEYIFD, 8);
    memcpy(hash_in + 12, KEYICC, 8);
    memcpy(hash_in + 20, aHashingAlgorithm, 2);
    memcpy(hash_in + 22, RNDICC, 8);
    memcpy(hash_in + 30, RNDIFD, 8);

    // PrintAndLogEx(INFO, "--- " _CYAN_("Mutual Auth Keys") " ---------------------------");
    // PrintAndLogEx(SUCCESS, "Prefix........................... " _YELLOW_("%s"), sprint_hex_inrow(prefix, ARRAYLEN(prefix)));
    // PrintAndLogEx(SUCCESS, "KeyIFD........................... " _YELLOW_("%s"), sprint_hex_inrow(KEYIFD, 8));
    // PrintAndLogEx(SUCCESS, "KeyICC........................... " _YELLOW_("%s"), sprint_hex_inrow(KEYICC, 8));
    // PrintAndLogEx(SUCCESS, "HashingAlgo...................... " _YELLOW_("%s"), sprint_hex_inrow(aHashingAlgorithm, ARRAYLEN(aHashingAlgorithm)));
    // PrintAndLogEx(SUCCESS, "RandomICC........................ " _YELLOW_("%s"), sprint_hex_inrow(RNDICC, 8));
    // PrintAndLogEx(SUCCESS, "RandomIFD........................ " _YELLOW_("%s"), sprint_hex_inrow(RNDIFD, 8));
    // PrintAndLogEx(SUCCESS, "hash Input....................... " _YELLOW_("%s"), sprint_hex_inrow(hash_in,ARRAYLEN(hash_in)));

    uint8_t output[32]; // Buffer to store the two 16-byte keys
    uint32_t counter = 1;

    // Generate the first key
    set_counter_big_endian(hash_in, counter); // Set the counter in big-endian format
    // PrintAndLogEx(SUCCESS, "key_out_temp..................... " _YELLOW_("%s"), sprint_hex_inrow(hash_in,ARRAYLEN(hash_in)));

    if (HashingAlgorithm == 0x06) {
        sha1hash(hash_in, sizeof(hash_in), output);
        //PrintAndLogEx(SUCCESS, "key_out_temp..................... " _YELLOW_("%s"), sprint_hex_inrow(hash_in,ARRAYLEN(hash_in)));
        counter++;
        set_counter_big_endian(hash_in, counter);
        uint8_t hashedOutput[20];
        sha1hash(hash_in, sizeof(hash_in), hashedOutput);
        memcpy(output + 20, hashedOutput, 12);
        //PrintAndLogEx(SUCCESS, "key_out_temp..................... " _YELLOW_("%s"), sprint_hex_inrow(hash_in,ARRAYLEN(hash_in)));
    } else if (HashingAlgorithm == 0x07) {
        sha256hash(hash_in, sizeof(hash_in), output);
    } else {
        // Yes they generate their encryption keys and mac keys in a weird way for no fucking reason, the 2nd cycle isn't required.
        PrintAndLogEx(ERR, _RED_("Unknown Hashing Algorithm"));
        return;
    }


    memcpy(EncryptionKey, output, 16);
    memcpy(MACKey, output + 16, 16);


    // PrintAndLogEx(INFO, "--- " _CYAN_("New Key Generation") " ---------------------------");
    // PrintAndLogEx(SUCCESS, "Hash Output...................... " _YELLOW_("%s"), sprint_hex_inrow(output,ARRAYLEN(output)));
    // PrintAndLogEx(SUCCESS, "Encryption Key................... " _YELLOW_("%s"), sprint_hex_inrow(EncryptionKey, 16));
    // PrintAndLogEx(SUCCESS, "MAC Key.......................... " _YELLOW_("%s"), sprint_hex_inrow(MACKey, 16));
}

static int seos_challenge_get(uint8_t *RNDICC, uint8_t RNDICC_len, uint8_t keyslot) {
    uint8_t response[PM3_CMD_DATA_SIZE];
    int resplen = 0;

    bool activate_field = false;
    bool keep_field_on = true;

    // The Get Challenge seems to be static across all tested cards
    // 00870001047c02810000

    char getChallengePre[21];
    strcpy(getChallengePre, "008700");

    // const char keyslot_str[3] = "01";
    //strcat(getChallengePre, keyslot_str);
    snprintf(getChallengePre + strlen(getChallengePre), 3, "%02X", keyslot);
    strcat(getChallengePre, "047c02810000");

    uint8_t aGET_CHALLENGE[12];
    int aGET_CHALLENGE_n = 0;
    param_gethex_to_eol(getChallengePre, 0, aGET_CHALLENGE, sizeof(aGET_CHALLENGE), &aGET_CHALLENGE_n);
    int res = ExchangeAPDU14a(aGET_CHALLENGE, aGET_CHALLENGE_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
    if (res != PM3_SUCCESS) {
        DropField();
        return res;
    }

    uint16_t sw = get_sw(response, resplen);
    if (sw != ISO7816_OK) {
        PrintAndLogEx(ERR, "Get Challenge Failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
        DropField();
        return PM3_ESOFT;
    }
    memcpy(RNDICC, &response[4], 8);

    // Response looks like
    // 7C0A810897460AC7535731F8
    // |----------|------------------|
    // | 7C0A8108 | 97460AC7535731F8 |
    // |  Static  |      RND.ICC     |
    // |----------|------------------|
    // 7C 0A 81 08 18 1E 43 80 10 10 20 11
    // 81 is the ASN.1 tag

    uint8_t staticResponse[8] = {0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01};

    PrintAndLogEx(INFO, "--- " _CYAN_("Get Challenge") " ---------------------------");
    //PrintAndLogEx(SUCCESS, "Challenge Input: " _YELLOW_("%s"), getChallengePre);
    if (memcmp(RNDICC, staticResponse, 8) == 0) {
        PrintAndLogEx(SUCCESS, "Static Response Detected......... " _GREEN_("%s"), sprint_hex_inrow(RNDICC, sizeof(RNDICC)));
    } else {
        PrintAndLogEx(SUCCESS, "RND.ICC.......................... " _YELLOW_("%s"), sprint_hex_inrow(RNDICC, sizeof(RNDICC)));
    }

    return PM3_SUCCESS;
};

int seos_kdf(bool encryption, uint8_t *masterKey, uint8_t keyslot,
             uint8_t *adfOid, size_t adfoid_len, uint8_t *diversifier, uint8_t diversifier_len, uint8_t *out, int encryption_algorithm, int hash_algorithm) {

    // Encryption key      = 04
    // KEK Encryption key  = 05
    // MAC key             = 06
    // KEK MAC key         = 07

    uint8_t typeOfKey = 0x06;
    if (encryption == true) {
        typeOfKey = 0x04;
    }

    uint8_t inputPre[] = {
        // Padding
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, typeOfKey, 0x00, 0x00, 0x80, 0x01,
        encryption_algorithm, hash_algorithm, keyslot
    };

    // 00000000000000000000000600008001 09 07 00 06112B0601040181E438010102011801010202 EFB08A28B0529F
    // 00000000000000000000000400008001 09 07 00 06112B0601040181E438010102011801010202 EFB08A28B0529F
    // 06112B0601040181E438010102011801010202 CF 07 EFB08A28B0529F DBA240413B0969B7111F4B6133A3DEFAD934B6DC


    uint8_t input[sizeof(inputPre) + adfoid_len + diversifier_len];

    memset(input, 0, sizeof(input));

    memcpy(input, inputPre, sizeof(inputPre));
    memcpy(input + sizeof(inputPre), adfOid, adfoid_len);
    memcpy(input + sizeof(inputPre) + adfoid_len, diversifier, diversifier_len);

    // PrintAndLogEx(SUCCESS, "adfOid: " _YELLOW_("%s"), sprint_hex_inrow(adfOid, 16));
    // PrintAndLogEx(SUCCESS, "diversifier: " _YELLOW_("%s"), sprint_hex_inrow(diversifier, 7));
    // PrintAndLogEx(SUCCESS, "Input: " _YELLOW_("%s"), sprint_hex_inrow(input, (sizeof(input))));

    // ----------------- AES Key Generation -----------------
    uint8_t AES_iv[16] = {};

    aes_cmac(AES_iv, masterKey, input, out, sizeof(input));
    return PM3_SUCCESS;
};

static int select_DF_verify(uint8_t *response, uint8_t response_length, uint8_t *MAC_value, size_t MAC_value_len, int encryption_algorithm, int key_index) {
    uint8_t input[response_length - 10];
    int input_len = 0;
    // Response is an ASN.1 encoded structure
    // Extract everything before the 8E tag

    int res = PM3_EWRONGANSWER;
    for (int i = 0; i < response_length - 2;) {
        // extract MAC
        if (response[i] == 0x8E) {
            if (response[i + 1] != MAC_value_len) {
                goto out;
            }
            // Ensure there's enough bytes remaining
            // in the response for the full MAC
            if (i + 2 + MAC_value_len > response_length) {
                goto out;
            }
            input_len = i;
            memcpy(input, response, input_len);
            memcpy(MAC_value, response + (input_len + 2), MAC_value_len);
            res = PM3_SUCCESS;
            break;
        }
        // skip to next tag
        i += 2 + response[i + 1];
    }
    if (res != PM3_SUCCESS) {
        goto out;
    }

    // ----------------- MAC Key Generation -----------------
    uint8_t cmac[16];
    uint8_t MAC_key[24] = {0x00};
    memcpy(MAC_key, keys[key_index].privMacKey, 16);
    create_cmac(MAC_key, input, cmac, input_len, sizeof(cmac), encryption_algorithm);

    // PrintAndLogEx(INFO, "--- " _CYAN_("MAC") " ---------------------------");
    // PrintAndLogEx(SUCCESS, "MAC Key: "_YELLOW_("%s"), sprint_hex_inrow(MAC_key,sizeof(MAC_key)));
    // PrintAndLogEx(SUCCESS, "Message: " _YELLOW_("%s"), sprint_hex_inrow(input,input_len));

    if (memcmp(cmac, MAC_value, MAC_value_len) == 0) {
        // PrintAndLogEx(SUCCESS, _GREEN_("MAC Verification successful"));
        return PM3_SUCCESS;
    }
    // PrintAndLogEx(INFO, "MAC Type......................... " _YELLOW_("%s"), algorithm_name1);
    // PrintAndLogEx(INFO, "Supp MAC......................... " _YELLOW_("%s"), sprint_hex_inrow(MAC_value, MAC_value_len));
    // PrintAndLogEx(INFO, "Calc MAC......................... " _YELLOW_("%s"), sprint_hex_inrow(cmac, sizeof(cmac)));

out:
    PrintAndLogEx(INFO, "--- " _CYAN_("MAC") " ---------------------------");
    PrintAndLogEx(ERR, _RED_("MAC Verification Failed"));
    PrintAndLogEx(ERR, _YELLOW_("Likely wrong Priv Mac Key"));
    return PM3_ESOFT;
}

static int select_df_decode(uint8_t *response, uint8_t response_length, int *ALGORITHM_INFO_value1, int *ALGORITHM_INFO_value2, uint8_t *CRYPTOGRAM_encrypted_data, uint8_t *MAC_value) {
    // Response is an ASN.1 encoded structure
    // ASN.1 Information
    // CF = Diversifier
    //
    // CD = ALGORITHM_INFO
    //      02 = 3DES 2K    (TWO_KEY_3DES_CBC_MODE)
    //      04 = 3K3DES     (THREE_KEY_3DES_CBC_MODE)
    //      06 = SHA-1      (hash assigned to RSA-1024)
    //      07 = SHA-256
    //      09 = AES-128    (AES_128_CBC)
    // 85 = CRYPTOGRAM
    //      First 16 bytes what I guess is a nonce
    //      Followed with the ADF selected, then the diversifier
    // 8E = MAC

    /*
    [+] Raw ADF Response: CD0209078540EAA1E1966D666D1FBA14098700071D1DEE24B74CAC87D182EF1700B9946D697E60F87B0FB703C12AE0F83F579A9BF4888DF6B7691BBA6A404C797356F8457E488E088149C86A535EF86A
    [=]  -- CD [02] 'elem'
    [=]     00: 09 07                                           | ..
    [=]  -- 85 [40] 'elem'
    [=]     00: EA A1 E1 96 6D 66 6D 1F BA 14 09 87 00 07 1D 1D | ....mfm.........
    [=]     10: EE 24 B7 4C AC 87 D1 82 EF 17 00 B9 94 6D 69 7E | .$.L.........mi~
    [=]     20: 60 F8 7B 0F B7 03 C1 2A E0 F8 3F 57 9A 9B F4 88 | `.{....*..?W....
    [=]     30: 8D F6 B7 69 1B BA 6A 40 4C 79 73 56 F8 45 7E 48 | ...i..j@LysV.E~H
    [=]  -- 8E [08] 'elem'
    [=]     00: 81 49 C8 6A 53 5E F8 6A                         | .I.jS^.j
    */
    if (response == NULL || ALGORITHM_INFO_value1 == NULL || ALGORITHM_INFO_value2 == NULL || CRYPTOGRAM_encrypted_data == NULL || MAC_value == NULL) {
        PrintAndLogEx(ERR, "Invalid input to ADF response decoder");
        return PM3_ESOFT;
    }

    bool has_algorithm_info = false;
    bool has_cryptogram = false;
    bool has_mac = false;
    bool plaintext_adf = (response_length > 4 && response[0] == 0xCD && response[1] == 0x02 && response[4] == 0x06);

    for (size_t i = 0; i + 1 < response_length;) {
        uint8_t tag = response[i];
        uint8_t tag_len = response[i + 1];

        if (i + 2 + tag_len > response_length) {
            PrintAndLogEx(ERR, "Malformed ADF response: tag 0x%02X length %u exceeds payload size %u", tag, tag_len, response_length);
            return PM3_ESOFT;
        }

        if (tag == 0xCD) {
            if (tag_len < 2) {
                PrintAndLogEx(ERR, "Malformed ADF response: ALGORITHM_INFO (CD) length %u is too short", tag_len);
                return PM3_ESOFT;
            }
            *ALGORITHM_INFO_value1 = response[i + 2];
            *ALGORITHM_INFO_value2 = response[i + 3];
            has_algorithm_info = true;
        } else if (tag == 0x85) {
            if (tag_len == 0 || tag_len > 64) {
                PrintAndLogEx(ERR, "Malformed ADF response: CRYPTOGRAM (85) length %u, expected 64", tag_len);
                return PM3_ESOFT;
            }
            memcpy(CRYPTOGRAM_encrypted_data, &response[i + 2], tag_len);
            has_cryptogram = true;
        } else if (tag == 0x8E) {
            if (tag_len != 8) {
                PrintAndLogEx(ERR, "Malformed ADF response: MAC (8E) length %u, expected 8", tag_len);
                return PM3_ESOFT;
            }
            memcpy(MAC_value, &response[i + 2], tag_len);
            has_mac = true;
        }

        i += 2 + tag_len;
    }

    if (has_algorithm_info == false || (plaintext_adf == false && (has_cryptogram == false || has_mac == false))) {
        if (has_algorithm_info == false) {
            PrintAndLogEx(ERR, "ADF response missing ALGORITHM_INFO tag (CD)");
        }
        if (plaintext_adf == false && has_cryptogram == false) {
            PrintAndLogEx(ERR, "ADF response missing CRYPTOGRAM tag (85)");
        }
        if (plaintext_adf == false && has_mac == false) {
            PrintAndLogEx(ERR, "ADF response missing MAC tag (8E)");
        }
        PrintAndLogEx(ERR, "Raw ADF response.................. %s", sprint_hex_inrow(response, response_length));
        return PM3_ESOFT;
    }

    int ALGORITHM_INFO_value1_n = *ALGORITHM_INFO_value1;
    int ALGORITHM_INFO_value2_n = *ALGORITHM_INFO_value2;

    const char *algorithm_name1 = NULL;
    for (int i = 0; i < ARRAYLEN(known_algorithm_map); i++) {
        if ((known_algorithm_map[i].value) == ALGORITHM_INFO_value1_n) {
            algorithm_name1 = known_algorithm_map[i].name;
            break;
        }
    }

    const char *algorithm_name2 = NULL;
    for (int i = 0; i < ARRAYLEN(known_algorithm_map); i++) {
        if (known_algorithm_map[i].value == ALGORITHM_INFO_value2_n) {
            algorithm_name2 = known_algorithm_map[i].name;
            break;
        }
    }

    PrintAndLogEx(INFO, "--- " _CYAN_("Raw ADF Information") " ---------------------------");
    if (algorithm_name1 != NULL) {
        PrintAndLogEx(SUCCESS, "algoIdCipher (Encryption)........ "_YELLOW_("%i (%s)"), ALGORITHM_INFO_value1_n, algorithm_name1);
    } else {
        PrintAndLogEx(ERR, "algoIdCipher (Encryption)........ %d (Unknown)", ALGORITHM_INFO_value1_n);
    }

    if (algorithm_name2 != NULL) {
        PrintAndLogEx(SUCCESS, "algoIdHash (MAC)................. "_YELLOW_("%i (%s)"), ALGORITHM_INFO_value2_n, algorithm_name2);
    } else {
        PrintAndLogEx(ERR, "algoIdHash (MAC)............... %d (Unknown)", ALGORITHM_INFO_value2_n);
    }

    if (plaintext_adf == false) {
        if (plaintext_adf == false) {
            PrintAndLogEx(SUCCESS, "CRYPTOGRAM Encrypted Data........ " _YELLOW_("%s"), sprint_hex_inrow(CRYPTOGRAM_encrypted_data, 64));
        }
    }
    // PrintAndLogEx(SUCCESS, "MAC.............................. " _YELLOW_("%s"), sprint_hex_inrow(MAC_value, 8));

    return PM3_SUCCESS;
}

static bool select_df_is_plaintext(const uint8_t *response, uint8_t response_length) {
    return (response_length > 4 && response[0] == 0xCD && response[1] == 0x02 && response[4] == 0x06);
}

static int select_df_extract_plaintext_diversifier(const uint8_t *response, uint8_t response_length, uint8_t *diversifier, int *diversifier_length) {
    int adf_length = response[5];
    int cf_index = 6 + adf_length;
    if (adf_length > MAX_OID_LEN || cf_index + 2 > response_length || response[4] != 0x06 || response[cf_index] != 0xCF) {
        PrintAndLogEx(ERR, "Plaintext ADF response format is invalid");
        return PM3_ESOFT;
    }

    int plaintext_diversifier_length = response[cf_index + 1];
    if (plaintext_diversifier_length > *diversifier_length || cf_index + 2 + plaintext_diversifier_length > response_length) {
        PrintAndLogEx(ERR, "Plaintext ADF diversifier too long");
        return PM3_ESOFT;
    }

    memcpy(diversifier, &response[cf_index + 2], plaintext_diversifier_length);
    *diversifier_length = plaintext_diversifier_length;
    return PM3_SUCCESS;
}

static int select_ADF_decrypt(const char *selectADFOID, uint8_t *CRYPTOGRAM_encrypted_data_raw, uint8_t *CRYPTOGRAM_Diversifier, int *diversifier_length_out, int encryption_algorithm, int privacy_key_index) {
    // --------------- Decrypt ----------------

    // 1. MAC Verify - AES/CBC-decrypt (IV || cryptogram || 16 bytes after 8e 08) with the MAC key & keep the last block
    // 2. Decrypt the CRYPTOGRAM_encrypted_data - AES/CBC-decrypt with the encryption key & IV (the previous 16 bytes)
    // 3. Verify the Decryption
    // 3.1 - CF tag for diversifier at 44 chars in
    // 4. Extract the data
    // 4.1 Selected ADF
    // 4.2 Diversifier
    // 4.3 Nonce

    // Synthesized IV works on half-blocks:
    // Half of a block of random data, followed
    // by half a block of the CMAC of that data
    uint8_t iv_size;
    if (encryption_algorithm == 0x02) {
        iv_size = 4;
    } else if (encryption_algorithm == 0x09) {
        iv_size = 8;
    } else {
        PrintAndLogEx(ERR, _RED_("Unknown Encryption Algorithm"));
        return PM3_ESOFT;
    }

    uint8_t mac[iv_size];
    uint8_t MAC_key[24] = {0x00};
    memcpy(MAC_key, keys[privacy_key_index].privMacKey, 16);
    create_cmac(MAC_key, CRYPTOGRAM_encrypted_data_raw, mac, iv_size, sizeof(mac), encryption_algorithm);
    if (memcmp(CRYPTOGRAM_encrypted_data_raw + iv_size, mac, iv_size) != 0) {
        PrintAndLogEx(ERR, "Synthesized IV Verification Failed");
        PrintAndLogEx(ERR, _YELLOW_("Likely wrong Priv Mac Key"));
        return PM3_ESOFT;
    }

    uint8_t privEncKey[16] = {};
    memcpy(privEncKey, keys[privacy_key_index].privEncKey, 16);
    uint8_t CRYPTOGRAM_decrypted_data[64];

    decrypt_cryptogram(privEncKey, CRYPTOGRAM_encrypted_data_raw, CRYPTOGRAM_decrypted_data, ARRAYLEN(CRYPTOGRAM_decrypted_data), encryption_algorithm);

    // PrintAndLogEx(SUCCESS, "CRYPTOGRAM_encrypted_data_raw: " _YELLOW_("%s"), sprint_hex_inrow(CRYPTOGRAM_encrypted_data_raw, 64));
    // PrintAndLogEx(SUCCESS, "Raw Decrypted Data............... "_YELLOW_("%s"), sprint_hex_inrow(CRYPTOGRAM_decrypted_data,sizeof(CRYPTOGRAM_decrypted_data)));

    // Rough Output
    // 06112B0601040181E438010102011801010202 CF 07 EFB08A28B0529F 5282752803B485BABF8CD88F3DA5515DF7712CF3


    // Extract the data
    int diversifier_length = 0;
    int adf_length = 0;

    int CRYPTOGRAM_decrypted_data_length = sizeof(CRYPTOGRAM_decrypted_data);

    // Skip synthesized IV
    for (int i = iv_size * 2; i < CRYPTOGRAM_decrypted_data_length; i++) {

        // ADF OID tag
        if (CRYPTOGRAM_decrypted_data[i] == 0x06 && CRYPTOGRAM_decrypted_data[i + 1] <= MAX_OID_LEN) {

            adf_length = ((CRYPTOGRAM_decrypted_data[i + 1]));
            if (adf_length > 64) {
                PrintAndLogEx(ERR, "adf length too large, have 64, ( got %u )", adf_length);
                return PM3_ESOFT;
            }

            diversifier_length = CRYPTOGRAM_decrypted_data[i + adf_length + 3];
            if (*diversifier_length_out < diversifier_length) {
                PrintAndLogEx(ERR, "Diversifier too long");
                return PM3_ESOFT;
            }

            uint8_t CRYPTOGRAM_ADF[strlen(selectADFOID) / 2];

            memcpy(CRYPTOGRAM_ADF, &CRYPTOGRAM_decrypted_data[i], strlen(selectADFOID) / 2);
            memcpy(CRYPTOGRAM_Diversifier, &CRYPTOGRAM_decrypted_data[i + adf_length + 4], diversifier_length);

            const char *CRYPTOGRAM_ADF_CMP = (sprint_hex_inrow(CRYPTOGRAM_ADF, ARRAYLEN(CRYPTOGRAM_ADF)));

            char *CRYPTOGRAM_ADF_UPPER = strdup(CRYPTOGRAM_ADF_CMP);
            char *selectADFOID_UPPER = strdup(selectADFOID);

            // Convert both strings to uppercase
            for (int x = 0; CRYPTOGRAM_ADF_UPPER[x]; x++) {
                CRYPTOGRAM_ADF_UPPER[x] = toupper(CRYPTOGRAM_ADF_UPPER[x]);
            }
            for (int x = 0; selectADFOID_UPPER[x]; x++) {
                selectADFOID_UPPER[x] = toupper(selectADFOID_UPPER[x]);
            }

            // Compare the 2 ADF responses, if they don't match then the decryption is wrong
            // We do the + 4 to remove the first 4 bytes of the ADF OID ASN.1 Tag (0611)
            if (strcmp(CRYPTOGRAM_ADF_UPPER + 4, selectADFOID_UPPER + 4) != 0) {
                PrintAndLogEx(ERR, "ADF does not match decrypted ADF");
                PrintAndLogEx(ERR, "Likely wrong Auth Key");
                // PrintAndLogEx(SUCCESS, "Decoded ADF....................... "_YELLOW_("%s"), CRYPTOGRAM_ADF_UPPER);                              // ADF Selected
                // PrintAndLogEx(SUCCESS, "Supplied ADF...................... "_YELLOW_("%s"), selectADFOID_UPPER);                                // ADF Selected
                return PM3_ESOFT;
            }
            *diversifier_length_out = diversifier_length;

            // PrintAndLogEx(INFO, "--- " _CYAN_("Decrypted Response") " ---------------------------");
            // PrintAndLogEx(SUCCESS, "Decoded ADF...................... "_YELLOW_("%s"), sprint_hex_inrow(&CRYPTOGRAM_ADF[2],adf_length));                 // ADF Selected
            // PrintAndLogEx(SUCCESS, "Diversifier...................... "_YELLOW_("%s"), sprint_hex_inrow(CRYPTOGRAM_Diversifier,diversifier_length));     // ADF Diversifier
            return PM3_SUCCESS;

        }
    }
    PrintAndLogEx(ERR, _RED_("ADF OID tag not found"));
    PrintAndLogEx(ERR, _YELLOW_("Likely wrong Priv Enc Key"));
    return PM3_ESOFT;
};

static int seos_mutual_auth(uint8_t *adfOID, size_t adfoid_len, uint8_t *randomICC, uint8_t *CRYPTOGRAM_Diversifier, uint8_t diversifier_len, uint8_t *mutual_auth_randomIFD, uint8_t *mutual_auth_keyICC, uint8_t *randomIFD, uint8_t randomIFD_len, uint8_t *keyIFD, uint8_t keyIFD_len, int encryption_algorithm, int hash_algorithm, int auth_key_index, uint8_t auth_key_slot) {
    uint8_t response[PM3_CMD_DATA_SIZE];

    // ---------------- Diversify Keys ----------------
    uint8_t mk[16] = { 0x00 };
    memcpy(mk, keys[auth_key_index].authKey, 16);
    uint8_t AES_key[24] = {0x00};
    uint8_t MAC_key[24] = {0x00};

    // Null AES IV
    uint8_t nullDiversifier[MAX_DIVERSIFIER_LEN] = {0};

    if (memcmp(CRYPTOGRAM_Diversifier, nullDiversifier, diversifier_len) == 0) {
        PrintAndLogEx(ERR, "No Diversifier found");
        return PM3_ESOFT;
    }

    seos_kdf(true, mk, auth_key_slot, adfOID, adfoid_len, CRYPTOGRAM_Diversifier, diversifier_len, AES_key, encryption_algorithm, hash_algorithm);
    seos_kdf(false, mk, auth_key_slot, adfOID, adfoid_len, CRYPTOGRAM_Diversifier, diversifier_len, MAC_key, encryption_algorithm, hash_algorithm);

    memcpy(&MAC_key[16], &MAC_key[0], 8);
    memcpy(&AES_key[16], &AES_key[0], 8);

    // PrintAndLogEx(INFO, "--- " _CYAN_("Diversified Keys") " ---------------------------");
    // PrintAndLogEx(SUCCESS, "Diversified Enc Key.............. " _YELLOW_("%s"), sprint_hex_inrow(AES_key, (sizeof(AES_key))));
    // PrintAndLogEx(SUCCESS, "Diversified Mac Key.............. " _YELLOW_("%s"), sprint_hex_inrow(MAC_key, (sizeof(MAC_key))));
    // PrintAndLogEx(INFO, "--- " _CYAN_("Mutual Auth") " ---------------------------");

    // ----------------- Command Generation -----------------
    uint8_t mutual_auth_plain[32];
    memcpy(mutual_auth_plain, randomIFD, 8);
    memcpy(mutual_auth_plain + 8, randomICC, 8);
    memcpy(mutual_auth_plain + 8 + 8, keyIFD, 16);

    // ----------------- Encryption and MAC Generation -----------------
    uint8_t mac[8] = { 0x00 };
    uint8_t mutual_auth_enc[32];
    create_cryptogram(AES_key, mutual_auth_plain, mutual_auth_enc, sizeof(mutual_auth_plain), encryption_algorithm);
    create_cmac(MAC_key, mutual_auth_enc, mac, sizeof(mutual_auth_enc), sizeof(mac), encryption_algorithm);

    uint8_t message_authenticated[40];
    memcpy(message_authenticated, mutual_auth_enc, sizeof(mutual_auth_enc));
    memcpy(message_authenticated + sizeof(mutual_auth_enc), mac, sizeof(mac));

    // ----------------- Debugging -----------------
    // PrintAndLogEx(SUCCESS, "AES IV : "_YELLOW_("%s"), sprint_hex_inrow(AES_iv,sizeof(AES_iv)));
    // PrintAndLogEx(SUCCESS, "AES Key: "_YELLOW_("%s"), sprint_hex_inrow(AES_key,sizeof(AES_key)));
    // PrintAndLogEx(SUCCESS, "mutual_auth_plain... " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_plain, sizeof(mutual_auth_plain)));
    // PrintAndLogEx(SUCCESS, "mutual_auth_enc..... " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_enc, sizeof(mutual_auth_enc)));

    // PrintAndLogEx(INFO, "--- " _CYAN_("MAC") " ---------------------------");
    // PrintAndLogEx(SUCCESS, "AES IV: "_YELLOW_("%s"), sprint_hex_inrow(AES_iv,sizeof(AES_iv)));
    // PrintAndLogEx(SUCCESS, "MAC Key: "_YELLOW_("%s"), sprint_hex_inrow(MAC_key,sizeof(MAC_key)));
    // PrintAndLogEx(SUCCESS, "Message.......................... " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_enc,sizeof(mutual_auth_enc)));
    // PrintAndLogEx(SUCCESS, "MAC.............................. " _YELLOW_("%s"), sprint_hex_inrow(mac,sizeof(mac)));

    // ----------------- Command Generation -----------------

    const char *prefixLenHex = "2c";
    const char *ASN1_tagAboveLenHex = "2a";
    const char *ASN1_auth_encryptedLenHex = "28";

    const char *mutual_auth_message = sprint_hex_inrow(message_authenticated, sizeof(message_authenticated));

    char keyslot_str[3];
    snprintf(keyslot_str, sizeof(keyslot_str), "%02X", auth_key_slot);

    const char *prefix = "008700";
    const char *ASN1_tagAbove = "7c";
    const char *ASN1_auth_encrypted = "82";
    const char *suffix = "00";

    char mutual_auth[102];
    snprintf(mutual_auth, sizeof(mutual_auth), "%s%s%s%s%s%s%s%s%s", prefix, keyslot_str, prefixLenHex, ASN1_tagAbove, ASN1_tagAboveLenHex, ASN1_auth_encrypted, ASN1_auth_encryptedLenHex, mutual_auth_message, suffix);
    // PrintAndLogEx(SUCCESS, "Mutual Auth Encrypted Request.... " _YELLOW_("%s"), mutual_auth);

    // BLOCKS MUTUAL AUTH BEFORE REQUIRED
    // return PM3_SUCCESS;
    //

    int resplen = 0;
    bool activate_field = false;
    bool keep_field_on = true;

    uint8_t aMUTUAL_AUTH[102] = {0};
    int aMUTUAL_AUTH_n = 0;
    param_gethex_to_eol(mutual_auth, 0, aMUTUAL_AUTH, sizeof(aMUTUAL_AUTH), &aMUTUAL_AUTH_n);
    int res = ExchangeAPDU14a(aMUTUAL_AUTH, aMUTUAL_AUTH_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
    if (res != PM3_SUCCESS) {
        PrintAndLogEx(ERR, "Mutual Auth Request Failed");
        DropField();
        return PM3_ESOFT;
    }

    uint16_t sw = get_sw(response, resplen);
    if (sw != ISO7816_OK) {
        PrintAndLogEx(ERR, "Mutual Auth Request Failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
        DropField();
        return PM3_ESOFT;
    }

    // PrintAndLogEx(INFO, "--- " _CYAN_("Get Challenge") " ---------------------------");
    // PrintAndLogEx(SUCCESS, "Raw Mutual Auth Response: " _YELLOW_("%s"), sprint_hex_inrow(response, (resplen - 2)));
    // PrintAndLogEx(SUCCESS, "Mutual Auth Encrypted Response... " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_response, sizeof(mutual_auth_response)));
    // PrintAndLogEx(SUCCESS, "Mutual Auth MAC Response: " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_mac_response, sizeof(mutual_auth_mac_response)));
    // PrintAndLogEx(SUCCESS, "Mutual Auth MAC Input: " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_validate, sizeof(mutual_auth_validate)));
    // PrintAndLogEx(SUCCESS, "Mutual Auth MAC Calculated: " _YELLOW_("%s"), sprint_hex_inrow(mac_calculated, sizeof(mac_calculated)));

    // Process Response
    uint8_t iv[16] = {};
    uint8_t mutual_auth_response[32];
    uint8_t mutual_auth_mac_response[8];
    memcpy(mutual_auth_response, &response[4], 32);
    memcpy(mutual_auth_mac_response, &response[4 + 32], 8);

    // PrintAndLogEx(SUCCESS, "Mutual Auth Encrypted Response... " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_response, sizeof(mutual_auth_response)));
    // PrintAndLogEx(SUCCESS, "Mutual Auth MAC Response: " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_mac_response, sizeof(mutual_auth_mac_response)));

    uint8_t mutual_auth_response_decrypted[32];
    if (encryption_algorithm == 0x09) {
        aes_decode(iv, AES_key, mutual_auth_response, mutual_auth_response_decrypted, sizeof(mutual_auth_response));
    } else if (encryption_algorithm == 0x02) {
        mbedtls_des3_context ctx3;
        mbedtls_des3_set2key_dec(&ctx3, AES_key);
        mbedtls_des3_crypt_cbc(&ctx3, MBEDTLS_DES_DECRYPT, sizeof(mutual_auth_response), iv, mutual_auth_response, mutual_auth_response_decrypted);
        mbedtls_des3_free(&ctx3);
    }

    // Validate response with comparison between nonce and randomICC
    uint8_t mutual_auth_RandomICC[8];
    memcpy(mutual_auth_RandomICC, &mutual_auth_response_decrypted, 8);

    // PrintAndLogEx(SUCCESS, "Mutual Auth Decrypted Response... " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_response_decrypted, sizeof(mutual_auth_response_decrypted)));

    if (memcmp(randomICC, mutual_auth_RandomICC, 8) != 0) {
        PrintAndLogEx(ERR, "RandomICC does not match decrypted RandomICC");
        PrintAndLogEx(ERR, "Likely wrong Key or IV");
        return PM3_ESOFT;
    }

    memcpy(mutual_auth_randomIFD, &mutual_auth_response_decrypted[8], 8);
    memcpy(mutual_auth_keyICC, &mutual_auth_response_decrypted[16], 16);

    // PrintAndLogEx(SUCCESS, _GREEN_("Mutual Auth Completed"));
    // PrintAndLogEx(INFO, "--- " _CYAN_("Decrypted Response") " ---------------------------");
    // PrintAndLogEx(SUCCESS, "Mutual Auth Decrypted Response... " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_response_decrypted, sizeof(mutual_auth_response_decrypted)));
    // PrintAndLogEx(SUCCESS, "Mutual Auth RandomICC............ " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_RandomICC, sizeof(mutual_auth_RandomICC)));
    // PrintAndLogEx(SUCCESS, "Mutual Auth RandomIFD............ " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_randomIFD, sizeof(mutual_auth_randomIFD)));
    // PrintAndLogEx(SUCCESS, "Mutual Auth KeyICC............... " _YELLOW_("%s"), sprint_hex_inrow(mutual_auth_keyICC, sizeof(mutual_auth_keyICC)));

    return PM3_SUCCESS;
};

static int seos_try_aid_select(const uint8_t *aid, int aid_len, bool activate_field, bool keep_field_on) {
    uint8_t response[PM3_CMD_DATA_SIZE];
    int resplen = 0;

    uint8_t aSELECT_AID[5 + MAX_SEOS_AID_LEN + 1] = {0};
    aSELECT_AID[0] = 0x00;
    aSELECT_AID[1] = 0xA4;
    aSELECT_AID[2] = 0x04;
    aSELECT_AID[3] = 0x00;
    aSELECT_AID[4] = aid_len;
    memcpy(aSELECT_AID + 5, aid, aid_len);
    aSELECT_AID[5 + aid_len] = 0x00;

    int res = ExchangeAPDU14a(aSELECT_AID, 6 + aid_len, activate_field, keep_field_on, response, sizeof(response), &resplen);
    if (res != PM3_SUCCESS) {
        DropField();
        return res;
    }

    if (resplen < 2) {
        PrintAndLogEx(ERR, "Selecting SEOS applet aid %s failed (short response).", sprint_hex_inrow(aid, aid_len));
        DropField();
        return PM3_EWRONGANSWER;
    }

    uint16_t sw = get_sw(response, resplen);
    if (sw != ISO7816_OK) {
        PrintAndLogEx(ERR, "Selecting SEOS applet aid %s failed (%04x - %s).", sprint_hex_inrow(aid, aid_len), sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
        DropField();
        return PM3_ESOFT;
    }

    return PM3_SUCCESS;
}

static int seos_aid_select(const uint8_t *custom_aid, int custom_aid_len) {
    // Working 100%, pulls from live card
    bool activate_field = true;
    bool keep_field_on = true;

    if (custom_aid_len > 0) {
        return seos_try_aid_select(custom_aid, custom_aid_len, activate_field, keep_field_on);
    }

    int res = PM3_ESOFT;
    for (size_t i = 0; i < ARRAYLEN(known_seos_aids); i++) {
        uint8_t aid[MAX_SEOS_AID_LEN] = {0};
        int aid_len = 0;
        param_gethex_to_eol(known_seos_aids[i], 0, aid, sizeof(aid), &aid_len);
        res = seos_try_aid_select(aid, aid_len, activate_field, keep_field_on);
        if (res == PM3_SUCCESS) {
            return res;
        }
    }

    return res;
};

static int seos_pacs_adf_select(char *oid, int oid_len, uint8_t *data_tag, int data_tag_len, int privacy_key_index, int auth_key_index, uint8_t *write, int write_len) {
    int resplen = 0;
    uint8_t response[PM3_CMD_DATA_SIZE];
    bool activate_field = false;
    bool keep_field_on = true;

    // --------------- ADF file Selection ----------------

    // breaks down to
    // 06 = ASN.1 Tag
    // 11 = Len
    // 2b0601040181e438010102011801010202 = ADF-OID

    // --------------- OID Selection ----------------
    const char *ADFprefix = "06";
    char selectedOID[100];
    snprintf(selectedOID, sizeof(selectedOID), "%s", oid);

    uint16_t selectedOIDLen = strlen(selectedOID);
    char selectedOIDLenHex[3];
    snprintf(selectedOIDLenHex, sizeof(selectedOIDLenHex), "%02X", (selectedOIDLen >> 1) & 0xFF);

    char selectedADF[strlen(ADFprefix) + strlen(selectedOIDLenHex) + selectedOIDLen + 1];
    snprintf(selectedADF, sizeof(selectedADF), "%s%s%s", ADFprefix, selectedOIDLenHex, selectedOID);

    // --------------- Command Builder Selection ----------------
    // prefix is the APDU command we are sending
    const char *prefix = "80A504";
    const char *suffix = "00";
    const char *keyReference = "00";

    uint16_t selectedADFLen = strlen(selectedADF);

    char adflenHex[3];
    snprintf(adflenHex, sizeof(adflenHex), "%02X", (selectedADFLen >> 1) & 0xFF);

    char selectADF[strlen(prefix) + strlen(keyReference) + strlen(adflenHex) + selectedADFLen + strlen(suffix) + 1];

    // 80 A5 04 00 13 06 11 2B 06 01 04 01 81 E4 38 01 01 02 01 18 01 01 02 02 00
    snprintf(selectADF, sizeof(selectADF), "%s%s%s%s%s", prefix, keyReference, adflenHex, selectedADF, suffix);


    PrintAndLogEx(INFO, "--- " _CYAN_("Select ADF") " ---------------------------");
    PrintAndLogEx(SUCCESS, "Selected ADF..................... "_YELLOW_("%s"), selectedOID);

    // ---------------  Send APDU Command ----------------

    uint8_t aSELECT_FILE_ADF[124];
    int aSELECT_FILE_ADF_n = 0;
    // Input into getHextoEOL is a Char string
    param_gethex_to_eol(selectADF, 0, aSELECT_FILE_ADF, sizeof(aSELECT_FILE_ADF), &aSELECT_FILE_ADF_n);
    int res = ExchangeAPDU14a(aSELECT_FILE_ADF, aSELECT_FILE_ADF_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
    if (res != PM3_SUCCESS) {
        DropField();
        return res;
    }

    uint16_t sw = get_sw(response, resplen);
    if (sw != ISO7816_OK) {
        PrintAndLogEx(ERR, "Selecting ADF file failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
        DropField();
        return PM3_ESOFT;
    }

    // --------------- Decrypt ADF Response ----------------
    // Information returned from the GetChallenge command
    int ALGORITHM_INFO_value1 = 0;              // Encryption Algorithm
    int ALGORITHM_INFO_value2 = 0;              // Hash Algorithm
    uint8_t CRYPTOGRAM_encrypted_data[64];      // Encrypted Data
    uint8_t MAC_value[8] = {0};                 // MAC Value

    uint8_t diversifier[MAX_DIVERSIFIER_LEN] = {0};
    uint8_t RNDICC[8] = {0};
    uint8_t KeyICC[16] = {0};
    uint8_t RNDIFD[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    uint8_t KeyIFD[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    uint8_t Diversified_New_EncryptionKey[24] = {0};
    uint8_t Diversified_New_MACKey[24] = {0};

    if (resplen <= 2) {
        PrintAndLogEx(ERR, "Selecting ADF file failed (short response).");
        return PM3_EWRONGANSWER;
    }
    resplen -= 2;

    uint8_t auth_slot = auth_key_index;
    res = seos_challenge_get(RNDICC, sizeof(RNDICC), auth_slot);
    if (res != PM3_SUCCESS) {
        return res;
    }
    res = select_df_decode(response, resplen, &ALGORITHM_INFO_value1, &ALGORITHM_INFO_value2, CRYPTOGRAM_encrypted_data, MAC_value);
    if (res != PM3_SUCCESS) {
        return res;
    }

    bool plaintext_adf = select_df_is_plaintext(response, resplen);
    if (plaintext_adf == false) {
        res = select_DF_verify(response, resplen, MAC_value, sizeof(MAC_value), ALGORITHM_INFO_value1, privacy_key_index);
        if (res != PM3_SUCCESS) {
            return res;
        }
    }

    if (ALGORITHM_INFO_value1 == 0x09 || ALGORITHM_INFO_value1 == 0x02) {
        uint8_t adf_bytes[124];
        int adf_bytes_len = 0;
        // Input into getHextoEOL is a Char string
        param_gethex_to_eol(selectedOID, 0, adf_bytes, sizeof(adf_bytes), &adf_bytes_len);


        int diversifier_length = sizeof(diversifier);
        if (plaintext_adf) {
            res = select_df_extract_plaintext_diversifier(response, resplen, diversifier, &diversifier_length);
            if (res != PM3_SUCCESS) {
                return res;
            }
            PrintAndLogEx(INFO, "--- " _CYAN_("Plaintext ADF") " ---------------------------");
            PrintAndLogEx(SUCCESS, "Diversifier...................... " _YELLOW_("%s"), sprint_hex_inrow(diversifier, diversifier_length));
        } else {
            res = select_ADF_decrypt(selectedADF, CRYPTOGRAM_encrypted_data, diversifier, &diversifier_length, ALGORITHM_INFO_value1, privacy_key_index);
            if (res != PM3_SUCCESS) {
                return res;
            }
        }
        res = seos_mutual_auth(adf_bytes, adf_bytes_len, RNDICC, diversifier, diversifier_length, RNDIFD, KeyICC, RNDIFD, sizeof(RNDIFD), KeyIFD, sizeof(KeyIFD), ALGORITHM_INFO_value1, ALGORITHM_INFO_value2, auth_key_index, auth_key_index);
        if (res != PM3_SUCCESS) {
            return res;
        }
        create_mutual_auth_key(KeyIFD, KeyICC, RNDICC, RNDIFD, Diversified_New_EncryptionKey, Diversified_New_MACKey, ALGORITHM_INFO_value1, ALGORITHM_INFO_value2);

        if (write == NULL) {
            uint8_t sio_buffer_out[PM3_CMD_DATA_SIZE];
            int sio_size = 0;
            res = seos_get_data(RNDICC, RNDIFD, Diversified_New_EncryptionKey, Diversified_New_MACKey, sio_buffer_out, &sio_size, ALGORITHM_INFO_value1, data_tag, data_tag_len);
            if (res != PM3_SUCCESS) {
                return res;
            }

            if (sio_size == 0) {
                return PM3_ESOFT;
            }

            if (sio_buffer_out[0] == 0x30) {
                uint8_t sioOutput[sio_size];
                memcpy(sioOutput, sio_buffer_out, sio_size);

                PrintAndLogEx(INFO, "--- " _CYAN_("Key Data") " ---------------------------");
                PrintAndLogEx(SUCCESS, "SIO.............................. "_YELLOW_("%s"), sprint_hex_inrow(sioOutput, sizeof(sioOutput)));             // SIO
                PrintAndLogEx(SUCCESS, "SIO Size......................... "_YELLOW_("%i"), sio_size);                                                   // SIO Size
                PrintAndLogEx(SUCCESS, "Diversifier...................... "_YELLOW_("%s"), sprint_hex_inrow(diversifier, diversifier_length));          // Diversifier
            };
        } else {
            seos_write_data(RNDICC, RNDIFD, Diversified_New_EncryptionKey, Diversified_New_MACKey, write, write_len, ALGORITHM_INFO_value1, data_tag, data_tag_len);
        }

    } else {
        PrintAndLogEx(ERR, "Unknown encryption algorithm");
        return PM3_ESOFT;
    };

    return PM3_SUCCESS;
};

static int seos_adf_select(char *oid, int oid_len, int privacy_key_index, int auth_key_index) {
    int resplen = 0;
    uint8_t response[PM3_CMD_DATA_SIZE];
    bool activate_field = false;
    bool keep_field_on = true;

    // --------------- OID Selection ----------------
    const char *ADFprefix = "06";
    char selectedOID[100];
    snprintf(selectedOID, sizeof(selectedOID), "%s", oid);
    uint16_t selectedOIDLen = strlen(selectedOID);
    char selectedOIDLenHex[3];
    snprintf(selectedOIDLenHex, sizeof(selectedOIDLenHex), "%02X", (selectedOIDLen >> 1) & 0xFF);

    char selectedADF[strlen(ADFprefix) + strlen(selectedOIDLenHex) + selectedOIDLen + 1];
    snprintf(selectedADF, sizeof(selectedADF), "%s%s%s", ADFprefix, selectedOIDLenHex, selectedOID);

    // --------------- Command Builder Selection ----------------
    // prefix is the APDU command we are sending
    const char *prefix = "80A504";
    const char *suffix = "00";
    const char *keyReference = "00";

    uint16_t selectedADFLen = strlen(selectedADF);
    char adflenHex[3];
    snprintf(adflenHex, sizeof(adflenHex), "%02X", (selectedADFLen >> 1) & 0xFF);
    char selectADF[strlen(prefix) + strlen(keyReference) + strlen(adflenHex) + selectedADFLen + strlen(suffix) + 1];

    // 80 A5 04 00 13 06 11 2B 06 01 04 01 81 E4 38 01 01 02 01 18 01 01 02 02 00
    snprintf(selectADF, sizeof(selectADF), "%s%s%s%s%s", prefix, keyReference, adflenHex, selectedADF, suffix);
    PrintAndLogEx(INFO, "--- " _CYAN_("Select ADF") " ---------------------------");
    PrintAndLogEx(SUCCESS, "Selected ADF..................... "_YELLOW_("%s"), selectedADF);

    // ---------------  Send APDU Command ----------------
    uint8_t aSELECT_FILE_ADF[124];
    int aSELECT_FILE_ADF_n = 0;

    // Input into getHextoEOL is a Char string
    param_gethex_to_eol(selectADF, 0, aSELECT_FILE_ADF, sizeof(aSELECT_FILE_ADF), &aSELECT_FILE_ADF_n);

    int res = ExchangeAPDU14a(aSELECT_FILE_ADF, aSELECT_FILE_ADF_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
    if (res != PM3_SUCCESS) {
        DropField();
        return res;
    }

    uint16_t sw = get_sw(response, resplen);
    if (sw != ISO7816_OK) {
        PrintAndLogEx(ERR, "Selecting ADF file failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
        DropField();
        return PM3_ESOFT;
    }

    // --------------- Decrypt ADF Response ----------------
    // Information returned from the GetChallenge command
    int ALGORITHM_INFO_value1 = 0;               // Encryption Algorithm
    int ALGORITHM_INFO_value2 = 0;               // Hash Algorithm
    uint8_t CRYPTOGRAM_encrypted_data[64] = {0}; // Encrypted Data
    uint8_t MAC_value[8] = {0};                  // MAC Value
    uint8_t RNDICC[8] = {0};

    if (resplen <= 2) {
        PrintAndLogEx(ERR, "Selecting ADF file failed (short response).");
        return PM3_EWRONGANSWER;
    }
    resplen -= 2;

    res = seos_challenge_get(RNDICC, sizeof(RNDICC), auth_key_index);
    if (res != PM3_SUCCESS) {
        return res;
    }

    res = select_df_decode(response, resplen, &ALGORITHM_INFO_value1, &ALGORITHM_INFO_value2, CRYPTOGRAM_encrypted_data, MAC_value);
    if (res != PM3_SUCCESS) {
        return res;
    }

    if (select_df_is_plaintext(response, resplen) == false) {
        res = select_DF_verify(response, resplen, MAC_value, sizeof(MAC_value), ALGORITHM_INFO_value1, privacy_key_index);
        if (res != PM3_SUCCESS) {
            return res;
        }
    } else {
        int diversifier_length = MAX_DIVERSIFIER_LEN;
        uint8_t diversifier[MAX_DIVERSIFIER_LEN] = {0};
        res = select_df_extract_plaintext_diversifier(response, resplen, diversifier, &diversifier_length);
        if (res == PM3_SUCCESS) {
            PrintAndLogEx(INFO, "--- " _CYAN_("Plaintext ADF") " ---------------------------");
            PrintAndLogEx(SUCCESS, "Diversifier...................... " _YELLOW_("%s"), sprint_hex_inrow(diversifier, diversifier_length));
        }
    }
    return PM3_SUCCESS;
};

static int seos_gdf_select(int key_index) {
    uint8_t response[PM3_CMD_DATA_SIZE];
    int resplen = 0;

    bool activate_field = false;
    bool keep_field_on = true;
    // ---------------  Select Global_df for SEOS Card ----------------
    // SelectGDF = 00A507 + referenceDataQualifier + 00
    // 00A5070600
    // SelectGlobalDF = 00A50000

    const char *getGDF = "00A5070600";

    uint8_t agetGDF[10];
    int agetGDF_n = 0;
    param_gethex_to_eol(getGDF, 0, agetGDF, sizeof(agetGDF), &agetGDF_n);
    int res = ExchangeAPDU14a(agetGDF, agetGDF_n, activate_field, keep_field_on, response, sizeof(response), &resplen);
    if (res != PM3_SUCCESS) {
        DropField();
        return res;
    }

    uint16_t sw = get_sw(response, resplen);
    if (sw != ISO7816_OK) {
        PrintAndLogEx(ERR, "Get Global_df failed (%04x - %s).", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
        DropField();
        return PM3_ESOFT;
    }

    // --------------- Decrypt GDF Response ----------------
    // Information returned from the GetChallenge command
    int ALGORITHM_INFO_value1 = 0;               // Encryption Algorithm
    int ALGORITHM_INFO_value2 = 0;               // Hash Algorithm
    uint8_t CRYPTOGRAM_encrypted_data[64] = {0}; // Encrypted Data
    uint8_t MAC_value[8] = {0};                  // MAC Value
    uint8_t RNDICC[8] = {0};

    if (resplen <= 2) {
        PrintAndLogEx(ERR, "Get Global_df failed (short response).");
        return PM3_EWRONGANSWER;
    }
    resplen -= 2;

    res = seos_challenge_get(RNDICC, sizeof(RNDICC), key_index);
    if (res != PM3_SUCCESS) {
        return res;
    }

    res = select_df_decode(response, resplen, &ALGORITHM_INFO_value1, &ALGORITHM_INFO_value2, CRYPTOGRAM_encrypted_data, MAC_value);
    if (res != PM3_SUCCESS) {
        return res;
    }

    if (select_df_is_plaintext(response, resplen) == false) {
        res = select_DF_verify(response, resplen, MAC_value, sizeof(MAC_value), ALGORITHM_INFO_value1, key_index);
        if (res != PM3_SUCCESS) {
            return res;
        }
    }

    return PM3_SUCCESS;
};

static int seos_select(const uint8_t *aid, int aid_len, int privacy_key_index, int auth_key_index) {
    int res = seos_aid_select(aid, aid_len);
    if (res != PM3_SUCCESS) {
        DropField();
        return res;
    }

    const char *oid = "2B0601040181E438010102011801010202";
    int oid_len = strlen(oid);
    res = seos_adf_select((char *)oid, oid_len, privacy_key_index, auth_key_index);
    DropField();
    return res;
}

static int seos_pacs(char *oid, int oid_len, uint8_t *data_tag, int data_tag_len, int privacy_key_index, int auth_key_index, const uint8_t *aid, int aid_len) {
    int res = seos_aid_select(aid, aid_len);
    if (res != PM3_SUCCESS) {
        DropField();
        return res;
    }

    res = seos_pacs_adf_select(oid, oid_len, data_tag, data_tag_len, privacy_key_index, auth_key_index, NULL, 0);
    DropField();
    return res;
}

static int seos_write(char *oid, int oid_len, uint8_t *data_tag, int data_tag_len, int privacy_key_index, int auth_key_index, uint8_t *write, int write_len, const uint8_t *aid, int aid_len) {
    int res = seos_aid_select(aid, aid_len);
    if (res != PM3_SUCCESS) {
        DropField();
        return res;
    }

    res = seos_pacs_adf_select(oid, oid_len, data_tag, data_tag_len, privacy_key_index, auth_key_index, write, write_len);
    DropField();
    return res;
}

static int seos_global_df(int key_index, const uint8_t *aid, int aid_len) {
    int res = seos_aid_select(aid, aid_len);
    if (res == PM3_SUCCESS) {
        res = seos_gdf_select(key_index);
    }
    DropField();
    return res;
}

static int seos_print_keys(bool verbose) {
    PrintAndLogEx(NORMAL, "");
    if (verbose) {
        for (int i = 0; i < ARRAYLEN(keys); i++) {
            uint8_t privacy_key[32] = {0};
            memcpy(privacy_key, keys[i].privEncKey, sizeof(keys[i].privEncKey));
            memcpy(privacy_key + sizeof(keys[i].privEncKey), keys[i].privMacKey, sizeof(keys[i].privMacKey));
            PrintAndLogEx(INFO, "Key Index........................ " _YELLOW_("%u"), i);
            PrintAndLogEx(INFO, "Privacy Key...................... " _YELLOW_("%s"), sprint_hex(privacy_key, sizeof(privacy_key)));
            PrintAndLogEx(INFO, "Auth Key......................... " _YELLOW_("%s"), sprint_hex(keys[i].authKey, 16));
            PrintAndLogEx(INFO, "----------------------------");
        }
    } else {
        PrintAndLogEx(INFO, "idx| privacy | auth");
        PrintAndLogEx(INFO, "---+---------+-----");
        for (uint8_t i = 0; i < ARRAYLEN(keys); i++) {
            bool has_privacy = (memcmp(keys[i].privEncKey, zeros, sizeof(zeros)) != 0) || (memcmp(keys[i].privMacKey, zeros, sizeof(zeros)) != 0);
            bool has_auth = (memcmp(keys[i].authKey, zeros, sizeof(zeros)) != 0);
            PrintAndLogEx(INFO, " %u |    %c    |  %c", i, has_privacy ? 'Y' : ' ', has_auth ? 'Y' : ' ');
        }
        PrintAndLogEx(INFO, "---+---------+-----");
    };
    PrintAndLogEx(NORMAL, "");
    return PM3_SUCCESS;
}

static int seos_load_keys(char *filename) {
    uint8_t *dump = NULL;
    size_t bytes_read = 0;
    if (loadFile_safe(filename, "", (void **)&dump, &bytes_read) != PM3_SUCCESS) {
        PrintAndLogEx(FAILED, "File: " _YELLOW_("%s") ": not found or locked.", filename);
        return PM3_EFILE;
    }

    if (bytes_read > sizeof(keys)) {
        PrintAndLogEx(WARNING, "File is too long to load - exp: %zu got: %zu", sizeof(keys), bytes_read);
        free(dump);
        return PM3_EFILE;
    }

    size_t kn = sizeof(keyset_t);
    memset(keys, 0, sizeof(keys));

    size_t i = 0;
    if ((bytes_read % kn) != 0) {
        PrintAndLogEx(ERR, "Key file size is invalid for current SEOS key layout");
        free(dump);
        return PM3_EFILE;
    }
    for (; i < bytes_read / kn; i++) {
        memcpy(keys[i].privEncKey, dump + (i * kn), 16);
        memcpy(keys[i].privMacKey, dump + ((i * kn) + 16), 16);
        memcpy(keys[i].authKey, dump + ((i * kn) + 32), 16);
    }

    free(dump);
    PrintAndLogEx(SUCCESS, "Loaded" _GREEN_("%2zd") " keys from %s", i, filename);
    return PM3_SUCCESS;
}

static int CmdHfSeosInfo(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf seos info",
                  "Requests the unauthenticated information from the default ADF of a SEOS card\n"
                  "- If the card is a SEOS card\n"
                  "- Are static RND.ICC keys used (can detect SEOS default keyset)\n"
                  "- What encryption and hashing algorithm is use\n\n"
                  "By default:\n"
                  "  - Privacy Key: 0\n"
                  "    The selected privacy slot provides both encryption and MAC subkeys for ADF handling.\n"
                  "  - Auth Key   : 0\n"
                  "    The selected auth slot is sent to the card for challenge/authentication.\n",
                  "hf seos info\n"
                  "hf seos info --privacy-key 8 --auth-key 9\n"
                  "hf seos info --privacy-key 8 --auth-key 9 --aid A0000004400001010001"
                 );
    void *argtable[] = {
        arg_param_begin,
        arg_str0(NULL, "privacy-key", "<idx>", "Privacy key slot index. The selected slot provides both privacy encryption and MAC subkeys for ADF handling"),
        arg_str0(NULL, "auth-key", "<idx>", "Auth key slot index. The selected slot is sent to the card for challenge/authentication"),
        arg_str0(NULL, "aid", "<hex>", "Use a custom SEOS AID"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    int privacy_key_index = 0;
    int auth_key_index = 0;
    int privacy_key_len = 0;
    int auth_key_len = 0;
    uint8_t aid[MAX_SEOS_AID_LEN] = {0};
    int aid_len = 0;
    char privacy_key_str[12] = {0};
    char auth_key_str[12] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)privacy_key_str, sizeof(privacy_key_str), &privacy_key_len);
    CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)auth_key_str, sizeof(auth_key_str), &auth_key_len);
    if (privacy_key_len != 0 && seos_parse_key_index(privacy_key_str, &privacy_key_index) == false) {
        CLIParserFree(ctx);
        PrintAndLogEx(ERR, "Invalid privacy key. Expected --privacy-key N with index in range 0-%zu", ARRAYLEN(keys) - 1);
        return PM3_EINVARG;
    }
    if (auth_key_len != 0 && seos_parse_key_index(auth_key_str, &auth_key_index) == false) {
        CLIParserFree(ctx);
        PrintAndLogEx(ERR, "Invalid auth key. Expected --auth-key N with index in range 0-%zu", ARRAYLEN(keys) - 1);
        return PM3_EINVARG;
    }
    int res = seos_get_custom_aid(ctx, 3, aid, &aid_len);

    CLIParserFree(ctx);
    if (res != PM3_SUCCESS) {
        return res;
    }
    return seos_select(aid, aid_len, privacy_key_index, auth_key_index);
}

static int CmdHfSeosGDF(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf seos gdf",
                  "Get Global Data File (GDF) from SEOS card\n\n"
                  "By default:\n"
                  "  - Privacy Key: 3\n",
                  "hf seos gdf\n"
                  "hf seos gdf --privacy-key 3\n"
                  "hf seos gdf --privacy-key 3 --aid A0000004400001010001"
                 );
    void *argtable[] = {
        arg_param_begin,
        arg_str0(NULL, "privacy-key", "<idx>", "Privacy key slot index. The selected slot provides both privacy encryption and MAC subkeys and is sent as the privacy slot identifier"),
        arg_str0(NULL, "aid", "<hex>", "Use a custom SEOS AID"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    int privacy_key_index = 3;
    int privacy_key_len = 0;
    uint8_t aid[MAX_SEOS_AID_LEN] = {0};
    int aid_len = 0;
    char privacy_key_str[12] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)privacy_key_str, sizeof(privacy_key_str), &privacy_key_len);
    if (privacy_key_len != 0 && seos_parse_key_index(privacy_key_str, &privacy_key_index) == false) {
        CLIParserFree(ctx);
        PrintAndLogEx(ERR, "Invalid privacy key. Expected --privacy-key N with index in range 0-%zu", ARRAYLEN(keys) - 1);
        return PM3_EINVARG;
    }
    int res = seos_get_custom_aid(ctx, 2, aid, &aid_len);

    CLIParserFree(ctx);
    if (res != PM3_SUCCESS) {
        return res;
    }
    return seos_global_df(privacy_key_index, aid, aid_len);
}

static int CmdHfSeosPACS(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf seos pacs",
                  "Make a GET DATA request to an ADF of a SEOS card\n\n"
                  "By default:\n"
                  "  - ADF OID      : 2B0601040181E438010102011801010202\n"
                  "  - Privacy Key  : 0\n"
                  "    The selected privacy slot provides both encryption and MAC subkeys and is sent as the privacy slot identifier.\n"
                  "  - Auth Key     : 0\n"
                  "    The selected auth slot provides the auth key and is sent as the auth slot identifier.\n",
                  "hf seos pacs\n"
                  "hf seos pacs --privacy-key 8 --auth-key 9\n"
                  "hf seos pacs -o 2B0601040181E438010102011801010202 --privacy-key 8 --auth-key 9\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_str0("o", "oid", "<hex>", "<0-100> hex bytes for OID (Default: 2B0601040181E438010102011801010202)"),
        arg_str0(NULL, "privacy-key", "<idx>", "Privacy key slot index. The selected slot provides both privacy encryption and MAC subkeys and is sent as the privacy slot identifier"),
        arg_str0(NULL, "auth-key", "<idx>", "Auth key slot index. The selected slot provides the auth key and is sent as the auth slot identifier"),
        arg_str0(NULL, "aid", "<hex>", "Use a custom SEOS AID"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    int data_tag_len = 2;
    uint8_t data_tag[2] = {0xff, 0x00};

    int oid_len = 0;
    uint8_t oid_hex[256] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x18, 0x01, 0x01, 0x02, 0x02};
    CLIGetHexWithReturn(ctx, 1, oid_hex, &oid_len);

    int privacy_key_index = 0;
    int auth_key_index = 0;
    int privacy_key_len = 0;
    int auth_key_index_len = 0;
    uint8_t aid[MAX_SEOS_AID_LEN] = {0};
    int aid_len = 0;
    char privacy_key_str[12] = {0};
    char auth_key_index_str[12] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)privacy_key_str, sizeof(privacy_key_str), &privacy_key_len);
    CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)auth_key_index_str, sizeof(auth_key_index_str), &auth_key_index_len);
    if (privacy_key_len != 0 && seos_parse_key_index(privacy_key_str, &privacy_key_index) == false) {
        CLIParserFree(ctx);
        PrintAndLogEx(ERR, "Invalid privacy key. Expected --privacy-key N with index in range 0-%zu", ARRAYLEN(keys) - 1);
        return PM3_EINVARG;
    }
    if (auth_key_index_len != 0 && seos_parse_key_index(auth_key_index_str, &auth_key_index) == false) {
        CLIParserFree(ctx);
        PrintAndLogEx(ERR, "Invalid auth key. Expected --auth-key N with index in range 0-%zu", ARRAYLEN(keys) - 1);
        return PM3_EINVARG;
    }
    int res = seos_get_custom_aid(ctx, 4, aid, &aid_len);

    CLIParserFree(ctx);
    if (res != PM3_SUCCESS) {
        return res;
    }

    // Fall back to default OID
    if (oid_len == 0) {
        oid_len = 17;
    }

    // convert OID hex to literal string

    unsigned char oid_buffer[256] = "";
    hex_to_buffer(oid_buffer, oid_hex, oid_len, 256, 0, 0, 1);

    const unsigned char *oid = oid_buffer;

    if (oid_len == 0) {
        PrintAndLogEx(ERR, "OID value must be supplied");
        return PM3_ESOFT;
    }

    return seos_pacs((char *)oid, oid_len, data_tag, data_tag_len, privacy_key_index, auth_key_index, aid, aid_len);
}

static int CmdHfSeosWrite(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf seos write",
                  "Make a PUT DATA request to an ADF of a SEOS card\n\n"
                  "By default:\n"
                  "  - ADF OID      : 2B0601040181E438010102011801010202\n"
                  "  - Privacy Key  : 2\n"
                  "  - Auth Key     : 2\n",
                  "hf seos write -d 12345678\n"
                  "hf seos write --privacy-key 8 --auth-key 9 -d 12345678\n"
                  "hf seos write -o 2B0601040181E438010102011801010202 --privacy-key 8 --auth-key 9 -d 12345678\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_str0("t", "tag", "<hex>", "<0-100> hex bytes for tag to read (Default: FF00)"),
        arg_str0("o", "oid", "<hex>", "<0-100> hex bytes for OID (Default: 2B0601040181E438010102011801010202)"),
        arg_str0(NULL, "privacy-key", "<idx>", "Privacy key slot index. The selected slot provides both privacy encryption and MAC subkeys and is sent as the privacy slot identifier"),
        arg_str0(NULL, "auth-key", "<idx>", "Auth key slot index. The selected slot provides the auth key and is sent as the auth slot identifier"),
        arg_str0(NULL, "aid", "<hex>", "Use a custom SEOS AID"),
        arg_str0("d", "data", "<hex>", "<0-128> hex bytes for data (Must be valid BER-TLV)"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    int data_tag_len = 0;
    uint8_t data_tag[16] = {0xff, 0x00};
    CLIGetHexWithReturn(ctx, 1, data_tag, &data_tag_len);

    int oid_len = 0;
    uint8_t oid_hex[256] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x18, 0x01, 0x01, 0x02, 0x02};
    CLIGetHexWithReturn(ctx, 2, oid_hex, &oid_len);

    int privacy_key_index = 2;
    int auth_key_index = 2;
    int privacy_key_len = 0;
    int auth_key_len = 0;
    uint8_t aid[MAX_SEOS_AID_LEN] = {0};
    int aid_len = 0;
    char privacy_key_str[12] = {0};
    char auth_key_str[12] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)privacy_key_str, sizeof(privacy_key_str), &privacy_key_len);
    CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)auth_key_str, sizeof(auth_key_str), &auth_key_len);
    if (privacy_key_len != 0 && seos_parse_key_index(privacy_key_str, &privacy_key_index) == false) {
        CLIParserFree(ctx);
        PrintAndLogEx(ERR, "Invalid privacy key. Expected --privacy-key N with index in range 0-%zu", ARRAYLEN(keys) - 1);
        return PM3_EINVARG;
    }
    if (auth_key_len != 0 && seos_parse_key_index(auth_key_str, &auth_key_index) == false) {
        CLIParserFree(ctx);
        PrintAndLogEx(ERR, "Invalid auth key. Expected --auth-key N with index in range 0-%zu", ARRAYLEN(keys) - 1);
        return PM3_EINVARG;
    }
    int res = seos_get_custom_aid(ctx, 5, aid, &aid_len);

    int data_len = 0;
    uint8_t data[256] = {};
    CLIGetHexWithReturn(ctx, 6, data, &data_len);

    CLIParserFree(ctx);
    if (res != PM3_SUCCESS) {
        return res;
    }

    // Fall back to default OID
    if (oid_len == 0) {
        oid_len = 17;
    }

    // convert OID hex to literal string

    unsigned char oid_buffer[256] = "";
    hex_to_buffer(oid_buffer, oid_hex, oid_len, 256, 0, 0, 1);

    const unsigned char *oid = oid_buffer;

    if (oid_len == 0) {
        PrintAndLogEx(ERR, "OID value must be supplied");
        return PM3_ESOFT;
    }

    if (data_len == 0) {
        PrintAndLogEx(ERR, "Data to write must be supplied");
        return PM3_ESOFT;
    }

    return seos_write((char *)oid, oid_len, data_tag, data_tag_len, privacy_key_index, auth_key_index, data, data_len, aid, aid_len);
}

static int CmdHfSeosADF(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf seos adf",
                  "Make a GET DATA request to an Application Data File (ADF) of a SEOS Tag\n"
                  "The ADF is meant to be read by an application\n"
                  "You still need the valid authentication keys to read a card\n\n"
                  "By default:\n"
                  "  - ADF OID      : 2B0601040181E438010102011801010202\n"
                  "  - Privacy Key  : 0\n"
                  "    The selected privacy slot provides both encryption and MAC subkeys and is sent as the privacy slot identifier.\n"
                  "  - Auth Key     : 0\n"
                  "    The selected auth slot provides the auth key and is sent as the auth slot identifier.\n"
                  "  - Data Tag     : FF00\n",
                  "hf seos adf\n"
                  "hf seos adf -o 2B0601040181E438010102011801010202\n"
                  "hf seos adf -o 2B0601040181E438010102011801010202 --privacy-key 8 --auth-key 9\n"
                  "hf seos adf -o 2B0601040181E438010102011801010202 -t FF41 --privacy-key 8 --auth-key 9\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_str0("t", "tag", "<hex>", "<0-100> hex bytes for tag to read (Default: FF00)"),
        arg_str0("o", "oid", "<hex>", "<0-100> hex bytes for OID (Default: 2B0601040181E438010102011801010202)"),
        arg_str0(NULL, "privacy-key", "<idx>", "Privacy key slot index. The selected slot provides both privacy encryption and MAC subkeys and is sent as the privacy slot identifier"),
        arg_str0(NULL, "auth-key", "<idx>", "Auth key slot index. The selected slot provides the auth key and is sent as the auth slot identifier"),
        arg_str0(NULL, "aid", "<hex>", "Use a custom SEOS AID"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    int data_tag_len = 0;
    uint8_t data_tag[16] = {0xff, 0x00};
    CLIGetHexWithReturn(ctx, 1, data_tag, &data_tag_len);

    int oid_len = 0;
    uint8_t oid_hex[256] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x18, 0x01, 0x01, 0x02, 0x02};
    CLIGetHexWithReturn(ctx, 2, oid_hex, &oid_len);

    int privacy_key_index = 0;
    int auth_key_index = 0;
    int privacy_key_len = 0;
    int auth_key_index_len = 0;
    uint8_t aid[MAX_SEOS_AID_LEN] = {0};
    int aid_len = 0;
    char privacy_key_str[12] = {0};
    char auth_key_index_str[12] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)privacy_key_str, sizeof(privacy_key_str), &privacy_key_len);
    CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)auth_key_index_str, sizeof(auth_key_index_str), &auth_key_index_len);
    if (privacy_key_len != 0 && seos_parse_key_index(privacy_key_str, &privacy_key_index) == false) {
        CLIParserFree(ctx);
        PrintAndLogEx(ERR, "Invalid privacy key. Expected --privacy-key N with index in range 0-%zu", ARRAYLEN(keys) - 1);
        return PM3_EINVARG;
    }
    if (auth_key_index_len != 0 && seos_parse_key_index(auth_key_index_str, &auth_key_index) == false) {
        CLIParserFree(ctx);
        PrintAndLogEx(ERR, "Invalid auth key. Expected --auth-key N with index in range 0-%zu", ARRAYLEN(keys) - 1);
        return PM3_EINVARG;
    }
    int res = seos_get_custom_aid(ctx, 5, aid, &aid_len);

    CLIParserFree(ctx);
    if (res != PM3_SUCCESS) {
        return res;
    }

    if (data_tag_len == 0) {
        data_tag_len = 2;
    }

    // Catching when the OID value is not supplied
    if (oid_len == 0) {
        oid_len = 17;
    }

    // convert OID hex to literal string
    unsigned char oid_buffer[256] = "";
    hex_to_buffer(oid_buffer, oid_hex, oid_len, 256, 0, 0, 1);

    const unsigned char *oid = oid_buffer;

    if (oid_len == 0) {
        PrintAndLogEx(ERR, "OID value must be supplied");
        return PM3_ESOFT;
    }

    return seos_pacs((char *)oid, oid_len, data_tag, data_tag_len, privacy_key_index, auth_key_index, aid, aid_len);
}

static int CmdHfSeosSim(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf seos sim",
                  "Simulate a SEOS card with the provided keys and data\n\n"
                  "By default:\n"
                  "  - ADF OID    : 2B0601040181E438010102011801010202\n"
                  "  - Diversifier: 01020304050607\n"
                  "  - Privacy Key: 2\n"
                  "  - Auth Key   : 2\n"
                  "  - Data Tag   : FF00\n"
                  "  - Encryption : AES128\n"
                  "  - Hashing    : SHA256\n",
                  "hf seos sim -d 12345678\n"
                  "hf seos sim --privacy-key 8 --auth-key 9 -d 03020500\n"
                  "hf seos sim -o 2B0601040181E438010102011801010202 --div 01020304050607 --privacy-key 8 --auth-key 9 -d 03020500\n"
                  "hf seos sim -o 2B0601040181E438010102011801010202 --legacy -t FF41 -d 03020500\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_str0("t", "tag", "<hex>", "<0-100> hex bytes for tag to simulate (Default: FF00)"),
        arg_str0("o", "oid", "<hex>", "<0-100> hex bytes for OID (Default: 2B0601040181E438010102011801010202)"),
        arg_str0(NULL, "privacy-key", "<idx>", "Privacy key slot index. The selected slot provides both privacy encryption and MAC subkeys"),
        arg_str0(NULL, "auth-key", "<idx>", "Auth key slot index. The selected slot provides the auth key"),
        arg_str0(NULL, "div", "<hex>", "<0-16> hex bytes for diversifier (Equivalent of UID)"),
        arg_str0("d", "data", "<hex>", "<0-128> hex bytes for data (Must be valid BER-TLV)"),
        arg_str0("u", "uid", "<hex>", "<0-10> hex bytes for UID (Must be a RID i.e. [0]=0x08)"),
        arg_lit0("l", "legacy", "Use legacy algorithms (3DES/SHA1)"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    int data_tag_len = 0;
    uint8_t data_tag[16] = {0xff, 0x00};
    CLIGetHexWithReturn(ctx, 1, data_tag, &data_tag_len);

    int oid_len = 0;
    uint8_t oid[256] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x18, 0x01, 0x01, 0x02, 0x02};
    CLIGetHexWithReturn(ctx, 2, oid, &oid_len);

    int privacy_key_index = 2;
    int auth_key_index = 2;
    int privacy_key_len = 0;
    int auth_key_len = 0;
    char privacy_key_str[12] = {0};
    char auth_key_str[12] = {0};
    CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)privacy_key_str, sizeof(privacy_key_str), &privacy_key_len);
    CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)auth_key_str, sizeof(auth_key_str), &auth_key_len);
    if (privacy_key_len != 0 && seos_parse_key_index(privacy_key_str, &privacy_key_index) == false) {
        CLIParserFree(ctx);
        PrintAndLogEx(ERR, "Invalid privacy key. Expected --privacy-key N with index in range 0-%zu", ARRAYLEN(keys) - 1);
        return PM3_EINVARG;
    }
    if (auth_key_len != 0 && seos_parse_key_index(auth_key_str, &auth_key_index) == false) {
        CLIParserFree(ctx);
        PrintAndLogEx(ERR, "Invalid auth key. Expected --auth-key N with index in range 0-%zu", ARRAYLEN(keys) - 1);
        return PM3_EINVARG;
    }

    int diversifier_len = 0;
    uint8_t diversifier[256] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
    CLIGetHexWithReturn(ctx, 5, diversifier, &diversifier_len);

    int data_len = 0;
    uint8_t data[256] = {};
    CLIGetHexWithReturn(ctx, 6, data, &data_len);

    int uid_len = 0;
    uint8_t uid[10] = {0x08, 0x01, 0x02, 0x03};
    CLIGetHexWithReturn(ctx, 7, uid, &uid_len);

    uint8_t encryption_algorithm = SEOS_ENCRYPTION_AES;
    uint8_t hashing_algorithm = SEOS_HASHING_SHA256;
    if (arg_get_lit(ctx, 8)) {  // legacy algorithms
        encryption_algorithm = SEOS_ENCRYPTION_2K3DES,
        hashing_algorithm = SEOS_HASHING_SHA1;
    }

    CLIParserFree(ctx);

    // Fall back to default values
    if (data_tag_len == 0) {
        data_tag_len = 2;
    }
    if (oid_len == 0) {
        oid_len = 17;
    }
    if (diversifier_len == 0) {
        diversifier_len = 7;
    }
    if (uid_len == 0) {
        uid_len = 4;
    }

    if (data_len == 0) {
        PrintAndLogEx(ERR, "Data to simulate must be supplied");
        return PM3_ESOFT;
    }

    seos_emulate_req_t request = {
        .encr_alg = encryption_algorithm,
        .hash_alg = hashing_algorithm,

        .uid_len = uid_len,
        .diversifier_len = diversifier_len,
        .data_tag_len = data_tag_len,
        .data_len = data_len,
        .oid_len = oid_len,
    };

    // Copy all the provided values into the request object
    memcpy(request.privenc, keys[privacy_key_index].privEncKey, 16);
    memcpy(request.privmac, keys[privacy_key_index].privMacKey, 16);
    memcpy(request.authkey, keys[auth_key_index].authKey, 16);

    memcpy(request.uid, uid, uid_len);
    memcpy(request.diversifier, diversifier, diversifier_len);
    memcpy(request.data_tag, data_tag, data_tag_len);
    memcpy(request.data, data, data_len);
    memcpy(request.oid, oid, oid_len);

    PacketResponseNG resp;
    clearCommandBuffer();
    SendCommandNG(CMD_HF_SEOS_SIMULATE, (uint8_t *)&request, sizeof(request));

    PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("<Enter>") " to abort simulation");
    while (WaitForResponseTimeout(CMD_HF_SEOS_SIMULATE, &resp, 1000) == false) {
        if (kbd_enter_pressed()) {
            PrintAndLogEx(WARNING, "\naborted via keyboard.");
            // inform device to break the sim loop since client has exited
            SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
            return PM3_EOPABORTED;
        }
    }

    return PM3_SUCCESS;
}

static int CmdHfSeosManageKeys(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf seos managekeys",
                  "Manage SEOS Keys in client memory, keys are required to authenticate with SEOS cards\n",
                  "hf seos managekeys -p\n"
                  "hf seos managekeys -p -v\n"
                  "hf seos managekeys --ki 8 --privacy 0000000000000000000000000000000000000000000000000000000000000000\n"
                  "hf seos managekeys --ki 9 --auth 00000000000000000000000000000000\n"
                  "hf seos managekeys --load -f mykeys.bin -p          -> load from file and prints keys\n"
                  "hf seos managekeys --save -f mykeys.bin             -> saves keys to file\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_int0(NULL, "ki", "<dec>", "Specify key index to set key in memory"),
        arg_str0(NULL, "privacy", "<hex>", "Privacy key as 32 hex bytes (enc || mac)"),
        arg_str0(NULL, "auth", "<hex>", "Auth key as 16 hex bytes"),

        arg_str0("f", "file", "<fn>", "Specify a filename for load / save operations"),
        arg_lit0(NULL, "save", "Save keys in memory to file specified by filename"),
        arg_lit0(NULL, "load", "Load keys to memory from file specified by filename"),

        arg_lit0("p", "print", "Print keys loaded into memory"),
        arg_lit0("v", "verbose", "verbose (print all key info)"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, false);

    int fnlen = 0;
    char filename[FILE_PATH_SIZE] = {0};
    uint8_t privacy[32] = {0};
    uint8_t auth[16] = {0};
    int privacy_len = 0;
    int auth_len = 0;

    int key_index = arg_get_int_def(ctx, 1, -1);

    CLIGetHexWithReturn(ctx, 2, privacy, &privacy_len);
    CLIGetHexWithReturn(ctx, 3, auth, &auth_len);

    CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);

    if (key_index >= (int)ARRAYLEN(keys)) {
        PrintAndLogEx(ERR, "Key index is out-of-range");
        CLIParserFree(ctx);
        return PM3_EINVARG;
    }

    bool do_save = arg_get_lit(ctx, 5);
    bool do_load = arg_get_lit(ctx, 6);
    bool do_print = arg_get_lit(ctx, 7);
    bool verbose = arg_get_lit(ctx, 8);
    bool do_update = (privacy_len > 0) || (auth_len > 0);

    CLIParserFree(ctx);

    if (do_update == false && do_save == false && do_load == false && do_print == false) {
        PrintAndLogEx(ERR, "No operation specified (load, save, or print)\n");
        return PM3_EINVARG;
    }
    if ((do_save + do_load + do_update) > 1) {
        PrintAndLogEx(ERR, "Too many operations specified\n");
        return PM3_EINVARG;
    }
    if ((do_save || do_load) && fnlen == 0) {
        PrintAndLogEx(ERR, "You must enter a filename when loading or saving\n");
        return PM3_EINVARG;
    }
    if (do_update && key_index == -1) {
        PrintAndLogEx(ERR, "Please specify key index when specifying key");
        return PM3_EINVARG;
    }
    if (privacy_len != 0 && privacy_len != 32) {
        PrintAndLogEx(ERR, "Privacy key must be exactly 32 bytes");
        return PM3_EINVARG;
    }
    if (auth_len != 0 && auth_len != 16) {
        PrintAndLogEx(ERR, "Auth key must be exactly 16 bytes");
        return PM3_EINVARG;
    }

    if (do_update) {

        if (privacy_len) {
            memcpy(keys[key_index].privEncKey, privacy, 16);
            memcpy(keys[key_index].privMacKey, privacy + 16, 16);
            PrintAndLogEx(SUCCESS, "New Privacy Key[%d] " _GREEN_("%s"), key_index, sprint_hex_inrow(privacy, sizeof(privacy)));
        }

        if (auth_len) {
            memcpy(keys[key_index].authKey, auth, 16);
            PrintAndLogEx(SUCCESS, "New Auth Key[%d] " _GREEN_("%s"), key_index, sprint_hex_inrow(keys[key_index].authKey, 16));
        }
        return PM3_SUCCESS;
    }

    if (do_load) {
        int res = seos_load_keys(filename);
        if (res != PM3_SUCCESS || do_print == false) {
            return res;
        }
        return seos_print_keys(verbose);
    }

    if (do_save) {
        bool isOK = saveFile(filename, ".bin", keys, sizeof(keys));
        if (isOK == false) {
            return PM3_EFILE;
        }
        return PM3_SUCCESS;
    }

    if (do_print) {
        return seos_print_keys(verbose);
    }

    return PM3_SUCCESS;
}

static int CmdHfSeosList(const char *Cmd) {
    return CmdTraceListAlias(Cmd, "hf seos", "seos -c");
}

static int CmdHfSeosSAM(const char *Cmd) {
    CLIParserContext *ctx;
    CLIParserInit(&ctx, "hf seos sam",
                  "Extract PACS information via a HID SAM\n"
                  "Make sure you got a SAM inserted in your sim module",
                  "hf seos sam\n"
                  "hf seos sam -d a005a103800104 -> get PACS data\n"
                 );

    void *argtable[] = {
        arg_param_begin,
        arg_lit0("v", "verbose", "verbose output"),
        arg_lit0("k", "keep", "keep the field active after command executed"),
        arg_lit0("n", "nodetect", "skip selecting the card and sending card details to SAM"),
        arg_lit0("t",  "tlv",      "decode TLV"),
        arg_strx0("d", "data",     "<hex>", "DER encoded command to send to SAM"),
        arg_param_end
    };
    CLIExecWithReturn(ctx, Cmd, argtable, true);

    bool verbose = arg_get_lit(ctx, 1);
    bool disconnectAfter = !arg_get_lit(ctx, 2);
    bool skipDetect = arg_get_lit(ctx, 3);
    bool decodeTLV = arg_get_lit(ctx, 4);

    uint8_t flags = 0;
    if (disconnectAfter) {
        flags |= BITMASK(0);
    }

    if (skipDetect) {
        flags |= BITMASK(1);
    }

    uint8_t data[PM3_CMD_DATA_SIZE] = {0};
    data[0] = flags;

    int cmdlen = 0;
    if (CLIParamHexToBuf(arg_get_str(ctx, 5), data + 1, PM3_CMD_DATA_SIZE - 1, &cmdlen) != PM3_SUCCESS) {
        CLIParserFree(ctx);
        return PM3_ESOFT;
    }

    CLIParserFree(ctx);

    if (IsHIDSamPresent(verbose) == false) {
        return PM3_ESOFT;
    }

    // iceman:  this command should use a struct for its data being transfered.

    clearCommandBuffer();
    SendCommandNG(CMD_HF_SAM_SEOS, data, cmdlen + 1);
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_HF_SAM_SEOS, &resp, 4000) == false) {
        PrintAndLogEx(WARNING, "timeout while waiting for reply");
        return PM3_ETIMEOUT;
    }

    switch (resp.status) {
        case PM3_SUCCESS: {
            break;
        }
        case PM3_ENOPACS: {
            PrintAndLogEx(SUCCESS, "No PACS data found. Card empty?");
            return resp.status;
        }
        default: {
            PrintAndLogEx(WARNING, "SAM select failed");
            return resp.status;
        }
    }

    uint8_t *d = resp.data.asBytes;
    // check for standard SamCommandGetContentElement response
    // bd 09
    //    8a 07
    //       03 05 <- tag + length
    //          06 85 80 6d c0 <- decoded PACS data
    if (d[0] == 0xBD && d[2] == 0x8A && d[4] == 0x03) {
        uint8_t pacs_length = d[5];
        uint8_t *pacs_data = d + 6;
        int res = HIDDumpPACSBits(pacs_data, pacs_length, verbose);
        if (res != PM3_SUCCESS) {
            return res;
        }
        // check for standard samCommandGetContentElement2:
        // bd 1e
        //    b3 1c
        //       a0 1a
        //          80 05
        //             06 85 80 6d c0
        //          81 0e
        //             2b 06 01 04 01 81 e4 38 01 01 02 04 3c ff
        //          82 01
        //             07
    } else if (d[0] == 0xbd && d[2] == 0xb3 && d[4] == 0xa0) {
        const uint8_t *pacs = d + 6;
        const uint8_t pacs_length = pacs[1];
        const uint8_t *pacs_data = pacs + 2;
        int res = HIDDumpPACSBits(pacs_data, pacs_length, verbose);
        if (res != PM3_SUCCESS) {
            return res;
        }

        const uint8_t *oid = pacs + 2 + pacs_length;
        const uint8_t oid_length = oid[1];
        const uint8_t *oid_data = oid + 2;
        PrintAndLogEx(SUCCESS, "SIO OID.......: " _GREEN_("%s"), sprint_hex_inrow(oid_data, oid_length));

        const uint8_t *mediaType = oid + 2 + oid_length;
        const uint8_t mediaType_data = mediaType[2];
        PrintAndLogEx(SUCCESS, "SIO Media Type: " _GREEN_("%s"), getSioMediaTypeInfo(mediaType_data));

    } else {
        print_hex(d, resp.length);
    }
    if (decodeTLV) {
        asn1_print(d, d[1] + 2, " ");
    }

    return PM3_SUCCESS;
}

static command_t CommandTable[] = {
    {"-----------", CmdHelp,            AlwaysAvailable, "----------------------- " _CYAN_("General") " -----------------------"},
    {"help",    CmdHelp,                AlwaysAvailable, "This help"},
    {"list",    CmdHfSeosList,          AlwaysAvailable, "List SEOS history"},
    {"sam",     CmdHfSeosSAM,           IfPm3Smartcard,  "SAM tests"},
    {"-----------", CmdHelp,            IfPm3Iso14443a,  "---------------------- " _CYAN_("Operations") " ---------------------"},
    {"info",    CmdHfSeosInfo,          IfPm3Iso14443a,  "Tag information"},
    {"pacs",    CmdHfSeosPACS,          IfPm3Iso14443a,  "Extract PACS Information from card"},
    {"write",   CmdHfSeosWrite,         IfPm3Iso14443a,  "Write an ADF to the card"},
    {"adf",     CmdHfSeosADF,           IfPm3Iso14443a,  "Read an ADF from the card"},
    {"gdf",     CmdHfSeosGDF,           IfPm3Iso14443a,  "Read an GDF from card"},
    {"-----------", CmdHelp,            IfPm3Seos,       "---------------------- " _CYAN_("Simulation") " ---------------------"},
    {"sim",     CmdHfSeosSim,           IfPm3Seos,       "Simulate Seos tag"},
    {"-----------", CmdHelp,            AlwaysAvailable, "------------------------ " _CYAN_("Utils") " ------------------------"},
    {"managekeys", CmdHfSeosManageKeys, AlwaysAvailable, "Manage keys to use with SEOS commands"},
    {NULL, NULL, NULL, NULL}
};

static int CmdHelp(const char *Cmd) {
    (void)Cmd; // Cmd is not used so far
    CmdsHelp(CommandTable);
    return PM3_SUCCESS;
}

int CmdHFSeos(const char *Cmd) {
    clearCommandBuffer();
    return CmdsParse(CommandTable, Cmd);
}
